Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

compiler: Known hooks/nonescaping scopes dont count as pruned #29820

Merged
merged 3 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -331,29 +331,8 @@ class CountMemoBlockVisitor extends ReactiveFunctionVisitor<void> {
scopeBlock: PrunedReactiveScopeBlock,
state: void
): void {
let isHookOnlyMemoBlock = false;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this logic is now in FlattenScopesWithHooksOrUse, and also takes account of use() and not just hooks

if (
scopeBlock.instructions.length === 1 &&
scopeBlock.instructions[0].kind === "instruction"
) {
const instr = scopeBlock.instructions[0]!.instruction;
if (
instr.value.kind === "MethodCall" ||
instr.value.kind === "CallExpression"
) {
const callee =
instr.value.kind === "MethodCall"
? instr.value.property
: instr.value.callee;
if (getHookKind(this.env, callee.identifier) != null) {
isHookOnlyMemoBlock = true;
}
}
}
if (!isHookOnlyMemoBlock) {
this.prunedMemoBlocks += 1;
this.prunedMemoValues += scopeBlock.scope.declarations.size;
}
this.prunedMemoBlocks += 1;
this.prunedMemoValues += scopeBlock.scope.declarations.size;
this.traversePrunedScope(scopeBlock, state);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ class Transform extends ReactiveFunctionTransform<State> {
this.visitScope(scope, innerState);
outerState.hasHook ||= innerState.hasHook;
if (innerState.hasHook) {
if (scope.instructions.length === 1) {
/*
* This was a scope just for a hook call, which doesn't need memoization.
* flatten it away
*/
return {
kind: "replace-many",
value: scope.instructions,
};
}
/*
* else this scope had multiple instructions and produced some other value:
* mark it as pruned
*/
return {
kind: "replace",
value: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -951,12 +951,8 @@ class PruneScopesTransform extends ReactiveFunctionTransform<
} else {
this.prunedScopes.add(scopeBlock.scope.id);
return {
kind: "replace",
value: {
kind: "pruned-scope",
scope: scopeBlock.scope,
instructions: scopeBlock.instructions,
},
kind: "replace-many",
value: scopeBlock.instructions,
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@

```javascript
// @logger
import { useState } from "react";
import { identity, makeObject_Primitives, useHook } from "shared-runtime";
import { createContext, use, useState } from "react";
import {
Stringify,
identity,
makeObject_Primitives,
useHook,
} from "shared-runtime";

function Component() {
const w = use(Context);

// The scopes for x and x2 are interleaved, so this is one scope with two values
const x = makeObject_Primitives();
const x2 = makeObject_Primitives();
Expand All @@ -26,11 +33,21 @@ function Component() {
}

// Overall we expect two pruned scopes (for x+x2, and obj), with 3 pruned scope values.
return [x, x2, y, z];
return <Stringify items={[w, x, x2, y, z]} />;
}

const Context = createContext();

function Wrapper() {
return (
<Context value={42}>
<Component />
</Context>
);
}

export const FIXTURE_ENTRYPOINT = {
fn: Component,
fn: Wrapper,
params: [{}],
};

Expand All @@ -40,11 +57,17 @@ export const FIXTURE_ENTRYPOINT = {

```javascript
import { c as _c } from "react/compiler-runtime"; // @logger
import { useState } from "react";
import { identity, makeObject_Primitives, useHook } from "shared-runtime";
import { createContext, use, useState } from "react";
import {
Stringify,
identity,
makeObject_Primitives,
useHook,
} from "shared-runtime";

function Component() {
const $ = _c(5);
const $ = _c(6);
const w = use(Context);

const x = makeObject_Primitives();
const x2 = makeObject_Primitives();
Expand All @@ -65,20 +88,39 @@ function Component() {
z = $[0];
}
let t0;
if ($[1] !== x || $[2] !== x2 || $[3] !== y) {
t0 = [x, x2, y, z];
$[1] = x;
$[2] = x2;
$[3] = y;
$[4] = t0;
if ($[1] !== w || $[2] !== x || $[3] !== x2 || $[4] !== y) {
t0 = <Stringify items={[w, x, x2, y, z]} />;
$[1] = w;
$[2] = x;
$[3] = x2;
$[4] = y;
$[5] = t0;
} else {
t0 = $[5];
}
return t0;
}

const Context = createContext();

function Wrapper() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = (
<Context value={42}>
<Component />
</Context>
);
$[0] = t0;
} else {
t0 = $[4];
t0 = $[0];
}
return t0;
}

export const FIXTURE_ENTRYPOINT = {
fn: Component,
fn: Wrapper,
params: [{}],
};

Expand All @@ -87,8 +129,9 @@ export const FIXTURE_ENTRYPOINT = {
## Logs

```
{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":121},"end":{"line":26,"column":1,"index":813},"filename":"log-pruned-memoization.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":2,"prunedMemoValues":3}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":10,"column":0,"index":161},"end":{"line":33,"column":1,"index":905},"filename":"log-pruned-memoization.ts"},"fnName":"Component","memoSlots":6,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":2,"prunedMemoValues":3}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":37,"column":0,"index":941},"end":{"line":43,"column":1,"index":1039},"filename":"log-pruned-memoization.ts"},"fnName":"Wrapper","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0}
```
### Eval output
(kind: ok) [{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},[{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true}]]
(kind: ok) <div>{"items":[42,{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},[{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true},{"a":0,"b":"value1","c":true}]]}</div>
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
// @logger
import { useState } from "react";
import { identity, makeObject_Primitives, useHook } from "shared-runtime";
import { createContext, use, useState } from "react";
import {
Stringify,
identity,
makeObject_Primitives,
useHook,
} from "shared-runtime";

function Component() {
const w = use(Context);

// The scopes for x and x2 are interleaved, so this is one scope with two values
const x = makeObject_Primitives();
const x2 = makeObject_Primitives();
Expand All @@ -22,10 +29,20 @@ function Component() {
}

// Overall we expect two pruned scopes (for x+x2, and obj), with 3 pruned scope values.
return [x, x2, y, z];
return <Stringify items={[w, x, x2, y, z]} />;
}

const Context = createContext();

function Wrapper() {
return (
<Context value={42}>
<Component />
</Context>
);
}

export const FIXTURE_ENTRYPOINT = {
fn: Component,
fn: Wrapper,
params: [{}],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

## Input

```javascript
// @flow
function Component() {
return (
<div
className={stylex(
// this value is a) in its own scope, b) non-reactive, and c) non-escaping
// its scope gets pruned bc it's non-escaping, but this doesn't mean we need to
// create a temporary for it
flags.feature("feature-name") ? styles.featureNameStyle : null
)}
></div>
);
}

```

## Code

```javascript
import { c as _c } from "react/compiler-runtime";
function Component() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = (
<div
className={stylex(
flags.feature("feature-name") ? styles.featureNameStyle : null,
)}
/>
);
Comment on lines +28 to +35
Copy link
Contributor Author

@josephsavona josephsavona Jun 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This output matches what we have on main today. Whereas with the previous PR in the stack the output was:

const t0 = flags.feature("feature-name") ? styles.featureNameStyle : null;
  let t1;

  if ($[0] !== t0) {
    t1 = stylex(t0);
    $[0] = t0;
    $[1] = t1;
  } else {
    t1 = $[1];
  }

  let t2;

  if ($[2] !== t1) {
    t2 = <div className={t1} />;
    $[2] = t1;
    $[3] = t2;
  } else {
    t2 = $[3];
  }

  return t2;

Way more code and 4 cache slots instead of 1.

$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}

```

### Eval output
(kind: exception) Fixture not implemented
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// @flow
function Component() {
return (
<div
className={stylex(
// this value is a) in its own scope, b) non-reactive, and c) non-escaping
// its scope gets pruned bc it's non-escaping, but this doesn't mean we need to
// create a temporary for it
flags.feature("feature-name") ? styles.featureNameStyle : null
)}
></div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(2);
const $ = _c(1);

const a = [];
const b = [];
Expand All @@ -53,12 +53,11 @@ function Component(props) {
x = 1;
}
let t0;
if ($[0] !== x) {
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

x is non-escaping (and therefore counted as pruned) and non-reactive. before this PR, we would have promoted x to reactive because of the pruned+nonreactive combination — now it doesn't count as pruned so we revert back to the output from main

t0 = [x];
$[0] = x;
$[1] = t0;
$[0] = t0;
} else {
t0 = $[1];
t0 = $[0];
}
return t0;
}
Expand Down