mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(compiler): Create an AlwaysInline variable mode (#52001)
The template pipeline now supports lexical variables that are always inlined into their call sites, even if multiple call sites exist. An `AlwaysInline` variable may not rely on the current context, because it will potentially be inlined at several different locations. Co-authored-by: Alex Rickabaugh <alxhub@users.noreply.github.com> PR Close #52001
This commit is contained in:
@@ -336,6 +336,16 @@ export enum ExpressionKind {
|
||||
ConditionalCase,
|
||||
}
|
||||
|
||||
export enum VariableFlags {
|
||||
None = 0b0000,
|
||||
|
||||
/**
|
||||
* Always inline this variable, regardless of the number of times it's used.
|
||||
* An `AlwaysInline` variable may not depend on context, because doing so may cause side effects
|
||||
* that are illegal when multi-inlined. (The optimizer will enforce this constraint.)
|
||||
*/
|
||||
AlwaysInline = 0b0001,
|
||||
}
|
||||
/**
|
||||
* Distinguishes between different kinds of `SemanticVariable`s.
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import * as o from '../../../../../output/output_ast';
|
||||
import {OpKind} from '../enums';
|
||||
import {OpKind, VariableFlags} from '../enums';
|
||||
import {Op, XrefId} from '../operations';
|
||||
import {SemanticVariable} from '../variable';
|
||||
|
||||
@@ -68,18 +68,22 @@ export interface VariableOp<OpT extends Op<OpT>> extends Op<OpT> {
|
||||
* Expression representing the value of the variable.
|
||||
*/
|
||||
initializer: o.Expression;
|
||||
|
||||
flags: VariableFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `VariableOp`.
|
||||
*/
|
||||
export function createVariableOp<OpT extends Op<OpT>>(
|
||||
xref: XrefId, variable: SemanticVariable, initializer: o.Expression): VariableOp<OpT> {
|
||||
xref: XrefId, variable: SemanticVariable, initializer: o.Expression,
|
||||
flags: VariableFlags): VariableOp<OpT> {
|
||||
return {
|
||||
kind: OpKind.Variable,
|
||||
xref,
|
||||
variable,
|
||||
initializer,
|
||||
flags,
|
||||
...NEW_OP,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -177,7 +177,8 @@ function generateVariablesInScopeForView(
|
||||
// view with a `nextContext` expression. This context switching operation itself declares a
|
||||
// variable, because the context of the view may be referenced directly.
|
||||
newOps.push(ir.createVariableOp(
|
||||
view.job.allocateXrefId(), scope.viewContextVariable, new ir.NextContextExpr()));
|
||||
view.job.allocateXrefId(), scope.viewContextVariable, new ir.NextContextExpr(),
|
||||
ir.VariableFlags.None));
|
||||
}
|
||||
|
||||
// Add variables for all context variables available in this scope's view.
|
||||
@@ -187,13 +188,15 @@ function generateVariablesInScopeForView(
|
||||
const variable = value === ir.CTX_REF ? context : new o.ReadPropExpr(context, value);
|
||||
// Add the variable declaration.
|
||||
newOps.push(ir.createVariableOp(
|
||||
view.job.allocateXrefId(), scope.contextVariables.get(name)!, variable));
|
||||
view.job.allocateXrefId(), scope.contextVariables.get(name)!, variable,
|
||||
ir.VariableFlags.None));
|
||||
}
|
||||
|
||||
// Add variables for all local references declared for elements in this scope.
|
||||
for (const ref of scope.references) {
|
||||
newOps.push(ir.createVariableOp(
|
||||
view.job.allocateXrefId(), ref.variable, new ir.ReferenceExpr(ref.targetId, ref.offset)));
|
||||
view.job.allocateXrefId(), ref.variable, new ir.ReferenceExpr(ref.targetId, ref.offset),
|
||||
ir.VariableFlags.None));
|
||||
}
|
||||
|
||||
if (scope.parent !== null) {
|
||||
|
||||
@@ -30,6 +30,15 @@ import {CompilationJob} from '../compilation';
|
||||
*/
|
||||
export function phaseVariableOptimization(job: CompilationJob): void {
|
||||
for (const unit of job.units) {
|
||||
inlineAlwaysInlineVariables(unit.create);
|
||||
inlineAlwaysInlineVariables(unit.update);
|
||||
|
||||
for (const op of unit.create) {
|
||||
if (op.kind === ir.OpKind.Listener) {
|
||||
inlineAlwaysInlineVariables(op.handlerOps);
|
||||
}
|
||||
}
|
||||
|
||||
optimizeVariablesInOpList(unit.create, job.compatibility);
|
||||
optimizeVariablesInOpList(unit.update, job.compatibility);
|
||||
|
||||
@@ -95,6 +104,33 @@ interface OpInfo {
|
||||
fences: Fence;
|
||||
}
|
||||
|
||||
function inlineAlwaysInlineVariables(ops: ir.OpList<ir.CreateOp|ir.UpdateOp>): void {
|
||||
const vars = new Map<ir.XrefId, ir.VariableOp<ir.CreateOp|ir.UpdateOp>>();
|
||||
for (const op of ops) {
|
||||
if (op.kind === ir.OpKind.Variable) {
|
||||
ir.visitExpressionsInOp(op, expr => {
|
||||
if (ir.isIrExpression(expr) && fencesForIrExpression(expr) !== Fence.None) {
|
||||
throw new Error(`AssertionError: A context-sensitive variable was marked AlwaysInline`);
|
||||
}
|
||||
});
|
||||
vars.set(op.xref, op);
|
||||
}
|
||||
|
||||
ir.transformExpressionsInOp(op, expr => {
|
||||
if (expr instanceof ir.ReadVariableExpr && vars.has(expr.xref)) {
|
||||
const varOp = vars.get(expr.xref)!;
|
||||
// Inline by cloning, because we might inline into multiple places.
|
||||
return varOp.initializer.clone();
|
||||
}
|
||||
return expr;
|
||||
}, ir.VisitorContextFlag.None);
|
||||
}
|
||||
|
||||
for (const op of vars.values()) {
|
||||
ir.OpList.remove(op as ir.CreateOp | ir.UpdateOp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a list of operations and optimize variables within that list.
|
||||
*/
|
||||
@@ -174,10 +210,15 @@ function optimizeVariablesInOpList(
|
||||
// Next, inline any remaining variables with exactly one usage.
|
||||
const toInline: ir.XrefId[] = [];
|
||||
for (const [id, count] of varUsages) {
|
||||
const decl = varDecls.get(id)!;
|
||||
const varInfo = opMap.get(decl as ir.CreateOp | ir.UpdateOp)!;
|
||||
// We can inline variables that:
|
||||
// - are used once
|
||||
// - are used exactly once, and
|
||||
// - are not used remotely
|
||||
if (count !== 1) {
|
||||
// OR
|
||||
// - are marked for always inlining
|
||||
const isAlwaysInline = !!(decl.flags & ir.VariableFlags.AlwaysInline);
|
||||
if (count !== 1 || isAlwaysInline) {
|
||||
// We can't inline this variable as it's used more than once.
|
||||
continue;
|
||||
}
|
||||
@@ -196,6 +237,12 @@ function optimizeVariablesInOpList(
|
||||
// no future operation will make inlining legal.
|
||||
const decl = varDecls.get(candidate)!;
|
||||
const varInfo = opMap.get(decl as ir.CreateOp | ir.UpdateOp)!;
|
||||
const isAlwaysInline = !!(decl.flags & ir.VariableFlags.AlwaysInline);
|
||||
|
||||
if (isAlwaysInline) {
|
||||
throw new Error(
|
||||
`AssertionError: Found an 'AlwaysInline' variable after the always inlining pass.`);
|
||||
}
|
||||
|
||||
// Scan operations following the variable declaration and look for the point where that variable
|
||||
// is used. There should only be one usage given the precondition above.
|
||||
|
||||
Reference in New Issue
Block a user