Skip to content

Commit 15ec9d3

Browse files
committed
[compiler] Delete LoweredFunction.dependencies and hoisted instructions
LoweredFunction dependencies were exclusively used for dependency extraction (in `propagateScopeDeps`). Now that we have a `propagateScopeDepsHIR` that recursively traverses into nested functions, we can delete `dependencies` and their associated artificial `LoadLocal`/`PropertyLoad` instructions. ' Summary: Test Plan: Reviewers: Subscribers: Tasks: Tags: Summary: Test Plan: Reviewers: Subscribers: Tasks: Tags: Summary: Test Plan: Reviewers: Subscribers: Tasks: Tags: Summary: Test Plan: Reviewers: Subscribers: Tasks: Tags:
1 parent ff889d9 commit 15ec9d3

File tree

56 files changed

+1142
-477
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1142
-477
lines changed

compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts

Lines changed: 12 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import {NodePath, Scope} from '@babel/traverse';
99
import * as t from '@babel/types';
10-
import {Expression} from '@babel/types';
1110
import invariant from 'invariant';
1211
import {
1312
CompilerError,
@@ -75,7 +74,7 @@ export function lower(
7574
parent: NodePath<t.Function> | null = null,
7675
): Result<HIRFunction, CompilerError> {
7776
const builder = new HIRBuilder(env, parent ?? func, bindings, capturedRefs);
78-
const context: Array<Place> = [];
77+
const context: HIRFunction['context'] = [];
7978

8079
for (const ref of capturedRefs ?? []) {
8180
context.push({
@@ -3377,7 +3376,7 @@ function lowerFunction(
33773376
>,
33783377
): LoweredFunction | null {
33793378
const componentScope: Scope = builder.parentFunction.scope;
3380-
const captured = gatherCapturedDeps(builder, expr, componentScope);
3379+
const capturedContext = gatherCapturedContext(expr, componentScope);
33813380

33823381
/*
33833382
* TODO(gsn): In the future, we could only pass in the context identifiers
@@ -3391,7 +3390,7 @@ function lowerFunction(
33913390
expr,
33923391
builder.environment,
33933392
builder.bindings,
3394-
[...builder.context, ...captured.identifiers],
3393+
[...builder.context, ...capturedContext],
33953394
builder.parentFunction,
33963395
);
33973396
let loweredFunc: HIRFunction;
@@ -3404,7 +3403,6 @@ function lowerFunction(
34043403
loweredFunc = lowering.unwrap();
34053404
return {
34063405
func: loweredFunc,
3407-
dependencies: captured.refs,
34083406
};
34093407
}
34103408

@@ -4078,14 +4076,6 @@ function lowerAssignment(
40784076
}
40794077
}
40804078

4081-
function isValidDependency(path: NodePath<t.MemberExpression>): boolean {
4082-
const parent: NodePath<t.Node> = path.parentPath;
4083-
return (
4084-
!path.node.computed &&
4085-
!(parent.isCallExpression() && parent.get('callee') === path)
4086-
);
4087-
}
4088-
40894079
function captureScopes({from, to}: {from: Scope; to: Scope}): Set<Scope> {
40904080
let scopes: Set<Scope> = new Set();
40914081
while (from) {
@@ -4100,19 +4090,16 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set<Scope> {
41004090
return scopes;
41014091
}
41024092

4103-
function gatherCapturedDeps(
4104-
builder: HIRBuilder,
4093+
function gatherCapturedContext(
41054094
fn: NodePath<
41064095
| t.FunctionExpression
41074096
| t.ArrowFunctionExpression
41084097
| t.FunctionDeclaration
41094098
| t.ObjectMethod
41104099
>,
41114100
componentScope: Scope,
4112-
): {identifiers: Array<t.Identifier>; refs: Array<Place>} {
4113-
const capturedIds: Map<t.Identifier, number> = new Map();
4114-
const capturedRefs: Set<Place> = new Set();
4115-
const seenPaths: Set<string> = new Set();
4101+
): Array<t.Identifier> {
4102+
const capturedIds = new Set<t.Identifier>();
41164103

41174104
/*
41184105
* Capture all the scopes from the parent of this function up to and including
@@ -4123,33 +4110,11 @@ function gatherCapturedDeps(
41234110
to: componentScope,
41244111
});
41254112

4126-
function addCapturedId(bindingIdentifier: t.Identifier): number {
4127-
if (!capturedIds.has(bindingIdentifier)) {
4128-
const index = capturedIds.size;
4129-
capturedIds.set(bindingIdentifier, index);
4130-
return index;
4131-
} else {
4132-
return capturedIds.get(bindingIdentifier)!;
4133-
}
4134-
}
4135-
41364113
function handleMaybeDependency(
4137-
path:
4138-
| NodePath<t.MemberExpression>
4139-
| NodePath<t.Identifier>
4140-
| NodePath<t.JSXOpeningElement>,
4114+
path: NodePath<t.Identifier> | NodePath<t.JSXOpeningElement>,
41414115
): void {
41424116
// Base context variable to depend on
41434117
let baseIdentifier: NodePath<t.Identifier> | NodePath<t.JSXIdentifier>;
4144-
/*
4145-
* Base expression to depend on, which (for now) may contain non side-effectful
4146-
* member expressions
4147-
*/
4148-
let dependency:
4149-
| NodePath<t.MemberExpression>
4150-
| NodePath<t.JSXMemberExpression>
4151-
| NodePath<t.Identifier>
4152-
| NodePath<t.JSXIdentifier>;
41534118
if (path.isJSXOpeningElement()) {
41544119
const name = path.get('name');
41554120
if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) {
@@ -4165,115 +4130,20 @@ function gatherCapturedDeps(
41654130
'Invalid logic in gatherCapturedDeps',
41664131
);
41674132
baseIdentifier = current;
4168-
4169-
/*
4170-
* Get the expression to depend on, which may involve PropertyLoads
4171-
* for member expressions
4172-
*/
4173-
let currentDep:
4174-
| NodePath<t.JSXMemberExpression>
4175-
| NodePath<t.Identifier>
4176-
| NodePath<t.JSXIdentifier> = baseIdentifier;
4177-
4178-
while (true) {
4179-
const nextDep: null | NodePath<t.Node> = currentDep.parentPath;
4180-
if (nextDep && nextDep.isJSXMemberExpression()) {
4181-
currentDep = nextDep;
4182-
} else {
4183-
break;
4184-
}
4185-
}
4186-
dependency = currentDep;
4187-
} else if (path.isMemberExpression()) {
4188-
// Calculate baseIdentifier
4189-
let currentId: NodePath<Expression> = path;
4190-
while (currentId.isMemberExpression()) {
4191-
currentId = currentId.get('object');
4192-
}
4193-
if (!currentId.isIdentifier()) {
4194-
return;
4195-
}
4196-
baseIdentifier = currentId;
4197-
4198-
/*
4199-
* Get the expression to depend on, which may involve PropertyLoads
4200-
* for member expressions
4201-
*/
4202-
let currentDep:
4203-
| NodePath<t.MemberExpression>
4204-
| NodePath<t.Identifier>
4205-
| NodePath<t.JSXIdentifier> = baseIdentifier;
4206-
4207-
while (true) {
4208-
const nextDep: null | NodePath<t.Node> = currentDep.parentPath;
4209-
if (
4210-
nextDep &&
4211-
nextDep.isMemberExpression() &&
4212-
isValidDependency(nextDep)
4213-
) {
4214-
currentDep = nextDep;
4215-
} else {
4216-
break;
4217-
}
4218-
}
4219-
4220-
dependency = currentDep;
42214133
} else {
42224134
baseIdentifier = path;
4223-
dependency = path;
42244135
}
42254136

42264137
/*
42274138
* Skip dependency path, as we already tried to recursively add it (+ all subexpressions)
42284139
* as a dependency.
42294140
*/
4230-
dependency.skip();
4141+
path.skip();
42314142

42324143
// Add the base identifier binding as a dependency.
42334144
const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name);
4234-
if (binding === undefined || !pureScopes.has(binding.scope)) {
4235-
return;
4236-
}
4237-
const idKey = String(addCapturedId(binding.identifier));
4238-
4239-
// Add the expression (potentially a memberexpr path) as a dependency.
4240-
let exprKey = idKey;
4241-
if (dependency.isMemberExpression()) {
4242-
let pathTokens = [];
4243-
let current: NodePath<Expression> = dependency;
4244-
while (current.isMemberExpression()) {
4245-
const property = current.get('property') as NodePath<t.Identifier>;
4246-
pathTokens.push(property.node.name);
4247-
current = current.get('object');
4248-
}
4249-
4250-
exprKey += '.' + pathTokens.reverse().join('.');
4251-
} else if (dependency.isJSXMemberExpression()) {
4252-
let pathTokens = [];
4253-
let current: NodePath<t.JSXMemberExpression | t.JSXIdentifier> =
4254-
dependency;
4255-
while (current.isJSXMemberExpression()) {
4256-
const property = current.get('property');
4257-
pathTokens.push(property.node.name);
4258-
current = current.get('object');
4259-
}
4260-
}
4261-
4262-
if (!seenPaths.has(exprKey)) {
4263-
let loweredDep: Place;
4264-
if (dependency.isJSXIdentifier()) {
4265-
loweredDep = lowerValueToTemporary(builder, {
4266-
kind: 'LoadLocal',
4267-
place: lowerIdentifier(builder, dependency),
4268-
loc: path.node.loc ?? GeneratedSource,
4269-
});
4270-
} else if (dependency.isJSXMemberExpression()) {
4271-
loweredDep = lowerJsxMemberExpression(builder, dependency);
4272-
} else {
4273-
loweredDep = lowerExpressionToTemporary(builder, dependency);
4274-
}
4275-
capturedRefs.add(loweredDep);
4276-
seenPaths.add(exprKey);
4145+
if (binding !== undefined && pureScopes.has(binding.scope)) {
4146+
capturedIds.add(binding.identifier);
42774147
}
42784148
}
42794149

@@ -4304,13 +4174,13 @@ function gatherCapturedDeps(
43044174
return;
43054175
} else if (path.isJSXElement()) {
43064176
handleMaybeDependency(path.get('openingElement'));
4307-
} else if (path.isMemberExpression() || path.isIdentifier()) {
4177+
} else if (path.isIdentifier()) {
43084178
handleMaybeDependency(path);
43094179
}
43104180
},
43114181
});
43124182

4313-
return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]};
4183+
return [...capturedIds.keys()];
43144184
}
43154185

43164186
function notNull<T>(value: T | null): value is T {

compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -131,15 +131,7 @@ function collectHoistablePropertyLoadsImpl(
131131
fn: HIRFunction,
132132
context: CollectHoistablePropertyLoadsContext,
133133
): ReadonlyMap<BlockId, BlockInfo> {
134-
const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn);
135-
const actuallyEvaluatedTemporaries = new Map(
136-
[...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)),
137-
);
138-
139-
const nodes = collectNonNullsInBlocks(fn, {
140-
...context,
141-
temporaries: actuallyEvaluatedTemporaries,
142-
});
134+
const nodes = collectNonNullsInBlocks(fn, context);
143135
propagateNonNull(fn, nodes, context.registry);
144136

145137
if (DEBUG_PRINT) {
@@ -598,30 +590,3 @@ function reduceMaybeOptionalChains(
598590
}
599591
} while (changed);
600592
}
601-
602-
function collectFunctionExpressionFakeLoads(
603-
fn: HIRFunction,
604-
): Set<IdentifierId> {
605-
const sources = new Map<IdentifierId, IdentifierId>();
606-
const functionExpressionReferences = new Set<IdentifierId>();
607-
608-
for (const [_, block] of fn.body.blocks) {
609-
for (const {lvalue, value} of block.instructions) {
610-
if (
611-
value.kind === 'FunctionExpression' ||
612-
value.kind === 'ObjectMethod'
613-
) {
614-
for (const reference of value.loweredFunc.dependencies) {
615-
let curr: IdentifierId | undefined = reference.identifier.id;
616-
while (curr != null) {
617-
functionExpressionReferences.add(curr);
618-
curr = sources.get(curr);
619-
}
620-
}
621-
} else if (value.kind === 'PropertyLoad') {
622-
sources.set(lvalue.identifier.id, value.object.identifier.id);
623-
}
624-
}
625-
}
626-
return functionExpressionReferences;
627-
}

compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,6 @@ const EnvironmentConfigSchema = z.object({
239239
*/
240240
enableUseTypeAnnotations: z.boolean().default(false),
241241

242-
enableFunctionDependencyRewrite: z.boolean().default(true),
243-
244242
/**
245243
* Enables inference of optional dependency chains. Without this flag
246244
* a property chain such as `props?.items?.foo` will infer as a dep on

compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,6 @@ export type ObjectProperty = {
722722
};
723723

724724
export type LoweredFunction = {
725-
dependencies: Array<Place>;
726725
func: HIRFunction;
727726
};
728727

compiler/packages/babel-plugin-react-compiler/src/HIR/MergeOverlappingReactiveScopesHIR.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,13 +248,21 @@ function visitPlace(
248248
id: InstructionId,
249249
place: Place,
250250
{activeScopes, joined}: TraversalState,
251+
isFnExpr: boolean,
251252
): void {
253+
// Here, behavior differs:
254+
// With LoweredFunction.dependencies, we never infer functions as mutating primitives
255+
// as the layer of indirection (LoadLocal etc) ensures we don't treat this as a write
256+
// We make the same "hack" in InferReactiveScopeVariables
252257
/**
253258
* If an instruction mutates an outer scope, flatten all scopes from the top
254259
* of the stack to the mutated outer scope.
255260
*/
256261
const placeScope = getPlaceScope(id, place);
257262
if (placeScope != null && isMutable({id} as any, place)) {
263+
if (isFnExpr && place.identifier.type.kind === 'Primitive') {
264+
return;
265+
}
258266
const placeScopeIdx = activeScopes.indexOf(placeScope);
259267
if (placeScopeIdx !== -1 && placeScopeIdx !== activeScopes.length - 1) {
260268
joined.union([placeScope, ...activeScopes.slice(placeScopeIdx + 1)]);
@@ -275,15 +283,21 @@ function getOverlappingReactiveScopes(
275283
for (const instr of block.instructions) {
276284
visitInstructionId(instr.id, context, state);
277285
for (const place of eachInstructionOperand(instr)) {
278-
visitPlace(instr.id, place, state);
286+
visitPlace(
287+
instr.id,
288+
place,
289+
state,
290+
instr.value.kind === 'FunctionExpression' ||
291+
instr.value.kind === 'ObjectMethod',
292+
);
279293
}
280294
for (const place of eachInstructionLValue(instr)) {
281-
visitPlace(instr.id, place, state);
295+
visitPlace(instr.id, place, state, false);
282296
}
283297
}
284298
visitInstructionId(block.terminal.id, context, state);
285299
for (const place of eachTerminalOperand(block.terminal)) {
286-
visitPlace(block.terminal.id, place, state);
300+
visitPlace(block.terminal.id, place, state, false);
287301
}
288302
}
289303

compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -538,11 +538,8 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
538538
.split('\n')
539539
.map(line => ` ${line}`)
540540
.join('\n');
541-
const deps = instrValue.loweredFunc.dependencies
542-
.map(dep => printPlace(dep))
543-
.join(',');
544541
const context = instrValue.loweredFunc.func.context
545-
.map(dep => printPlace(dep))
542+
.map(dep => `${printPlace(dep)}`)
546543
.join(',');
547544
const effects =
548545
instrValue.loweredFunc.func.effects
@@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
557554
})
558555
.join(', ') ?? '';
559556
const type = printType(instrValue.loweredFunc.func.returnType).trim();
560-
value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`;
557+
value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`;
561558
break;
562559
}
563560
case 'TaggedTemplateExpression': {

compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -738,9 +738,8 @@ function collectDependencies(
738738
}
739739
for (const instr of block.instructions) {
740740
if (
741-
fn.env.config.enableFunctionDependencyRewrite &&
742-
(instr.value.kind === 'FunctionExpression' ||
743-
instr.value.kind === 'ObjectMethod')
741+
instr.value.kind === 'FunctionExpression' ||
742+
instr.value.kind === 'ObjectMethod'
744743
) {
745744
context.declare(instr.lvalue.identifier, {
746745
id: instr.id,

0 commit comments

Comments
 (0)