Skip to content

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]
@vercel
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: 63aad5c
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
Member

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
Member

@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
Member

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
Member

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
Member

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
Member

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ugh, this is because the LHS of the ?? is not considered nullable, so the RHS is ignored. Fixed!

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

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We're only generating the undefinedIdentifiers result if we hit a compiler error, so this defers computing it until we see an error. Probably a premature optimization tho.


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

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
Member

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

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]
mvitousek added a commit that referenced this pull request Jan 13, 2025
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: 03c5805
Pull Request resolved: #31882
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]
mvitousek added a commit that referenced this pull request May 5, 2025
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: 4f6a7e6
Pull Request resolved: #31882
@mvitousek
Copy link
Contributor Author

@josephsavona Resurrecting this diff! Updated to address your comments from January (using declarationId again) and integrating your changes from #33062. Internal diff TK

@github-actions
Copy link

github-actions bot commented Aug 3, 2025

This pull request has been automatically marked as stale. If this pull request is still relevant, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize reviewing it yet. Your contribution is very much appreciated.

@github-actions github-actions bot added the Resolution: Stale Automatically closed due to inactivity label Aug 3, 2025
@josephsavona
Copy link
Member

Oh whoops we lost track of this

@github-actions github-actions bot removed the Resolution: Stale Automatically closed due to inactivity label Aug 4, 2025
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