Compare commits
3 Commits
rewrite-va
...
pr33571
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a427c3c1d9 | ||
|
|
fc2c4f2f15 | ||
|
|
963970b7a2 |
@@ -20,10 +20,11 @@ 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} from '../Utils/utils';
|
import {assertExhaustive, retainWhere} from '../Utils/utils';
|
||||||
import {inferMutationAliasingEffects} from './InferMutationAliasingEffects';
|
import {inferMutationAliasingEffects} from './InferMutationAliasingEffects';
|
||||||
import {inferFunctionExpressionAliasingEffectsSignature} from './InferFunctionExpressionAliasingEffectsSignature';
|
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) {
|
||||||
@@ -81,6 +82,17 @@ function lowerWithMutationAliasing(fn: HIRFunction): void {
|
|||||||
fn.aliasingEffects ??= [];
|
fn.aliasingEffects ??= [];
|
||||||
fn.aliasingEffects?.push(...effects);
|
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
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
printAliasingEffect,
|
printAliasingEffect,
|
||||||
printAliasingSignature,
|
printAliasingSignature,
|
||||||
printFunction,
|
|
||||||
printIdentifier,
|
printIdentifier,
|
||||||
printInstruction,
|
printInstruction,
|
||||||
printInstructionValue,
|
printInstructionValue,
|
||||||
@@ -194,19 +193,15 @@ export function inferMutationAliasingEffects(
|
|||||||
hoistedContextDeclarations,
|
hoistedContextDeclarations,
|
||||||
);
|
);
|
||||||
|
|
||||||
let count = 0;
|
let iterationCount = 0;
|
||||||
while (queuedStates.size !== 0) {
|
while (queuedStates.size !== 0) {
|
||||||
count++;
|
iterationCount++;
|
||||||
if (count > 100) {
|
if (iterationCount > 100) {
|
||||||
console.log(
|
CompilerError.invariant(false, {
|
||||||
'oops infinite loop',
|
reason: `[InferMutationAliasingEffects] Potential infinite loop`,
|
||||||
fn.id,
|
description: `A value, temporary place, or effect was not cached properly`,
|
||||||
typeof fn.loc !== 'symbol' ? fn.loc?.filename : null,
|
loc: fn.loc,
|
||||||
);
|
});
|
||||||
if (DEBUG) {
|
|
||||||
console.log(printFunction(fn));
|
|
||||||
}
|
|
||||||
throw new Error('infinite loop');
|
|
||||||
}
|
}
|
||||||
for (const [blockId, block] of fn.body.blocks) {
|
for (const [blockId, block] of fn.body.blocks) {
|
||||||
const incomingState = queuedStates.get(blockId);
|
const incomingState = queuedStates.get(blockId);
|
||||||
@@ -217,11 +212,6 @@ export function inferMutationAliasingEffects(
|
|||||||
|
|
||||||
statesByBlock.set(blockId, incomingState);
|
statesByBlock.set(blockId, incomingState);
|
||||||
const state = incomingState.clone();
|
const state = incomingState.clone();
|
||||||
if (DEBUG) {
|
|
||||||
console.log('*************');
|
|
||||||
console.log(`bb${block.id}`);
|
|
||||||
console.log('*************');
|
|
||||||
}
|
|
||||||
inferBlock(context, state, block);
|
inferBlock(context, state, block);
|
||||||
|
|
||||||
for (const nextBlockId of eachTerminalSuccessor(block.terminal)) {
|
for (const nextBlockId of eachTerminalSuccessor(block.terminal)) {
|
||||||
@@ -362,6 +352,11 @@ function inferBlock(
|
|||||||
} else if (terminal.kind === 'maybe-throw') {
|
} else if (terminal.kind === 'maybe-throw') {
|
||||||
const handlerParam = context.catchHandlers.get(terminal.handler);
|
const handlerParam = context.catchHandlers.get(terminal.handler);
|
||||||
if (handlerParam != null) {
|
if (handlerParam != null) {
|
||||||
|
CompilerError.invariant(state.kind(handlerParam) != null, {
|
||||||
|
reason:
|
||||||
|
'Expected catch binding to be intialized with a DeclareLocal Catch instruction',
|
||||||
|
loc: terminal.loc,
|
||||||
|
});
|
||||||
const effects: Array<AliasingEffect> = [];
|
const effects: Array<AliasingEffect> = [];
|
||||||
for (const instr of block.instructions) {
|
for (const instr of block.instructions) {
|
||||||
if (
|
if (
|
||||||
@@ -476,14 +471,14 @@ function applySignature(
|
|||||||
* Track which values we've already aliased once, so that we can switch to
|
* Track which values we've already aliased once, so that we can switch to
|
||||||
* appendAlias() for subsequent aliases into the same value
|
* appendAlias() for subsequent aliases into the same value
|
||||||
*/
|
*/
|
||||||
const aliased = new Set<IdentifierId>();
|
const initialized = new Set<IdentifierId>();
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
console.log(printInstruction(instruction));
|
console.log(printInstruction(instruction));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const effect of signature.effects) {
|
for (const effect of signature.effects) {
|
||||||
applyEffect(context, state, effect, aliased, effects);
|
applyEffect(context, state, effect, initialized, effects);
|
||||||
}
|
}
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
console.log(
|
console.log(
|
||||||
@@ -508,7 +503,7 @@ function applyEffect(
|
|||||||
context: Context,
|
context: Context,
|
||||||
state: InferenceState,
|
state: InferenceState,
|
||||||
_effect: AliasingEffect,
|
_effect: AliasingEffect,
|
||||||
aliased: Set<IdentifierId>,
|
initialized: Set<IdentifierId>,
|
||||||
effects: Array<AliasingEffect>,
|
effects: Array<AliasingEffect>,
|
||||||
): void {
|
): void {
|
||||||
const effect = context.internEffect(_effect);
|
const effect = context.internEffect(_effect);
|
||||||
@@ -524,6 +519,13 @@ function applyEffect(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'Create': {
|
case 'Create': {
|
||||||
|
CompilerError.invariant(!initialized.has(effect.into.identifier.id), {
|
||||||
|
reason: `Cannot re-initialize variable within an instruction`,
|
||||||
|
description: `Re-initialized ${printPlace(effect.into)} in ${printAliasingEffect(effect)}`,
|
||||||
|
loc: effect.into.loc,
|
||||||
|
});
|
||||||
|
initialized.add(effect.into.identifier.id);
|
||||||
|
|
||||||
let value = context.effectInstructionValueCache.get(effect);
|
let value = context.effectInstructionValueCache.get(effect);
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
value = {
|
value = {
|
||||||
@@ -538,6 +540,7 @@ function applyEffect(
|
|||||||
reason: new Set([effect.reason]),
|
reason: new Set([effect.reason]),
|
||||||
});
|
});
|
||||||
state.define(effect.into, value);
|
state.define(effect.into, value);
|
||||||
|
effects.push(effect);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'ImmutableCapture': {
|
case 'ImmutableCapture': {
|
||||||
@@ -555,6 +558,13 @@ function applyEffect(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'CreateFrom': {
|
case 'CreateFrom': {
|
||||||
|
CompilerError.invariant(!initialized.has(effect.into.identifier.id), {
|
||||||
|
reason: `Cannot re-initialize variable within an instruction`,
|
||||||
|
description: `Re-initialized ${printPlace(effect.into)} in ${printAliasingEffect(effect)}`,
|
||||||
|
loc: effect.into.loc,
|
||||||
|
});
|
||||||
|
initialized.add(effect.into.identifier.id);
|
||||||
|
|
||||||
const fromValue = state.kind(effect.from);
|
const fromValue = state.kind(effect.from);
|
||||||
let value = context.effectInstructionValueCache.get(effect);
|
let value = context.effectInstructionValueCache.get(effect);
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
@@ -573,10 +583,21 @@ function applyEffect(
|
|||||||
switch (fromValue.kind) {
|
switch (fromValue.kind) {
|
||||||
case ValueKind.Primitive:
|
case ValueKind.Primitive:
|
||||||
case ValueKind.Global: {
|
case ValueKind.Global: {
|
||||||
// no need to track this data flow
|
effects.push({
|
||||||
|
kind: 'Create',
|
||||||
|
value: fromValue.kind,
|
||||||
|
into: effect.into,
|
||||||
|
reason: [...fromValue.reason][0] ?? ValueReason.Other,
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ValueKind.Frozen: {
|
case ValueKind.Frozen: {
|
||||||
|
effects.push({
|
||||||
|
kind: 'Create',
|
||||||
|
value: fromValue.kind,
|
||||||
|
into: effect.into,
|
||||||
|
reason: [...fromValue.reason][0] ?? ValueReason.Other,
|
||||||
|
});
|
||||||
applyEffect(
|
applyEffect(
|
||||||
context,
|
context,
|
||||||
state,
|
state,
|
||||||
@@ -585,7 +606,7 @@ function applyEffect(
|
|||||||
from: effect.from,
|
from: effect.from,
|
||||||
into: effect.into,
|
into: effect.into,
|
||||||
},
|
},
|
||||||
aliased,
|
initialized,
|
||||||
effects,
|
effects,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
@@ -597,6 +618,13 @@ function applyEffect(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'CreateFunction': {
|
case 'CreateFunction': {
|
||||||
|
CompilerError.invariant(!initialized.has(effect.into.identifier.id), {
|
||||||
|
reason: `Cannot re-initialize variable within an instruction`,
|
||||||
|
description: `Re-initialized ${printPlace(effect.into)} in ${printAliasingEffect(effect)}`,
|
||||||
|
loc: effect.into.loc,
|
||||||
|
});
|
||||||
|
initialized.add(effect.into.identifier.id);
|
||||||
|
|
||||||
effects.push(effect);
|
effects.push(effect);
|
||||||
/**
|
/**
|
||||||
* We consider the function mutable if it has any mutable context variables or
|
* We consider the function mutable if it has any mutable context variables or
|
||||||
@@ -653,7 +681,7 @@ function applyEffect(
|
|||||||
from: capture,
|
from: capture,
|
||||||
into: effect.into,
|
into: effect.into,
|
||||||
},
|
},
|
||||||
aliased,
|
initialized,
|
||||||
effects,
|
effects,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -661,6 +689,14 @@ function applyEffect(
|
|||||||
}
|
}
|
||||||
case 'Alias':
|
case 'Alias':
|
||||||
case 'Capture': {
|
case 'Capture': {
|
||||||
|
CompilerError.invariant(
|
||||||
|
effect.kind === 'Capture' || initialized.has(effect.into.identifier.id),
|
||||||
|
{
|
||||||
|
reason: `Expected destination value to already be initialized within this instruction for Alias effect`,
|
||||||
|
description: `Destination ${printPlace(effect.into)} is not initialized in this instruction`,
|
||||||
|
loc: effect.into.loc,
|
||||||
|
},
|
||||||
|
);
|
||||||
/*
|
/*
|
||||||
* Capture describes potential information flow: storing a pointer to one value
|
* Capture describes potential information flow: storing a pointer to one value
|
||||||
* within another. If the destination is not mutable, or the source value has
|
* within another. If the destination is not mutable, or the source value has
|
||||||
@@ -698,7 +734,7 @@ function applyEffect(
|
|||||||
from: effect.from,
|
from: effect.from,
|
||||||
into: effect.into,
|
into: effect.into,
|
||||||
},
|
},
|
||||||
aliased,
|
initialized,
|
||||||
effects,
|
effects,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
@@ -714,6 +750,13 @@ function applyEffect(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'Assign': {
|
case 'Assign': {
|
||||||
|
CompilerError.invariant(!initialized.has(effect.into.identifier.id), {
|
||||||
|
reason: `Cannot re-initialize variable within an instruction`,
|
||||||
|
description: `Re-initialized ${printPlace(effect.into)} in ${printAliasingEffect(effect)}`,
|
||||||
|
loc: effect.into.loc,
|
||||||
|
});
|
||||||
|
initialized.add(effect.into.identifier.id);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Alias represents potential pointer aliasing. If the type is a global,
|
* Alias represents potential pointer aliasing. If the type is a global,
|
||||||
* a primitive (copy-on-write semantics) then we can prune the effect
|
* a primitive (copy-on-write semantics) then we can prune the effect
|
||||||
@@ -730,7 +773,7 @@ function applyEffect(
|
|||||||
from: effect.from,
|
from: effect.from,
|
||||||
into: effect.into,
|
into: effect.into,
|
||||||
},
|
},
|
||||||
aliased,
|
initialized,
|
||||||
effects,
|
effects,
|
||||||
);
|
);
|
||||||
let value = context.effectInstructionValueCache.get(effect);
|
let value = context.effectInstructionValueCache.get(effect);
|
||||||
@@ -768,12 +811,7 @@ function applyEffect(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
if (aliased.has(effect.into.identifier.id)) {
|
state.assign(effect.into, effect.from);
|
||||||
state.appendAlias(effect.into, effect.from);
|
|
||||||
} else {
|
|
||||||
aliased.add(effect.into.identifier.id);
|
|
||||||
state.alias(effect.into, effect.from);
|
|
||||||
}
|
|
||||||
effects.push(effect);
|
effects.push(effect);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -819,18 +857,15 @@ function applyEffect(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (signatureEffects != null) {
|
if (signatureEffects != null) {
|
||||||
if (DEBUG) {
|
|
||||||
console.log('apply function expression effects');
|
|
||||||
}
|
|
||||||
applyEffect(
|
applyEffect(
|
||||||
context,
|
context,
|
||||||
state,
|
state,
|
||||||
{kind: 'MutateTransitiveConditionally', value: effect.function},
|
{kind: 'MutateTransitiveConditionally', value: effect.function},
|
||||||
aliased,
|
initialized,
|
||||||
effects,
|
effects,
|
||||||
);
|
);
|
||||||
for (const signatureEffect of signatureEffects) {
|
for (const signatureEffect of signatureEffects) {
|
||||||
applyEffect(context, state, signatureEffect, aliased, effects);
|
applyEffect(context, state, signatureEffect, initialized, effects);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -854,16 +889,10 @@ function applyEffect(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (signatureEffects != null) {
|
if (signatureEffects != null) {
|
||||||
if (DEBUG) {
|
|
||||||
console.log('apply aliasing signature effects');
|
|
||||||
}
|
|
||||||
for (const signatureEffect of signatureEffects) {
|
for (const signatureEffect of signatureEffects) {
|
||||||
applyEffect(context, state, signatureEffect, aliased, effects);
|
applyEffect(context, state, signatureEffect, initialized, effects);
|
||||||
}
|
}
|
||||||
} else if (effect.signature != null) {
|
} else if (effect.signature != null) {
|
||||||
if (DEBUG) {
|
|
||||||
console.log('apply legacy signature effects');
|
|
||||||
}
|
|
||||||
const legacyEffects = computeEffectsForLegacySignature(
|
const legacyEffects = computeEffectsForLegacySignature(
|
||||||
state,
|
state,
|
||||||
effect.signature,
|
effect.signature,
|
||||||
@@ -873,12 +902,9 @@ function applyEffect(
|
|||||||
effect.loc,
|
effect.loc,
|
||||||
);
|
);
|
||||||
for (const legacyEffect of legacyEffects) {
|
for (const legacyEffect of legacyEffects) {
|
||||||
applyEffect(context, state, legacyEffect, aliased, effects);
|
applyEffect(context, state, legacyEffect, initialized, effects);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (DEBUG) {
|
|
||||||
console.log('default effects');
|
|
||||||
}
|
|
||||||
applyEffect(
|
applyEffect(
|
||||||
context,
|
context,
|
||||||
state,
|
state,
|
||||||
@@ -888,7 +914,7 @@ function applyEffect(
|
|||||||
value: ValueKind.Mutable,
|
value: ValueKind.Mutable,
|
||||||
reason: ValueReason.Other,
|
reason: ValueReason.Other,
|
||||||
},
|
},
|
||||||
aliased,
|
initialized,
|
||||||
effects,
|
effects,
|
||||||
);
|
);
|
||||||
/*
|
/*
|
||||||
@@ -911,21 +937,21 @@ function applyEffect(
|
|||||||
kind: 'MutateTransitiveConditionally',
|
kind: 'MutateTransitiveConditionally',
|
||||||
value: operand,
|
value: operand,
|
||||||
},
|
},
|
||||||
aliased,
|
initialized,
|
||||||
effects,
|
effects,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const mutateIterator =
|
const mutateIterator =
|
||||||
arg.kind === 'Spread' ? conditionallyMutateIterator(operand) : null;
|
arg.kind === 'Spread' ? conditionallyMutateIterator(operand) : null;
|
||||||
if (mutateIterator) {
|
if (mutateIterator) {
|
||||||
applyEffect(context, state, mutateIterator, aliased, effects);
|
applyEffect(context, state, mutateIterator, initialized, effects);
|
||||||
}
|
}
|
||||||
applyEffect(
|
applyEffect(
|
||||||
context,
|
context,
|
||||||
state,
|
state,
|
||||||
// OK: recording information flow
|
// OK: recording information flow
|
||||||
{kind: 'Alias', from: operand, into: effect.into},
|
{kind: 'Alias', from: operand, into: effect.into},
|
||||||
aliased,
|
initialized,
|
||||||
effects,
|
effects,
|
||||||
);
|
);
|
||||||
for (const otherArg of [
|
for (const otherArg of [
|
||||||
@@ -953,7 +979,7 @@ function applyEffect(
|
|||||||
from: operand,
|
from: operand,
|
||||||
into: other,
|
into: other,
|
||||||
},
|
},
|
||||||
aliased,
|
initialized,
|
||||||
effects,
|
effects,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1009,7 +1035,7 @@ function applyEffect(
|
|||||||
suggestions: null,
|
suggestions: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
aliased,
|
initialized,
|
||||||
effects,
|
effects,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1028,7 +1054,7 @@ function applyEffect(
|
|||||||
suggestions: null,
|
suggestions: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
aliased,
|
initialized,
|
||||||
effects,
|
effects,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -1059,7 +1085,7 @@ function applyEffect(
|
|||||||
suggestions: null,
|
suggestions: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
aliased,
|
initialized,
|
||||||
effects,
|
effects,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1166,7 +1192,7 @@ class InferenceState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Updates the value at @param place to point to the same value as @param value.
|
// Updates the value at @param place to point to the same value as @param value.
|
||||||
alias(place: Place, value: Place): void {
|
assign(place: Place, value: Place): void {
|
||||||
const values = this.#variables.get(value.identifier.id);
|
const values = this.#variables.get(value.identifier.id);
|
||||||
CompilerError.invariant(values != null, {
|
CompilerError.invariant(values != null, {
|
||||||
reason: `[InferMutationAliasingEffects] Expected value for identifier to be initialized`,
|
reason: `[InferMutationAliasingEffects] Expected value for identifier to be initialized`,
|
||||||
@@ -1244,9 +1270,6 @@ class InferenceState {
|
|||||||
kind: ValueKind.Frozen,
|
kind: ValueKind.Frozen,
|
||||||
reason: new Set([reason]),
|
reason: new Set([reason]),
|
||||||
});
|
});
|
||||||
if (DEBUG) {
|
|
||||||
console.log(`freeze value: ${printInstructionValue(value)} ${reason}`);
|
|
||||||
}
|
|
||||||
if (
|
if (
|
||||||
value.kind === 'FunctionExpression' &&
|
value.kind === 'FunctionExpression' &&
|
||||||
(this.env.config.enablePreserveExistingMemoizationGuarantees ||
|
(this.env.config.enablePreserveExistingMemoizationGuarantees ||
|
||||||
@@ -2286,17 +2309,6 @@ function computeEffectsForSignature(
|
|||||||
// Too many args and there is no rest param to hold them
|
// Too many args and there is no rest param to hold them
|
||||||
(args.length > signature.params.length && signature.rest == null)
|
(args.length > signature.params.length && signature.rest == null)
|
||||||
) {
|
) {
|
||||||
if (DEBUG) {
|
|
||||||
if (signature.params.length > args.length) {
|
|
||||||
console.log(
|
|
||||||
`not enough args: ${args.length} args for ${signature.params.length} params`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
`too many args: ${args.length} args for ${signature.params.length} params, with no rest param`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// Build substitutions
|
// Build substitutions
|
||||||
@@ -2311,9 +2323,6 @@ function computeEffectsForSignature(
|
|||||||
continue;
|
continue;
|
||||||
} else if (params == null || i >= params.length || arg.kind === 'Spread') {
|
} else if (params == null || i >= params.length || arg.kind === 'Spread') {
|
||||||
if (signature.rest == null) {
|
if (signature.rest == null) {
|
||||||
if (DEBUG) {
|
|
||||||
console.log(`no rest value to hold param`);
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const place = arg.kind === 'Identifier' ? arg : arg.place;
|
const place = arg.kind === 'Identifier' ? arg : arg.place;
|
||||||
@@ -2421,23 +2430,14 @@ function computeEffectsForSignature(
|
|||||||
case 'Apply': {
|
case 'Apply': {
|
||||||
const applyReceiver = substitutions.get(effect.receiver.identifier.id);
|
const applyReceiver = substitutions.get(effect.receiver.identifier.id);
|
||||||
if (applyReceiver == null || applyReceiver.length !== 1) {
|
if (applyReceiver == null || applyReceiver.length !== 1) {
|
||||||
if (DEBUG) {
|
|
||||||
console.log(`too many substitutions for receiver`);
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const applyFunction = substitutions.get(effect.function.identifier.id);
|
const applyFunction = substitutions.get(effect.function.identifier.id);
|
||||||
if (applyFunction == null || applyFunction.length !== 1) {
|
if (applyFunction == null || applyFunction.length !== 1) {
|
||||||
if (DEBUG) {
|
|
||||||
console.log(`too many substitutions for function`);
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const applyInto = substitutions.get(effect.into.identifier.id);
|
const applyInto = substitutions.get(effect.into.identifier.id);
|
||||||
if (applyInto == null || applyInto.length !== 1) {
|
if (applyInto == null || applyInto.length !== 1) {
|
||||||
if (DEBUG) {
|
|
||||||
console.log(`too many substitutions for into`);
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const applyArgs: Array<Place | SpreadPattern | Hole> = [];
|
const applyArgs: Array<Place | SpreadPattern | Hole> = [];
|
||||||
@@ -2447,18 +2447,12 @@ function computeEffectsForSignature(
|
|||||||
} else if (arg.kind === 'Identifier') {
|
} else if (arg.kind === 'Identifier') {
|
||||||
const applyArg = substitutions.get(arg.identifier.id);
|
const applyArg = substitutions.get(arg.identifier.id);
|
||||||
if (applyArg == null || applyArg.length !== 1) {
|
if (applyArg == null || applyArg.length !== 1) {
|
||||||
if (DEBUG) {
|
|
||||||
console.log(`too many substitutions for arg`);
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
applyArgs.push(applyArg[0]);
|
applyArgs.push(applyArg[0]);
|
||||||
} else {
|
} else {
|
||||||
const applyArg = substitutions.get(arg.place.identifier.id);
|
const applyArg = substitutions.get(arg.place.identifier.id);
|
||||||
if (applyArg == null || applyArg.length !== 1) {
|
if (applyArg == null || applyArg.length !== 1) {
|
||||||
if (DEBUG) {
|
|
||||||
console.log(`too many substitutions for arg`);
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
applyArgs.push({kind: 'Spread', place: applyArg[0]});
|
applyArgs.push({kind: 'Spread', place: applyArg[0]});
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import prettyFormat from 'pretty-format';
|
|
||||||
import {CompilerError, SourceLocation} from '..';
|
import {CompilerError, SourceLocation} from '..';
|
||||||
import {
|
import {
|
||||||
BlockId,
|
BlockId,
|
||||||
@@ -23,14 +22,9 @@ import {
|
|||||||
eachTerminalOperand,
|
eachTerminalOperand,
|
||||||
} from '../HIR/visitors';
|
} from '../HIR/visitors';
|
||||||
import {assertExhaustive, getOrInsertWith} from '../Utils/utils';
|
import {assertExhaustive, getOrInsertWith} from '../Utils/utils';
|
||||||
import {printFunction} from '../HIR';
|
|
||||||
import {printIdentifier, printPlace} from '../HIR/PrintHIR';
|
|
||||||
import {MutationKind} from './InferFunctionExpressionAliasingEffectsSignature';
|
import {MutationKind} from './InferFunctionExpressionAliasingEffectsSignature';
|
||||||
import {Result} from '../Utils/Result';
|
import {Result} from '../Utils/Result';
|
||||||
|
|
||||||
const DEBUG = false;
|
|
||||||
const VERBOSE = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Infers mutable ranges for all values in the program, using previously inferred
|
* Infers mutable ranges for all values in the program, using previously inferred
|
||||||
* mutation/aliasing effects. This pass builds a data flow graph using the effects,
|
* mutation/aliasing effects. This pass builds a data flow graph using the effects,
|
||||||
@@ -56,10 +50,6 @@ export function inferMutationAliasingRanges(
|
|||||||
fn: HIRFunction,
|
fn: HIRFunction,
|
||||||
{isFunctionExpression}: {isFunctionExpression: boolean},
|
{isFunctionExpression}: {isFunctionExpression: boolean},
|
||||||
): Result<void, CompilerError> {
|
): Result<void, CompilerError> {
|
||||||
if (VERBOSE) {
|
|
||||||
console.log();
|
|
||||||
console.log(printFunction(fn));
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@@ -115,20 +105,6 @@ export function inferMutationAliasingRanges(
|
|||||||
seenBlocks.add(block.id);
|
seenBlocks.add(block.id);
|
||||||
|
|
||||||
for (const instr of block.instructions) {
|
for (const instr of block.instructions) {
|
||||||
if (
|
|
||||||
instr.value.kind === 'FunctionExpression' ||
|
|
||||||
instr.value.kind === 'ObjectMethod'
|
|
||||||
) {
|
|
||||||
state.create(instr.lvalue, {
|
|
||||||
kind: 'Function',
|
|
||||||
function: instr.value.loweredFunc.func,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
for (const lvalue of eachInstructionLValue(instr)) {
|
|
||||||
state.create(lvalue, {kind: 'Object'});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (instr.effects == null) continue;
|
if (instr.effects == null) continue;
|
||||||
for (const effect of instr.effects) {
|
for (const effect of instr.effects) {
|
||||||
if (effect.kind === 'Create') {
|
if (effect.kind === 'Create') {
|
||||||
@@ -141,6 +117,15 @@ export function inferMutationAliasingRanges(
|
|||||||
} else if (effect.kind === 'CreateFrom') {
|
} else if (effect.kind === 'CreateFrom') {
|
||||||
state.createFrom(index++, effect.from, effect.into);
|
state.createFrom(index++, effect.from, effect.into);
|
||||||
} else if (effect.kind === 'Assign') {
|
} else if (effect.kind === 'Assign') {
|
||||||
|
/**
|
||||||
|
* TODO: Invariant that the node is not initialized yet
|
||||||
|
*
|
||||||
|
* InferFunctionExpressionAliasingEffectSignatures currently infers
|
||||||
|
* Assign effects in some places that should be Alias, leading to
|
||||||
|
* Assign effects that reinitialize a value. The end result appears to
|
||||||
|
* be fine, but we should fix that inference pass so that we add the
|
||||||
|
* invariant here.
|
||||||
|
*/
|
||||||
if (!state.nodes.has(effect.into.identifier)) {
|
if (!state.nodes.has(effect.into.identifier)) {
|
||||||
state.create(effect.into, {kind: 'Object'});
|
state.create(effect.into, {kind: 'Object'});
|
||||||
}
|
}
|
||||||
@@ -216,10 +201,6 @@ export function inferMutationAliasingRanges(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (VERBOSE) {
|
|
||||||
console.log(state.debug());
|
|
||||||
console.log(pretty(mutations));
|
|
||||||
}
|
|
||||||
for (const mutation of mutations) {
|
for (const mutation of mutations) {
|
||||||
state.mutate(
|
state.mutate(
|
||||||
mutation.index,
|
mutation.index,
|
||||||
@@ -234,9 +215,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);
|
||||||
}
|
}
|
||||||
if (DEBUG) {
|
|
||||||
console.log(pretty([...state.nodes.keys()]));
|
|
||||||
}
|
|
||||||
fn.aliasingEffects ??= [];
|
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;
|
||||||
@@ -458,9 +436,6 @@ export function inferMutationAliasingRanges(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (VERBOSE) {
|
|
||||||
console.log(printFunction(fn));
|
|
||||||
}
|
|
||||||
return errors.asResult();
|
return errors.asResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,11 +486,6 @@ class AliasingState {
|
|||||||
const fromNode = this.nodes.get(from.identifier);
|
const fromNode = this.nodes.get(from.identifier);
|
||||||
const toNode = this.nodes.get(into.identifier);
|
const toNode = this.nodes.get(into.identifier);
|
||||||
if (fromNode == null || toNode == null) {
|
if (fromNode == null || toNode == null) {
|
||||||
if (VERBOSE) {
|
|
||||||
console.log(
|
|
||||||
`skip: createFrom ${printPlace(from)}${!!fromNode} -> ${printPlace(into)}${!!toNode}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fromNode.edges.push({index, node: into.identifier, kind: 'alias'});
|
fromNode.edges.push({index, node: into.identifier, kind: 'alias'});
|
||||||
@@ -528,11 +498,6 @@ class AliasingState {
|
|||||||
const fromNode = this.nodes.get(from.identifier);
|
const fromNode = this.nodes.get(from.identifier);
|
||||||
const toNode = this.nodes.get(into.identifier);
|
const toNode = this.nodes.get(into.identifier);
|
||||||
if (fromNode == null || toNode == null) {
|
if (fromNode == null || toNode == null) {
|
||||||
if (VERBOSE) {
|
|
||||||
console.log(
|
|
||||||
`skip: capture ${printPlace(from)}${!!fromNode} -> ${printPlace(into)}${!!toNode}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fromNode.edges.push({index, node: into.identifier, kind: 'capture'});
|
fromNode.edges.push({index, node: into.identifier, kind: 'capture'});
|
||||||
@@ -545,11 +510,6 @@ class AliasingState {
|
|||||||
const fromNode = this.nodes.get(from.identifier);
|
const fromNode = this.nodes.get(from.identifier);
|
||||||
const toNode = this.nodes.get(into.identifier);
|
const toNode = this.nodes.get(into.identifier);
|
||||||
if (fromNode == null || toNode == null) {
|
if (fromNode == null || toNode == null) {
|
||||||
if (VERBOSE) {
|
|
||||||
console.log(
|
|
||||||
`skip: assign ${printPlace(from)}${!!fromNode} -> ${printPlace(into)}${!!toNode}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fromNode.edges.push({index, node: into.identifier, kind: 'alias'});
|
fromNode.edges.push({index, node: into.identifier, kind: 'alias'});
|
||||||
@@ -604,11 +564,6 @@ class AliasingState {
|
|||||||
loc: SourceLocation,
|
loc: SourceLocation,
|
||||||
errors: CompilerError,
|
errors: CompilerError,
|
||||||
): void {
|
): void {
|
||||||
if (DEBUG) {
|
|
||||||
console.log(
|
|
||||||
`mutate ix=${index} start=$${start.id} end=[${end}]${transitive ? ' transitive' : ''} kind=${kind}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const seen = new Set<Identifier>();
|
const seen = new Set<Identifier>();
|
||||||
const queue: Array<{
|
const queue: Array<{
|
||||||
place: Identifier;
|
place: Identifier;
|
||||||
@@ -623,18 +578,8 @@ class AliasingState {
|
|||||||
seen.add(current);
|
seen.add(current);
|
||||||
const node = this.nodes.get(current);
|
const node = this.nodes.get(current);
|
||||||
if (node == null) {
|
if (node == null) {
|
||||||
if (DEBUG) {
|
|
||||||
console.log(
|
|
||||||
`no node! ${printIdentifier(start)} for identifier ${printIdentifier(current)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (DEBUG) {
|
|
||||||
console.log(
|
|
||||||
` mutate $${node.id.id} transitive=${transitive} direction=${direction}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
node.id.mutableRange.end = makeInstructionId(
|
node.id.mutableRange.end = makeInstructionId(
|
||||||
Math.max(node.id.mutableRange.end, end),
|
Math.max(node.id.mutableRange.end, end),
|
||||||
);
|
);
|
||||||
@@ -701,37 +646,5 @@ class AliasingState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (DEBUG) {
|
|
||||||
const nodes = new Map();
|
|
||||||
for (const id of seen) {
|
|
||||||
const node = this.nodes.get(id);
|
|
||||||
nodes.set(id.id, node);
|
|
||||||
}
|
|
||||||
console.log(pretty(nodes));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debug(): string {
|
|
||||||
return pretty(this.nodes);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pretty(v: any): string {
|
|
||||||
return prettyFormat(v, {
|
|
||||||
plugins: [
|
|
||||||
{
|
|
||||||
test: v =>
|
|
||||||
v !== null && typeof v === 'object' && v.kind === 'Identifier',
|
|
||||||
serialize: v => printPlace(v),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: v =>
|
|
||||||
v !== null &&
|
|
||||||
typeof v === 'object' &&
|
|
||||||
typeof v.declarationId === 'number',
|
|
||||||
serialize: v =>
|
|
||||||
`${printIdentifier(v)}:${v.mutableRange.start}:${v.mutableRange.end}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -23,14 +23,9 @@ function Component({a, b, c}: {a: number; b: number; c: number}) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ValidateMemoization inputs={[a, b, c]} output={x} alwaysCheck={true} />;
|
<ValidateMemoization inputs={[a, b, c]} output={x} />;
|
||||||
{/* TODO: should only depend on c */}
|
{/* TODO: should only depend on c */}
|
||||||
<ValidateMemoization
|
<ValidateMemoization inputs={[a, b, c]} output={x[0]} />;
|
||||||
inputs={[a, b, c]}
|
|
||||||
output={x[0]}
|
|
||||||
alwaysCheck={true}
|
|
||||||
/>
|
|
||||||
;
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -98,7 +93,7 @@ function Component(t0) {
|
|||||||
}
|
}
|
||||||
let t3;
|
let t3;
|
||||||
if ($[9] !== t2 || $[10] !== x) {
|
if ($[9] !== t2 || $[10] !== x) {
|
||||||
t3 = <ValidateMemoization inputs={t2} output={x} alwaysCheck={true} />;
|
t3 = <ValidateMemoization inputs={t2} output={x} />;
|
||||||
$[9] = t2;
|
$[9] = t2;
|
||||||
$[10] = x;
|
$[10] = x;
|
||||||
$[11] = t3;
|
$[11] = t3;
|
||||||
@@ -117,7 +112,7 @@ function Component(t0) {
|
|||||||
}
|
}
|
||||||
let t5;
|
let t5;
|
||||||
if ($[16] !== t4 || $[17] !== x[0]) {
|
if ($[16] !== t4 || $[17] !== x[0]) {
|
||||||
t5 = <ValidateMemoization inputs={t4} output={x[0]} alwaysCheck={true} />;
|
t5 = <ValidateMemoization inputs={t4} output={x[0]} />;
|
||||||
$[16] = t4;
|
$[16] = t4;
|
||||||
$[17] = x[0];
|
$[17] = x[0];
|
||||||
$[18] = t5;
|
$[18] = t5;
|
||||||
|
|||||||
@@ -19,14 +19,9 @@ function Component({a, b, c}: {a: number; b: number; c: number}) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ValidateMemoization inputs={[a, b, c]} output={x} alwaysCheck={true} />;
|
<ValidateMemoization inputs={[a, b, c]} output={x} />;
|
||||||
{/* TODO: should only depend on c */}
|
{/* TODO: should only depend on c */}
|
||||||
<ValidateMemoization
|
<ValidateMemoization inputs={[a, b, c]} output={x[0]} />;
|
||||||
inputs={[a, b, c]}
|
|
||||||
output={x[0]}
|
|
||||||
alwaysCheck={true}
|
|
||||||
/>
|
|
||||||
;
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function Component({a, b}) {
|
|||||||
typedMutate(z, b);
|
typedMutate(z, b);
|
||||||
|
|
||||||
// TODO: this *should* only depend on `a`
|
// TODO: this *should* only depend on `a`
|
||||||
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
|
return <ValidateMemoization inputs={[a, b]} output={x} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FIXTURE_ENTRYPOINT = {
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
@@ -86,7 +86,7 @@ function Component(t0) {
|
|||||||
}
|
}
|
||||||
let t3;
|
let t3;
|
||||||
if ($[7] !== t2 || $[8] !== x) {
|
if ($[7] !== t2 || $[8] !== x) {
|
||||||
t3 = <ValidateMemoization inputs={t2} output={x} alwaysCheck={true} />;
|
t3 = <ValidateMemoization inputs={t2} output={x} />;
|
||||||
$[7] = t2;
|
$[7] = t2;
|
||||||
$[8] = x;
|
$[8] = x;
|
||||||
$[9] = t3;
|
$[9] = t3;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ function Component({a, b}) {
|
|||||||
typedMutate(z, b);
|
typedMutate(z, b);
|
||||||
|
|
||||||
// TODO: this *should* only depend on `a`
|
// TODO: this *should* only depend on `a`
|
||||||
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
|
return <ValidateMemoization inputs={[a, b]} output={x} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FIXTURE_ENTRYPOINT = {
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ function Component({a, b}) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ValidateMemoization inputs={[a]} output={o} alwaysCheck={true} />;
|
<ValidateMemoization inputs={[a]} output={o} />;
|
||||||
<ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
|
<ValidateMemoization inputs={[a, b]} output={x} />;
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ function Component(t0) {
|
|||||||
}
|
}
|
||||||
let t5;
|
let t5;
|
||||||
if ($[8] !== o || $[9] !== t4) {
|
if ($[8] !== o || $[9] !== t4) {
|
||||||
t5 = <ValidateMemoization inputs={t4} output={o} alwaysCheck={true} />;
|
t5 = <ValidateMemoization inputs={t4} output={o} />;
|
||||||
$[8] = o;
|
$[8] = o;
|
||||||
$[9] = t4;
|
$[9] = t4;
|
||||||
$[10] = t5;
|
$[10] = t5;
|
||||||
@@ -110,7 +110,7 @@ function Component(t0) {
|
|||||||
}
|
}
|
||||||
let t7;
|
let t7;
|
||||||
if ($[14] !== t6 || $[15] !== x) {
|
if ($[14] !== t6 || $[15] !== x) {
|
||||||
t7 = <ValidateMemoization inputs={t6} output={x} alwaysCheck={true} />;
|
t7 = <ValidateMemoization inputs={t6} output={x} />;
|
||||||
$[14] = t6;
|
$[14] = t6;
|
||||||
$[15] = x;
|
$[15] = x;
|
||||||
$[16] = t7;
|
$[16] = t7;
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ function Component({a, b}) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ValidateMemoization inputs={[a]} output={o} alwaysCheck={true} />;
|
<ValidateMemoization inputs={[a]} output={o} />;
|
||||||
<ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
|
<ValidateMemoization inputs={[a, b]} output={x} />;
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ function Component({a, b}: {a: number; b: number}) {
|
|||||||
// mutates x
|
// mutates x
|
||||||
typedMutate(z, b);
|
typedMutate(z, b);
|
||||||
|
|
||||||
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
|
return <ValidateMemoization inputs={[a, b]} output={x} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FIXTURE_ENTRYPOINT = {
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
@@ -85,7 +85,7 @@ function Component(t0) {
|
|||||||
}
|
}
|
||||||
let t3;
|
let t3;
|
||||||
if ($[7] !== t2 || $[8] !== x) {
|
if ($[7] !== t2 || $[8] !== x) {
|
||||||
t3 = <ValidateMemoization inputs={t2} output={x} alwaysCheck={true} />;
|
t3 = <ValidateMemoization inputs={t2} output={x} />;
|
||||||
$[7] = t2;
|
$[7] = t2;
|
||||||
$[8] = x;
|
$[8] = x;
|
||||||
$[9] = t3;
|
$[9] = t3;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ function Component({a, b}: {a: number; b: number}) {
|
|||||||
// mutates x
|
// mutates x
|
||||||
typedMutate(z, b);
|
typedMutate(z, b);
|
||||||
|
|
||||||
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
|
return <ValidateMemoization inputs={[a, b]} output={x} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FIXTURE_ENTRYPOINT = {
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ function Component({a, b}: {a: number; b: number}) {
|
|||||||
// mutates x
|
// mutates x
|
||||||
typedMutate(z, b);
|
typedMutate(z, b);
|
||||||
|
|
||||||
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
|
return <ValidateMemoization inputs={[a, b]} output={x} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FIXTURE_ENTRYPOINT = {
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
@@ -76,7 +76,7 @@ function Component(t0) {
|
|||||||
}
|
}
|
||||||
let t3;
|
let t3;
|
||||||
if ($[7] !== t2 || $[8] !== x) {
|
if ($[7] !== t2 || $[8] !== x) {
|
||||||
t3 = <ValidateMemoization inputs={t2} output={x} alwaysCheck={true} />;
|
t3 = <ValidateMemoization inputs={t2} output={x} />;
|
||||||
$[7] = t2;
|
$[7] = t2;
|
||||||
$[8] = x;
|
$[8] = x;
|
||||||
$[9] = t3;
|
$[9] = t3;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ function Component({a, b}: {a: number; b: number}) {
|
|||||||
// mutates x
|
// mutates x
|
||||||
typedMutate(z, b);
|
typedMutate(z, b);
|
||||||
|
|
||||||
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
|
return <ValidateMemoization inputs={[a, b]} output={x} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FIXTURE_ENTRYPOINT = {
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ function Component({a, b}) {
|
|||||||
// does not mutate x, so x should not depend on b
|
// does not mutate x, so x should not depend on b
|
||||||
typedMutate(z, b);
|
typedMutate(z, b);
|
||||||
|
|
||||||
return <ValidateMemoization inputs={[a]} output={x} alwaysCheck={true} />;
|
return <ValidateMemoization inputs={[a]} output={x} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FIXTURE_ENTRYPOINT = {
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
@@ -73,7 +73,7 @@ function Component(t0) {
|
|||||||
}
|
}
|
||||||
let t4;
|
let t4;
|
||||||
if ($[4] !== t3 || $[5] !== x) {
|
if ($[4] !== t3 || $[5] !== x) {
|
||||||
t4 = <ValidateMemoization inputs={t3} output={x} alwaysCheck={true} />;
|
t4 = <ValidateMemoization inputs={t3} output={x} />;
|
||||||
$[4] = t3;
|
$[4] = t3;
|
||||||
$[5] = x;
|
$[5] = x;
|
||||||
$[6] = t4;
|
$[6] = t4;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ function Component({a, b}) {
|
|||||||
// does not mutate x, so x should not depend on b
|
// does not mutate x, so x should not depend on b
|
||||||
typedMutate(z, b);
|
typedMutate(z, b);
|
||||||
|
|
||||||
return <ValidateMemoization inputs={[a]} output={x} alwaysCheck={true} />;
|
return <ValidateMemoization inputs={[a]} output={x} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FIXTURE_ENTRYPOINT = {
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ function Component({a, b}) {
|
|||||||
// could mutate x
|
// could mutate x
|
||||||
typedMutate(z, b);
|
typedMutate(z, b);
|
||||||
|
|
||||||
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
|
return <ValidateMemoization inputs={[a, b]} output={x} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FIXTURE_ENTRYPOINT = {
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
@@ -92,7 +92,7 @@ function Component(t0) {
|
|||||||
}
|
}
|
||||||
let t4;
|
let t4;
|
||||||
if ($[9] !== t3 || $[10] !== x) {
|
if ($[9] !== t3 || $[10] !== x) {
|
||||||
t4 = <ValidateMemoization inputs={t3} output={x} alwaysCheck={true} />;
|
t4 = <ValidateMemoization inputs={t3} output={x} />;
|
||||||
$[9] = t3;
|
$[9] = t3;
|
||||||
$[10] = x;
|
$[10] = x;
|
||||||
$[11] = t4;
|
$[11] = t4;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ function Component({a, b}) {
|
|||||||
// could mutate x
|
// could mutate x
|
||||||
typedMutate(z, b);
|
typedMutate(z, b);
|
||||||
|
|
||||||
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
|
return <ValidateMemoization inputs={[a, b]} output={x} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FIXTURE_ENTRYPOINT = {
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
```javascript
|
```javascript
|
||||||
// @enableNewMutationAliasingModel
|
// @enableNewMutationAliasingModel
|
||||||
|
|
||||||
|
import {useMemo} from 'react';
|
||||||
import {
|
import {
|
||||||
identity,
|
identity,
|
||||||
makeObject_Primitives,
|
makeObject_Primitives,
|
||||||
@@ -14,7 +15,7 @@ import {
|
|||||||
|
|
||||||
function Component({a, b}) {
|
function Component({a, b}) {
|
||||||
// create a mutable value with input `a`
|
// create a mutable value with input `a`
|
||||||
const x = makeObject_Primitives(a);
|
const x = useMemo(() => makeObject_Primitives(a), [a]);
|
||||||
|
|
||||||
// freeze the value
|
// freeze the value
|
||||||
useIdentity(x);
|
useIdentity(x);
|
||||||
@@ -49,6 +50,7 @@ export const FIXTURE_ENTRYPOINT = {
|
|||||||
```javascript
|
```javascript
|
||||||
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
|
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
|
||||||
|
|
||||||
|
import { useMemo } from "react";
|
||||||
import {
|
import {
|
||||||
identity,
|
identity,
|
||||||
makeObject_Primitives,
|
makeObject_Primitives,
|
||||||
@@ -61,13 +63,15 @@ function Component(t0) {
|
|||||||
const $ = _c(7);
|
const $ = _c(7);
|
||||||
const { a, b } = t0;
|
const { a, b } = t0;
|
||||||
let t1;
|
let t1;
|
||||||
|
let t2;
|
||||||
if ($[0] !== a) {
|
if ($[0] !== a) {
|
||||||
t1 = makeObject_Primitives(a);
|
t2 = makeObject_Primitives(a);
|
||||||
$[0] = a;
|
$[0] = a;
|
||||||
$[1] = t1;
|
$[1] = t2;
|
||||||
} else {
|
} else {
|
||||||
t1 = $[1];
|
t2 = $[1];
|
||||||
}
|
}
|
||||||
|
t1 = t2;
|
||||||
const x = t1;
|
const x = t1;
|
||||||
|
|
||||||
useIdentity(x);
|
useIdentity(x);
|
||||||
@@ -75,24 +79,24 @@ function Component(t0) {
|
|||||||
const x2 = typedIdentity(x);
|
const x2 = typedIdentity(x);
|
||||||
|
|
||||||
identity(x2, b);
|
identity(x2, b);
|
||||||
let t2;
|
|
||||||
if ($[2] !== a) {
|
|
||||||
t2 = [a];
|
|
||||||
$[2] = a;
|
|
||||||
$[3] = t2;
|
|
||||||
} else {
|
|
||||||
t2 = $[3];
|
|
||||||
}
|
|
||||||
let t3;
|
let t3;
|
||||||
if ($[4] !== t2 || $[5] !== x) {
|
if ($[2] !== a) {
|
||||||
t3 = <ValidateMemoization inputs={t2} output={x} />;
|
t3 = [a];
|
||||||
$[4] = t2;
|
$[2] = a;
|
||||||
$[5] = x;
|
$[3] = t3;
|
||||||
$[6] = t3;
|
|
||||||
} else {
|
} else {
|
||||||
t3 = $[6];
|
t3 = $[3];
|
||||||
}
|
}
|
||||||
return t3;
|
let t4;
|
||||||
|
if ($[4] !== t3 || $[5] !== x) {
|
||||||
|
t4 = <ValidateMemoization inputs={t3} output={x} />;
|
||||||
|
$[4] = t3;
|
||||||
|
$[5] = x;
|
||||||
|
$[6] = t4;
|
||||||
|
} else {
|
||||||
|
t4 = $[6];
|
||||||
|
}
|
||||||
|
return t4;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FIXTURE_ENTRYPOINT = {
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// @enableNewMutationAliasingModel
|
// @enableNewMutationAliasingModel
|
||||||
|
|
||||||
|
import {useMemo} from 'react';
|
||||||
import {
|
import {
|
||||||
identity,
|
identity,
|
||||||
makeObject_Primitives,
|
makeObject_Primitives,
|
||||||
@@ -10,7 +11,7 @@ import {
|
|||||||
|
|
||||||
function Component({a, b}) {
|
function Component({a, b}) {
|
||||||
// create a mutable value with input `a`
|
// create a mutable value with input `a`
|
||||||
const x = makeObject_Primitives(a);
|
const x = useMemo(() => makeObject_Primitives(a), [a]);
|
||||||
|
|
||||||
// freeze the value
|
// freeze the value
|
||||||
useIdentity(x);
|
useIdentity(x);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
## Input
|
## Input
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
import {useMemo} from 'react';
|
||||||
import {ValidateMemoization} from 'shared-runtime';
|
import {ValidateMemoization} from 'shared-runtime';
|
||||||
|
|
||||||
function Component({a, b, c}) {
|
function Component({a, b, c}) {
|
||||||
@@ -13,9 +14,21 @@ function Component({a, b, c}) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ValidateMemoization inputs={[a, c]} output={map} />
|
<ValidateMemoization
|
||||||
<ValidateMemoization inputs={[a, c]} output={mapAlias} />
|
inputs={[a, c]}
|
||||||
<ValidateMemoization inputs={[b]} output={[hasB]} />
|
output={map}
|
||||||
|
onlyCheckCompiled={true}
|
||||||
|
/>
|
||||||
|
<ValidateMemoization
|
||||||
|
inputs={[a, c]}
|
||||||
|
output={mapAlias}
|
||||||
|
onlyCheckCompiled={true}
|
||||||
|
/>
|
||||||
|
<ValidateMemoization
|
||||||
|
inputs={[b]}
|
||||||
|
output={[hasB]}
|
||||||
|
onlyCheckCompiled={true}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -44,6 +57,7 @@ export const FIXTURE_ENTRYPOINT = {
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import { c as _c } from "react/compiler-runtime";
|
import { c as _c } from "react/compiler-runtime";
|
||||||
|
import { useMemo } from "react";
|
||||||
import { ValidateMemoization } from "shared-runtime";
|
import { ValidateMemoization } from "shared-runtime";
|
||||||
|
|
||||||
function Component(t0) {
|
function Component(t0) {
|
||||||
@@ -76,7 +90,9 @@ function Component(t0) {
|
|||||||
}
|
}
|
||||||
let t2;
|
let t2;
|
||||||
if ($[7] !== map || $[8] !== t1) {
|
if ($[7] !== map || $[8] !== t1) {
|
||||||
t2 = <ValidateMemoization inputs={t1} output={map} />;
|
t2 = (
|
||||||
|
<ValidateMemoization inputs={t1} output={map} onlyCheckCompiled={true} />
|
||||||
|
);
|
||||||
$[7] = map;
|
$[7] = map;
|
||||||
$[8] = t1;
|
$[8] = t1;
|
||||||
$[9] = t2;
|
$[9] = t2;
|
||||||
@@ -94,7 +110,13 @@ function Component(t0) {
|
|||||||
}
|
}
|
||||||
let t4;
|
let t4;
|
||||||
if ($[13] !== mapAlias || $[14] !== t3) {
|
if ($[13] !== mapAlias || $[14] !== t3) {
|
||||||
t4 = <ValidateMemoization inputs={t3} output={mapAlias} />;
|
t4 = (
|
||||||
|
<ValidateMemoization
|
||||||
|
inputs={t3}
|
||||||
|
output={mapAlias}
|
||||||
|
onlyCheckCompiled={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
$[13] = mapAlias;
|
$[13] = mapAlias;
|
||||||
$[14] = t3;
|
$[14] = t3;
|
||||||
$[15] = t4;
|
$[15] = t4;
|
||||||
@@ -119,7 +141,9 @@ function Component(t0) {
|
|||||||
}
|
}
|
||||||
let t7;
|
let t7;
|
||||||
if ($[20] !== t5 || $[21] !== t6) {
|
if ($[20] !== t5 || $[21] !== t6) {
|
||||||
t7 = <ValidateMemoization inputs={t5} output={t6} />;
|
t7 = (
|
||||||
|
<ValidateMemoization inputs={t5} output={t6} onlyCheckCompiled={true} />
|
||||||
|
);
|
||||||
$[20] = t5;
|
$[20] = t5;
|
||||||
$[21] = t6;
|
$[21] = t6;
|
||||||
$[22] = t7;
|
$[22] = t7;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import {useMemo} from 'react';
|
||||||
import {ValidateMemoization} from 'shared-runtime';
|
import {ValidateMemoization} from 'shared-runtime';
|
||||||
|
|
||||||
function Component({a, b, c}) {
|
function Component({a, b, c}) {
|
||||||
@@ -9,9 +10,21 @@ function Component({a, b, c}) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ValidateMemoization inputs={[a, c]} output={map} />
|
<ValidateMemoization
|
||||||
<ValidateMemoization inputs={[a, c]} output={mapAlias} />
|
inputs={[a, c]}
|
||||||
<ValidateMemoization inputs={[b]} output={[hasB]} />
|
output={map}
|
||||||
|
onlyCheckCompiled={true}
|
||||||
|
/>
|
||||||
|
<ValidateMemoization
|
||||||
|
inputs={[a, c]}
|
||||||
|
output={mapAlias}
|
||||||
|
onlyCheckCompiled={true}
|
||||||
|
/>
|
||||||
|
<ValidateMemoization
|
||||||
|
inputs={[b]}
|
||||||
|
output={[hasB]}
|
||||||
|
onlyCheckCompiled={true}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
## Input
|
## Input
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
import {useMemo} from 'react';
|
||||||
import {ValidateMemoization} from 'shared-runtime';
|
import {ValidateMemoization} from 'shared-runtime';
|
||||||
|
|
||||||
function Component({a, b, c}) {
|
function Component({a, b, c}) {
|
||||||
@@ -13,9 +14,21 @@ function Component({a, b, c}) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ValidateMemoization inputs={[a, c]} output={set} />
|
<ValidateMemoization
|
||||||
<ValidateMemoization inputs={[a, c]} output={setAlias} />
|
inputs={[a, c]}
|
||||||
<ValidateMemoization inputs={[b]} output={[hasB]} />
|
output={set}
|
||||||
|
onlyCheckCompiled={true}
|
||||||
|
/>
|
||||||
|
<ValidateMemoization
|
||||||
|
inputs={[a, c]}
|
||||||
|
output={setAlias}
|
||||||
|
onlyCheckCompiled={true}
|
||||||
|
/>
|
||||||
|
<ValidateMemoization
|
||||||
|
inputs={[b]}
|
||||||
|
output={[hasB]}
|
||||||
|
onlyCheckCompiled={true}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -44,6 +57,7 @@ export const FIXTURE_ENTRYPOINT = {
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import { c as _c } from "react/compiler-runtime";
|
import { c as _c } from "react/compiler-runtime";
|
||||||
|
import { useMemo } from "react";
|
||||||
import { ValidateMemoization } from "shared-runtime";
|
import { ValidateMemoization } from "shared-runtime";
|
||||||
|
|
||||||
function Component(t0) {
|
function Component(t0) {
|
||||||
@@ -76,7 +90,9 @@ function Component(t0) {
|
|||||||
}
|
}
|
||||||
let t2;
|
let t2;
|
||||||
if ($[7] !== set || $[8] !== t1) {
|
if ($[7] !== set || $[8] !== t1) {
|
||||||
t2 = <ValidateMemoization inputs={t1} output={set} />;
|
t2 = (
|
||||||
|
<ValidateMemoization inputs={t1} output={set} onlyCheckCompiled={true} />
|
||||||
|
);
|
||||||
$[7] = set;
|
$[7] = set;
|
||||||
$[8] = t1;
|
$[8] = t1;
|
||||||
$[9] = t2;
|
$[9] = t2;
|
||||||
@@ -94,7 +110,13 @@ function Component(t0) {
|
|||||||
}
|
}
|
||||||
let t4;
|
let t4;
|
||||||
if ($[13] !== setAlias || $[14] !== t3) {
|
if ($[13] !== setAlias || $[14] !== t3) {
|
||||||
t4 = <ValidateMemoization inputs={t3} output={setAlias} />;
|
t4 = (
|
||||||
|
<ValidateMemoization
|
||||||
|
inputs={t3}
|
||||||
|
output={setAlias}
|
||||||
|
onlyCheckCompiled={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
$[13] = setAlias;
|
$[13] = setAlias;
|
||||||
$[14] = t3;
|
$[14] = t3;
|
||||||
$[15] = t4;
|
$[15] = t4;
|
||||||
@@ -119,7 +141,9 @@ function Component(t0) {
|
|||||||
}
|
}
|
||||||
let t7;
|
let t7;
|
||||||
if ($[20] !== t5 || $[21] !== t6) {
|
if ($[20] !== t5 || $[21] !== t6) {
|
||||||
t7 = <ValidateMemoization inputs={t5} output={t6} />;
|
t7 = (
|
||||||
|
<ValidateMemoization inputs={t5} output={t6} onlyCheckCompiled={true} />
|
||||||
|
);
|
||||||
$[20] = t5;
|
$[20] = t5;
|
||||||
$[21] = t6;
|
$[21] = t6;
|
||||||
$[22] = t7;
|
$[22] = t7;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import {useMemo} from 'react';
|
||||||
import {ValidateMemoization} from 'shared-runtime';
|
import {ValidateMemoization} from 'shared-runtime';
|
||||||
|
|
||||||
function Component({a, b, c}) {
|
function Component({a, b, c}) {
|
||||||
@@ -9,9 +10,21 @@ function Component({a, b, c}) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ValidateMemoization inputs={[a, c]} output={set} />
|
<ValidateMemoization
|
||||||
<ValidateMemoization inputs={[a, c]} output={setAlias} />
|
inputs={[a, c]}
|
||||||
<ValidateMemoization inputs={[b]} output={[hasB]} />
|
output={set}
|
||||||
|
onlyCheckCompiled={true}
|
||||||
|
/>
|
||||||
|
<ValidateMemoization
|
||||||
|
inputs={[a, c]}
|
||||||
|
output={setAlias}
|
||||||
|
onlyCheckCompiled={true}
|
||||||
|
/>
|
||||||
|
<ValidateMemoization
|
||||||
|
inputs={[b]}
|
||||||
|
output={[hasB]}
|
||||||
|
onlyCheckCompiled={true}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -269,12 +269,10 @@ export function ValidateMemoization({
|
|||||||
inputs,
|
inputs,
|
||||||
output: rawOutput,
|
output: rawOutput,
|
||||||
onlyCheckCompiled = false,
|
onlyCheckCompiled = false,
|
||||||
alwaysCheck = false,
|
|
||||||
}: {
|
}: {
|
||||||
inputs: Array<any>;
|
inputs: Array<any>;
|
||||||
output: any;
|
output: any;
|
||||||
onlyCheckCompiled?: boolean;
|
onlyCheckCompiled?: boolean;
|
||||||
alwaysCheck?: boolean;
|
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
'use no forget';
|
'use no forget';
|
||||||
// Wrap rawOutput as it might be a function, which useState would invoke.
|
// Wrap rawOutput as it might be a function, which useState would invoke.
|
||||||
@@ -282,7 +280,7 @@ export function ValidateMemoization({
|
|||||||
const [previousInputs, setPreviousInputs] = React.useState(inputs);
|
const [previousInputs, setPreviousInputs] = React.useState(inputs);
|
||||||
const [previousOutput, setPreviousOutput] = React.useState(output);
|
const [previousOutput, setPreviousOutput] = React.useState(output);
|
||||||
if (
|
if (
|
||||||
alwaysCheck ||
|
!onlyCheckCompiled ||
|
||||||
(onlyCheckCompiled &&
|
(onlyCheckCompiled &&
|
||||||
(globalThis as any).__SNAP_EVALUATOR_MODE === 'forget')
|
(globalThis as any).__SNAP_EVALUATOR_MODE === 'forget')
|
||||||
) {
|
) {
|
||||||
|
|||||||
Reference in New Issue
Block a user