Skip to content
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

[compiler] Migrate PruneNonEscapingScopes to HIR #31882

Open
wants to merge 1 commit into
base: gh/mvitousek/42/base
Choose a base branch
from

Conversation

mvitousek
Copy link
Contributor

@mvitousek mvitousek commented Dec 20, 2024

Stack from ghstack (oldest at bottom):

Summary:
PruneNonEscapingScopes does a pretty powerful escape analysis, which we might want to apply for other purposes in our HIR passes. This ports this pass to HIR. For the most part, this implementation is identical to the ReactiveFunction version. It now handles phis instead of conditional ReactiveExpressions, which it does by treating all the phi operands as possibly aliasing the lvalue. This also requires that we iterate the aliasing analysis to a fixpoint, because the HIR has backedges which the ReactiveFunctions don't.

In our fixtures, this only changes one result, which appears to have become more accurate. I plan on testing this internally in a sync before landing.

Summary:
PruneNonEscapingScopes does a pretty powerful escape analysis, which we might want to apply for other purposes in our HIR passes. This ports this pass to HIR. For the most part, this implementation is identical to the ReactiveFunction version. It now handles phis instead of conditional ReactiveExpressions, which it does by treating all the phi operands as possibly aliasing the lvalue. This also requires that we iterate the aliasing analysis to a fixpoint, because the HIR has backedges which the ReactiveFunctions don't.

In our fixtures, this only changes one result, which appears to have become more accurate. I plan on testing this internally in a sync before landing.

[ghstack-poisoned]
Copy link

vercel bot commented Dec 20, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
react-compiler-playground ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 20, 2024 10:52pm

mvitousek added a commit that referenced this pull request Dec 20, 2024
Summary:
PruneNonEscapingScopes does a pretty powerful escape analysis, which we might want to apply for other purposes in our HIR passes. This ports this pass to HIR. For the most part, this implementation is identical to the ReactiveFunction version. It now handles phis instead of conditional ReactiveExpressions, which it does by treating all the phi operands as possibly aliasing the lvalue. This also requires that we iterate the aliasing analysis to a fixpoint, because the HIR has backedges which the ReactiveFunctions don't.

In our fixtures, this only changes one result, which appears to have become more accurate. I plan on testing this internally in a sync before landing.

ghstack-source-id: 63aad5c97e8bd6ea58fb1363854d081f50a31fa3
Pull Request resolved: #31882
Comment on lines +31 to +32
while ((foo(), true)) {
x = (foo(), 2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did this fixture change due to the compiler now inferring x to be a primitive?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Within this pass, yeah. Type inference was always precise and inferred x as a primitive, but the escape analysis pass was lumping in the rest of the values from the sequence expressions and seeing a function call plus an escaping value and thinking it had to memoize.

Copy link
Contributor

@josephsavona josephsavona left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome. Please do test internally and post a link to the diff, but it all looks great. Just a few minor things.

Comment on lines +475 to +478
const lvalues = [
{place: value.lvalue.place, level: MemoizationLevel.Memoized},
];
lvalues.push({place: lvalue, level: MemoizationLevel.Unmemoized});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could merge the push into the literal now that it's unconditional

const lvalues = [
{place: value.lvalue.place, level: MemoizationLevel.Unmemoized},
];
lvalues.push({place: lvalue, level: MemoizationLevel.Unmemoized});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

const lvalues = [
{place: value.lvalue, level: MemoizationLevel.Conditional},
];
lvalues.push({place: lvalue, level: MemoizationLevel.Conditional});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here

const rvalues = rvaluesUnfiltered.filter(
place =>
!state.inScope(
block.instructions[0]?.id ?? block.terminal,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is an instruction id, right? i'm surprised this passes, i thought you'd need block.terminal.id on the RHS

if (rvalues.length !== rvaluesUnfiltered.length) {
undefinedIdentifiers = (): Array<Place> =>
(undefinedIdentifiers?.() ?? []).concat(
rvaluesUnfiltered.filter(v => new Set(rvalues).has(v)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is O(n^2) since it's creating a new Set for each instance, let's cache the Set. Also, i'm curious why make undefinedIdentifiers a function rather than an array or set of places?


function pruneScopes(fn: HIRFunction, state: Set<IdentifierId>): void {
const prunedScopes = new Set<ScopeId>();
const reassignments = new Map<IdentifierId, Set<Identifier>>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the change to use IdentifierId instead of DeclarationId makes sense for most cases, but for reassignments it seems a bit suspect because the id value may not line up btw the reassignment and the FinishMemoize instruction.

to me what this points to is that we may not need this logic anymore, would be good to check if we can remove it (or at least add a TODO so we can come back to this)

* If the manual memo was a useMemo that got inlined, iterate through
* all reassignments to the iife temporary to ensure they're memoized.
*/
decls = reassignments.get(value.decl.identifier.id) ?? [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

specifically the id may not match up here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants