Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32e058e01f |
@@ -1770,6 +1770,10 @@ export function isUseStateType(id: Identifier): boolean {
|
|||||||
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseState';
|
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseState';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isJsxType(type: Type): boolean {
|
||||||
|
return type.kind === 'Object' && type.shapeId === 'BuiltInJsx';
|
||||||
|
}
|
||||||
|
|
||||||
export function isRefOrRefValue(id: Identifier): boolean {
|
export function isRefOrRefValue(id: Identifier): boolean {
|
||||||
return isUseRefType(id) || isRefValueType(id);
|
return isUseRefType(id) || isRefValueType(id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,9 @@ import {inferReactiveScopeVariables} from '../ReactiveScopes';
|
|||||||
import {rewriteInstructionKindsBasedOnReassignment} from '../SSA';
|
import {rewriteInstructionKindsBasedOnReassignment} from '../SSA';
|
||||||
import {inferMutableRanges} from './InferMutableRanges';
|
import {inferMutableRanges} from './InferMutableRanges';
|
||||||
import inferReferenceEffects from './InferReferenceEffects';
|
import inferReferenceEffects from './InferReferenceEffects';
|
||||||
import {assertExhaustive, retainWhere} from '../Utils/utils';
|
import {assertExhaustive} from '../Utils/utils';
|
||||||
import {inferMutationAliasingEffects} from './InferMutationAliasingEffects';
|
import {inferMutationAliasingEffects} from './InferMutationAliasingEffects';
|
||||||
import {inferFunctionExpressionAliasingEffectsSignature} from './InferFunctionExpressionAliasingEffectsSignature';
|
|
||||||
import {inferMutationAliasingRanges} from './InferMutationAliasingRanges';
|
import {inferMutationAliasingRanges} from './InferMutationAliasingRanges';
|
||||||
import {hashEffect} from './AliasingEffects';
|
|
||||||
|
|
||||||
export default function analyseFunctions(func: HIRFunction): void {
|
export default function analyseFunctions(func: HIRFunction): void {
|
||||||
for (const [_, block] of func.body.blocks) {
|
for (const [_, block] of func.body.blocks) {
|
||||||
@@ -69,30 +67,12 @@ function lowerWithMutationAliasing(fn: HIRFunction): void {
|
|||||||
analyseFunctions(fn);
|
analyseFunctions(fn);
|
||||||
inferMutationAliasingEffects(fn, {isFunctionExpression: true});
|
inferMutationAliasingEffects(fn, {isFunctionExpression: true});
|
||||||
deadCodeElimination(fn);
|
deadCodeElimination(fn);
|
||||||
inferMutationAliasingRanges(fn, {isFunctionExpression: true});
|
const functionEffects = inferMutationAliasingRanges(fn, {
|
||||||
|
isFunctionExpression: true,
|
||||||
|
}).unwrap();
|
||||||
rewriteInstructionKindsBasedOnReassignment(fn);
|
rewriteInstructionKindsBasedOnReassignment(fn);
|
||||||
inferReactiveScopeVariables(fn);
|
inferReactiveScopeVariables(fn);
|
||||||
const effects = inferFunctionExpressionAliasingEffectsSignature(fn);
|
fn.aliasingEffects = functionEffects;
|
||||||
fn.env.logger?.debugLogIRs?.({
|
|
||||||
kind: 'hir',
|
|
||||||
name: 'AnalyseFunction (inner)',
|
|
||||||
value: fn,
|
|
||||||
});
|
|
||||||
if (effects != null) {
|
|
||||||
fn.aliasingEffects ??= [];
|
|
||||||
fn.aliasingEffects?.push(...effects);
|
|
||||||
}
|
|
||||||
if (fn.aliasingEffects != null) {
|
|
||||||
const seen = new Set<string>();
|
|
||||||
retainWhere(fn.aliasingEffects, effect => {
|
|
||||||
const hash = hashEffect(effect);
|
|
||||||
if (seen.has(hash)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
seen.add(hash);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Phase 2: populate the Effect of each context variable to use in inferring
|
* Phase 2: populate the Effect of each context variable to use in inferring
|
||||||
@@ -100,7 +80,7 @@ function lowerWithMutationAliasing(fn: HIRFunction): void {
|
|||||||
* effects to decide if the function may be mutable or not.
|
* effects to decide if the function may be mutable or not.
|
||||||
*/
|
*/
|
||||||
const capturedOrMutated = new Set<IdentifierId>();
|
const capturedOrMutated = new Set<IdentifierId>();
|
||||||
for (const effect of effects ?? []) {
|
for (const effect of functionEffects) {
|
||||||
switch (effect.kind) {
|
switch (effect.kind) {
|
||||||
case 'Assign':
|
case 'Assign':
|
||||||
case 'Alias':
|
case 'Alias':
|
||||||
@@ -152,6 +132,12 @@ function lowerWithMutationAliasing(fn: HIRFunction): void {
|
|||||||
operand.effect = Effect.Read;
|
operand.effect = Effect.Read;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn.env.logger?.debugLogIRs?.({
|
||||||
|
kind: 'hir',
|
||||||
|
name: 'AnalyseFunction (inner)',
|
||||||
|
value: fn,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function lower(func: HIRFunction): void {
|
function lower(func: HIRFunction): void {
|
||||||
|
|||||||
@@ -1,206 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {HIRFunction, IdentifierId, Place, ValueKind, ValueReason} from '../HIR';
|
|
||||||
import {getOrInsertDefault} from '../Utils/utils';
|
|
||||||
import {AliasingEffect} from './AliasingEffects';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function tracks data flow within an inner function expression in order to
|
|
||||||
* compute a set of data-flow aliasing effects describing data flow between the function's
|
|
||||||
* params, context variables, and return value.
|
|
||||||
*
|
|
||||||
* For example, consider the following function expression:
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* (x) => { return [x, y] }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* This function captures both param `x` and context variable `y` into the return value.
|
|
||||||
* Unlike our previous inference which counted this as a mutation of x and y, we want to
|
|
||||||
* build a signature for the function that describes the data flow. We would infer
|
|
||||||
* `Capture x -> return, Capture y -> return` effects for this function.
|
|
||||||
*
|
|
||||||
* This function *also* propagates more ambient-style effects (MutateFrozen, MutateGlobal, Impure, Render)
|
|
||||||
* from instructions within the function up to the function itself.
|
|
||||||
*/
|
|
||||||
export function inferFunctionExpressionAliasingEffectsSignature(
|
|
||||||
fn: HIRFunction,
|
|
||||||
): Array<AliasingEffect> | null {
|
|
||||||
const effects: Array<AliasingEffect> = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map used to identify tracked variables: params, context vars, return value
|
|
||||||
* This is used to detect mutation/capturing/aliasing of params/context vars
|
|
||||||
*/
|
|
||||||
const tracked = new Map<IdentifierId, Place>();
|
|
||||||
tracked.set(fn.returns.identifier.id, fn.returns);
|
|
||||||
for (const operand of [...fn.context, ...fn.params]) {
|
|
||||||
const place = operand.kind === 'Identifier' ? operand : operand.place;
|
|
||||||
tracked.set(place.identifier.id, place);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Track capturing/aliasing of context vars and params into each other and into the return.
|
|
||||||
* We don't need to track locals and intermediate values, since we're only concerned with effects
|
|
||||||
* as they relate to arguments visible outside the function.
|
|
||||||
*
|
|
||||||
* For each aliased identifier we track capture/alias/createfrom and then merge this with how
|
|
||||||
* the value is used. Eg capturing an alias => capture. See joinEffects() helper.
|
|
||||||
*/
|
|
||||||
type AliasedIdentifier = {
|
|
||||||
kind: AliasingKind;
|
|
||||||
place: Place;
|
|
||||||
};
|
|
||||||
const dataFlow = new Map<IdentifierId, Array<AliasedIdentifier>>();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Check for aliasing of tracked values. Also joins the effects of how the value is
|
|
||||||
* used (@param kind) with the aliasing type of each value
|
|
||||||
*/
|
|
||||||
function lookup(
|
|
||||||
place: Place,
|
|
||||||
kind: AliasedIdentifier['kind'],
|
|
||||||
): Array<AliasedIdentifier> | null {
|
|
||||||
if (tracked.has(place.identifier.id)) {
|
|
||||||
return [{kind, place}];
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
dataFlow.get(place.identifier.id)?.map(aliased => ({
|
|
||||||
kind: joinEffects(aliased.kind, kind),
|
|
||||||
place: aliased.place,
|
|
||||||
})) ?? null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: fixpoint
|
|
||||||
for (const block of fn.body.blocks.values()) {
|
|
||||||
for (const phi of block.phis) {
|
|
||||||
const operands: Array<AliasedIdentifier> = [];
|
|
||||||
for (const operand of phi.operands.values()) {
|
|
||||||
const inputs = lookup(operand, 'Alias');
|
|
||||||
if (inputs != null) {
|
|
||||||
operands.push(...inputs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (operands.length !== 0) {
|
|
||||||
dataFlow.set(phi.place.identifier.id, operands);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const instr of block.instructions) {
|
|
||||||
if (instr.effects == null) continue;
|
|
||||||
for (const effect of instr.effects) {
|
|
||||||
if (
|
|
||||||
effect.kind === 'Assign' ||
|
|
||||||
effect.kind === 'Capture' ||
|
|
||||||
effect.kind === 'Alias' ||
|
|
||||||
effect.kind === 'CreateFrom'
|
|
||||||
) {
|
|
||||||
const from = lookup(effect.from, effect.kind);
|
|
||||||
if (from == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const into = lookup(effect.into, 'Alias');
|
|
||||||
if (into == null) {
|
|
||||||
getOrInsertDefault(dataFlow, effect.into.identifier.id, []).push(
|
|
||||||
...from,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
for (const aliased of into) {
|
|
||||||
getOrInsertDefault(
|
|
||||||
dataFlow,
|
|
||||||
aliased.place.identifier.id,
|
|
||||||
[],
|
|
||||||
).push(...from);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
effect.kind === 'Create' ||
|
|
||||||
effect.kind === 'CreateFunction'
|
|
||||||
) {
|
|
||||||
getOrInsertDefault(dataFlow, effect.into.identifier.id, [
|
|
||||||
{kind: 'Alias', place: effect.into},
|
|
||||||
]);
|
|
||||||
} else if (
|
|
||||||
effect.kind === 'MutateFrozen' ||
|
|
||||||
effect.kind === 'MutateGlobal' ||
|
|
||||||
effect.kind === 'Impure' ||
|
|
||||||
effect.kind === 'Render'
|
|
||||||
) {
|
|
||||||
effects.push(effect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (block.terminal.kind === 'return') {
|
|
||||||
const from = lookup(block.terminal.value, 'Alias');
|
|
||||||
if (from != null) {
|
|
||||||
getOrInsertDefault(dataFlow, fn.returns.identifier.id, []).push(
|
|
||||||
...from,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create aliasing effects based on observed data flow
|
|
||||||
let hasReturn = false;
|
|
||||||
for (const [into, from] of dataFlow) {
|
|
||||||
const input = tracked.get(into);
|
|
||||||
if (input == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (const aliased of from) {
|
|
||||||
if (
|
|
||||||
aliased.place.identifier.id === input.identifier.id ||
|
|
||||||
!tracked.has(aliased.place.identifier.id)
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const effect = {kind: aliased.kind, from: aliased.place, into: input};
|
|
||||||
effects.push(effect);
|
|
||||||
if (
|
|
||||||
into === fn.returns.identifier.id &&
|
|
||||||
(aliased.kind === 'Assign' || aliased.kind === 'CreateFrom')
|
|
||||||
) {
|
|
||||||
hasReturn = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: more precise return effect inference
|
|
||||||
if (!hasReturn) {
|
|
||||||
effects.unshift({
|
|
||||||
kind: 'Create',
|
|
||||||
into: fn.returns,
|
|
||||||
value:
|
|
||||||
fn.returnType.kind === 'Primitive'
|
|
||||||
? ValueKind.Primitive
|
|
||||||
: ValueKind.Mutable,
|
|
||||||
reason: ValueReason.KnownReturnSignature,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return effects;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum MutationKind {
|
|
||||||
None = 0,
|
|
||||||
Conditional = 1,
|
|
||||||
Definite = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
type AliasingKind = 'Alias' | 'Capture' | 'CreateFrom' | 'Assign';
|
|
||||||
function joinEffects(
|
|
||||||
effect1: AliasingKind,
|
|
||||||
effect2: AliasingKind,
|
|
||||||
): AliasingKind {
|
|
||||||
if (effect1 === 'Capture' || effect2 === 'Capture') {
|
|
||||||
return 'Capture';
|
|
||||||
} else if (effect1 === 'Assign' || effect2 === 'Assign') {
|
|
||||||
return 'Assign';
|
|
||||||
} else {
|
|
||||||
return 'Alias';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -822,7 +822,8 @@ function applyEffect(
|
|||||||
const functionValues = state.values(effect.function);
|
const functionValues = state.values(effect.function);
|
||||||
if (
|
if (
|
||||||
functionValues.length === 1 &&
|
functionValues.length === 1 &&
|
||||||
functionValues[0].kind === 'FunctionExpression'
|
functionValues[0].kind === 'FunctionExpression' &&
|
||||||
|
functionValues[0].loweredFunc.func.aliasingEffects != null
|
||||||
) {
|
) {
|
||||||
/*
|
/*
|
||||||
* We're calling a locally declared function, we already know it's effects!
|
* We're calling a locally declared function, we already know it's effects!
|
||||||
@@ -2126,8 +2127,6 @@ function computeEffectsForLegacySignature(
|
|||||||
const mutateIterator = conditionallyMutateIterator(place);
|
const mutateIterator = conditionallyMutateIterator(place);
|
||||||
if (mutateIterator != null) {
|
if (mutateIterator != null) {
|
||||||
effects.push(mutateIterator);
|
effects.push(mutateIterator);
|
||||||
// TODO: should we always push to captures?
|
|
||||||
captures.push(place);
|
|
||||||
}
|
}
|
||||||
effects.push({
|
effects.push({
|
||||||
kind: 'Capture',
|
kind: 'Capture',
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ import {
|
|||||||
Identifier,
|
Identifier,
|
||||||
IdentifierId,
|
IdentifierId,
|
||||||
InstructionId,
|
InstructionId,
|
||||||
|
isJsxType,
|
||||||
makeInstructionId,
|
makeInstructionId,
|
||||||
|
ValueKind,
|
||||||
|
ValueReason,
|
||||||
Place,
|
Place,
|
||||||
} from '../HIR/HIR';
|
} from '../HIR/HIR';
|
||||||
import {
|
import {
|
||||||
@@ -22,34 +25,58 @@ import {
|
|||||||
eachTerminalOperand,
|
eachTerminalOperand,
|
||||||
} from '../HIR/visitors';
|
} from '../HIR/visitors';
|
||||||
import {assertExhaustive, getOrInsertWith} from '../Utils/utils';
|
import {assertExhaustive, getOrInsertWith} from '../Utils/utils';
|
||||||
import {MutationKind} from './InferFunctionExpressionAliasingEffectsSignature';
|
import {Err, Ok, Result} from '../Utils/Result';
|
||||||
import {Result} from '../Utils/Result';
|
import {AliasingEffect} from './AliasingEffects';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Infers mutable ranges for all values in the program, using previously inferred
|
* This pass builds an abstract model of the heap and interprets the effects of the
|
||||||
* mutation/aliasing effects. This pass builds a data flow graph using the effects,
|
* given function in order to determine the following:
|
||||||
* tracking an abstract notion of "when" each effect occurs relative to the others.
|
* - The mutable ranges of all identifiers in the function
|
||||||
* It then walks each mutation effect against the graph, updating the range of each
|
* - The externally-visible effects of the function, such as mutations of params and
|
||||||
* node that would be reachable at the "time" that the effect occurred.
|
* context-vars, aliasing between params/context-vars/return-value, and impure side
|
||||||
|
* effects.
|
||||||
|
* - The legacy `Effect` to store on each Place.
|
||||||
|
*
|
||||||
|
* This pass builds a data flow graph using the effects, tracking an abstract notion
|
||||||
|
* of "when" each effect occurs relative to the others. It then walks each mutation
|
||||||
|
* effect against the graph, updating the range of each node that would be reachable
|
||||||
|
* at the "time" that the effect occurred.
|
||||||
*
|
*
|
||||||
* This pass also validates against invalid effects: any function that is reachable
|
* This pass also validates against invalid effects: any function that is reachable
|
||||||
* by being called, or via a Render effect, is validated against mutating globals
|
* by being called, or via a Render effect, is validated against mutating globals
|
||||||
* or calling impure code.
|
* or calling impure code.
|
||||||
*
|
*
|
||||||
* Note that this function also populates the outer function's aliasing effects with
|
* Note that this function also populates the outer function's aliasing effects with
|
||||||
* any mutations that apply to its params or context variables. For example, a
|
* any mutations that apply to its params or context variables.
|
||||||
* function expression such as the following:
|
*
|
||||||
|
* ## Example
|
||||||
|
* A function expression such as the following:
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* (x) => { x.y = true }
|
* (x) => { x.y = true }
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* Would populate a `Mutate x` aliasing effect on the outer function.
|
* Would populate a `Mutate x` aliasing effect on the outer function.
|
||||||
|
*
|
||||||
|
* ## Returned Function Effects
|
||||||
|
*
|
||||||
|
* The function returns (if successful) a list of externally-visible effects.
|
||||||
|
* This is determined by simulating a conditional, transitive mutation against
|
||||||
|
* each param, context variable, and return value in turn, and seeing which other
|
||||||
|
* such values are affected. If they're affected, they must be captured, so we
|
||||||
|
* record a Capture.
|
||||||
|
*
|
||||||
|
* The only tricky bit is the return value, which could _alias_ (or even assign)
|
||||||
|
* one or more of the params/context-vars rather than just capturing. So we have
|
||||||
|
* to do a bit more tracking for returns.
|
||||||
*/
|
*/
|
||||||
export function inferMutationAliasingRanges(
|
export function inferMutationAliasingRanges(
|
||||||
fn: HIRFunction,
|
fn: HIRFunction,
|
||||||
{isFunctionExpression}: {isFunctionExpression: boolean},
|
{isFunctionExpression}: {isFunctionExpression: boolean},
|
||||||
): Result<void, CompilerError> {
|
): Result<Array<AliasingEffect>, CompilerError> {
|
||||||
|
// The set of externally-visible effects
|
||||||
|
const functionEffects: Array<AliasingEffect> = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Part 1: Infer mutable ranges for values. We build an abstract model of
|
* Part 1: Infer mutable ranges for values. We build an abstract model of
|
||||||
* values, the alias/capture edges between them, and the set of mutations.
|
* values, the alias/capture edges between them, and the set of mutations.
|
||||||
@@ -168,8 +195,10 @@ export function inferMutationAliasingRanges(
|
|||||||
effect.kind === 'Impure'
|
effect.kind === 'Impure'
|
||||||
) {
|
) {
|
||||||
errors.push(effect.error);
|
errors.push(effect.error);
|
||||||
|
functionEffects.push(effect);
|
||||||
} else if (effect.kind === 'Render') {
|
} else if (effect.kind === 'Render') {
|
||||||
renders.push({index: index++, place: effect.place});
|
renders.push({index: index++, place: effect.place});
|
||||||
|
functionEffects.push(effect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,7 +244,6 @@ export function inferMutationAliasingRanges(
|
|||||||
for (const render of renders) {
|
for (const render of renders) {
|
||||||
state.render(render.index, render.place.identifier, errors);
|
state.render(render.index, render.place.identifier, errors);
|
||||||
}
|
}
|
||||||
fn.aliasingEffects ??= [];
|
|
||||||
for (const param of [...fn.context, ...fn.params]) {
|
for (const param of [...fn.context, ...fn.params]) {
|
||||||
const place = param.kind === 'Identifier' ? param : param.place;
|
const place = param.kind === 'Identifier' ? param : param.place;
|
||||||
const node = state.nodes.get(place.identifier);
|
const node = state.nodes.get(place.identifier);
|
||||||
@@ -226,13 +254,13 @@ export function inferMutationAliasingRanges(
|
|||||||
if (node.local != null) {
|
if (node.local != null) {
|
||||||
if (node.local.kind === MutationKind.Conditional) {
|
if (node.local.kind === MutationKind.Conditional) {
|
||||||
mutated = true;
|
mutated = true;
|
||||||
fn.aliasingEffects.push({
|
functionEffects.push({
|
||||||
kind: 'MutateConditionally',
|
kind: 'MutateConditionally',
|
||||||
value: {...place, loc: node.local.loc},
|
value: {...place, loc: node.local.loc},
|
||||||
});
|
});
|
||||||
} else if (node.local.kind === MutationKind.Definite) {
|
} else if (node.local.kind === MutationKind.Definite) {
|
||||||
mutated = true;
|
mutated = true;
|
||||||
fn.aliasingEffects.push({
|
functionEffects.push({
|
||||||
kind: 'Mutate',
|
kind: 'Mutate',
|
||||||
value: {...place, loc: node.local.loc},
|
value: {...place, loc: node.local.loc},
|
||||||
});
|
});
|
||||||
@@ -241,13 +269,13 @@ export function inferMutationAliasingRanges(
|
|||||||
if (node.transitive != null) {
|
if (node.transitive != null) {
|
||||||
if (node.transitive.kind === MutationKind.Conditional) {
|
if (node.transitive.kind === MutationKind.Conditional) {
|
||||||
mutated = true;
|
mutated = true;
|
||||||
fn.aliasingEffects.push({
|
functionEffects.push({
|
||||||
kind: 'MutateTransitiveConditionally',
|
kind: 'MutateTransitiveConditionally',
|
||||||
value: {...place, loc: node.transitive.loc},
|
value: {...place, loc: node.transitive.loc},
|
||||||
});
|
});
|
||||||
} else if (node.transitive.kind === MutationKind.Definite) {
|
} else if (node.transitive.kind === MutationKind.Definite) {
|
||||||
mutated = true;
|
mutated = true;
|
||||||
fn.aliasingEffects.push({
|
functionEffects.push({
|
||||||
kind: 'MutateTransitive',
|
kind: 'MutateTransitive',
|
||||||
value: {...place, loc: node.transitive.loc},
|
value: {...place, loc: node.transitive.loc},
|
||||||
});
|
});
|
||||||
@@ -436,7 +464,82 @@ export function inferMutationAliasingRanges(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.asResult();
|
/**
|
||||||
|
* Part 3
|
||||||
|
* Finish populating the externally visible effects. Above we bubble-up the side effects
|
||||||
|
* (MutateFrozen/MutableGlobal/Impure/Render) as well as mutations of context variables.
|
||||||
|
* Here we populate an effect to create the return value as well as populating alias/capture
|
||||||
|
* effects for how data flows between the params, context vars, and return.
|
||||||
|
*/
|
||||||
|
functionEffects.push({
|
||||||
|
kind: 'Create',
|
||||||
|
into: fn.returns,
|
||||||
|
value:
|
||||||
|
fn.returnType.kind === 'Primitive'
|
||||||
|
? ValueKind.Primitive
|
||||||
|
: isJsxType(fn.returnType)
|
||||||
|
? ValueKind.Frozen
|
||||||
|
: ValueKind.Mutable,
|
||||||
|
reason: ValueReason.KnownReturnSignature,
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* Determine precise data-flow effects by simulating transitive mutations of the params/
|
||||||
|
* captures and seeing what other params/context variables are affected. Anything that
|
||||||
|
* would be transitively mutated needs a capture relationship.
|
||||||
|
*/
|
||||||
|
const tracked: Array<Place> = [];
|
||||||
|
const ignoredErrors = new CompilerError();
|
||||||
|
for (const param of [...fn.params, ...fn.context, fn.returns]) {
|
||||||
|
const place = param.kind === 'Identifier' ? param : param.place;
|
||||||
|
tracked.push(place);
|
||||||
|
}
|
||||||
|
for (const into of tracked) {
|
||||||
|
const mutationIndex = index++;
|
||||||
|
state.mutate(
|
||||||
|
mutationIndex,
|
||||||
|
into.identifier,
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
MutationKind.Conditional,
|
||||||
|
into.loc,
|
||||||
|
ignoredErrors,
|
||||||
|
);
|
||||||
|
for (const from of tracked) {
|
||||||
|
if (
|
||||||
|
from.identifier.id === into.identifier.id ||
|
||||||
|
from.identifier.id === fn.returns.identifier.id
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const fromNode = state.nodes.get(from.identifier);
|
||||||
|
CompilerError.invariant(fromNode != null, {
|
||||||
|
reason: `Expected a node to exist for all parameters and context variables`,
|
||||||
|
loc: into.loc,
|
||||||
|
});
|
||||||
|
if (fromNode.lastMutated === mutationIndex) {
|
||||||
|
if (into.identifier.id === fn.returns.identifier.id) {
|
||||||
|
// The return value could be any of the params/context variables
|
||||||
|
functionEffects.push({
|
||||||
|
kind: 'Alias',
|
||||||
|
from,
|
||||||
|
into,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Otherwise params/context-vars can only capture each other
|
||||||
|
functionEffects.push({
|
||||||
|
kind: 'Capture',
|
||||||
|
from,
|
||||||
|
into,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.hasErrors() && !isFunctionExpression) {
|
||||||
|
return Err(errors);
|
||||||
|
}
|
||||||
|
return Ok(functionEffects);
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendFunctionErrors(errors: CompilerError, fn: HIRFunction): void {
|
function appendFunctionErrors(errors: CompilerError, fn: HIRFunction): void {
|
||||||
@@ -452,6 +555,12 @@ function appendFunctionErrors(errors: CompilerError, fn: HIRFunction): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum MutationKind {
|
||||||
|
None = 0,
|
||||||
|
Conditional = 1,
|
||||||
|
Definite = 2,
|
||||||
|
}
|
||||||
|
|
||||||
type Node = {
|
type Node = {
|
||||||
id: Identifier;
|
id: Identifier;
|
||||||
createdFrom: Map<Identifier, number>;
|
createdFrom: Map<Identifier, number>;
|
||||||
@@ -460,6 +569,7 @@ type Node = {
|
|||||||
edges: Array<{index: number; node: Identifier; kind: 'capture' | 'alias'}>;
|
edges: Array<{index: number; node: Identifier; kind: 'capture' | 'alias'}>;
|
||||||
transitive: {kind: MutationKind; loc: SourceLocation} | null;
|
transitive: {kind: MutationKind; loc: SourceLocation} | null;
|
||||||
local: {kind: MutationKind; loc: SourceLocation} | null;
|
local: {kind: MutationKind; loc: SourceLocation} | null;
|
||||||
|
lastMutated: number;
|
||||||
value:
|
value:
|
||||||
| {kind: 'Object'}
|
| {kind: 'Object'}
|
||||||
| {kind: 'Phi'}
|
| {kind: 'Phi'}
|
||||||
@@ -477,6 +587,7 @@ class AliasingState {
|
|||||||
edges: [],
|
edges: [],
|
||||||
transitive: null,
|
transitive: null,
|
||||||
local: null,
|
local: null,
|
||||||
|
lastMutated: 0,
|
||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -558,7 +669,8 @@ class AliasingState {
|
|||||||
mutate(
|
mutate(
|
||||||
index: number,
|
index: number,
|
||||||
start: Identifier,
|
start: Identifier,
|
||||||
end: InstructionId,
|
// Null is used for simulated mutations
|
||||||
|
end: InstructionId | null,
|
||||||
transitive: boolean,
|
transitive: boolean,
|
||||||
kind: MutationKind,
|
kind: MutationKind,
|
||||||
loc: SourceLocation,
|
loc: SourceLocation,
|
||||||
@@ -580,9 +692,12 @@ class AliasingState {
|
|||||||
if (node == null) {
|
if (node == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
node.id.mutableRange.end = makeInstructionId(
|
node.lastMutated = Math.max(node.lastMutated, index);
|
||||||
Math.max(node.id.mutableRange.end, end),
|
if (end != null) {
|
||||||
);
|
node.id.mutableRange.end = makeInstructionId(
|
||||||
|
Math.max(node.id.mutableRange.end, end),
|
||||||
|
);
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
node.value.kind === 'Function' &&
|
node.value.kind === 'Function' &&
|
||||||
node.transitive == null &&
|
node.transitive == null &&
|
||||||
|
|||||||
@@ -514,9 +514,9 @@ Intuition: these effects are inverses of each other (capturing into an object, e
|
|||||||
Capture then CreatFrom is equivalent to Alias: we have to assume that the result _is_ the original value and that a local mutation of the result could mutate the original.
|
Capture then CreatFrom is equivalent to Alias: we have to assume that the result _is_ the original value and that a local mutation of the result could mutate the original.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const y = [x]; // capture
|
const b = [a]; // capture
|
||||||
const z = y[0]; // createfrom
|
const c = b[0]; // createfrom
|
||||||
mutate(z); // this clearly can mutate x, so the result must be one of Assign/Alias/CreateFrom
|
mutate(c); // this clearly can mutate a, so the result must be one of Assign/Alias/CreateFrom
|
||||||
```
|
```
|
||||||
|
|
||||||
We use Alias as the return type because the mutability kind of the result is not derived from the source value (there's a fresh object in between due to the capture), so the full set of effects in practice would be a Create+Alias.
|
We use Alias as the return type because the mutability kind of the result is not derived from the source value (there's a fresh object in between due to the capture), so the full set of effects in practice would be a Create+Alias.
|
||||||
@@ -528,17 +528,17 @@ CreateFrom c <- b
|
|||||||
Alias c <- a
|
Alias c <- a
|
||||||
```
|
```
|
||||||
|
|
||||||
Meanwhile the opposite direction preservers the capture, because the result is not the same as the source:
|
Meanwhile the opposite direction preserves the capture, because the result is not the same as the source:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const y = x[0]; // createfrom
|
const b = a[0]; // createfrom
|
||||||
const z = [y]; // capture
|
const c = [b]; // capture
|
||||||
mutate(z); // does not mutate x, so the result must be Capture
|
mutate(c); // does not mutate a, so the result must be Capture
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
Capture b <- a
|
CreateFrom b <- a
|
||||||
CreateFrom c <- b
|
Capture c <- b
|
||||||
=>
|
=>
|
||||||
Capture b <- a
|
Capture c <- a
|
||||||
```
|
```
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
|
||||||
|
## Input
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import {Stringify, mutate} from 'shared-runtime';
|
||||||
|
|
||||||
|
function Component({foo, bar}) {
|
||||||
|
let x = {foo};
|
||||||
|
let y = {bar};
|
||||||
|
const f0 = function () {
|
||||||
|
let a = {y};
|
||||||
|
let b = {x};
|
||||||
|
a.y.x = b;
|
||||||
|
};
|
||||||
|
f0();
|
||||||
|
mutate(y);
|
||||||
|
return <Stringify x={y} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
|
fn: Component,
|
||||||
|
params: [{foo: 2, bar: 3}],
|
||||||
|
sequentialRenders: [
|
||||||
|
{foo: 2, bar: 3},
|
||||||
|
{foo: 2, bar: 3},
|
||||||
|
{foo: 2, bar: 4},
|
||||||
|
{foo: 3, bar: 4},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { c as _c } from "react/compiler-runtime";
|
||||||
|
import { Stringify, mutate } from "shared-runtime";
|
||||||
|
|
||||||
|
function Component(t0) {
|
||||||
|
const $ = _c(3);
|
||||||
|
const { foo, bar } = t0;
|
||||||
|
let t1;
|
||||||
|
if ($[0] !== bar || $[1] !== foo) {
|
||||||
|
const x = { foo };
|
||||||
|
const y = { bar };
|
||||||
|
const f0 = function () {
|
||||||
|
const a = { y };
|
||||||
|
const b = { x };
|
||||||
|
a.y.x = b;
|
||||||
|
};
|
||||||
|
|
||||||
|
f0();
|
||||||
|
mutate(y);
|
||||||
|
t1 = <Stringify x={y} />;
|
||||||
|
$[0] = bar;
|
||||||
|
$[1] = foo;
|
||||||
|
$[2] = t1;
|
||||||
|
} else {
|
||||||
|
t1 = $[2];
|
||||||
|
}
|
||||||
|
return t1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
|
fn: Component,
|
||||||
|
params: [{ foo: 2, bar: 3 }],
|
||||||
|
sequentialRenders: [
|
||||||
|
{ foo: 2, bar: 3 },
|
||||||
|
{ foo: 2, bar: 3 },
|
||||||
|
{ foo: 2, bar: 4 },
|
||||||
|
{ foo: 3, bar: 4 },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Eval output
|
||||||
|
(kind: ok) <div>{"x":{"bar":3,"x":{"x":{"foo":2}},"wat0":"joe"}}</div>
|
||||||
|
<div>{"x":{"bar":3,"x":{"x":{"foo":2}},"wat0":"joe"}}</div>
|
||||||
|
<div>{"x":{"bar":4,"x":{"x":{"foo":2}},"wat0":"joe"}}</div>
|
||||||
|
<div>{"x":{"bar":4,"x":{"x":{"foo":3}},"wat0":"joe"}}</div>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import {Stringify, mutate} from 'shared-runtime';
|
||||||
|
|
||||||
|
function Component({foo, bar}) {
|
||||||
|
let x = {foo};
|
||||||
|
let y = {bar};
|
||||||
|
const f0 = function () {
|
||||||
|
let a = {y};
|
||||||
|
let b = {x};
|
||||||
|
a.y.x = b;
|
||||||
|
};
|
||||||
|
f0();
|
||||||
|
mutate(y);
|
||||||
|
return <Stringify x={y} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
|
fn: Component,
|
||||||
|
params: [{foo: 2, bar: 3}],
|
||||||
|
sequentialRenders: [
|
||||||
|
{foo: 2, bar: 3},
|
||||||
|
{foo: 2, bar: 3},
|
||||||
|
{foo: 2, bar: 4},
|
||||||
|
{foo: 3, bar: 4},
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
|
||||||
|
## Input
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import {useMemo} from 'react';
|
||||||
|
import {identity, ValidateMemoization} from 'shared-runtime';
|
||||||
|
|
||||||
|
function Component({a, b}) {
|
||||||
|
const x = useMemo(() => ({a}), [a, b]);
|
||||||
|
const f = () => {
|
||||||
|
return identity(x);
|
||||||
|
};
|
||||||
|
const x2 = f();
|
||||||
|
x2.b = b;
|
||||||
|
|
||||||
|
return <ValidateMemoization inputs={[a, b]} output={x} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
|
fn: Component,
|
||||||
|
params: [{a: 0, b: 0}],
|
||||||
|
sequentialRenders: [
|
||||||
|
{a: 0, b: 0},
|
||||||
|
{a: 0, b: 1},
|
||||||
|
{a: 1, b: 1},
|
||||||
|
{a: 0, b: 0},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { c as _c } from "react/compiler-runtime";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import { identity, ValidateMemoization } from "shared-runtime";
|
||||||
|
|
||||||
|
function Component(t0) {
|
||||||
|
const $ = _c(10);
|
||||||
|
const { a, b } = t0;
|
||||||
|
let t1;
|
||||||
|
let x;
|
||||||
|
if ($[0] !== a || $[1] !== b) {
|
||||||
|
t1 = { a };
|
||||||
|
x = t1;
|
||||||
|
const f = () => identity(x);
|
||||||
|
|
||||||
|
const x2 = f();
|
||||||
|
x2.b = b;
|
||||||
|
$[0] = a;
|
||||||
|
$[1] = b;
|
||||||
|
$[2] = x;
|
||||||
|
$[3] = t1;
|
||||||
|
} else {
|
||||||
|
x = $[2];
|
||||||
|
t1 = $[3];
|
||||||
|
}
|
||||||
|
let t2;
|
||||||
|
if ($[4] !== a || $[5] !== b) {
|
||||||
|
t2 = [a, b];
|
||||||
|
$[4] = a;
|
||||||
|
$[5] = b;
|
||||||
|
$[6] = t2;
|
||||||
|
} else {
|
||||||
|
t2 = $[6];
|
||||||
|
}
|
||||||
|
let t3;
|
||||||
|
if ($[7] !== t2 || $[8] !== x) {
|
||||||
|
t3 = <ValidateMemoization inputs={t2} output={x} />;
|
||||||
|
$[7] = t2;
|
||||||
|
$[8] = x;
|
||||||
|
$[9] = t3;
|
||||||
|
} else {
|
||||||
|
t3 = $[9];
|
||||||
|
}
|
||||||
|
return t3;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
|
fn: Component,
|
||||||
|
params: [{ a: 0, b: 0 }],
|
||||||
|
sequentialRenders: [
|
||||||
|
{ a: 0, b: 0 },
|
||||||
|
{ a: 0, b: 1 },
|
||||||
|
{ a: 1, b: 1 },
|
||||||
|
{ a: 0, b: 0 },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Eval output
|
||||||
|
(kind: ok) <div>{"inputs":[0,0],"output":{"a":0,"b":0}}</div>
|
||||||
|
<div>{"inputs":[0,1],"output":{"a":0,"b":1}}</div>
|
||||||
|
<div>{"inputs":[1,1],"output":{"a":1,"b":1}}</div>
|
||||||
|
<div>{"inputs":[0,0],"output":{"a":0,"b":0}}</div>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import {useMemo} from 'react';
|
||||||
|
import {identity, ValidateMemoization} from 'shared-runtime';
|
||||||
|
|
||||||
|
function Component({a, b}) {
|
||||||
|
const x = useMemo(() => ({a}), [a, b]);
|
||||||
|
const f = () => {
|
||||||
|
return identity(x);
|
||||||
|
};
|
||||||
|
const x2 = f();
|
||||||
|
x2.b = b;
|
||||||
|
|
||||||
|
return <ValidateMemoization inputs={[a, b]} output={x} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
|
fn: Component,
|
||||||
|
params: [{a: 0, b: 0}],
|
||||||
|
sequentialRenders: [
|
||||||
|
{a: 0, b: 0},
|
||||||
|
{a: 0, b: 1},
|
||||||
|
{a: 1, b: 1},
|
||||||
|
{a: 0, b: 0},
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
|
||||||
|
## Input
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import {useMemo} from 'react';
|
||||||
|
import {identity, ValidateMemoization} from 'shared-runtime';
|
||||||
|
|
||||||
|
function Component({a, b}) {
|
||||||
|
const x = useMemo(() => ({a}), [a, b]);
|
||||||
|
const x2 = identity(x);
|
||||||
|
x2.b = b;
|
||||||
|
|
||||||
|
return <ValidateMemoization inputs={[a, b]} output={x} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
|
fn: Component,
|
||||||
|
params: [{a: 0, b: 0}],
|
||||||
|
sequentialRenders: [
|
||||||
|
{a: 0, b: 0},
|
||||||
|
{a: 0, b: 1},
|
||||||
|
{a: 1, b: 1},
|
||||||
|
{a: 0, b: 0},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { c as _c } from "react/compiler-runtime";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import { identity, ValidateMemoization } from "shared-runtime";
|
||||||
|
|
||||||
|
function Component(t0) {
|
||||||
|
const $ = _c(10);
|
||||||
|
const { a, b } = t0;
|
||||||
|
let t1;
|
||||||
|
let x;
|
||||||
|
if ($[0] !== a || $[1] !== b) {
|
||||||
|
t1 = { a };
|
||||||
|
x = t1;
|
||||||
|
const x2 = identity(x);
|
||||||
|
x2.b = b;
|
||||||
|
$[0] = a;
|
||||||
|
$[1] = b;
|
||||||
|
$[2] = x;
|
||||||
|
$[3] = t1;
|
||||||
|
} else {
|
||||||
|
x = $[2];
|
||||||
|
t1 = $[3];
|
||||||
|
}
|
||||||
|
let t2;
|
||||||
|
if ($[4] !== a || $[5] !== b) {
|
||||||
|
t2 = [a, b];
|
||||||
|
$[4] = a;
|
||||||
|
$[5] = b;
|
||||||
|
$[6] = t2;
|
||||||
|
} else {
|
||||||
|
t2 = $[6];
|
||||||
|
}
|
||||||
|
let t3;
|
||||||
|
if ($[7] !== t2 || $[8] !== x) {
|
||||||
|
t3 = <ValidateMemoization inputs={t2} output={x} />;
|
||||||
|
$[7] = t2;
|
||||||
|
$[8] = x;
|
||||||
|
$[9] = t3;
|
||||||
|
} else {
|
||||||
|
t3 = $[9];
|
||||||
|
}
|
||||||
|
return t3;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
|
fn: Component,
|
||||||
|
params: [{ a: 0, b: 0 }],
|
||||||
|
sequentialRenders: [
|
||||||
|
{ a: 0, b: 0 },
|
||||||
|
{ a: 0, b: 1 },
|
||||||
|
{ a: 1, b: 1 },
|
||||||
|
{ a: 0, b: 0 },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Eval output
|
||||||
|
(kind: ok) <div>{"inputs":[0,0],"output":{"a":0,"b":0}}</div>
|
||||||
|
<div>{"inputs":[0,1],"output":{"a":0,"b":1}}</div>
|
||||||
|
<div>{"inputs":[1,1],"output":{"a":1,"b":1}}</div>
|
||||||
|
<div>{"inputs":[0,0],"output":{"a":0,"b":0}}</div>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import {useMemo} from 'react';
|
||||||
|
import {identity, ValidateMemoization} from 'shared-runtime';
|
||||||
|
|
||||||
|
function Component({a, b}) {
|
||||||
|
const x = useMemo(() => ({a}), [a, b]);
|
||||||
|
const x2 = identity(x);
|
||||||
|
x2.b = b;
|
||||||
|
|
||||||
|
return <ValidateMemoization inputs={[a, b]} output={x} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
|
fn: Component,
|
||||||
|
params: [{a: 0, b: 0}],
|
||||||
|
sequentialRenders: [
|
||||||
|
{a: 0, b: 0},
|
||||||
|
{a: 0, b: 1},
|
||||||
|
{a: 1, b: 1},
|
||||||
|
{a: 0, b: 0},
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
|
||||||
|
## Input
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function Component() {
|
||||||
|
const x = {};
|
||||||
|
const fn = () => {
|
||||||
|
new Object()
|
||||||
|
.build(x)
|
||||||
|
.build({})
|
||||||
|
.build({})
|
||||||
|
.build({})
|
||||||
|
.build({})
|
||||||
|
.build({})
|
||||||
|
.build({});
|
||||||
|
};
|
||||||
|
return <Stringify x={x} fn={fn} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { c as _c } from "react/compiler-runtime";
|
||||||
|
function Component() {
|
||||||
|
const $ = _c(2);
|
||||||
|
let t0;
|
||||||
|
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||||
|
t0 = {};
|
||||||
|
$[0] = t0;
|
||||||
|
} else {
|
||||||
|
t0 = $[0];
|
||||||
|
}
|
||||||
|
const x = t0;
|
||||||
|
let t1;
|
||||||
|
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||||
|
const fn = () => {
|
||||||
|
new Object()
|
||||||
|
.build(x)
|
||||||
|
.build({})
|
||||||
|
.build({})
|
||||||
|
.build({})
|
||||||
|
.build({})
|
||||||
|
.build({})
|
||||||
|
.build({});
|
||||||
|
};
|
||||||
|
|
||||||
|
t1 = <Stringify x={x} fn={fn} />;
|
||||||
|
$[1] = t1;
|
||||||
|
} else {
|
||||||
|
t1 = $[1];
|
||||||
|
}
|
||||||
|
return t1;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Eval output
|
||||||
|
(kind: exception) Fixture not implemented
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
function Component() {
|
||||||
|
const x = {};
|
||||||
|
const fn = () => {
|
||||||
|
new Object()
|
||||||
|
.build(x)
|
||||||
|
.build({})
|
||||||
|
.build({})
|
||||||
|
.build({})
|
||||||
|
.build({})
|
||||||
|
.build({})
|
||||||
|
.build({});
|
||||||
|
};
|
||||||
|
return <Stringify x={x} fn={fn} />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
|
||||||
|
## Input
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function Component({a, b}) {
|
||||||
|
const y = {a};
|
||||||
|
const x = {b};
|
||||||
|
const f = () => {
|
||||||
|
let z = null;
|
||||||
|
while (z == null) {
|
||||||
|
z = x;
|
||||||
|
}
|
||||||
|
// z is a phi with a backedge, and we don't realize it could be x,
|
||||||
|
// and therefore fail to record a Capture x <- y effect for this
|
||||||
|
// function expression
|
||||||
|
z.y = y;
|
||||||
|
};
|
||||||
|
f();
|
||||||
|
mutate(x);
|
||||||
|
return <div>{x}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { c as _c } from "react/compiler-runtime";
|
||||||
|
function Component(t0) {
|
||||||
|
const $ = _c(3);
|
||||||
|
const { a, b } = t0;
|
||||||
|
let t1;
|
||||||
|
if ($[0] !== a || $[1] !== b) {
|
||||||
|
const y = { a };
|
||||||
|
const x = { b };
|
||||||
|
const f = () => {
|
||||||
|
let z = null;
|
||||||
|
while (z == null) {
|
||||||
|
z = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
z.y = y;
|
||||||
|
};
|
||||||
|
|
||||||
|
f();
|
||||||
|
mutate(x);
|
||||||
|
t1 = <div>{x}</div>;
|
||||||
|
$[0] = a;
|
||||||
|
$[1] = b;
|
||||||
|
$[2] = t1;
|
||||||
|
} else {
|
||||||
|
t1 = $[2];
|
||||||
|
}
|
||||||
|
return t1;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Eval output
|
||||||
|
(kind: exception) Fixture not implemented
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
function Component({a, b}) {
|
||||||
|
const y = {a};
|
||||||
|
const x = {b};
|
||||||
|
const f = () => {
|
||||||
|
let z = null;
|
||||||
|
while (z == null) {
|
||||||
|
z = x;
|
||||||
|
}
|
||||||
|
// z is a phi with a backedge, and we don't realize it could be x,
|
||||||
|
// and therefore fail to record a Capture x <- y effect for this
|
||||||
|
// function expression
|
||||||
|
z.y = y;
|
||||||
|
};
|
||||||
|
f();
|
||||||
|
mutate(x);
|
||||||
|
return <div>{x}</div>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
|
||||||
|
## Input
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// @enableNewMutationAliasingModel:true
|
||||||
|
|
||||||
|
export const App = () => {
|
||||||
|
const [selected, setSelected] = useState(new Set<string>());
|
||||||
|
const onSelectedChange = (value: string) => {
|
||||||
|
const newSelected = new Set(selected);
|
||||||
|
if (newSelected.has(value)) {
|
||||||
|
// This should not count as a mutation of `selected`
|
||||||
|
newSelected.delete(value);
|
||||||
|
} else {
|
||||||
|
// This should not count as a mutation of `selected`
|
||||||
|
newSelected.add(value);
|
||||||
|
}
|
||||||
|
setSelected(newSelected);
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Stringify selected={selected} onSelectedChange={onSelectedChange} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel:true
|
||||||
|
|
||||||
|
export const App = () => {
|
||||||
|
const $ = _c(6);
|
||||||
|
let t0;
|
||||||
|
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||||
|
t0 = new Set();
|
||||||
|
$[0] = t0;
|
||||||
|
} else {
|
||||||
|
t0 = $[0];
|
||||||
|
}
|
||||||
|
const [selected, setSelected] = useState(t0);
|
||||||
|
let t1;
|
||||||
|
if ($[1] !== selected) {
|
||||||
|
t1 = (value) => {
|
||||||
|
const newSelected = new Set(selected);
|
||||||
|
if (newSelected.has(value)) {
|
||||||
|
newSelected.delete(value);
|
||||||
|
} else {
|
||||||
|
newSelected.add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelected(newSelected);
|
||||||
|
};
|
||||||
|
$[1] = selected;
|
||||||
|
$[2] = t1;
|
||||||
|
} else {
|
||||||
|
t1 = $[2];
|
||||||
|
}
|
||||||
|
const onSelectedChange = t1;
|
||||||
|
let t2;
|
||||||
|
if ($[3] !== onSelectedChange || $[4] !== selected) {
|
||||||
|
t2 = <Stringify selected={selected} onSelectedChange={onSelectedChange} />;
|
||||||
|
$[3] = onSelectedChange;
|
||||||
|
$[4] = selected;
|
||||||
|
$[5] = t2;
|
||||||
|
} else {
|
||||||
|
t2 = $[5];
|
||||||
|
}
|
||||||
|
return t2;
|
||||||
|
};
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Eval output
|
||||||
|
(kind: exception) Fixture not implemented
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// @enableNewMutationAliasingModel:true
|
||||||
|
|
||||||
|
export const App = () => {
|
||||||
|
const [selected, setSelected] = useState(new Set<string>());
|
||||||
|
const onSelectedChange = (value: string) => {
|
||||||
|
const newSelected = new Set(selected);
|
||||||
|
if (newSelected.has(value)) {
|
||||||
|
// This should not count as a mutation of `selected`
|
||||||
|
newSelected.delete(value);
|
||||||
|
} else {
|
||||||
|
// This should not count as a mutation of `selected`
|
||||||
|
newSelected.add(value);
|
||||||
|
}
|
||||||
|
setSelected(newSelected);
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Stringify selected={selected} onSelectedChange={onSelectedChange} />;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user