Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Optimizations and fixes for @let declarations #60512

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

Closed
wants to merge 2 commits into from

Conversation

crisbeto
Copy link
Member

@crisbeto crisbeto commented Mar 21, 2025

Includes the following optimizations/fixes for how @let declarations are handled:

perf(compiler): reduce allocations for let declarations only used in the same view

We have some code that avoids storeLet calls for declarations only used in the same view, however we didn't previously remove the corresponding declareLet calls, because of the following case:

@let foo = something$ | async; <!-- First in the template -->
{{foo}}

Here we need a TNode (created by declareLet) in order for DI to work correctly. Since this is only required when using pipes, we can optimize away expressions that do not have pipes.

fix(compiler): incorrectly handling let declarations inside i18n

The compiler wasn't handling @let declarations placed inside i18n blocks. The problem is that assignI18nSlotDependencies phase assigns the target of i18n ops much earlier than the @let optimization. If the @let ends up getting optimized because it isn't used in any child views, the pointer in the i18n instruction becomes invalid. This hadn't surfaced so far, because we didn't optimize declareLet ops, however once we do, we start hitting assertions that the optimized declareLet isn't used anywhere.

These changes resolve the issue by moving the i18n phases after the @let optimization.

@crisbeto crisbeto added action: review The PR is still awaiting reviews from at least one requested reviewer target: patch This PR is targeted for the next patch release labels Mar 21, 2025
vars: 2,
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵtext(0);
$r3$.ɵɵdeclareLet(1)(2)(3)(4);
$r3$.ɵɵtext(5);
$r3$.ɵɵtext(1);
Copy link
Member Author

Choose a reason for hiding this comment

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

Note to self: we should also chain text instructions.

Choose a reason for hiding this comment

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

Would we have cases where 2 text nodes are side-by-side?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think the only case would be something like this where they were before/after a @let that was optimized away.

@angular-robot angular-robot bot added area: performance Issues related to performance area: compiler Issues related to `ngc`, Angular's template compiler labels Mar 21, 2025
@ngbot ngbot bot added this to the Backlog milestone Mar 21, 2025
@crisbeto crisbeto force-pushed the declare-let branch 2 times, most recently from 3396a35 to 9eefeb9 Compare March 21, 2025 21:17
@crisbeto crisbeto requested a review from alxhub March 21, 2025 21:18
@crisbeto crisbeto marked this pull request as ready for review March 21, 2025 21:18
@crisbeto crisbeto added the action: global presubmit The PR is in need of a google3 global presubmit label Mar 21, 2025
@crisbeto crisbeto changed the title perf(compiler): reduce allocations for let declarations only used in the same view Optimizations and fixes for @let declarations Mar 24, 2025
@crisbeto crisbeto removed the action: global presubmit The PR is in need of a google3 global presubmit label Mar 24, 2025
@crisbeto
Copy link
Member Author

Passing TGP

@mmalerba mmalerba self-requested a review April 29, 2025 16:27
function hasPipe(root: ir.StoreLetExpr): boolean {
let result = false;

ir.transformExpressionsInExpression(
Copy link
Contributor

Choose a reason for hiding this comment

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

(no action required) kind of odd that there's know way to walk this thing without "transforming" it

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed, but I think it's been like that since we switched to the pipeline.

target = updateOp.target;
} else if (
// Some expressions may consume slots as well (e.g. `storeLet`).
updateOp.kind === ir.OpKind.Statement ||
Copy link
Contributor

Choose a reason for hiding this comment

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

This assumes an op can only have one slotful expression, is that true?

Copy link
Member Author

Choose a reason for hiding this comment

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

In theory it could, it practice it can't since the only case right now is storeLet of which there can only be one per op. That being said, I've updated it to account for multiple so we don't get some subtle breakages if we introduce more expressions like that.

crisbeto added 2 commits May 9, 2025 18:01
…the same view

We have some code that avoids `storeLet` calls for declarations only used in the same view, however we didn't previously remove the corresponding `declareLet` calls, because of the following case:

```
@let foo = something$ | async; <!-- First in the template -->
{{foo}}
```

Here we need a `TNode` (created by `declareLet`) in order for DI to work correctly. Since this is only required when using pipes, we can optimize away expressions that do not have pipes.
The compiler wasn't handling `@let` declarations placed inside i18n blocks. The problem is that `assignI18nSlotDependencies` phase assigns the `target` of i18n ops much earlier than the `@let` optimization. If the `@let` ends up getting optimized because it isn't used in any child views, the pointer in the i18n instruction becomes invalid. This hadn't surfaced so far, because we didn't optimize `declareLet` ops, however once we do, we start hitting assertions that the optimized `declareLet` isn't used anywhere.

These changes resolve the issue by moving the i18n phases after the `@let` optimization.
@crisbeto crisbeto removed the request for review from alxhub May 9, 2025 17:24
@crisbeto crisbeto added action: merge The PR is ready for merge by the caretaker target: rc This PR is targeted for the next release-candidate and removed action: review The PR is still awaiting reviews from at least one requested reviewer target: patch This PR is targeted for the next patch release labels May 9, 2025
@alxhub
Copy link
Member

alxhub commented May 13, 2025

This PR was merged into the repository by commit 8f2874e.

The changes were merged into the following branches: main, 20.0.x

alxhub pushed a commit that referenced this pull request May 13, 2025
…the same view (#60512)

We have some code that avoids `storeLet` calls for declarations only used in the same view, however we didn't previously remove the corresponding `declareLet` calls, because of the following case:

```
@let foo = something$ | async; <!-- First in the template -->
{{foo}}
```

Here we need a `TNode` (created by `declareLet`) in order for DI to work correctly. Since this is only required when using pipes, we can optimize away expressions that do not have pipes.

PR Close #60512
alxhub pushed a commit that referenced this pull request May 13, 2025
)

The compiler wasn't handling `@let` declarations placed inside i18n blocks. The problem is that `assignI18nSlotDependencies` phase assigns the `target` of i18n ops much earlier than the `@let` optimization. If the `@let` ends up getting optimized because it isn't used in any child views, the pointer in the i18n instruction becomes invalid. This hadn't surfaced so far, because we didn't optimize `declareLet` ops, however once we do, we start hitting assertions that the optimized `declareLet` isn't used anywhere.

These changes resolve the issue by moving the i18n phases after the `@let` optimization.

PR Close #60512
@alxhub alxhub closed this in a6b7b9b May 13, 2025
alxhub pushed a commit that referenced this pull request May 13, 2025
)

The compiler wasn't handling `@let` declarations placed inside i18n blocks. The problem is that `assignI18nSlotDependencies` phase assigns the `target` of i18n ops much earlier than the `@let` optimization. If the `@let` ends up getting optimized because it isn't used in any child views, the pointer in the i18n instruction becomes invalid. This hadn't surfaced so far, because we didn't optimize `declareLet` ops, however once we do, we start hitting assertions that the optimized `declareLet` isn't used anywhere.

These changes resolve the issue by moving the i18n phases after the `@let` optimization.

PR Close #60512
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
action: merge The PR is ready for merge by the caretaker area: compiler Issues related to `ngc`, Angular's template compiler area: performance Issues related to performance target: rc This PR is targeted for the next release-candidate
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants