-
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?
Conversation
This is a stab at addressing a pattern that @mofeiZ and I have both stumbled across. Today, FunctionExpression's context list describes values from the outer context that are accessed in the function, and with what effect they were accessed. This allows us to describe the fact that a value from the outer context is known to be mutated inside a function expression, or is known to be captured (aliased) into some other value in the function expression. However, the basic `Effect` kind is insufficient to describe the full semantics. Notably, it doesn't let us describe more complex aliasing relationships. From an example @mofeiZ added: ```js const x = {}; const y = {}; const f = () => { const a = [y]; const b = x; // this sets y.x = x a[0].x = b; } f(); mutate(y.x); // which means this mutates x! ``` Here, the Effect on the context operands are `[mutate y, read x]`. The `mutate y` is bc of the array push. But the `read x` is surprising — `x` is captured into `y`, but there is no subsequent mutation of y or x, so we consider this a read. But as the comments indicate, the final line mutates x! We need to reflect the fact that even though x isn't mutated inside the function, it is aliased into y, such that if y is subsequently mutated that this should count as a mutation of x too. The idea of this PR is to extend the FunctionEffect type with a CaptureEffect variant which lists out the aliasing groups that occur inside the function expression. This allows us to bubble up the results of alias analysis from inside a function. The idea is to: * Return the alias sets from InferMutableRanges * Augment them with capturing of the form above, handling cases such as the `a[0].x = b` * For each alias group, record a CaptureEffect for any group that contains 2+ context operands * Extend the alias sets in the _outer_ function with the CaptureEffect sets from FunctionExpression/ObjectMethod instructions. This isn't quite right yet, just sharing early hacking. [ghstack-poisoned]
This is a stab at addressing a pattern that mofeiz and I have both stumbled across. Today, FunctionExpression's context list describes values from the outer context that are accessed in the function, and with what effect they were accessed. This allows us to describe the fact that a value from the outer context is known to be mutated inside a function expression, or is known to be captured (aliased) into some other value in the function expression. However, the basic `Effect` kind is insufficient to describe the full semantics. Notably, it doesn't let us describe more complex aliasing relationships. From an example mofeiz added: ```js const x = {}; const y = {}; const f = () => { const a = [y]; const b = x; // this sets y.x = x a[0].x = b; } f(); mutate(y.x); // which means this mutates x! ``` Here, the Effect on the context operands are `[mutate y, read x]`. The `mutate y` is bc of the array push. But the `read x` is surprising — `x` is captured into `y`, but there is no subsequent mutation of y or x, so we consider this a read. But as the comments indicate, the final line mutates x! We need to reflect the fact that even though x isn't mutated inside the function, it is aliased into y, such that if y is subsequently mutated that this should count as a mutation of x too. The idea of this PR is to extend the FunctionEffect type with a CaptureEffect variant which lists out the aliasing groups that occur inside the function expression. This allows us to bubble up the results of alias analysis from inside a function. The idea is to: * Return the alias sets from InferMutableRanges * Augment them with capturing of the form above, handling cases such as the `a[0].x = b` * For each alias group, record a CaptureEffect for any group that contains 2+ context operands * Extend the alias sets in the _outer_ function with the CaptureEffect sets from FunctionExpression/ObjectMethod instructions. This isn't quite right yet, just sharing early hacking. ghstack-source-id: 0056007 Pull Request resolved: #33151
This is a stab at addressing a pattern that mofeiz and I have both stumbled across. Today, FunctionExpression's context list describes values from the outer context that are accessed in the function, and with what effect they were accessed. This allows us to describe the fact that a value from the outer context is known to be mutated inside a function expression, or is known to be captured (aliased) into some other value in the function expression. However, the basic `Effect` kind is insufficient to describe the full semantics. Notably, it doesn't let us describe more complex aliasing relationships. From an example mofeiz added: ```js const x = {}; const y = {}; const f = () => { const a = [y]; const b = x; // this sets y.x = x a[0].x = b; } f(); mutate(y.x); // which means this mutates x! ``` Here, the Effect on the context operands are `[mutate y, read x]`. The `mutate y` is bc of the array push. But the `read x` is surprising — `x` is captured into `y`, but there is no subsequent mutation of y or x, so we consider this a read. But as the comments indicate, the final line mutates x! We need to reflect the fact that even though x isn't mutated inside the function, it is aliased into y, such that if y is subsequently mutated that this should count as a mutation of x too. The idea of this PR is to extend the FunctionEffect type with a CaptureEffect variant which lists out the aliasing groups that occur inside the function expression. This allows us to bubble up the results of alias analysis from inside a function. The idea is to: * Return the alias sets from InferMutableRanges * Augment them with capturing of the form above, handling cases such as the `a[0].x = b` * For each alias group, record a CaptureEffect for any group that contains 2+ context operands * Extend the alias sets in the _outer_ function with the CaptureEffect sets from FunctionExpression/ObjectMethod instructions. This isn't quite right yet, just sharing early hacking. [ghstack-poisoned]
This is a stab at addressing a pattern that mofeiz and I have both stumbled across. Today, FunctionExpression's context list describes values from the outer context that are accessed in the function, and with what effect they were accessed. This allows us to describe the fact that a value from the outer context is known to be mutated inside a function expression, or is known to be captured (aliased) into some other value in the function expression. However, the basic `Effect` kind is insufficient to describe the full semantics. Notably, it doesn't let us describe more complex aliasing relationships. From an example mofeiz added: ```js const x = {}; const y = {}; const f = () => { const a = [y]; const b = x; // this sets y.x = x a[0].x = b; } f(); mutate(y.x); // which means this mutates x! ``` Here, the Effect on the context operands are `[mutate y, read x]`. The `mutate y` is bc of the array push. But the `read x` is surprising — `x` is captured into `y`, but there is no subsequent mutation of y or x, so we consider this a read. But as the comments indicate, the final line mutates x! We need to reflect the fact that even though x isn't mutated inside the function, it is aliased into y, such that if y is subsequently mutated that this should count as a mutation of x too. The idea of this PR is to extend the FunctionEffect type with a CaptureEffect variant which lists out the aliasing groups that occur inside the function expression. This allows us to bubble up the results of alias analysis from inside a function. The idea is to: * Return the alias sets from InferMutableRanges * Augment them with capturing of the form above, handling cases such as the `a[0].x = b` * For each alias group, record a CaptureEffect for any group that contains 2+ context operands * Extend the alias sets in the _outer_ function with the CaptureEffect sets from FunctionExpression/ObjectMethod instructions. As part of this, I realized that our handling of PropertyStore's effect wasn't quite right. We used a store effect for the object, but only if it was a known object type — otherwise we recorded it as a mutation. But a PropertyStore really always is a store — it only mutates direct aliases of a value, not any interior objects that are captured. So I updated to always use store for known properties, and use mutate for computed properties. The latter is still also wrong, but i want to debug the change there separately. ghstack-source-id: 761680a Pull Request resolved: #33151
This is a stab at addressing a pattern that mofeiz and I have both stumbled across. Today, FunctionExpression's context list describes values from the outer context that are accessed in the function, and with what effect they were accessed. This allows us to describe the fact that a value from the outer context is known to be mutated inside a function expression, or is known to be captured (aliased) into some other value in the function expression. However, the basic `Effect` kind is insufficient to describe the full semantics. Notably, it doesn't let us describe more complex aliasing relationships. From an example mofeiz added: ```js const x = {}; const y = {}; const f = () => { const a = [y]; const b = x; // this sets y.x = x a[0].x = b; } f(); mutate(y.x); // which means this mutates x! ``` Here, the Effect on the context operands are `[mutate y, read x]`. The `mutate y` is bc of the array push. But the `read x` is surprising — `x` is captured into `y`, but there is no subsequent mutation of y or x, so we consider this a read. But as the comments indicate, the final line mutates x! We need to reflect the fact that even though x isn't mutated inside the function, it is aliased into y, such that if y is subsequently mutated that this should count as a mutation of x too. The idea of this PR is to extend the FunctionEffect type with a CaptureEffect variant which lists out the aliasing groups that occur inside the function expression. This allows us to bubble up the results of alias analysis from inside a function. The idea is to: * Return the alias sets from InferMutableRanges * Augment them with capturing of the form above, handling cases such as the `a[0].x = b` * For each alias group, record a CaptureEffect for any group that contains 2+ context operands * Extend the alias sets in the _outer_ function with the CaptureEffect sets from FunctionExpression/ObjectMethod instructions. This isn't quite right yet, just sharing early hacking. [ghstack-poisoned]
This is a stab at addressing a pattern that mofeiz and I have both stumbled across. Today, FunctionExpression's context list describes values from the outer context that are accessed in the function, and with what effect they were accessed. This allows us to describe the fact that a value from the outer context is known to be mutated inside a function expression, or is known to be captured (aliased) into some other value in the function expression. However, the basic `Effect` kind is insufficient to describe the full semantics. Notably, it doesn't let us describe more complex aliasing relationships. From an example mofeiz added: ```js const x = {}; const y = {}; const f = () => { const a = [y]; const b = x; // this sets y.x = x a[0].x = b; } f(); mutate(y.x); // which means this mutates x! ``` Here, the Effect on the context operands are `[mutate y, read x]`. The `mutate y` is bc of the array push. But the `read x` is surprising — `x` is captured into `y`, but there is no subsequent mutation of y or x, so we consider this a read. But as the comments indicate, the final line mutates x! We need to reflect the fact that even though x isn't mutated inside the function, it is aliased into y, such that if y is subsequently mutated that this should count as a mutation of x too. The idea of this PR is to extend the FunctionEffect type with a CaptureEffect variant which lists out the aliasing groups that occur inside the function expression. This allows us to bubble up the results of alias analysis from inside a function. The idea is to: * Return the alias sets from InferMutableRanges * Augment them with capturing of the form above, handling cases such as the `a[0].x = b` * For each alias group, record a CaptureEffect for any group that contains 2+ context operands * Extend the alias sets in the _outer_ function with the CaptureEffect sets from FunctionExpression/ObjectMethod instructions. As part of this, I realized that our handling of PropertyStore's effect wasn't quite right. We used a store effect for the object, but only if it was a known object type — otherwise we recorded it as a mutation. But a PropertyStore really always is a store — it only mutates direct aliases of a value, not any interior objects that are captured. So I updated to always use store for known properties, and use mutate for computed properties. The latter is still also wrong, but i want to debug the change there separately. ghstack-source-id: d8bf611 Pull Request resolved: #33151
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.
still work in progress, a couple fixtures change when they shouldn't
...piler/src/__tests__/fixtures/compiler/context-variable-reassigned-reactive-capture.expect.md
Outdated
Show resolved
Hide resolved
...t-compiler/src/__tests__/fixtures/compiler/repro-returned-inner-fn-mutates-context.expect.md
Outdated
Show resolved
Hide resolved
This is a stab at addressing a pattern that mofeiz and I have both stumbled across. Today, FunctionExpression's context list describes values from the outer context that are accessed in the function, and with what effect they were accessed. This allows us to describe the fact that a value from the outer context is known to be mutated inside a function expression, or is known to be captured (aliased) into some other value in the function expression. However, the basic `Effect` kind is insufficient to describe the full semantics. Notably, it doesn't let us describe more complex aliasing relationships. From an example mofeiz added: ```js const x = {}; const y = {}; const f = () => { const a = [y]; const b = x; // this sets y.x = x a[0].x = b; } f(); mutate(y.x); // which means this mutates x! ``` Here, the Effect on the context operands are `[mutate y, read x]`. The `mutate y` is bc of the array push. But the `read x` is surprising — `x` is captured into `y`, but there is no subsequent mutation of y or x, so we consider this a read. But as the comments indicate, the final line mutates x! We need to reflect the fact that even though x isn't mutated inside the function, it is aliased into y, such that if y is subsequently mutated that this should count as a mutation of x too. The idea of this PR is to extend the FunctionEffect type with a CaptureEffect variant which lists out the aliasing groups that occur inside the function expression. This allows us to bubble up the results of alias analysis from inside a function. The idea is to: * Return the alias sets from InferMutableRanges * Augment them with capturing of the form above, handling cases such as the `a[0].x = b` * For each alias group, record a CaptureEffect for any group that contains 2+ context operands * Extend the alias sets in the _outer_ function with the CaptureEffect sets from FunctionExpression/ObjectMethod instructions. This isn't quite right yet, just sharing early hacking. [ghstack-poisoned]
This is a stab at addressing a pattern that mofeiz and I have both stumbled across. Today, FunctionExpression's context list describes values from the outer context that are accessed in the function, and with what effect they were accessed. This allows us to describe the fact that a value from the outer context is known to be mutated inside a function expression, or is known to be captured (aliased) into some other value in the function expression. However, the basic `Effect` kind is insufficient to describe the full semantics. Notably, it doesn't let us describe more complex aliasing relationships. From an example mofeiz added: ```js const x = {}; const y = {}; const f = () => { const a = [y]; const b = x; // this sets y.x = x a[0].x = b; } f(); mutate(y.x); // which means this mutates x! ``` Here, the Effect on the context operands are `[mutate y, read x]`. The `mutate y` is bc of the array push. But the `read x` is surprising — `x` is captured into `y`, but there is no subsequent mutation of y or x, so we consider this a read. But as the comments indicate, the final line mutates x! We need to reflect the fact that even though x isn't mutated inside the function, it is aliased into y, such that if y is subsequently mutated that this should count as a mutation of x too. The idea of this PR is to extend the FunctionEffect type with a CaptureEffect variant which lists out the aliasing groups that occur inside the function expression. This allows us to bubble up the results of alias analysis from inside a function. The idea is to: * Return the alias sets from InferMutableRanges * Augment them with capturing of the form above, handling cases such as the `a[0].x = b` * For each alias group, record a CaptureEffect for any group that contains 2+ context operands * Extend the alias sets in the _outer_ function with the CaptureEffect sets from FunctionExpression/ObjectMethod instructions. This isn't quite right yet, just sharing early hacking. [ghstack-poisoned]
return `CaptureEffect places=[${[...effect.places] | ||
.map(place => printPlace(place)) | ||
.join(', ')}]`; | ||
} |
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
case 'IteratorNext': { | ||
alias = instrValue.collection; | ||
break; | ||
} |
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.
this was a missing alias, oops
case 'CaptureEffect': { | ||
return []; | ||
} | ||
default: { | ||
assertExhaustive( | ||
effect, | ||
`Unexpected effect kind '${(effect as any).kind}'`, | ||
); | ||
} |
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 (no changes to the existing cases) and adding the new case
case 'CaptureEffect': { | ||
return null; | ||
} |
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.
adding the new case
@@ -521,7 +520,7 @@ class InferenceState { | |||
* `expected valueKind to be 'Mutable' but found to be \`${valueKind}\`` | |||
* ); | |||
*/ | |||
effect = isObjectType(place.identifier) ? Effect.Store : Effect.Mutate; |
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.
This was always sketchy: why is only a Store effect on a known object type? A Store is a Store: you're either doing a transitive mutation or a local mutation. The conditional here was hiding incorrect code elsewhere, fixed in this PR.
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.
My comment here is technically correct but the fix is very involved (substantial change to InferMutableRanges) so it's easier to keep this. See later PRs in the stack where this conditionally effectively moves: we choose which effect to use for PropertyStore/ComputedStore based on whether the object is an object type or not, rather than apply this to all Store effects
const valueKind: AbstractValue = { | ||
kind: ValueKind.Mutable, | ||
reason: new Set([ValueReason.Other]), | ||
context: new Set(), | ||
}; |
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.
an array expression always produces a new mutable value. producing a context type was a hack
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.
Makes sense. So with this change, context
is only set for real context variables (from hirFn.context
) or when looking up + merging aliases.
const valueKind: AbstractValue = { | ||
kind: ValueKind.Mutable, | ||
reason: new Set([ValueReason.Other]), | ||
context: new Set(), | ||
}; |
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.
same here
state.referenceAndRecordEffects( | ||
freezeActions, | ||
instrValue.value, | ||
effect, | ||
Effect.Capture, | ||
ValueReason.Other, | ||
); |
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.
Similar to the above hack for Store effects, a PropertyStore is always capturing its rvalue, never mutating it.
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.
This makes more sense, I was always kind of confused about the conditionally-mutate-if-its-a-context-variable logic
typeof instrValue.property === 'string' | ||
? Effect.Store | ||
: Effect.Mutate, |
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.
I need to follow-up on this: this is handling some cases i saw with array access, but i think i've subsequently fixed them by changing the rvalue to be capture and not mutate. I'll double-check.
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.
See later PRs in the stack, i confirmed that the effect here needs to be Store (if known object type) or else Mutate, regardless of the property kind
This is a stab at addressing a pattern that mofeiz and I have both stumbled across. Today, FunctionExpression's context list describes values from the outer context that are accessed in the function, and with what effect they were accessed. This allows us to describe the fact that a value from the outer context is known to be mutated inside a function expression, or is known to be captured (aliased) into some other value in the function expression. However, the basic `Effect` kind is insufficient to describe the full semantics. Notably, it doesn't let us describe more complex aliasing relationships. From an example mofeiz added: ```js const x = {}; const y = {}; const f = () => { const a = [y]; const b = x; // this sets y.x = x a[0].x = b; } f(); mutate(y.x); // which means this mutates x! ``` Here, the Effect on the context operands are `[mutate y, read x]`. The `mutate y` is bc of the array push. But the `read x` is surprising — `x` is captured into `y`, but there is no subsequent mutation of y or x, so we consider this a read. But as the comments indicate, the final line mutates x! We need to reflect the fact that even though x isn't mutated inside the function, it is aliased into y, such that if y is subsequently mutated that this should count as a mutation of x too. The idea of this PR is to extend the FunctionEffect type with a CaptureEffect variant which lists out the aliasing groups that occur inside the function expression. This allows us to bubble up the results of alias analysis from inside a function. The idea is to: * Return the alias sets from InferMutableRanges * Augment them with capturing of the form above, handling cases such as the `a[0].x = b` * For each alias group, record a CaptureEffect for any group that contains 2+ context operands * Extend the alias sets in the _outer_ function with the CaptureEffect sets from FunctionExpression/ObjectMethod instructions. More details as comments on code [ghstack-poisoned]
This is a stab at addressing a pattern that mofeiz and I have both stumbled across. Today, FunctionExpression's context list describes values from the outer context that are accessed in the function, and with what effect they were accessed. This allows us to describe the fact that a value from the outer context is known to be mutated inside a function expression, or is known to be captured (aliased) into some other value in the function expression. However, the basic `Effect` kind is insufficient to describe the full semantics. Notably, it doesn't let us describe more complex aliasing relationships. From an example mofeiz added: ```js const x = {}; const y = {}; const f = () => { const a = [y]; const b = x; // this sets y.x = x a[0].x = b; } f(); mutate(y.x); // which means this mutates x! ``` Here, the Effect on the context operands are `[mutate y, read x]`. The `mutate y` is bc of the array push. But the `read x` is surprising — `x` is captured into `y`, but there is no subsequent mutation of y or x, so we consider this a read. But as the comments indicate, the final line mutates x! We need to reflect the fact that even though x isn't mutated inside the function, it is aliased into y, such that if y is subsequently mutated that this should count as a mutation of x too. The idea of this PR is to extend the FunctionEffect type with a CaptureEffect variant which lists out the aliasing groups that occur inside the function expression. This allows us to bubble up the results of alias analysis from inside a function. The idea is to: * Return the alias sets from InferMutableRanges * Augment them with capturing of the form above, handling cases such as the `a[0].x = b` * For each alias group, record a CaptureEffect for any group that contains 2+ context operands * Extend the alias sets in the _outer_ function with the CaptureEffect sets from FunctionExpression/ObjectMethod instructions. More details as comments on code [ghstack-poisoned]
This is a stab at addressing a pattern that mofeiz and I have both stumbled across. Today, FunctionExpression's context list describes values from the outer context that are accessed in the function, and with what effect they were accessed. This allows us to describe the fact that a value from the outer context is known to be mutated inside a function expression, or is known to be captured (aliased) into some other value in the function expression. However, the basic `Effect` kind is insufficient to describe the full semantics. Notably, it doesn't let us describe more complex aliasing relationships. From an example mofeiz added: ```js const x = {}; const y = {}; const f = () => { const a = [y]; const b = x; // this sets y.x = x a[0].x = b; } f(); mutate(y.x); // which means this mutates x! ``` Here, the Effect on the context operands are `[mutate y, read x]`. The `mutate y` is bc of the array push. But the `read x` is surprising — `x` is captured into `y`, but there is no subsequent mutation of y or x, so we consider this a read. But as the comments indicate, the final line mutates x! We need to reflect the fact that even though x isn't mutated inside the function, it is aliased into y, such that if y is subsequently mutated that this should count as a mutation of x too. The idea of this PR is to extend the FunctionEffect type with a CaptureEffect variant which lists out the aliasing groups that occur inside the function expression. This allows us to bubble up the results of alias analysis from inside a function. The idea is to: * Return the alias sets from InferMutableRanges * Augment them with capturing of the form above, handling cases such as the `a[0].x = b` * For each alias group, record a CaptureEffect for any group that contains 2+ context operands * Extend the alias sets in the _outer_ function with the CaptureEffect sets from FunctionExpression/ObjectMethod instructions. More details as comments on code [ghstack-poisoned]
This is a stab at addressing a pattern that mofeiz and I have both stumbled across. Today, FunctionExpression's context list describes values from the outer context that are accessed in the function, and with what effect they were accessed. This allows us to describe the fact that a value from the outer context is known to be mutated inside a function expression, or is known to be captured (aliased) into some other value in the function expression. However, the basic `Effect` kind is insufficient to describe the full semantics. Notably, it doesn't let us describe more complex aliasing relationships. From an example mofeiz added: ```js const x = {}; const y = {}; const f = () => { const a = [y]; const b = x; // this sets y.x = x a[0].x = b; } f(); mutate(y.x); // which means this mutates x! ``` Here, the Effect on the context operands are `[mutate y, read x]`. The `mutate y` is bc of the array push. But the `read x` is surprising — `x` is captured into `y`, but there is no subsequent mutation of y or x, so we consider this a read. But as the comments indicate, the final line mutates x! We need to reflect the fact that even though x isn't mutated inside the function, it is aliased into y, such that if y is subsequently mutated that this should count as a mutation of x too. The idea of this PR is to extend the FunctionEffect type with a CaptureEffect variant which lists out the aliasing groups that occur inside the function expression. This allows us to bubble up the results of alias analysis from inside a function. The idea is to: * Return the alias sets from InferMutableRanges * Augment them with capturing of the form above, handling cases such as the `a[0].x = b` * For each alias group, record a CaptureEffect for any group that contains 2+ context operands * Extend the alias sets in the _outer_ function with the CaptureEffect sets from FunctionExpression/ObjectMethod instructions. More details as comments on code [ghstack-poisoned]
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.
At a high level this makes sense, thanks for the detailed comments / walkthrough!
I checked this out locally to try to better understand the new alias effects. Am I misunderstanding something or is there more missing here? merged.inner.value = 'updated'
should set that contextObj
may be mutated somehow.
// input
function Component() {
const contextObj = {inner: {value: 'initial'}};
const update = newObj => {
const merged = { ...contextObj, ...newObj };
merged.inner.value = 'updated';
};
update({});
return contextObj;
}
// output, note that the mutable range *is* correctly extended if I return `merged` from this callback
function Component() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = { inner: { value: "initial" } };
$[0] = t0;
} else {
t0 = $[0];
}
const contextObj = t0;
const update = (newObj) => {
const merged = { ...contextObj, ...newObj };
merged.inner.value = "updated";
};
update({});
return contextObj;
}
Ah I see this fixture ends up being fixed somewhere up the stack
This is a stab at addressing a pattern that mofeiz and I have both stumbled across. Today, FunctionExpression's context list describes values from the outer context that are accessed in the function, and with what effect they were accessed. This allows us to describe the fact that a value from the outer context is known to be mutated inside a function expression, or is known to be captured (aliased) into some other value in the function expression. However, the basic `Effect` kind is insufficient to describe the full semantics. Notably, it doesn't let us describe more complex aliasing relationships. From an example mofeiz added: ```js const x = {}; const y = {}; const f = () => { const a = [y]; const b = x; // this sets y.x = x a[0].x = b; } f(); mutate(y.x); // which means this mutates x! ``` Here, the Effect on the context operands are `[mutate y, read x]`. The `mutate y` is bc of the array push. But the `read x` is surprising — `x` is captured into `y`, but there is no subsequent mutation of y or x, so we consider this a read. But as the comments indicate, the final line mutates x! We need to reflect the fact that even though x isn't mutated inside the function, it is aliased into y, such that if y is subsequently mutated that this should count as a mutation of x too. The idea of this PR is to extend the FunctionEffect type with a CaptureEffect variant which lists out the aliasing groups that occur inside the function expression. This allows us to bubble up the results of alias analysis from inside a function. The idea is to: * Return the alias sets from InferMutableRanges * Augment them with capturing of the form above, handling cases such as the `a[0].x = b` * For each alias group, record a CaptureEffect for any group that contains 2+ context operands * Extend the alias sets in the _outer_ function with the CaptureEffect sets from FunctionExpression/ObjectMethod instructions. More details as comments on code [ghstack-poisoned]
This is a stab at addressing a pattern that mofeiz and I have both stumbled across. Today, FunctionExpression's context list describes values from the outer context that are accessed in the function, and with what effect they were accessed. This allows us to describe the fact that a value from the outer context is known to be mutated inside a function expression, or is known to be captured (aliased) into some other value in the function expression. However, the basic `Effect` kind is insufficient to describe the full semantics. Notably, it doesn't let us describe more complex aliasing relationships. From an example mofeiz added: ```js const x = {}; const y = {}; const f = () => { const a = [y]; const b = x; // this sets y.x = x a[0].x = b; } f(); mutate(y.x); // which means this mutates x! ``` Here, the Effect on the context operands are `[mutate y, read x]`. The `mutate y` is bc of the array push. But the `read x` is surprising — `x` is captured into `y`, but there is no subsequent mutation of y or x, so we consider this a read. But as the comments indicate, the final line mutates x! We need to reflect the fact that even though x isn't mutated inside the function, it is aliased into y, such that if y is subsequently mutated that this should count as a mutation of x too. The idea of this PR is to extend the FunctionEffect type with a CaptureEffect variant which lists out the aliasing groups that occur inside the function expression. This allows us to bubble up the results of alias analysis from inside a function. The idea is to: * Return the alias sets from InferMutableRanges * Augment them with capturing of the form above, handling cases such as the `a[0].x = b` * For each alias group, record a CaptureEffect for any group that contains 2+ context operands * Extend the alias sets in the _outer_ function with the CaptureEffect sets from FunctionExpression/ObjectMethod instructions. More details as comments on code [ghstack-poisoned]
This is a stab at addressing a pattern that mofeiz and I have both stumbled across. Today, FunctionExpression's context list describes values from the outer context that are accessed in the function, and with what effect they were accessed. This allows us to describe the fact that a value from the outer context is known to be mutated inside a function expression, or is known to be captured (aliased) into some other value in the function expression. However, the basic `Effect` kind is insufficient to describe the full semantics. Notably, it doesn't let us describe more complex aliasing relationships. From an example mofeiz added: ```js const x = {}; const y = {}; const f = () => { const a = [y]; const b = x; // this sets y.x = x a[0].x = b; } f(); mutate(y.x); // which means this mutates x! ``` Here, the Effect on the context operands are `[mutate y, read x]`. The `mutate y` is bc of the array push. But the `read x` is surprising — `x` is captured into `y`, but there is no subsequent mutation of y or x, so we consider this a read. But as the comments indicate, the final line mutates x! We need to reflect the fact that even though x isn't mutated inside the function, it is aliased into y, such that if y is subsequently mutated that this should count as a mutation of x too. The idea of this PR is to extend the FunctionEffect type with a CaptureEffect variant which lists out the aliasing groups that occur inside the function expression. This allows us to bubble up the results of alias analysis from inside a function. The idea is to: * Return the alias sets from InferMutableRanges * Augment them with capturing of the form above, handling cases such as the `a[0].x = b` * For each alias group, record a CaptureEffect for any group that contains 2+ context operands * Extend the alias sets in the _outer_ function with the CaptureEffect sets from FunctionExpression/ObjectMethod instructions. As part of this, I realized that our handling of PropertyStore's effect wasn't quite right. We used a store effect for the object, but only if it was a known object type — otherwise we recorded it as a mutation. But a PropertyStore really always is a store — it only mutates direct aliases of a value, not any interior objects that are captured. So I updated to always use store for known properties, and use mutate for computed properties. The latter is still also wrong, but i want to debug the change there separately. ghstack-source-id: 3c4a370 Pull Request resolved: facebook/react#33151
Stack from ghstack (oldest at bottom):
This is a stab at addressing a pattern that @mofeiZ and I have both stumbled across. Today, FunctionExpression's context list describes values from the outer context that are accessed in the function, and with what effect they were accessed. This allows us to describe the fact that a value from the outer context is known to be mutated inside a function expression, or is known to be captured (aliased) into some other value in the function expression. However, the basic
Effect
kind is insufficient to describe the full semantics. Notably, it doesn't let us describe more complex aliasing relationships.From an example @mofeiZ added:
Here, the Effect on the context operands are
[mutate y, read x]
. Themutate y
is bc of the array push. But theread x
is surprising —x
is captured intoy
, but there is no subsequent mutation of y or x, so we consider this a read. But as the comments indicate, the final line mutates x! We need to reflect the fact that even though x isn't mutated inside the function, it is aliased into y, such that if y is subsequently mutated that this should count as a mutation of x too.The idea of this PR is to extend the FunctionEffect type with a CaptureEffect variant which lists out the aliasing groups that occur inside the function expression. This allows us to bubble up the results of alias analysis from inside a function. The idea is to:
a[0].x = b
More details as comments on code