mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[devx] emit calls to makeReadOnly for debugging
---
Emit calls to makeReadOnly for memoized values.
```js
function MyComponent() {
let x;
if (c_0) {
x = // ... (recompute x)
$[0] = __DEV__ ? makeReadOnly(x, "MyComponent") : x;
} else {
x = $[0]
}
}
```
- import source / specifier should be configurable, as
- we'll likely want to add gk gating to `makeReadOnly` itself to reduce codesize
bloat
- each Forget project needs different logging and filter configurations
- codegen function name as an argument for easier debugging
- only freeze memoized outputs
This commit is contained in:
@@ -12,10 +12,10 @@ import {
|
||||
CompilerErrorDetail,
|
||||
ErrorSeverity,
|
||||
} from "../CompilerError";
|
||||
import { compileFn } from "./Pipeline";
|
||||
import { GeneratedSource } from "../HIR";
|
||||
import { addInstrumentForget } from "./Instrumentation";
|
||||
import { ExternalFunction, PluginOptions, parsePluginOptions } from "./Options";
|
||||
import { compileFn } from "./Pipeline";
|
||||
|
||||
export type CompilerPass = {
|
||||
opts: PluginOptions;
|
||||
@@ -332,6 +332,12 @@ export function compileProgram(
|
||||
buildImportForExternalFunction(options.instrumentForget.instrumentFn)
|
||||
);
|
||||
}
|
||||
if (options.environment?.enableEmitFreeze != null) {
|
||||
program.unshiftContainer(
|
||||
"body",
|
||||
buildImportForExternalFunction(options.environment?.enableEmitFreeze)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
ShapeRegistry,
|
||||
addHook,
|
||||
} from "./ObjectShape";
|
||||
import { ExternalFunction } from "../Entrypoint/Options";
|
||||
|
||||
export type Hook = {
|
||||
effectKind: Effect;
|
||||
@@ -126,6 +127,29 @@ export type EnvironmentConfig = Partial<{
|
||||
* Defaults to false (ie, by default memoization is enabled)
|
||||
*/
|
||||
disableAllMemoization: boolean;
|
||||
|
||||
/**
|
||||
* Enables codegen mutability debugging. This emits a dev-mode only to log mutations
|
||||
* to values that Forget assumes are immutable (for Forget compiled code).
|
||||
* For example:
|
||||
* emitFreeze: {
|
||||
* source: 'ReactForgetRuntime',
|
||||
* importSpecifierName: 'makeReadOnly',
|
||||
* }
|
||||
*
|
||||
* produces:
|
||||
* import {makeReadOnly} from 'ReactForgetRuntime';
|
||||
*
|
||||
* function Component(props) {
|
||||
* if (c_0) {
|
||||
* // ...
|
||||
* $[0] = __DEV__ ? makeReadOnly(x) : x;
|
||||
* } else {
|
||||
* x = $[0];
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
enableEmitFreeze: ExternalFunction | null;
|
||||
}>;
|
||||
|
||||
export class Environment {
|
||||
@@ -140,6 +164,8 @@ export class Environment {
|
||||
enableAssumeHooksFollowRulesOfReact: boolean;
|
||||
enableTreatHooksAsFunctions: boolean;
|
||||
disableAllMemoization: boolean;
|
||||
enableEmitFreeze: ExternalFunction | null;
|
||||
|
||||
#contextIdentifiers: Set<t.Identifier>;
|
||||
|
||||
constructor(
|
||||
@@ -181,6 +207,8 @@ export class Environment {
|
||||
this.enableTreatHooksAsFunctions =
|
||||
config?.enableTreatHooksAsFunctions ?? true;
|
||||
this.disableAllMemoization = config?.disableAllMemoization ?? false;
|
||||
this.enableEmitFreeze = config?.enableEmitFreeze ?? null;
|
||||
|
||||
this.#contextIdentifiers = contextIdentifiers;
|
||||
}
|
||||
|
||||
|
||||
+26
-4
@@ -8,6 +8,7 @@
|
||||
import * as t from "@babel/types";
|
||||
import invariant from "invariant";
|
||||
import { CompilerError, ErrorSeverity } from "../CompilerError";
|
||||
import { Environment } from "../HIR";
|
||||
import {
|
||||
BlockId,
|
||||
GeneratedSource,
|
||||
@@ -35,7 +36,7 @@ import { assertExhaustive } from "../Utils/utils";
|
||||
export function codegenReactiveFunction(
|
||||
fn: ReactiveFunction
|
||||
): Result<t.FunctionDeclaration, CompilerError> {
|
||||
const cx = new Context();
|
||||
const cx = new Context(fn.env, fn.id?.name ?? "[[ anonymous ]]");
|
||||
if (fn.id !== null) {
|
||||
cx.temp.set(fn.id.id, null);
|
||||
}
|
||||
@@ -84,11 +85,16 @@ export function codegenReactiveFunction(
|
||||
}
|
||||
|
||||
class Context {
|
||||
env: Environment;
|
||||
fnName: string;
|
||||
#nextCacheIndex: number = 0;
|
||||
#declarations: Set<IdentifierId> = new Set();
|
||||
temp: Temporaries = new Map();
|
||||
errors: CompilerError = new CompilerError();
|
||||
|
||||
constructor(env: Environment, fnName: string) {
|
||||
this.env = env;
|
||||
this.fnName = fnName;
|
||||
}
|
||||
get nextCacheIndex(): number {
|
||||
return this.#nextCacheIndex++;
|
||||
}
|
||||
@@ -147,6 +153,22 @@ function codegenBlock(cx: Context, block: ReactiveBlock): t.BlockStatement {
|
||||
return t.blockStatement(statements);
|
||||
}
|
||||
|
||||
function wrapCacheDep(cx: Context, value: t.Expression): t.Expression {
|
||||
if (cx.env.enableEmitFreeze != null) {
|
||||
// The import declaration for emitFreeze is inserted in the Babel plugin
|
||||
return t.conditionalExpression(
|
||||
t.identifier("__DEV__"),
|
||||
t.callExpression(
|
||||
t.identifier(cx.env.enableEmitFreeze.importSpecifierName),
|
||||
[value, t.stringLiteral(cx.fnName)]
|
||||
),
|
||||
value
|
||||
);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
function codegenReactiveScope(
|
||||
cx: Context,
|
||||
statements: Array<t.Statement>,
|
||||
@@ -212,7 +234,7 @@ function codegenReactiveScope(
|
||||
t.assignmentExpression(
|
||||
"=",
|
||||
t.memberExpression(t.identifier("$"), t.numericLiteral(index), true),
|
||||
name
|
||||
wrapCacheDep(cx, name)
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -239,7 +261,7 @@ function codegenReactiveScope(
|
||||
t.assignmentExpression(
|
||||
"=",
|
||||
t.memberExpression(t.identifier("$"), t.numericLiteral(index), true),
|
||||
name
|
||||
wrapCacheDep(cx, name)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -97,6 +97,7 @@ export async function compile(
|
||||
let enableTreatHooksAsFunctions = true;
|
||||
let disableAllMemoization = false;
|
||||
let validateRefAccessDuringRender = true;
|
||||
let enableEmitFreeze = null;
|
||||
if (firstLine.indexOf("@forgetDirective") !== -1) {
|
||||
enableOnlyOnUseForgetDirective = true;
|
||||
}
|
||||
@@ -136,6 +137,12 @@ export async function compile(
|
||||
if (firstLine.indexOf("@validateRefAccessDuringRender false") !== -1) {
|
||||
validateRefAccessDuringRender = false;
|
||||
}
|
||||
if (firstLine.indexOf("@enableEmitFreeze") !== -1) {
|
||||
enableEmitFreeze = {
|
||||
source: "react-forget-runtime-emit-freeze",
|
||||
importSpecifierName: "makeReadOnly",
|
||||
};
|
||||
}
|
||||
|
||||
const language = parseLanguage(firstLine);
|
||||
|
||||
@@ -160,6 +167,7 @@ export async function compile(
|
||||
validateHooksUsage: true,
|
||||
validateRefAccessDuringRender,
|
||||
validateFrozenLambdas: true,
|
||||
enableEmitFreeze,
|
||||
},
|
||||
logger: null,
|
||||
gating,
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableEmitFreeze true
|
||||
|
||||
function MyComponentName(props) {
|
||||
let x = {};
|
||||
foo(x, props.a);
|
||||
foo(x, props.b);
|
||||
|
||||
let y = [];
|
||||
y.push(x);
|
||||
return y;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { makeReadOnly } from "react-forget-runtime-emit-freeze";
|
||||
import { unstable_useMemoCache as useMemoCache } from "react"; // @enableEmitFreeze true
|
||||
|
||||
function MyComponentName(props) {
|
||||
const $ = useMemoCache(5);
|
||||
const c_0 = $[0] !== props.a;
|
||||
const c_1 = $[1] !== props.b;
|
||||
let x;
|
||||
if (c_0 || c_1) {
|
||||
x = {};
|
||||
foo(x, props.a);
|
||||
foo(x, props.b);
|
||||
$[0] = props.a;
|
||||
$[1] = props.b;
|
||||
$[2] = __DEV__ ? makeReadOnly(x, "MyComponentName") : x;
|
||||
} else {
|
||||
x = $[2];
|
||||
}
|
||||
const c_3 = $[3] !== x;
|
||||
let y;
|
||||
if (c_3) {
|
||||
y = [];
|
||||
y.push(x);
|
||||
$[3] = x;
|
||||
$[4] = __DEV__ ? makeReadOnly(y, "MyComponentName") : y;
|
||||
} else {
|
||||
y = $[4];
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// @enableEmitFreeze true
|
||||
|
||||
function MyComponentName(props) {
|
||||
let x = {};
|
||||
foo(x, props.a);
|
||||
foo(x, props.b);
|
||||
|
||||
let y = [];
|
||||
y.push(x);
|
||||
return y;
|
||||
}
|
||||
Reference in New Issue
Block a user