Skip to content

Commit 6454f76

Browse files
mheveryjasonaden
authored andcommitted
refactor(ivy): remove ngPrivateData megamorphic prop access (angular#30548)
PR Close angular#30548
1 parent 28ae22e commit 6454f76

File tree

10 files changed

+50
-86
lines changed

10 files changed

+50
-86
lines changed

packages/core/src/render3/component.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,8 @@ export function createRootComponentView(
174174
const tView = rootView[TVIEW];
175175
const tNode: TElementNode = createNodeAtIndex(0, TNodeType.Element, rNode, null, null);
176176
const componentView = createLView(
177-
rootView, getOrCreateTView(
178-
def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs,
179-
def.viewQuery, def.schemas),
180-
null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, rootView[HEADER_OFFSET], tNode,
181-
rendererFactory, renderer, sanitizer);
177+
rootView, getOrCreateTView(def), null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways,
178+
rootView[HEADER_OFFSET], tNode, rendererFactory, renderer, sanitizer);
182179

183180
if (tView.firstTemplatePass) {
184181
diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, rootView), rootView, def.type);

packages/core/src/render3/definition.ts

+1
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ export function ΔdefineComponent<T>(componentDefinition: {
285285
_: null as never,
286286
setInput: null,
287287
schemas: componentDefinition.schemas || null,
288+
tView: null,
288289
};
289290
def._ = noSideEffects(() => {
290291
const directiveTypes = componentDefinition.directives !;

packages/core/src/render3/instructions/shared.ts

+6-24
Original file line numberDiff line numberDiff line change
@@ -548,29 +548,13 @@ function saveResolvedLocalsInData(
548548
* Gets TView from a template function or creates a new TView
549549
* if it doesn't already exist.
550550
*
551-
* @param templateFn The template from which to get static data
552-
* @param consts The number of nodes, local refs, and pipes in this view
553-
* @param vars The number of bindings and pure function bindings in this view
554-
* @param directives Directive defs that should be saved on TView
555-
* @param pipes Pipe defs that should be saved on TView
556-
* @param viewQuery View query that should be saved on TView
557-
* @param schemas Schemas that should be saved on TView
551+
* @param def ComponentDef
558552
* @returns TView
559553
*/
560-
export function getOrCreateTView(
561-
templateFn: ComponentTemplate<any>, consts: number, vars: number,
562-
directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null,
563-
viewQuery: ViewQueriesFunction<any>| null, schemas: SchemaMetadata[] | null): TView {
564-
// TODO(misko): reading `ngPrivateData` here is problematic for two reasons
565-
// 1. It is a megamorphic call on each invocation.
566-
// 2. For nested embedded views (ngFor inside ngFor) the template instance is per
567-
// outer template invocation, which means that no such property will exist
568-
// Correct solution is to only put `ngPrivateData` on the Component template
569-
// and not on embedded templates.
570-
571-
return templateFn.ngPrivateData ||
572-
(templateFn.ngPrivateData = createTView(
573-
-1, templateFn, consts, vars, directives, pipes, viewQuery, schemas) as never);
554+
export function getOrCreateTView(def: ComponentDef<any>): TView {
555+
return def.tView || (def.tView = createTView(
556+
-1, def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs,
557+
def.viewQuery, def.schemas));
574558
}
575559

576560
/**
@@ -1262,9 +1246,7 @@ function addComponentLogic<T>(
12621246
lView: LView, previousOrParentTNode: TNode, def: ComponentDef<T>): void {
12631247
const native = getNativeByTNode(previousOrParentTNode, lView);
12641248

1265-
const tView = getOrCreateTView(
1266-
def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs, def.viewQuery,
1267-
def.schemas);
1249+
const tView = getOrCreateTView(def);
12681250

12691251
// Only component views should be added to the view tree directly. Embedded views are
12701252
// accessed through their containers because they may be removed / re-added later.

packages/core/src/render3/interfaces/definition.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {SchemaMetadata, ViewEncapsulation} from '../../core';
1010
import {ProcessProvidersFunction} from '../../di/interface/provider';
1111
import {Type} from '../../interface/type';
1212
import {CssSelectorList} from './projection';
13+
import {TView} from './view';
1314

1415

1516
/**
@@ -19,7 +20,7 @@ export type ComponentTemplate<T> = {
1920
// Note: the ctx parameter is typed as T|U, as using only U would prevent a template with
2021
// e.g. ctx: {} from being assigned to ComponentTemplate<any> as TypeScript won't infer U = any
2122
// in that scenario. By including T this incompatibility is resolved.
22-
<U extends T>(rf: RenderFlags, ctx: T | U): void; ngPrivateData?: never;
23+
<U extends T>(rf: RenderFlags, ctx: T | U): void;
2324
};
2425

2526
/**
@@ -302,6 +303,12 @@ export interface ComponentDef<T> extends DirectiveDef<T> {
302303
*/
303304
schemas: SchemaMetadata[]|null;
304305

306+
/**
307+
* Ivy runtime uses this place to store the computed tView for the component. This gets filled on
308+
* the first run of component.
309+
*/
310+
tView: TView|null;
311+
305312
/**
306313
* Used to store the result of `noSideEffects` function so that it is not removed by closure
307314
* compiler. The property should never be read.

packages/core/src/render3/interfaces/view.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ export interface ExpandoInstructions extends Array<number|HostBindingsFunction<a
327327
* The static data for an LView (shared between all templates of a
328328
* given type).
329329
*
330-
* Stored on the template function as ngPrivateData.
330+
* Stored on the `ComponentDef.tView`.
331331
*/
332332
export interface TView {
333333
/**

packages/core/src/render3/jit/module.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ export function patchComponentDefWithScope<C>(
361361
// may face a problem where previously compiled defs available to a given Component/Directive
362362
// are cached in TView and may become stale (in case any of these defs gets recompiled). In
363363
// order to avoid this problem, we force fresh TView to be created.
364-
componentDef.template.ngPrivateData = undefined;
364+
componentDef.tView = null;
365365
}
366366

367367
/**

packages/core/test/acceptance/integration_spec.ts

+17
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88
import {CommonModule} from '@angular/common';
99
import {Component, ContentChild, Directive, ElementRef, EventEmitter, HostBinding, HostListener, Input, OnInit, Output, QueryList, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
10+
import {ngDevModeResetPerfCounters} from '@angular/core/src/util/ng_dev_mode';
1011
import {TestBed} from '@angular/core/testing';
1112
import {By} from '@angular/platform-browser';
1213
import {expect} from '@angular/platform-browser/testing/src/matchers';
@@ -29,23 +30,39 @@ describe('acceptance integration tests', () => {
2930
});
3031

3132
it('should render and update basic "Hello, World" template', () => {
33+
ngDevModeResetPerfCounters();
3234
@Component({template: '<h1>Hello, {{name}}!</h1>'})
3335
class App {
3436
name = '';
3537
}
3638

39+
onlyInIvy('perf counters').expectPerfCounters({
40+
tView: 0,
41+
tNode: 0,
42+
});
43+
3744
TestBed.configureTestingModule({declarations: [App]});
3845
const fixture = TestBed.createComponent(App);
3946

4047
fixture.componentInstance.name = 'World';
4148
fixture.detectChanges();
4249

4350
expect(fixture.nativeElement.innerHTML).toEqual('<h1>Hello, World!</h1>');
51+
onlyInIvy('perf counters').expectPerfCounters({
52+
tView: 2, // Host view + App
53+
tNode: 4, // Host Node + App Node + <span> + #text
54+
});
4455

4556
fixture.componentInstance.name = 'New World';
4657
fixture.detectChanges();
4758

4859
expect(fixture.nativeElement.innerHTML).toEqual('<h1>Hello, New World!</h1>');
60+
// Assert that the tView/tNode count does not increase (they are correctly cached)
61+
onlyInIvy('perf counters').expectPerfCounters({
62+
tView: 2,
63+
tNode: 4,
64+
});
65+
4966
});
5067
});
5168

packages/core/test/linker/ng_module_integration_spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ function declareTests(config?: {useJit: boolean}) {
142142
// may face a problem where previously compiled defs available to a given
143143
// Component/Directive are cached in TView and may become stale (in case any of these defs
144144
// gets recompiled). In order to avoid this problem, we force fresh TView to be created.
145-
componentDef.template.ngPrivateData = null;
145+
componentDef.TView = null;
146146
}
147147

148148
const ngModule = createModule(moduleType, injector);

packages/core/test/render3/integration_spec.ts

-50
Original file line numberDiff line numberDiff line change
@@ -196,56 +196,6 @@ describe('render3 integration test', () => {
196196

197197
});
198198

199-
describe('template data', () => {
200-
201-
it('should re-use template data and node data', () => {
202-
/**
203-
* % if (condition) {
204-
* <div></div>
205-
* % }
206-
*/
207-
function Template(rf: RenderFlags, ctx: any) {
208-
if (rf & RenderFlags.Create) {
209-
Δcontainer(0);
210-
}
211-
if (rf & RenderFlags.Update) {
212-
ΔcontainerRefreshStart(0);
213-
{
214-
if (ctx.condition) {
215-
let rf1 = ΔembeddedViewStart(0, 1, 0);
216-
if (rf1 & RenderFlags.Create) {
217-
Δelement(0, 'div');
218-
}
219-
ΔembeddedViewEnd();
220-
}
221-
}
222-
ΔcontainerRefreshEnd();
223-
}
224-
}
225-
226-
expect((Template as any).ngPrivateData).toBeUndefined();
227-
228-
renderToHtml(Template, {condition: true}, 1);
229-
230-
const oldTemplateData = (Template as any).ngPrivateData;
231-
const oldContainerData = (oldTemplateData as any).data[HEADER_OFFSET];
232-
const oldElementData = oldContainerData.tViews[0][HEADER_OFFSET];
233-
expect(oldContainerData).not.toBeNull();
234-
expect(oldElementData).not.toBeNull();
235-
236-
renderToHtml(Template, {condition: false}, 1);
237-
renderToHtml(Template, {condition: true}, 1);
238-
239-
const newTemplateData = (Template as any).ngPrivateData;
240-
const newContainerData = (oldTemplateData as any).data[HEADER_OFFSET];
241-
const newElementData = oldContainerData.tViews[0][HEADER_OFFSET];
242-
expect(newTemplateData === oldTemplateData).toBe(true);
243-
expect(newContainerData === oldContainerData).toBe(true);
244-
expect(newElementData === oldElementData).toBe(true);
245-
});
246-
247-
});
248-
249199
describe('component styles', () => {
250200
it('should pass in the component styles directly into the underlying renderer', () => {
251201
class StyledComp {

packages/core/test/render3/render_util.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {CreateComponentOptions} from '../../src/render3/component';
2828
import {getDirectivesAtNodeIndex, getLContext, isComponentInstance} from '../../src/render3/context_discovery';
2929
import {extractDirectiveDef, extractPipeDef} from '../../src/render3/definition';
3030
import {NG_ELEMENT_ID} from '../../src/render3/fields';
31-
import {ComponentTemplate, ComponentType, DirectiveDef, DirectiveType, RenderFlags, renderComponent as _renderComponent, tick, ΔProvidersFeature, ΔdefineComponent, ΔdefineDirective} from '../../src/render3/index';
31+
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveType, RenderFlags, renderComponent as _renderComponent, tick, ΔProvidersFeature, ΔdefineComponent, ΔdefineDirective} from '../../src/render3/index';
3232
import {DirectiveDefList, DirectiveDefListOrFactory, DirectiveTypesOrFactory, HostBindingsFunction, PipeDef, PipeDefList, PipeDefListOrFactory, PipeTypesOrFactory} from '../../src/render3/interfaces/definition';
3333
import {PlayerHandler} from '../../src/render3/interfaces/player';
3434
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, RendererFactory3, RendererStyleFlags3, domRendererFactory3} from '../../src/render3/interfaces/renderer';
@@ -258,8 +258,18 @@ export function renderTemplate<T>(
258258
LViewFlags.CheckAlways | LViewFlags.IsRoot, null, null, providedRendererFactory, renderer);
259259
enterView(hostLView, null); // SUSPECT! why do we need to enter the View?
260260

261-
const componentTView =
262-
getOrCreateTView(templateFn, consts, vars, directives || null, pipes || null, null, null);
261+
const def: ComponentDef<any> = ΔdefineComponent({
262+
factory: () => null,
263+
selectors: [],
264+
type: Object,
265+
template: templateFn,
266+
consts: consts,
267+
vars: vars,
268+
});
269+
def.directiveDefs = directives || null;
270+
def.pipeDefs = pipes || null;
271+
272+
const componentTView = getOrCreateTView(def);
263273
const hostTNode = createNodeAtIndex(0, TNodeType.Element, hostNode, null, null);
264274
componentView = createLView(
265275
hostLView, componentTView, context, LViewFlags.CheckAlways, hostNode, hostTNode,

0 commit comments

Comments
 (0)