refactor(compiler): Order elements before other phases (#52289)

Previously, we ran the ordering phase near the end of the compilation. However, this meant that phases like slot assignment and variable offset assignment would happen first, and then the nice, monotonically-increasing orders would be scrambled by the reordering.

It's much more intelligible to order first, and then perform these assignments. However, to make this happen, some modifications to the ordering phase are required. In particular, we can no longer rely on `advance` instructions to break up orderable groups.

PR Close #52289
This commit is contained in:
Dylan Hunn
2023-10-19 19:45:56 -07:00
parent 1fa5020739
commit e4df686ec6
3 changed files with 30 additions and 25 deletions
@@ -33,8 +33,7 @@
"failureMessage": "Incorrect template",
"files": ["pipe_bindings_slots.js"]
}
],
"skipForTemplatePipeline": true
]
},
{
"description": "should always generate advance() statements before any styling instructions",
@@ -88,6 +88,7 @@ const phases: Phase[] = [
{kind: Kind.Both, fn: phaseAttributeExtraction},
{kind: Kind.Both, fn: phaseParseExtractedStyles},
{kind: Kind.Tmpl, fn: phaseRemoveEmptyBindings},
{kind: Kind.Both, fn: phaseOrdering},
{kind: Kind.Tmpl, fn: phaseConditionals},
{kind: Kind.Tmpl, fn: phasePipeCreation},
{kind: Kind.Tmpl, fn: phaseI18nTextExtraction},
@@ -127,7 +128,6 @@ const phases: Phase[] = [
{kind: Kind.Tmpl, fn: phaseEmptyElements},
{kind: Kind.Tmpl, fn: phaseNonbindable},
{kind: Kind.Both, fn: phasePureFunctionExtraction},
{kind: Kind.Both, fn: phaseOrdering},
{kind: Kind.Both, fn: phaseReify},
{kind: Kind.Both, fn: phaseChaining},
];
@@ -58,34 +58,40 @@ export function phaseOrdering(job: CompilationJob) {
// still have ops pulled at the end, put them back in the correct order.
// Create mode:
let opsToOrder = [];
for (const op of unit.create) {
if (handledOpKinds.has(op.kind)) {
opsToOrder.push(op);
ir.OpList.remove(op);
} else {
ir.OpList.insertBefore(reorder(opsToOrder, CREATE_ORDERING), op);
opsToOrder = [];
}
}
unit.create.push(reorder(opsToOrder, CREATE_ORDERING));
orderWithin(unit.create, CREATE_ORDERING as Array<Rule<ir.CreateOp|ir.UpdateOp>>);
// Update mode:
opsToOrder = [];
for (const op of unit.update) {
if (handledOpKinds.has(op.kind)) {
opsToOrder.push(op);
ir.OpList.remove(op);
} else {
ir.OpList.insertBefore(reorder(opsToOrder, UPDATE_ORDERING), op);
opsToOrder = [];
}
}
unit.update.push(reorder(opsToOrder, UPDATE_ORDERING));
orderWithin(unit.update, UPDATE_ORDERING as Array<Rule<ir.CreateOp|ir.UpdateOp>>);
}
}
/**
* Order all the ops within the specified group.
*/
function orderWithin(
opList: ir.OpList<ir.CreateOp|ir.UpdateOp>, ordering: Array<Rule<ir.CreateOp|ir.UpdateOp>>) {
let opsToOrder = [];
// Only reorder ops that target the same xref; do not mix ops that target different xrefs.
let firstTargetInGroup: ir.XrefId|null = null;
for (const op of opList) {
const currentTarget = ir.hasDependsOnSlotContextTrait(op) ? op.target : null;
if (!handledOpKinds.has(op.kind) ||
(currentTarget !== firstTargetInGroup &&
(firstTargetInGroup !== null && currentTarget !== null))) {
ir.OpList.insertBefore(reorder(opsToOrder, ordering), op);
opsToOrder = [];
firstTargetInGroup = null;
}
if (handledOpKinds.has(op.kind)) {
opsToOrder.push(op);
ir.OpList.remove(op);
firstTargetInGroup = currentTarget ?? firstTargetInGroup;
}
}
opList.push(reorder(opsToOrder, ordering));
}
/**
* Reorders the given list of ops according to the ordering defined by `ORDERING`.
*/