-
Notifications
You must be signed in to change notification settings - Fork 48.5k
[compiler] Infer alias effects for function expressions #33151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: gh/josephsavona/81/base
Are you sure you want to change the base?
Changes from all commits
cd2537c
c3a733f
97442d7
1fec32e
25ed1a1
6719631
dd3625b
0111754
ba46290
ed8a0b4
49a8425
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,6 +60,10 @@ function inferInstr( | |
alias = instrValue.value; | ||
break; | ||
} | ||
case 'IteratorNext': { | ||
alias = instrValue.collection; | ||
break; | ||
} | ||
Comment on lines
+63
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this was a missing alias, oops |
||
default: | ||
return; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/** | ||
* 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, Identifier} from '../HIR/HIR'; | ||
import DisjointSet from '../Utils/DisjointSet'; | ||
|
||
export function inferAliasForFunctionCaptureEffects( | ||
func: HIRFunction, | ||
aliases: DisjointSet<Identifier>, | ||
): void { | ||
for (const [_, block] of func.body.blocks) { | ||
for (const instr of block.instructions) { | ||
const {value} = instr; | ||
if ( | ||
value.kind !== 'FunctionExpression' && | ||
value.kind !== 'ObjectMethod' | ||
) { | ||
continue; | ||
} | ||
const loweredFunction = value.loweredFunc.func; | ||
for (const effect of loweredFunction.effects ?? []) { | ||
if (effect.kind !== 'CaptureEffect') { | ||
continue; | ||
} | ||
aliases.union([...effect.places].map(place => place.identifier)); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -95,45 +95,58 @@ function inheritFunctionEffects( | |
|
||
return effects | ||
.flatMap(effect => { | ||
if (effect.kind === 'GlobalMutation' || effect.kind === 'ReactMutation') { | ||
return [effect]; | ||
} else { | ||
const effects: Array<FunctionEffect | null> = []; | ||
CompilerError.invariant(effect.kind === 'ContextMutation', { | ||
reason: 'Expected ContextMutation', | ||
loc: null, | ||
}); | ||
/** | ||
* Contextual effects need to be replayed against the current inference | ||
* state, which may know more about the value to which the effect applied. | ||
* The main cases are: | ||
* 1. The mutated context value is _still_ a context value in the current scope, | ||
* so we have to continue propagating the original context mutation. | ||
* 2. The mutated context value is a mutable value in the current scope, | ||
* so the context mutation was fine and we can skip propagating the effect. | ||
* 3. The mutated context value is an immutable value in the current scope, | ||
* resulting in a non-ContextMutation FunctionEffect. We propagate that new, | ||
* more detailed effect to the current function context. | ||
*/ | ||
for (const place of effect.places) { | ||
if (state.isDefined(place)) { | ||
const replayedEffect = inferOperandEffect(state, { | ||
...place, | ||
loc: effect.loc, | ||
effect: effect.effect, | ||
}); | ||
if (replayedEffect != null) { | ||
if (replayedEffect.kind === 'ContextMutation') { | ||
// Case 1, still a context value so propagate the original effect | ||
effects.push(effect); | ||
} else { | ||
// Case 3, immutable value so propagate the more precise effect | ||
effects.push(replayedEffect); | ||
} | ||
} // else case 2, local mutable value so this effect was fine | ||
switch (effect.kind) { | ||
case 'GlobalMutation': | ||
case 'ReactMutation': { | ||
return [effect]; | ||
} | ||
case 'ContextMutation': { | ||
const effects: Array<FunctionEffect | null> = []; | ||
CompilerError.invariant(effect.kind === 'ContextMutation', { | ||
reason: 'Expected ContextMutation', | ||
loc: null, | ||
}); | ||
/** | ||
* Contextual effects need to be replayed against the current inference | ||
* state, which may know more about the value to which the effect applied. | ||
* The main cases are: | ||
* 1. The mutated context value is _still_ a context value in the current scope, | ||
* so we have to continue propagating the original context mutation. | ||
* 2. The mutated context value is a mutable value in the current scope, | ||
* so the context mutation was fine and we can skip propagating the effect. | ||
* 3. The mutated context value is an immutable value in the current scope, | ||
* resulting in a non-ContextMutation FunctionEffect. We propagate that new, | ||
* more detailed effect to the current function context. | ||
*/ | ||
for (const place of effect.places) { | ||
if (state.isDefined(place)) { | ||
const replayedEffect = inferOperandEffect(state, { | ||
...place, | ||
loc: effect.loc, | ||
effect: effect.effect, | ||
}); | ||
if (replayedEffect != null) { | ||
if (replayedEffect.kind === 'ContextMutation') { | ||
// Case 1, still a context value so propagate the original effect | ||
effects.push(effect); | ||
} else { | ||
// Case 3, immutable value so propagate the more precise effect | ||
effects.push(replayedEffect); | ||
} | ||
} // else case 2, local mutable value so this effect was fine | ||
} | ||
} | ||
return effects; | ||
} | ||
case 'CaptureEffect': { | ||
return []; | ||
} | ||
default: { | ||
assertExhaustive( | ||
effect, | ||
`Unexpected effect kind '${(effect as any).kind}'`, | ||
); | ||
} | ||
Comment on lines
+141
to
149
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. refactoring to a switch (no changes to the existing cases) and adding the new case |
||
return effects; | ||
} | ||
}) | ||
.filter((effect): effect is FunctionEffect => effect != null); | ||
|
@@ -298,26 +311,31 @@ export function inferTerminalFunctionEffects( | |
export function transformFunctionEffectErrors( | ||
functionEffects: Array<FunctionEffect>, | ||
): Array<CompilerErrorDetailOptions> { | ||
return functionEffects.map(eff => { | ||
switch (eff.kind) { | ||
case 'ReactMutation': | ||
case 'GlobalMutation': { | ||
return eff.error; | ||
} | ||
case 'ContextMutation': { | ||
return { | ||
severity: ErrorSeverity.Invariant, | ||
reason: `Unexpected ContextMutation in top-level function effects`, | ||
loc: eff.loc, | ||
}; | ||
return functionEffects | ||
.map(eff => { | ||
switch (eff.kind) { | ||
case 'ReactMutation': | ||
case 'GlobalMutation': { | ||
return eff.error; | ||
} | ||
case 'ContextMutation': { | ||
return { | ||
severity: ErrorSeverity.Invariant, | ||
reason: `Unexpected ContextMutation in top-level function effects`, | ||
loc: eff.loc, | ||
}; | ||
} | ||
case 'CaptureEffect': { | ||
return null; | ||
} | ||
Comment on lines
+328
to
+330
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. adding the new case |
||
default: | ||
assertExhaustive( | ||
eff, | ||
`Unexpected function effect kind \`${(eff as any).kind}\``, | ||
); | ||
} | ||
default: | ||
assertExhaustive( | ||
eff, | ||
`Unexpected function effect kind \`${(eff as any).kind}\``, | ||
); | ||
} | ||
}); | ||
}) | ||
.filter(eff => eff != null) as Array<CompilerErrorDetailOptions>; | ||
} | ||
|
||
function isEffectSafeOutsideRender(effect: FunctionEffect): boolean { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
refactoring to a switch statement, handling ReactMutation case (prev printed incorrectly as "GlobalMutation") and handling the new effect type