mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[playground] Display only new HIR
Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react-forget/pull/824). * #825 * __->__ #824 [playground] Display only new HIR
This commit is contained in:
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*/
|
||||
import clsx from "clsx";
|
||||
import type { ForgetCompilerFlags } from "../../lib/compilerDriver";
|
||||
import { useStore, useStoreDispatch } from "../StoreContext";
|
||||
|
||||
// TODO: Allow collapsing the compiler options editor
|
||||
export default function CompilerFlagsEditor() {
|
||||
const store = useStore();
|
||||
const dispatch = useStoreDispatch();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 px-5 py-4 border-t border-gray-200">
|
||||
<div>
|
||||
<h3 className="font-medium">Compiler Options</h3>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
{Object.keys(store.compilerFlags).map((f) => {
|
||||
const flag = f as keyof ForgetCompilerFlags;
|
||||
const isChecked = store.compilerFlags[flag];
|
||||
|
||||
return (
|
||||
<label key={flag} className="flex gap-1.5 text-base">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isChecked}
|
||||
onChange={(e) =>
|
||||
dispatch({
|
||||
type: "setCompilerFlag",
|
||||
payload: { flag, value: e.target.checked },
|
||||
})
|
||||
}
|
||||
/>
|
||||
<span
|
||||
className={clsx("text-gray-500", { "text-black": isChecked })}
|
||||
>
|
||||
{flag}
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import clsx from "clsx";
|
||||
|
||||
import { HIR } from "babel-plugin-react-forget";
|
||||
import { useState } from "react";
|
||||
import generate from "@babel/generator";
|
||||
import prettier from "prettier";
|
||||
import prettierParserBabel from "prettier/parser-babel";
|
||||
|
||||
const {
|
||||
parseFunctions,
|
||||
Environment,
|
||||
enterSSA,
|
||||
eliminateRedundantPhi,
|
||||
inferReferenceEffects,
|
||||
inferMutableRanges,
|
||||
leaveSSA,
|
||||
lower,
|
||||
printHIR,
|
||||
codegen,
|
||||
} = HIR;
|
||||
|
||||
export default function HIRTabContent({ source }: { source: string }) {
|
||||
const astFunctions = parseFunctions(source);
|
||||
|
||||
const [flags, setFlags] = useState({
|
||||
codegen: true,
|
||||
eliminateRedundantPhi: true,
|
||||
inferReferenceEffects: true,
|
||||
inferMutableRanges: true,
|
||||
leaveSSA: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex-1">
|
||||
{astFunctions.map((func, index): React.ReactNode => {
|
||||
let body;
|
||||
try {
|
||||
const env = new Environment();
|
||||
const ir = lower(func, env);
|
||||
enterSSA(ir, env);
|
||||
if (flags.eliminateRedundantPhi) {
|
||||
eliminateRedundantPhi(ir);
|
||||
}
|
||||
if (flags.inferReferenceEffects) {
|
||||
inferReferenceEffects(ir);
|
||||
}
|
||||
if (flags.inferMutableRanges) {
|
||||
inferMutableRanges(ir);
|
||||
}
|
||||
if (flags.leaveSSA) {
|
||||
leaveSSA(ir);
|
||||
}
|
||||
|
||||
if (flags.codegen) {
|
||||
const ast = codegen(ir);
|
||||
const generated = generate(
|
||||
ast,
|
||||
{
|
||||
sourceMaps: true,
|
||||
sourceFileName: "input.js",
|
||||
},
|
||||
source
|
||||
);
|
||||
const formatted = prettier.format(generated.code, {
|
||||
semi: true,
|
||||
parser: "babel",
|
||||
plugins: [prettierParserBabel],
|
||||
});
|
||||
const sourceMapUrl = getSourceMapUrl(
|
||||
generated.code,
|
||||
JSON.stringify(generated.map)
|
||||
);
|
||||
body = (
|
||||
<>
|
||||
<pre>{formatted}</pre>
|
||||
{sourceMapUrl && (
|
||||
<iframe
|
||||
src={sourceMapUrl}
|
||||
className="w-full h-96"
|
||||
title="Generated Code"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
body = <pre>{printHIR(ir.body)}</pre>;
|
||||
}
|
||||
} catch (e: any) {
|
||||
body = <div>error: ${e.toString()}</div>;
|
||||
}
|
||||
const name = func.node.id?.name ?? "anonymous";
|
||||
return (
|
||||
<div key={index} className="px-2 py-4 border-b">
|
||||
<h3 className="mb-2 font-medium">Function: {name}</h3>
|
||||
{body}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<CompilerFlagsEditor flags={flags} setFlags={setFlags} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function utf16ToUTF8(s: string): string {
|
||||
return unescape(encodeURIComponent(s));
|
||||
}
|
||||
|
||||
function getSourceMapUrl(code: string, map: string): string | null {
|
||||
code = utf16ToUTF8(code);
|
||||
map = utf16ToUTF8(map);
|
||||
return `https://evanw.github.io/source-map-visualization/#${btoa(
|
||||
`${code.length}\0${code}${map.length}\0${map}`
|
||||
)}`;
|
||||
}
|
||||
|
||||
type Flags = {
|
||||
eliminateRedundantPhi: boolean;
|
||||
inferReferenceEffects: boolean;
|
||||
inferMutableRanges: boolean;
|
||||
leaveSSA: boolean;
|
||||
codegen: boolean;
|
||||
};
|
||||
|
||||
function CompilerFlagsEditor({
|
||||
flags,
|
||||
setFlags,
|
||||
}: {
|
||||
flags: Flags;
|
||||
setFlags: (flags: Flags) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex flex-col gap-2 px-5 py-4 border-t border-gray-200">
|
||||
<h3 className="font-medium">Compiler Options</h3>
|
||||
<div className="flex flex-col">
|
||||
<LabeledCheckbox
|
||||
label="Eliminate Redundant Phi"
|
||||
value={flags.eliminateRedundantPhi}
|
||||
onChange={(eliminateRedundantPhi) => {
|
||||
setFlags({ ...flags, eliminateRedundantPhi });
|
||||
}}
|
||||
/>
|
||||
<LabeledCheckbox
|
||||
label="Infer Reference Effects"
|
||||
value={flags.inferReferenceEffects}
|
||||
onChange={(inferReferenceEffects) => {
|
||||
setFlags({ ...flags, inferReferenceEffects });
|
||||
}}
|
||||
/>
|
||||
<LabeledCheckbox
|
||||
label="Infer Mutable Ranges"
|
||||
value={flags.inferMutableRanges}
|
||||
onChange={(inferMutableRanges) => {
|
||||
setFlags({ ...flags, inferMutableRanges });
|
||||
}}
|
||||
/>
|
||||
<LabeledCheckbox
|
||||
label="Leave SSA"
|
||||
value={flags.leaveSSA}
|
||||
onChange={(leaveSSA) => {
|
||||
setFlags({ ...flags, leaveSSA });
|
||||
}}
|
||||
/>
|
||||
<LabeledCheckbox
|
||||
label="Codegen"
|
||||
value={flags.codegen}
|
||||
onChange={(codegen) => {
|
||||
setFlags({ ...flags, codegen });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LabeledCheckbox({
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
label: string;
|
||||
value: boolean;
|
||||
onChange: (value: boolean) => void;
|
||||
}) {
|
||||
return (
|
||||
<label className="flex gap-1.5 text-base">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.checked);
|
||||
}}
|
||||
/>
|
||||
<span className={clsx({ "text-gray-500": !value, "text-black": value })}>
|
||||
{label}
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
@@ -5,21 +5,28 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import TabbedWindow from "../TabbedWindow";
|
||||
import generate from "@babel/generator";
|
||||
import MonacoEditor from "@monaco-editor/react";
|
||||
import {
|
||||
CompilerOutputs,
|
||||
OutputKind,
|
||||
stringifyCompilerOutputs,
|
||||
type Diagnostic,
|
||||
} from "babel-plugin-react-forget";
|
||||
import { memo, useEffect, useMemo } from "react";
|
||||
import compile from "../../lib/compilerDriver";
|
||||
import { Diagnostic, HIR, OutputKind } from "babel-plugin-react-forget";
|
||||
import prettier from "prettier";
|
||||
import prettierParserBabel from "prettier/parser-babel";
|
||||
import { memo } from "react";
|
||||
import type { Store } from "../../lib/stores";
|
||||
import GraphView from "../GraphView";
|
||||
import TabbedWindow from "../TabbedWindow";
|
||||
import { monacoOptions } from "./monacoOptions";
|
||||
import HIRTabContent from "./HIRTabContent";
|
||||
|
||||
const {
|
||||
parseFunctions,
|
||||
Environment,
|
||||
enterSSA,
|
||||
eliminateRedundantPhi,
|
||||
inferReferenceEffects,
|
||||
inferMutableRanges,
|
||||
leaveSSA,
|
||||
lower,
|
||||
printHIR,
|
||||
codegen,
|
||||
} = HIR;
|
||||
const MemoizedOutput = memo(Output);
|
||||
|
||||
export default MemoizedOutput;
|
||||
@@ -29,55 +36,141 @@ type Props = {
|
||||
updateDiagnostics: (newDiags: Diagnostic[]) => void;
|
||||
};
|
||||
|
||||
function Output({ store, updateDiagnostics }: Props) {
|
||||
const { outputs, diagnostics } = useMemo(
|
||||
() => compile(store.source, store.compilerFlags),
|
||||
[store]
|
||||
);
|
||||
useEffect(() => {
|
||||
updateDiagnostics(diagnostics);
|
||||
}, [diagnostics, updateDiagnostics]);
|
||||
return (
|
||||
<TabbedWindow
|
||||
defaultTab="HIR"
|
||||
tabs={{
|
||||
IR: <TextTabContent outputs={outputs} kind={OutputKind.IR} />,
|
||||
HIR: <HIRTabContent source={store.source} />,
|
||||
CFG: <TextTabContent outputs={outputs} kind={OutputKind.CFG} />,
|
||||
ValGraph: (
|
||||
<GraphTabContent outputs={outputs} kind={OutputKind.ValGraph} />
|
||||
),
|
||||
SCCGraph: (
|
||||
<GraphTabContent outputs={outputs} kind={OutputKind.SCCGraph} />
|
||||
),
|
||||
RedGraph: (
|
||||
<GraphTabContent outputs={outputs} kind={OutputKind.RedGraph} />
|
||||
),
|
||||
LIR: <TextTabContent outputs={outputs} kind={OutputKind.LIR} />,
|
||||
JS: <TextTabContent outputs={outputs} kind={OutputKind.JS} />,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
// TODO(gsn: Update diagnostics ƒrom HIR output
|
||||
function Output({ store }: Props) {
|
||||
const astFunctions = parseFunctions(store.source);
|
||||
if (astFunctions.length === 0) {
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: Handle multiple functions
|
||||
const func = astFunctions[0];
|
||||
const env = new Environment();
|
||||
const ir = lower(func, env);
|
||||
|
||||
const hirOutput = printHIR(ir.body);
|
||||
|
||||
enterSSA(ir, env);
|
||||
const ssaOutput = printHIR(ir.body);
|
||||
|
||||
eliminateRedundantPhi(ir);
|
||||
const eliminateRedundantPhiOutput = printHIR(ir.body);
|
||||
|
||||
inferReferenceEffects(ir);
|
||||
const inferReferenceEffectsOutput = printHIR(ir.body);
|
||||
|
||||
inferMutableRanges(ir);
|
||||
const inferMutableRangesOutput = printHIR(ir.body);
|
||||
|
||||
leaveSSA(ir);
|
||||
const leaveSSAOutput = printHIR(ir.body);
|
||||
|
||||
codegen(ir);
|
||||
const ast = codegen(ir);
|
||||
const generated = generate(
|
||||
ast,
|
||||
{
|
||||
sourceMaps: true,
|
||||
sourceFileName: "input.js",
|
||||
},
|
||||
store.source
|
||||
);
|
||||
const sourceMapUrl = getSourceMapUrl(
|
||||
generated.code,
|
||||
JSON.stringify(generated.map)
|
||||
);
|
||||
const codegenOutput = prettier.format(generated.code, {
|
||||
semi: true,
|
||||
parser: "babel",
|
||||
plugins: [prettierParserBabel],
|
||||
});
|
||||
return (
|
||||
<TabbedWindow
|
||||
defaultTab="HIR"
|
||||
tabs={{
|
||||
HIR: (
|
||||
<TextTabContent
|
||||
output={hirOutput}
|
||||
kind={OutputKind.IR}
|
||||
></TextTabContent>
|
||||
),
|
||||
SSA: (
|
||||
<TextTabContent
|
||||
output={ssaOutput}
|
||||
kind={OutputKind.IR}
|
||||
></TextTabContent>
|
||||
),
|
||||
EliminateRedundantPhi: (
|
||||
<TextTabContent
|
||||
output={eliminateRedundantPhiOutput}
|
||||
kind={OutputKind.IR}
|
||||
></TextTabContent>
|
||||
),
|
||||
InferReferenceEffects: (
|
||||
<TextTabContent
|
||||
output={inferReferenceEffectsOutput}
|
||||
kind={OutputKind.IR}
|
||||
></TextTabContent>
|
||||
),
|
||||
InferMutableRanges: (
|
||||
<TextTabContent
|
||||
output={inferMutableRangesOutput}
|
||||
kind={OutputKind.IR}
|
||||
></TextTabContent>
|
||||
),
|
||||
LeaveSSA: (
|
||||
<TextTabContent
|
||||
output={leaveSSAOutput}
|
||||
kind={OutputKind.IR}
|
||||
></TextTabContent>
|
||||
),
|
||||
JS: <TextTabContent output={codegenOutput} kind={OutputKind.JS} />,
|
||||
SourceMap: (
|
||||
<>
|
||||
{" "}
|
||||
{sourceMapUrl && (
|
||||
<iframe
|
||||
src={sourceMapUrl}
|
||||
className="w-full h-96"
|
||||
title="Generated Code"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} catch (e: any) {
|
||||
return <div>error: ${e.toString()}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
function utf16ToUTF8(s: string): string {
|
||||
return unescape(encodeURIComponent(s));
|
||||
}
|
||||
|
||||
function getSourceMapUrl(code: string, map: string): string | null {
|
||||
code = utf16ToUTF8(code);
|
||||
map = utf16ToUTF8(map);
|
||||
return `https://evanw.github.io/source-map-visualization/#${btoa(
|
||||
`${code.length}\0${code}${map.length}\0${map}`
|
||||
)}`;
|
||||
}
|
||||
|
||||
function TextTabContent({
|
||||
outputs,
|
||||
output,
|
||||
kind,
|
||||
}: {
|
||||
outputs: CompilerOutputs;
|
||||
output: string;
|
||||
kind: OutputKind;
|
||||
}) {
|
||||
const prettyOutputs = useMemo(
|
||||
() => stringifyCompilerOutputs(outputs),
|
||||
[outputs]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="w-full h-full">
|
||||
<MonacoEditor
|
||||
path={kind}
|
||||
defaultLanguage="javascript"
|
||||
value={prettyOutputs[kind] ?? "(Empty)"}
|
||||
value={output}
|
||||
options={{
|
||||
...monacoOptions,
|
||||
readOnly: true,
|
||||
@@ -86,17 +179,3 @@ function TextTabContent({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function GraphTabContent({
|
||||
outputs,
|
||||
kind,
|
||||
}: {
|
||||
outputs: CompilerOutputs;
|
||||
kind: OutputKind.ValGraph | OutputKind.SCCGraph | OutputKind.RedGraph;
|
||||
}) {
|
||||
return (
|
||||
<div className="w-full h-full overflow-auto">
|
||||
<GraphView graphs={outputs[kind]} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
type Store,
|
||||
} from "../../lib/stores";
|
||||
import { useStore, useStoreDispatch } from "../StoreContext";
|
||||
import CompilerFlagsEditor from "./CompilerFlagsEditor";
|
||||
import Input from "./Input";
|
||||
import Output from "./Output";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user