feat(ivy): add support for short-circuiting (#24039)

Short-circuitable expressions (using ternary & binary operators) could not use
the regular binding mechanism as it relies on the bindings being checked every
single time - the index is incremented as part of checking the bindings.

Then for pure function kind of bindings we use a different mechanism with a
fixed index. As such short circuiting a binding check does not mess with the
expected binding index.

Note that all pure function bindings are handled the same wether or not they
actually are short-circuitable. This allows to keep the compiler and compiled
code simple - and there is no runtime perf cost anyway.

PR Close #24039
This commit is contained in:
Victor Berchet
2018-05-21 15:59:25 -07:00
committed by Matias Niemelä
parent 83bb5d1922
commit 4f36340de7
13 changed files with 362 additions and 116 deletions

View File

@ -121,4 +121,7 @@ export class Identifiers {
static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE};
static listener: o.ExternalReference = {name: 'ɵL', moduleName: CORE};
// Reserve slots for pure functions
static reserveSlots: o.ExternalReference = {name: 'ɵrS', moduleName: CORE};
}

View File

@ -57,6 +57,9 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
// Maps of placeholder to node indexes for each of the i18n section
private _phToNodeIdxes: {[phName: string]: number[]}[] = [{}];
// Number of slots to reserve for pureFunctions
private _pureFunctionSlots = 0;
constructor(
private constantPool: ConstantPool, private contextParameter: string,
parentBindingScope: BindingScope, private level = 0, private contextName: string|null,
@ -70,6 +73,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
});
this._valueConverter = new ValueConverter(
constantPool, () => this.allocateDataSlot(),
(numSlots: number): number => this._pureFunctionSlots += numSlots,
(name, localName, slot, value: o.ReadVarExpr) => {
const pipeType = pipeTypeByName.get(name);
if (pipeType) {
@ -139,6 +143,11 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
t.visitAll(this, nodes);
if (this._pureFunctionSlots > 0) {
this.instruction(
this._creationCode, null, R3.reserveSlots, o.literal(this._pureFunctionSlots));
}
const creationCode = this._creationCode.length > 0 ?
[o.ifStmt(
o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(core.RenderFlags.Create), null, false),
@ -501,6 +510,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
class ValueConverter extends AstMemoryEfficientTransformer {
constructor(
private constantPool: ConstantPool, private allocateSlot: () => number,
private allocatePureFunctionSlots: (numSlots: number) => number,
private definePipe:
(name: string, localName: string, slot: number, value: o.Expression) => void) {
super();
@ -511,14 +521,20 @@ class ValueConverter extends AstMemoryEfficientTransformer {
// Allocate a slot to create the pipe
const slot = this.allocateSlot();
const slotPseudoLocal = `PIPE:${slot}`;
// Allocate one slot for the result plus one slot per pipe argument
const pureFunctionSlot = this.allocatePureFunctionSlots(2 + pipe.args.length);
const target = new PropertyRead(pipe.span, new ImplicitReceiver(pipe.span), slotPseudoLocal);
const bindingId = pipeBinding(pipe.args);
this.definePipe(pipe.name, slotPseudoLocal, slot, o.importExpr(bindingId));
const value = pipe.exp.visit(this);
const args = this.visitAll(pipe.args);
return new FunctionCall(
pipe.span, target, [new LiteralPrimitive(pipe.span, slot), value, ...args]);
return new FunctionCall(pipe.span, target, [
new LiteralPrimitive(pipe.span, slot),
new LiteralPrimitive(pipe.span, pureFunctionSlot),
value,
...args,
]);
}
visitLiteralArray(array: LiteralArray, context: any): AST {
@ -527,8 +543,9 @@ class ValueConverter extends AstMemoryEfficientTransformer {
// calls to literal factories that compose the literal and will cache intermediate
// values. Otherwise, just return an literal array that contains the values.
const literal = o.literalArr(values);
return values.every(a => a.isConstant()) ? this.constantPool.getConstLiteral(literal, true) :
getLiteralFactory(this.constantPool, literal);
return values.every(a => a.isConstant()) ?
this.constantPool.getConstLiteral(literal, true) :
getLiteralFactory(this.constantPool, literal, this.allocatePureFunctionSlots);
});
}
@ -539,14 +556,13 @@ class ValueConverter extends AstMemoryEfficientTransformer {
// values. Otherwise, just return an literal array that contains the values.
const literal = o.literalMap(values.map(
(value, index) => ({key: map.keys[index].key, value, quoted: map.keys[index].quoted})));
return values.every(a => a.isConstant()) ? this.constantPool.getConstLiteral(literal, true) :
getLiteralFactory(this.constantPool, literal);
return values.every(a => a.isConstant()) ?
this.constantPool.getConstLiteral(literal, true) :
getLiteralFactory(this.constantPool, literal, this.allocatePureFunctionSlots);
});
}
}
// Pipes always have at least one parameter, the value they operate on
const pipeBindingIdentifiers = [R3.pipeBind1, R3.pipeBind2, R3.pipeBind3, R3.pipeBind4];
@ -559,15 +575,20 @@ const pureFunctionIdentifiers = [
R3.pureFunction5, R3.pureFunction6, R3.pureFunction7, R3.pureFunction8
];
function getLiteralFactory(
constantPool: ConstantPool, literal: o.LiteralArrayExpr | o.LiteralMapExpr): o.Expression {
constantPool: ConstantPool, literal: o.LiteralArrayExpr | o.LiteralMapExpr,
allocateSlots: (numSlots: number) => number): o.Expression {
const {literalFactory, literalFactoryArguments} = constantPool.getLiteralFactory(literal);
// Allocate 1 slot for the result plus 1 per argument
const startSlot = allocateSlots(1 + literalFactoryArguments.length);
literalFactoryArguments.length > 0 || error(`Expected arguments to a literal factory function`);
let pureFunctionIdent =
pureFunctionIdentifiers[literalFactoryArguments.length] || R3.pureFunctionV;
// Literal factories are pure functions that only need to be re-invoked when the parameters
// change.
return o.importExpr(pureFunctionIdent).callFn([literalFactory, ...literalFactoryArguments]);
return o.importExpr(pureFunctionIdent).callFn([
o.literal(startSlot), literalFactory, ...literalFactoryArguments
]);
}
/**