Repository: facebook/react
File: compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts (TypeScript)

Code before:
 * - A and B have identical dependencies. This means they will invalidate together, so
 *    by merging the scopes we can avoid duplicate cache slots and duplicate checks of
 *    those dependencies.
 * - The output of A is the input to B. Any invalidation of A will change its output
 *    which invalidates B, so we can similarly merge scopes. Note that this optimization
 *    may not be beneficial if the outupts of A are not guaranteed to change if its input
 *    changes, but in practice this is generally the case.
 *
 * ## Nested Scopes
 *
 * In this case, if an inner scope has the same dependencies as its parent, then we can
 * flatten away the inner scope since it will always invalidate at the same time.
 *
 * Note that PropagateScopeDependencies propagates scope dependencies upwards. This ensures
 * that parent scopes have the union of their own direct dependencies as well as those of
 * their (transitive) children. As a result nested scopes may have the same or fewer
 * dependencies than their parents, but not more dependencies. If they have fewer dependncies,
 * it means that the inner scope does not always invalidate with the parent and we should not
 * flatten. If they inner scope has the exact same dependencies, however, then it's always
 * better to flatten.
 */
export function mergeReactiveScopesThatInvalidateTogether(
  fn: ReactiveFunction
): void {
  log("MergeReactiveScopesThatInvalidateTogether");
  log(printReactiveFunction(fn));
  const lastUsageVisitor = new FindLastUsageVisitor();
  visitReactiveFunction(fn, lastUsageVisitor, undefined);
  visitReactiveFunction(fn, new Transform(lastUsageVisitor.lastUsage), null);
}

const DEBUG: boolean = false;
function log(msg: string): void {
  if (DEBUG) {
    console.log(msg);
  }
}

class FindLastUsageVisitor extends ReactiveFunctionVisitor<void> {
  lastUsage: Map<IdentifierId, InstructionId> = new Map();

  override visitPlace(id: InstructionId, place: Place, _state: void): void {
    const previousUsage = this.lastUsage.get(place.identifier.id);
    const lastUsage =
      previousUsage !== undefined
        ? makeInstructionId(Math.max(previousUsage, id))
        : id;
    this.lastUsage.set(place.identifier.id, lastUsage);
  }
}


Review comment (bug):
Makes sense if we want to keep this for debugging. 

Maybe we should consider passing in a lambda that wraps `printReactiveFunction` or running a rollup transform that strips `log`, `logger.logDiagnostic`, etc argument expressions so that prod build systems don't need to call these functions.

Doesn't have to be this PR, just wanted to record for the future..

Code after:
 * - The output of A is the input to B. Any invalidation of A will change its output
 *    which invalidates B, so we can similarly merge scopes. Note that this optimization
 *    may not be beneficial if the outupts of A are not guaranteed to change if its input
 *    changes, but in practice this is generally the case.
 *
 * ## Nested Scopes
 *
 * In this case, if an inner scope has the same dependencies as its parent, then we can
 * flatten away the inner scope since it will always invalidate at the same time.
 *
 * Note that PropagateScopeDependencies propagates scope dependencies upwards. This ensures
 * that parent scopes have the union of their own direct dependencies as well as those of
 * their (transitive) children. As a result nested scopes may have the same or fewer
 * dependencies than their parents, but not more dependencies. If they have fewer dependncies,
 * it means that the inner scope does not always invalidate with the parent and we should not
 * flatten. If they inner scope has the exact same dependencies, however, then it's always
 * better to flatten.
 */
export function mergeReactiveScopesThatInvalidateTogether(
  fn: ReactiveFunction
): void {
  const lastUsageVisitor = new FindLastUsageVisitor();
  visitReactiveFunction(fn, lastUsageVisitor, undefined);
  visitReactiveFunction(fn, new Transform(lastUsageVisitor.lastUsage), null);
}

const DEBUG: boolean = false;
function log(msg: string): void {
  if (DEBUG) {
    console.log(msg);
  }
}

class FindLastUsageVisitor extends ReactiveFunctionVisitor<void> {
  lastUsage: Map<IdentifierId, InstructionId> = new Map();

  override visitPlace(id: InstructionId, place: Place, _state: void): void {
    const previousUsage = this.lastUsage.get(place.identifier.id);
    const lastUsage =
      previousUsage !== undefined
        ? makeInstructionId(Math.max(previousUsage, id))
        : id;
    this.lastUsage.set(place.identifier.id, lastUsage);
  }
}

class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | null> {
  lastUsage: Map<IdentifierId, InstructionId>;

  constructor(lastUsage: Map<IdentifierId, InstructionId>) {
    super();