mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(compiler): next context merging in the template pipeline (#49797)
This commit adds a phase to the template pipeline to merge `nextContext()` instructions that follow each other without context reads in between. That is, the sequence: ```typescript nextContext(); var v1 = nextContext(); ``` becomes: ```typescript var v1 = nextContext(2); ``` PR Close #49797
This commit is contained in:
committed by
Jessica Janiuk
parent
84af3e302d
commit
ead10aff41
@@ -132,6 +132,8 @@ export class ContextExpr extends ExpressionBase {
|
||||
export class NextContextExpr extends ExpressionBase {
|
||||
override readonly kind = ExpressionKind.NextContext;
|
||||
|
||||
steps = 1;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
@@ -139,7 +141,7 @@ export class NextContextExpr extends ExpressionBase {
|
||||
override visitExpression(): void {}
|
||||
|
||||
override isEquivalent(e: o.Expression): boolean {
|
||||
return e instanceof NextContextExpr;
|
||||
return e instanceof NextContextExpr && e.steps === this.steps;
|
||||
}
|
||||
|
||||
override isConstant(): boolean {
|
||||
|
||||
@@ -25,6 +25,7 @@ import {phaseResolveNames} from './phases/resolve_names';
|
||||
import {phaseResolveContexts} from './phases/resolve_contexts';
|
||||
import {phaseVariableOptimization} from './phases/variable_optimization';
|
||||
import {phaseChaining} from './phases/chaining';
|
||||
import {phaseMergeNextContext} from './phases/next_context_merging';
|
||||
|
||||
/**
|
||||
* Run all transformation phases in the correct order against a `ComponentCompilation`. After this
|
||||
@@ -42,6 +43,7 @@ export function transformTemplate(cpl: ComponentCompilation): void {
|
||||
phaseGenerateAdvance(cpl);
|
||||
phaseNaming(cpl);
|
||||
phaseVariableOptimization(cpl, {conservative: true});
|
||||
phaseMergeNextContext(cpl);
|
||||
phaseReify(cpl);
|
||||
phaseChaining(cpl);
|
||||
}
|
||||
|
||||
@@ -80,8 +80,8 @@ export function reference(slot: number): o.Expression {
|
||||
]);
|
||||
}
|
||||
|
||||
export function nextContext(): o.Expression {
|
||||
return o.importExpr(Identifiers.nextContext).callFn([]);
|
||||
export function nextContext(steps: number): o.Expression {
|
||||
return o.importExpr(Identifiers.nextContext).callFn(steps === 1 ? [] : [o.literal(steps)]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as o from '../../../../output/output_ast';
|
||||
import * as ir from '../../ir';
|
||||
|
||||
import type {ComponentCompilation} from '../compilation';
|
||||
|
||||
/**
|
||||
* Merges logically sequential `NextContextExpr` operations.
|
||||
*
|
||||
* `NextContextExpr` can be referenced repeatedly, "popping" the runtime's context stack each time.
|
||||
* When two such expressions appear back-to-back, it's possible to merge them together into a single
|
||||
* `NextContextExpr` that steps multiple contexts. This merging is possible if all conditions are
|
||||
* met:
|
||||
*
|
||||
* * The result of the `NextContextExpr` that's folded into the subsequent one is not stored (that
|
||||
* is, the call is purely side-effectful).
|
||||
* * No operations in between them uses the implicit context.
|
||||
*/
|
||||
export function phaseMergeNextContext(cpl: ComponentCompilation): void {
|
||||
for (const view of cpl.views.values()) {
|
||||
for (const op of view.create) {
|
||||
if (op.kind === ir.OpKind.Listener) {
|
||||
mergeNextContextsInOps(op.handlerOps);
|
||||
}
|
||||
}
|
||||
mergeNextContextsInOps(view.update);
|
||||
}
|
||||
}
|
||||
|
||||
function mergeNextContextsInOps(ops: ir.OpList<ir.UpdateOp>): void {
|
||||
for (const op of ops) {
|
||||
// Look for a candidate operation to maybe merge.
|
||||
if (op.kind !== ir.OpKind.Statement || !(op.statement instanceof o.ExpressionStatement) ||
|
||||
!(op.statement.expr instanceof ir.NextContextExpr)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const mergeSteps = op.statement.expr.steps;
|
||||
|
||||
// Try to merge this `ir.NextContextExpr`.
|
||||
let tryToMerge = true;
|
||||
for (let candidate = op.next!; candidate.kind !== ir.OpKind.ListEnd && tryToMerge;
|
||||
candidate = candidate.next!) {
|
||||
ir.visitExpressionsInOp(candidate, (expr, flags) => {
|
||||
if (!tryToMerge) {
|
||||
// Either we've already merged, or failed to merge.
|
||||
return;
|
||||
}
|
||||
|
||||
if (flags & ir.VisitorContextFlag.InChildOperation) {
|
||||
// We cannot merge into child operations.
|
||||
return;
|
||||
}
|
||||
|
||||
switch (expr.kind) {
|
||||
case ir.ExpressionKind.NextContext:
|
||||
// Merge the previous `ir.NextContextExpr` into this one.
|
||||
expr.steps += mergeSteps;
|
||||
ir.OpList.remove(op as ir.UpdateOp);
|
||||
tryToMerge = false;
|
||||
break;
|
||||
case ir.ExpressionKind.GetCurrentView:
|
||||
case ir.ExpressionKind.Reference:
|
||||
// Can't merge past a dependency on the context.
|
||||
tryToMerge = false;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,7 +128,7 @@ function reifyUpdateOperations(_view: ViewCompilation, ops: ir.OpList<ir.UpdateO
|
||||
function reifyIrExpression(expr: ir.Expression): o.Expression {
|
||||
switch (expr.kind) {
|
||||
case ir.ExpressionKind.NextContext:
|
||||
return ng.nextContext();
|
||||
return ng.nextContext(expr.steps);
|
||||
case ir.ExpressionKind.Reference:
|
||||
return ng.reference(expr.slot! + 1 + expr.offset);
|
||||
case ir.ExpressionKind.LexicalRead:
|
||||
|
||||
Reference in New Issue
Block a user