์ด์ ์ด์ผ๊ธฐ
์ง๋ ๋ํธ์ ํตํด ์ปดํ์ผ๋ฌ์ ์ง์ ์ ๊ณผ ์บ์ฑ๋ฐฉ๋ฒ์ธ useMemoCache์ ๋ํด์ ์์๋ณด์๋ค. ์ด๋ฒ ๋ถํฐ๋ ์ปดํ์ผ๋ฌ๊ฐ ์ด๋ค ๊ณผ์ ๋ค์ ํตํด์ ๊ตฌ๋ฌธ ๋ถ์์ ํ๊ณ ์ปดํ์ผ์ ํ๋์ง ์์๋ณด๋๋ก ํ์.
1ํธ์์ ์ดํด๋ดค๋ ์ค์ง์ ์ผ๋ก ์ปดํ์ผ์ ์งํํ๋ ํจ์์ธ compileFn
์ ๋ค์ ์ดํด๋ณด์.
export function compileFn(
func: NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
>,
config: EnvironmentConfig,
fnType: ReactFunctionType,
useMemoCacheIdentifier: string,
logger: Logger | null,
filename: string | null,
code: string | null
): CodegenFunction {
let generator = run(
func,
config,
fnType,
useMemoCacheIdentifier,
logger,
filename,
code
);
while (true) {
const next = generator.next();
if (next.done) {
return next.value;
}
}
}
compileFn
ํจ์๋ run
ํจ์๋ฅผ ํตํด ์ฝ๋๋ฅผ ์์ฑํ๊ณ , generator.next()
๋ฅผ ํตํด ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ณผ์ ์ ๋ฐ๋ณตํ๋ค.
// packages/babel-plugin-react-compiler/src/Entrypoint/Pipline.ts
export type CompilerPipelineValue =
| { kind: "ast"; name: string; value: CodegenFunction }
| { kind: "hir"; name: string; value: HIRFunction }
| { kind: "reactive"; name: string; value: ReactiveFunction }
| { kind: "debug"; name: string; value: string };
export function* run(
func: NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
>,
config: EnvironmentConfig,
fnType: ReactFunctionType,
useMemoCacheIdentifier: string,
logger: Logger | null,
filename: string | null,
code: string | null
): Generator<CompilerPipelineValue, CodegenFunction> {
const contextIdentifiers = findContextIdentifiers(func);
const env = new Environment(
fnType,
config,
contextIdentifiers,
logger,
filename,
code,
useMemoCacheIdentifier
);
yield {
kind: "debug",
name: "EnvironmentConfig",
value: prettyFormat(env.config),
};
const ast = yield* runWithEnvironment(func, env);
return ast;
}
run
ํจ์๋ Environment
๋ฅผ ์์ฑํ๊ณ , runWithEnvironment
ํจ์๋ฅผ ํตํด ์ฝ๋๋ฅผ ์์ฑํ๋ค.
Environment
๋ ์ปดํ์ผ ๊ณผ์ ์์ ์ ์ญ์ ์ธ ์ํ์ ์ค์ ์ ๊ด๋ฆฌํ๋ ๊ฐ์ฒด์ด๋ค.
์ด์ ๋ํ ์์ธํ ๋ด์ฉ์ ๋ค์์ ์ดํด๋ณด๋๋ก ํ๊ณ , ์ผ๋จ ๋์ด๊ฐ์.
์ด์ runWithEnvironment
ํจ์๋ฅผ ์ดํด๋ณด์.
์ค์ง์ ์ผ๋ก ์ฝ๋๋ฅผ ์์ฑํ๋ ํจ์์ด๋ค.
runWithEnvironment
// packages/babel-plugin-react-compiler/src/Entrypoint/Pipline.ts
function* runWithEnvironment(
func: NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
>,
env: Environment
): Generator<CompilerPipelineValue, CodegenFunction> {
const hir = lower(func, env).unwrap();
yield log({ kind: "hir", name: "HIR", value: hir });
pruneMaybeThrows(hir);
yield log({ kind: "hir", name: "PruneMaybeThrows", value: hir });
validateContextVariableLValues(hir);
validateUseMemo(hir);
dropManualMemoization(hir);
yield log({ kind: "hir", name: "DropManualMemoization", value: hir });
// ~~~~~~~~~~~~~~~~~~~~~
// --- ์ค๊ฐ ์ฌ๋ฌ ๊ณผ์ ๋ค ---
// ~~~~~~~~~~~~~~~~~~~~~
const ast = codegenFunction(reactiveFunction, uniqueIdentifiers).unwrap();
yield log({ kind: "ast", name: "Codegen", value: ast });
/**
* This flag should be only set for unit / fixture tests to check
* that Forget correctly handles unexpected errors (e.g. exceptions
* thrown by babel functions or other unexpected exceptions).
*/
if (env.config.throwUnknownException__testonly) {
throw new Error("unexpected error");
}
// ์ต์ข
์ ์ผ๋ก ์์ฑ๋ ์ฝ๋๋ฅผ ๋ฐํ
return ast;
}
์ ์ฒด ๊ณผ์ ์ ์ฝ 40๊ฐ ์ ๋์ ๊ณผ์ ์ ๊ฑฐ์น๋ฉฐ ์ฝ๋๋ฅผ ์์ฑํ๋ค.
๊ฐ ๊ณผ์ ์์ ์ฐ์ ํฌ๊ฒ ๋ถ๋ฅํด๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
- โLoweringโ: AST๋ฅผ HIR(High-level Intermediate Representation)๋ก ๋ณํ
- โNormalization, Optimizationโ : HIR์ ์ ๊ทํํ๊ณ ์ต์ ํํ๋ ๊ณผ์
- โStatic Analysis, Type Inferenceโ : SSA ํํ๋ก ๋ณํํ๊ณ ํ์ ์ถ๋ก
- โReactive Optimizationโ : ๋ฐ์์ฑ ์ต์ ํ
- โCode Generationโ : ์ฝ๋ ์์ฑ
๋ ์ธ๋ถ์ ์ผ๋ก ํผ์ณ๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
์์ง ๊น๊ฒ ๋ค์ฌ๋ค๋ณด์ง ๋ง๊ณ ์ด๋ค ๊ณผ์ ๋ค์ด ์๋์ง๋ง ํ์ด๋ณด์.
-
โLoweringโ
lower
: AST๋ฅผ HIR๋ก ๋ณํ
-
โ์ต์ ํ ๋ฐ ์ ๊ทํโ
pruneMaybeThrows
: HIR์์MaybeThrows
๋ฅผ ์ ๊ฑฐvalidateContextVariableLValues
: HIR์์ Context ๋ณ์์ LValues๋ฅผ ๊ฒ์ฆvalidateUseMemo
: HIR์์useMemo
๋ฅผ ๊ฒ์ฆdropManualMemoization
: ์๋์ผ๋ก ๋ฉ๋ชจ์ด์ ์ด์ ์ ์ ๊ฑฐinlineImmediatelyInvokedFunctionExpressions
: ์ฆ์ ํธ์ถ ํจ์ ํํ์(IIFE)์ ์ธ๋ผ์ธํmergeConsecutiveBlocks
: ์ฐ์๋ ๋ธ๋ก์ ๋ณํฉ
-
โ์ ์ ๋ถ์ ๋ฐ ํ์
์ถ๋ก โ
enterSSA
: SSA(Static Single Assignment) ํํ๋ก ๋ณํeliminateRedundantPhi
: ์ค๋ณต๋ Phi ๋ ธ๋ ์ ๊ฑฐconstantPropagation
: ์์ ์ ํinferTypes
: ํ์ ์ถ๋กvalidateHooksUsage
: ํ ์ฌ์ฉ์ ์ ํจ์ฑ ๊ฒ์ฌvalidateNoCapitalizedCalls
: ๋๋ฌธ์๋ก ์์ํ๋ ํจ์ ํธ์ถ ๊ฒ์ฌanalyseFunctions
: ํจ์ ๋ถ์inferReferenceEffects
: ์ฐธ์กฐ ํจ๊ณผ ์ถ๋กdeadCodeElimination
: ๋ฐ๋ ์ฝ๋ ์ ๊ฑฐinferMutableRanges
: ๊ฐ๋ณ ๋ฒ์ ์ถ๋กinferReactivePlaces
: ๋ฐ์ํ ์ฅ์ ์ถ๋กleaveSSA
: SSAํํ์์ ์ผ๋ฐ์ ์ธ ํํ๋ก ๋ณต๊ท
-
โ๋ฐ์ํ ์ต์ ํ(HIR)โ
inferReactiveScopeVariables
: ๋ฐ์ํ ์ค์ฝํ ๋ณ์ ์ถ๋กalignMethodCallScopes
: ๋ฉ์๋ ํธ์ถ ์ค์ฝํ ์ ๋ ฌalignObjectMethodScopes
: ๊ฐ์ฒด ๋ฉ์๋ ์ค์ฝํ ์ ๋ ฌmemoizeFbtOperandsInSameScope
: ๋์ผํ ์ค์ฝํ ๋ด Fbt ํผ์ฐ์ฐ์ ๋ฉ๋ชจ์ด์ ์ด์ pruneUnusedLabelsHIR
: ์ฌ์ฉ๋์ง ์๋ ๋ ์ด๋ธ ์ ๊ฑฐalignReactiveScopesToBlockScopesHIR
: ๋ฐ์ํ ์ค์ฝํ๋ฅผ ๋ธ๋ก ์ค์ฝํ์ ์ ๋ ฌmergeOverlappingReactiveScopesHIR
: ๊ฒน์น๋ ๋ฐ์ํ ์ค์ฝํ ๋ณํฉbuildReactiveScopeTerminalsHIR
: ๋ฐ์ํ ์ค์ฝํ ๋จ๋ง ์์ฑbuildReactiveFunction
: ๋ฐ์ํ ํจ์ ์์ฑ
-
โ๋ฐ์ํ ์ต์ ํ (Reactive function)โ
pruneUnusedLabels
: ์ฌ์ฉ๋์ง ์๋ ๋ ์ด๋ธ ์ ๊ฑฐflattenReactiveLoops
: ๋ฐ์ํ ๋ฃจํ ํํํpropagateScopeDependencies
: ์ค์ฝํ ์์กด์ฑ ์ ํpruneNonReactiveDependencies
: ๋น๋ฐ์ํ ์์กด์ฑ ์ ๊ฑฐpruneUnusedScopes
: ์ฌ์ฉ๋์ง ์๋ ์ค์ฝํ ์ ๊ฑฐmergeReactiveScopesThatInvalidateTogether
: ํจ๊ป ๋ฌดํจํ๋๋ ๋ฐ์ํ ์ค์ฝํ ๋ณํฉpruneAlwaysInvalidatingScopes
: ํญ์ ๋ฌดํจํ๋๋ ์ค์ฝํ ์ ๊ฑฐpropagateEarlyReturns
: ์กฐ๊ธฐ ๋ฐํ ์ ํpromoteUsedTemporaries
: ์ฌ์ฉ๋ ์์ ๋ณ์ ํ๋ก๋ชจ์ pruneUnusedLValues
: ์ฌ์ฉ๋์ง ์๋ LValue ์ ๊ฑฐextractScopeDeclarationsFromDestructuring
: ํด์ฒด์์ ์ค์ฝํ ์ ์ธ ์ถ์ถstabilizeBlockIds
: ๋ธ๋ก ID ์์ ํrenameVariables
: ๋ณ์ ์ด๋ฆ ๋ณ๊ฒฝpruneHoistedContexts
: ํธ์ด์คํ ๋ ์ปจํ ์คํธ ์ ๊ฑฐ
-
โCode Generationโ
codegenFunction
: ์ต์ข ์ฝ๋ ์์ฑ
๊ณผ์ ๋ค์ ๋ค ์ดํด๋ณผ์ง๋ ๋ชจ๋ฅด๊ฒ ์ง๋ง, ์ฐ์ ์์ํด๋ณด์.
Lowering
const hir = lower(func, env).unwrap();
๋ฆฌ์กํธ ์ปดํ์ผ๋ฌ์ ์ฒซ ๋ฒ์งธ ๋จ๊ณ์ธ Lowering์ AST(Abstract Syntax Tree)๋ฅผ HIR(High-level Intermediate Representation)๋ก ๋ณํํ๋ ๊ณผ์ ์ด๋ค.
๋จผ์ ์ปดํ์ผ์ ๋ํด ๋ค์ ์ง๊ณ ๋์ด๊ฐ๋ณด์.
to collect information from different places and arrange it in a book, report, or list
[Cambridge Dictionary]
์ฌ์ ์ ์๋ฏธ๋ก๋ ๋ค์ํ ์ ๋ณด๋ฅผ ์์งํ์ฌ ์ฑ
, ๋ณด๊ณ ์ ๋๋ ๋ชฉ๋ก์ผ๋ก ์ ๋ฆฌํ๋ ๊ฒ์ ์๋ฏธํ๋ค.
์ปดํจํฐ ๊ณผํ์์ ์ปดํ์ผ์ ์์ค ์ฝ๋๋ฅผ ๊ธฐ๊ณ์ด๋ก ๋ณํํ๋ ๊ณผ์ ์ ์๋ฏธํ๋ค.
๊ธฐ๊ณ์ด๋ก ๋ฐ๊พผ๋ค. ์ฝ๊ฒ ๋งํด, ์ปดํจํฐ๊ฐ ์ดํดํ ์ ์๋ ์ธ์ด๋ก ๋ฐ๊พผ๋ค๋ ๊ฒ์ด๋ค.

์์ค์ฝ๋์์ ๋ฐ๋ฒจ์ ์ํด AST๋ก ๋ณํ๋์๊ณ ์ด AST๋ฅผ ํ๋จ๊ณ ๋ ๋ฎ์ถฐ ์ค๊ฐํํ(IR)์ผ๋ก ๋ณํ ํ์๊ธฐ์ Lowering์ด๋ผ๊ณ ๋ถ๋ฅธ๋ค.
๊ทธ๋ผ HIR๋ก ๋ฐ๊ฟ์ฃผ๋ lower
ํจ์๋ฅผ ์ดํด๋ณด์.
// packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts
/*
* ํจ์๋ฅผ ์ ์ด ํ๋ฆ ๊ทธ๋ํ(Control-Flow Graph, CFG)๋ก ๋ํ๋ด๋ ๊ณ ์์ค ์ค๊ฐ ํํ(HIR)๋ก ๋ณํํฉ๋๋ค.
* ๋ชจ๋ ์ ์ ์ ์ด ํ๋ฆ์ ์ ํํ๊ฒ ๋ชจ๋ธ๋ง๋์ด ์ ํํ ํํ์์ค(expression-level)์ ๋ฉ๋ชจ์ด์ ์ด์
์ ํ์ฉํฉ๋๋ค.
* ์ฃผ์ ์์ธ๋ try/catch ๋ฌธ๊ณผ ์์ธ์
๋๋ค.
* ํ์ฌ try/catch๋ฅผ ์ํด ์ปดํ์ผ์ ๊ฑด๋๋ฐ๊ณ ์์ธ์ ์ ์ด ํ๋ฆ์ ๋ชจ๋ธ๋งํ๋ ค๊ณ ์๋ํ์ง ์์ต๋๋ค.
* ์ด๋ JavaScript์ ์ด๋์์๋ ๋ฐ์ํ ์ ์๋ ์์ธ์
๋๋ค.
* ์ปดํ์ผ๋ฌ๋ ์์ธ๊ฐ ๋ฐํ์์ ์ํด ์ฒ๋ฆฌ๋ ๊ฒ์ผ๋ก ๊ฐ์ ํ๋ฉฐ,
* ์ฆ ๋ฉ๋ชจ์ด์ ์ด์
์ ๋ฌดํจํํจ์ผ๋ก์จ ์ฒ๋ฆฌ๋ ๊ฒ์ผ๋ก ๊ฐ์ ํฉ๋๋ค.
*/
export function lower(
func: NodePath<t.Function>,
env: Environment,
bindings: Bindings | null = null,
capturedRefs: Array<t.Identifier> = [],
// ์ฌ๊ท์ ์ผ๋ก ํธ์ถ๋๋ ๊ฒฝ์ฐ(๋๋ค ํจ์์ ๊ฒฝ์ฐ) lower()๋ฅผ ํธ์ถํ๋ ๊ฐ์ฅ ๋ฐ๊นฅ์ชฝ ํจ์
parent: NodePath<t.Function> | null = null
): Result<HIRFunction, CompilerError> {
// ...
}
ํจ์ ์์ ์ฃผ์์ ์ฐธ๊ณ ํด๋ณด๋ฉด, AST(Abstract Syntax Tree)๋ฅผ CFG(Control-Flow Graph ์ดํ CFG)๋ก ๋ํ๋ด๋ ๊ณ ์์ค ์ค๊ฐ ํํ(HIR)๋ก ๋ณํํ๋ค๊ณ ํ๋ค.
AST๋ ๊ตฌ๋ฌธ ํธ๋ฆฌ๋ก, ํ๋ก๊ทธ๋จ์ ๊ตฌ๋ฌธ์ ๋ํ๋ด๋ ํธ๋ฆฌ ํํ์ ์๋ฃ๊ตฌ์กฐ์ธ ๋ฐ๋ฉด, CFG๋ ์ ์ด ํ๋ฆ ๊ทธ๋ํ๋ก, ํ๋ก๊ทธ๋จ์ ์ ์ด ํ๋ฆ์ ๋ํ๋ด๋ ๊ทธ๋ํ ํํ์ ์๋ฃ๊ตฌ์กฐ์ด๋ค.
๊ทธ๋ํ์ ๊ฐ ๋
ธ๋๋ ๊ธฐ๋ณธ ๋ธ๋ก(Basic Block)์ด๋ผ๊ณ ํ๋ ์ฝ๋์ ์ฐ์์ ์ธ ๋ถ๋ถ์ ๋ํ๋ด๋ฉฐ, ์ฃ์ง(edge)๋ ํ ๊ธฐ๋ณธ ๋ธ๋ก์์ ๋ค๋ฅธ ๊ธฐ๋ณธ ๋ธ๋ก์ผ๋ก์ ์ ์ด ํ๋ฆ์ ๋ํ ๋ธ๋ค.
์ ์ด ํ๋ฆ ๊ตฌ์กฐ๋, if/else, switch, loop์ ๊ฐ์ ๋ถ๊ธฐ์ ๋ฐ๋ณต์ ์๋ฏธํ๋ค.
CFG๋ ํ๋ก๊ทธ๋จ์ ๋ชจ๋ ๊ฐ๋ฅํ ์คํ ๊ฒฝ๋ก๋ฅผ ํฌ์ฐฉํ์ฌ ์ฝ๋ ์ต์ ํ์ ๋ถ์์ ์ฌ์ฉ๋๋ค.

๊ทธ๋ฆผ์ ์ด๋ ๊ฒ ๊ทธ๋ ธ์ง๋ง ๊ฐ ๋
ธ๋๋ ์๋ก ๋งค์นญ๋์ง ์๋๋ค.
์ดํด๋ฅผ ๋๊ธฐ ์ํด ๊ฒฐ๊ณผ๋ฌผ์ ๋จผ์ ์ดํด๋ณด์.
๋ค์์ ๊ฐ๋จํ ์ฝ๋๋ฅผ AST์ HIR๋ก ๋ณํํ ๊ฒฐ๊ณผ๋ฌผ์ด๋ค.
function Component({ color }: { color: string }) {
if (color === "red") {
return (<div styles={{ color }}>hello red</div>)
} else {
return (<div styles={{ color }}>hello etc</div>)
}
}
์ดํด๋ฅผ ๋๊ธฐ์ํ ์์ ์ฝ๋์ด๋ค. Component
ํจ์๋ color
๊ฐ red
์ผ ๋์ ์๋ ๋๋ฅผ ๋๋์ด ๋ค๋ฅธ JSX๋ฅผ ๋ฐํํ๋ค.
์ด ์ฝ๋๋ฅผ AST๋ก ๋ํ๋ด๋ฉด ๋ค์๊ณผ ๊ฐ์ ๊ตฌ๋ฌธ์ ๋ํ ํธ๋ฆฌ๋ก ํํ ๋ ๊ฒ์ด๋ค.
// Component ํจ์
FunctionDeclaration
Identifier
Parameter
ObjectBindingPattern
BindingElement
Identifier
TypeLiteral
Block
IfStatement
BinaryExpression
Identifier
EqualsEqualsEqualsToken
StringLiteral
Block
ReturnStatement
ParenthesizedExpression
JsxElement
JsxOpeningElement
Identifier
//...
//...
๊ทธ๋ํ๋ก ํํํ๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.

์ด์ ์ด AST๋ฅผ HIR๋ก ๋ณํํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด ๋์จ๋ค.
function Component
bb0 (block):
[1] <unknown> $2 = Destructure Let { color: <unknown> color$1 } = <unknown> $0
[2] <unknown> $11 = LoadLocal <unknown> color$1
[3] <unknown> $12 = "red"
[4] <unknown> $13 = Binary <unknown> $11 === <unknown> $12
[5] If (<unknown> $13) then:bb2 else:bb4 fallthrough=bb1
bb2 (block):
predecessor blocks: bb0
[6] <unknown> $3 = LoadLocal <unknown> color$1
[7] <unknown> $4 = Object { color: <unknown> $3 }
[8] <unknown> $5 = JSXText "hello red"
[9] <unknown> $6 = JSX <div styles={<unknown> $4} >{<unknown> $5}</div>
[10] Return <unknown> $6
bb4 (block):
predecessor blocks: bb0
[11] <unknown> $7 = LoadLocal <unknown> color$1
[12] <unknown> $8 = Object { color: <unknown> $7 }
[13] <unknown> $9 = JSXText "hello etc"
[14] <unknown> $10 = JSX <div styles={<unknown> $8} >{<unknown> $9}</div>
[15] Return <unknown> $10
bb1 (block):
[16] Unreachable
์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์ดํดํ์ง ์์๋ ๊ด์ฐฎ๋ค. ํ์ด๋ง ๋ณด์.
์ด ์์ ์ฝ๋์์์ ์ ์ดํ๋ฆ์ Component
ํจ์ ๋ด์ if
๋ฌธ์ด๋ค. ๊ทธ์ ๋ฐ๋ผ Component
ํจ์๋ bb0, bb2, bb4 ๊ทธ๋ฆฌ๊ณ bb1 ๋ค ๊ฐ์ ๊ธฐ๋ณธ ๋ธ๋ญ์ผ๋ก ๋๋์๋ค.
[5] If (<unknown> $13) then:bb2 else:bb4 fallthrough=bb1
์ด ๋ถ๋ถ์ ๋ณด๋ฉด If
๋ฌธ์ ํตํด bb2
๋ก ๊ฐ๋ ๊ฒฝ์ฐ์ bb4
๋ก ๊ฐ๋ ๊ฒฝ์ฐ, ๊ทธ๋ฆฌ๊ณ fallthrough
๋ก bb1
๋ก ๊ฐ๋ ํ๋ฆ์ด ํํ๋์ด ์๋ค.
fallthrough
๋ if
๋ฌธ์ ํต๊ณผํ ํ์ ํ๋ฆ์ ๋ํ๋ธ๋ค. ์ด ๊ฒฝ์ฐ์๋ ๋๋ฌ ํ ์ ์๋ ์ฝ๋๋ก ํ์๋์ด ์๋ค.
์ดํด๋ฅผ ๋๊ธฐ ์ํด ๊ทธ๋ฆผ์ผ๋ก ๊ทธ๋ ค๋ณด๋ฉด ์๋์ ๊ฐ์ ๋ชจ์ต์ด๋ค.

์~ ์ด์ ์ด๋ค ๋ชจ์ต์ผ๋ก ๋ณํ๋๋์ง ๊ฐ์ด ์ค๊ธฐ ์์ํ๋ค.
๊ตฌ๋ฌธ๋จ์์ ํธ๋ฆฌ๋ฅผ ์ด๋ฐ ํํ์ ์ ์ด ํ๋ฆ์ ๋ฐ๋ฅธ ๊ทธ๋ํ๋ก ํํํ๊ธฐ์ํ ๋ณํ ๊ณผ์ ์ด Lowering์ด๋ผ๋ ๊ฒ์ด๋ค.
๊ทธ๋ฆฌ๊ณ ๊ทธ ๋ณํ๋ ํํํํ๋ฅผ HIR๋ผ๊ณ ๋ถ๋ฅธ๋ค. (React Compiler ์์)
์ค๊ฐ ํ์ฐจ ์ง์
์ฌ๊ธฐ๊น์ง๋ง ์ดํด๋ณด์๋, HIR์ด ์ด๋ค ์๋ฃ๊ตฌ์กฐ๋ฅผ ์๋ฏธํ๋์ง ๋๋ต์ ์ผ๋ก ์ ์ ์์ด, ์ปดํ์ผ ๊ณผ์ ์ ์ดํดํ๋๋ฐ๋ ๋ฌธ์ ์์ ๊ฒ์ด๋ค.
๊ทธ๋ ๊ธฐ์ ๋ค์ ํธ์ผ๋ก ๋ฐ๋ก ์ด๋ํด๋ ์ข๋ค.
Detach!
๋ค์ lower ํจ์๋ก
๋ค์ ์ด์ด์๋ ๋จ์ ์ง์ ํธ๊ธฐ์ฌ์ผ๋ก lowering ๊ณผ์ ์ ์ดํด๋ณด๋๋ก ํ๊ฒ ๋ค. ๋ชจ๋ ๊ตฌ๋ฌธ์ ๋ํ lowering ๊ณผ์ ์ด ํฌํจ๋์ด์์ด. ์ฝ๋ ์์ฒด๋ 4,000์ค ๊ฐ๋ ๋๋ค.
๊ทธ๋ผ ์ฐจ๊ทผ์ฐจ๊ทผ ์ดํด๋ณด๋๋ก ํ์.
๋ค์ ์ฒ์์ผ๋ก ๋์์ lower
ํจ์๋ฅผ ์ดํด๋ณด์.
export function lower(
func: NodePath<t.Function>,
env: Environment,
bindings: Bindings | null = null,
capturedRefs: Array<t.Identifier> = [],
// the outermost function being compiled, in case lower() is called recursively (for lambdas)
parent: NodePath<t.Function> | null = null
): Result<HIRFunction, CompilerError> {}
lower
ํจ์๋ ๋ฐ๋ฒจ์์ ๋น๋กฏ๋ NodePath<t.Function>
๋ฅผ ์
๋ ฅ์ผ๋ก ๋ฐ์ HIRFunction
์ ๋ฐํํ๋ค. (์๋ฌ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ CompilerError
๋ฅผ ๋ฐํํ๋ค.)
๊ธฐํ ์ธ์๋ค๋ก๋, Environment
๊ฐ์ฒด, Bindings
, capturedRefs
, parent
๋ฑ์ด ์๋ค.
์ด๋ ์
๋ ฅ์ผ๋ก ๋ค์ด์ค๋ func์ ๋จ์๋ ์ด๋ค ๊ฒ์ธ๊ฐ?
์ด์ ์ 1ํธ์์ ์ดํด๋ดค๋ ๊ธฐ์ต์ ๋์ด๋ ค๋ณด์.
program ๋
ธ๋์ traverse ๋ฉ์๋๋ฅผ ํตํด์ ์ํํ๋ฉด์ ํจ์์ ๋ํ ๋
ธ๋๋ฅผ ์ฐพ์๋ด๊ณ , ๊ทธ ๋
ธ๋๋ฅผ compileFn
ํจ์์ ๋๊ฒจ์ฃผ์๋ค.
๊ทธ๋ ๊ธฐ์ func
๋ ๋ฐ๋ฒจ AST๋ก ๋ถํฐ ๋์ด์ค๋ ํจ์์ ๋ํ ๋
ธ๋๊ฐ ๋ ๊ฒ์ด๋ค.
FunctionDeclaration
, FunctionExpression
, ArrowFunctionExpression
์ด๋ ๊ฒ๊ฐ ๋ ๊ฒ์ด๋ค.
์ด์ด์โฆ
const builder = new HIRBuilder(env, parent ?? func, bindings, capturedRefs);
const context: Array<Place> = [];
HIRBuilder
์ธ์คํด์ค๋ฅผ ์์ฑํ์ฌ ํ ๋นํด๋๊ณ , context
๋ฐฐ์ด์ ์ด๊ธฐํ ํ๋ค. ํจ์ ์ปจํ
์คํธ ์ ๋ณด๋ฅผ ๋ด๋ ๋ฐฐ์ด์ด๋ค.
for (const ref of capturedRefs ?? []) {
context.push({
kind: "Identifier",
identifier: builder.resolveBinding(ref),
effect: Effect.Unknown,
reactive: false,
loc: ref.loc ?? GeneratedSource,
});
}
capturedRefs
๋ฐฐ์ด์ ์ํํ๋ฉฐ, context
๋ฐฐ์ด์ Place
๊ฐ์ฒด๋ฅผ ์ถ๊ฐํ๋ค.
์ด๋ถ๋ถ์ ์ฌ๊ท๋ก ํธ์ถ๋๋ ๊ฒฝ์ฐ์ด๋ ๋์ค์ ๋ค์ ๋ณด๋ฌ์ค๊ธฐ๋ก ํ๊ณ ์ผ๋จ ๋์ด๊ฐ์.
ํจ์ ์ด๋ฆ ์ถ์ถ(์๋ณ์, Identifier)
ํจ์๊ฐ FunctionDeclaration
๋๋ FunctionExpression
์ธ ๊ฒฝ์ฐ, ํจ์์ id
(Identifier) ์๋ณ์๋ฅผ ๊ฐ์ ธ์ id
๋ณ์์ ํ ๋นํ๋ค.
let id: string | null = null;
if (func.isFunctionDeclaration() || func.isFunctionExpression()) {
const idNode = (
func as NodePath<t.FunctionDeclaration | t.FunctionExpression>
).get("id");
if (hasNode(idNode)) {
id = idNode.node.name;
}
}
FunctionDeclaration(ํจ์ ์ ์ธ์) ๋ ธ๋์ FunctionExpression(ํจ์ ํํ์) ๋ ธ๋๋ ์๋ณ์๋ฅผ ๊ฐ์ง ์ ์์ง๋ง, ArrowFunctionExpression(ํ์ดํ ํจ์) ๋ ธ๋๋ ์๋ณ์๋ฅผ ๊ฐ์ง ์ ์๋ค.
// FunctionDeclaration
function foo() {}
// FunctionExpression
const foo = function bar() {}
// ArrowFunctionExpression
const foo = () => {}
์ธ์ ์ถ์ถ(Parameters)
ํจ์์ ์ธ์๋ฅผ ์ถ์ถํ๊ณ , params
๋ฐฐ์ด์ Place
๊ฐ์ฒด๋ฅผ ์ถ๊ฐํ๋ค.
params: Array<Identifier | Pattern | RestElement> (required)
params
์๋ Identifier
, Pattern
, RestElement
๊ฐ ์ฌ ์ ์๋ค.
Identifier
๋ ์๋ณ์, Pattern
์ ๊ฐ์ฒด๋ ๋ฐฐ์ด ํจํด, RestElement
๋ ๋๋จธ์ง ์์๋ฅผ ๋ํ๋ธ๋ค.
const params: Array<Place | SpreadPattern> = [];
func.get("params").forEach((param) => {
if (param.isIdentifier()) {}
else if (param.isObjectPattern() || param.isArrayPattern() || param.isAssignmentPattern()) {}
else if (param.isRestElement()) {}
else {}
// ...
});
Identifier(๋จ์ผ ๋ณ์ ์ด๋ฆ)
// ์์
const greet = function(name) { // name์ด Identifier
console.log(`Hello, ${name}!`);
};
์ธ์๊ฐ Identifier
์ธ ๊ฒฝ์ฐ, Place
๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ params
๋ฐฐ์ด์ ์ถ๊ฐํ๋ค.
if (param.isIdentifier()) {
const binding = builder.resolveIdentifier(param);
const place: Place = {
kind: "Identifier",
identifier: binding.identifier,
effect: Effect.Unknown,
reactive: false,
loc: param.node.loc ?? GeneratedSource,
};
params.push(place);
}
์์ผ๋ก ์์ฃผ ๋ฑ์ฅํ ๊ฒ์ธ๋ฐ Place
๊ฐ์ฒด๋ ๋ค์๊ณผ ๊ฐ์ด ์ ์๋์ด ์๋ค.
/*
* ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ฑฐ๋ ์ธ ์ ์๋ ์ฅ์:
* - ๋ณ์(์๋ณ์)
* - ์๋ณ์๋ก์ ๊ฒฝ๋ก
*/
export type Place = {
kind: "Identifier"; // ์ข
๋ฅ
identifier: Identifier; // ์๋ณ์
effect: Effect; // The effect with which a value is modified. (์์ ๋ ๊ฐ์ ํจ๊ณผ)
reactive: boolean;
loc: SourceLocation;
};
ObjectPattern(๊ฐ์ฒด ํจํด), ArrayPattern(๋ฐฐ์ด ํจํด), AssignmentPattern(ํ ๋น ํจํด)
// ์์
// ObjectPattern
const greet = function({ name }) {
console.log(`Hello, ${name}!`);
};
// ArrayPattern
const greet = function([name]) {
console.log(`Hello, ${name}!`);
};
// AssignmentPattern
const greet = function(name = 'world') {
console.log(`Hello, ${name}!`);
};
์ธ์๊ฐ ObjectPattern
, ArrayPattern
, AssignmentPattern
์ธ ๊ฒฝ์ฐ, ์์ ๋ณ์๋ฅผ ์์ฑํ๊ณ Place
๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ params
๋ฐฐ์ด์ ์ถ๊ฐํ๋ค.
lowerAssignment
ํจ์๋ฅผ ํตํด ํ ๋น์ ์ฒ๋ฆฌํ๋ค.
else if (
param.isObjectPattern() ||
param.isArrayPattern() ||
param.isAssignmentPattern()
) {
const place: Place = {
kind: "Identifier",
identifier: builder.makeTemporary(),
effect: Effect.Unknown,
reactive: false,
loc: param.node.loc ?? GeneratedSource,
};
params.push(place);
lowerAssignment(
builder,
param.node.loc ?? GeneratedSource,
InstructionKind.Let,
param,
place,
"Assignment"
);
}
RestElement(๋๋จธ์ง ์์)
// ์์
const greet = function(...names) {
console.log(`Hello, ${names.join(', ')}!`);
};
์ธ์๊ฐ RestElement
์ธ ๊ฒฝ์ฐ, ์์ ๋ณ์๋ฅผ ์์ฑํ๊ณ Spread
๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ params
๋ฐฐ์ด์ ์ถ๊ฐํ๋ค.
else if (param.isRestElement()) {
const place: Place = {
kind: "Identifier",
identifier: builder.makeTemporary(),
effect: Effect.Unknown,
reactive: false,
loc: param.node.loc ?? GeneratedSource,
};
params.push({
kind: "Spread",
place,
});
lowerAssignment(
builder,
param.node.loc ?? GeneratedSource,
InstructionKind.Let,
param.get("argument"),
place,
"Assignment"
);
}
์ด๋ ๊ฒ ํจ์์ ์ธ์๋ฅผ ์ถ์ถํ๊ณ params
๋ฐฐ์ด์ Place
๊ฐ์ฒด๋ฅผ ์ถ๊ฐํ๋ ๊ณผ์ ์ ๊ฑฐ์น๋ค.
์ด์ ํจ์์ ๋ณธ๋ฌธ์ ์ถ์ถํ๊ณ , body
๋ฐฐ์ด์ Instruction
๊ฐ์ฒด๋ฅผ ์ถ๊ฐํ๋ ๊ณผ์ ์ ์ดํด๋ณด์.
ํจ์ ๋ณธ๋ฌธ ์ถ์ถ(Body)
let directives: Array<string> = [];
const body = func.get("body");
ํจ์์ ๋ณธ๋ฌธ์ ์ถ์ถํ๊ณ , directives
๋ฐฐ์ด์ ์ด๊ธฐํํ๋ค.
Expression(ํํ์)
if (body.isExpression()) {
const fallthrough = builder.reserve("block");
const terminal: ReturnTerminal = {
kind: "return",
loc: GeneratedSource,
value: lowerExpressionToTemporary(builder, body),
id: makeInstructionId(0),
};
builder.terminateWithContinuation(terminal, fallthrough);
}
BlockStatement(๋ธ๋ก๋ฌธ)
๋ธ๋ญ๋ฌธ์ผ ๊ฒฝ์ฐ, lowerStatement
ํจ์๋ฅผ ํตํด body
๋ฅผ ์ฒ๋ฆฌํ๋ค.
else if (body.isBlockStatement()) {
lowerStatement(builder, body);
directives = body.get("directives").map((d) => d.node.value.value);
}
์ด ๋ถ๋ถ์ด ์ฌ์ค ์ฝ์ดํ ๋ถ๋ถ์ด๋ผ๊ณ ๋ณผ ์ ์๋ค.
lowerStatement
ํจ์๋ฅผ ์ดํด๋ณด์.
function lowerStatement(
builder: HIRBuilder,
stmtPath: NodePath<t.Statement>,
label: string | null = null
): void {
const stmtNode = stmtPath.node;
switch (stmtNode.type) {
case "ThrowStatement":
case "ReturnStatement":
case "IfStatement":
case "BlockStatement":
case "BreakStatement":
case "ContinueStatement":
case "ForStatement":
case "WhileStatement":
case "LabeledStatement":
case "SwitchStatement":
case "VariableDeclaration":
case "ExpressionStatement":
case "DoWhileStatement":
case "FunctionDeclaration":
case "ForOfStatement":
case "ForInStatement":
case "DebuggerStatement":
case "EmptyStatement":
case "TryStatement":
// ------- skip -------
case "TypeAlias":
case "TSTypeAliasDeclaration":
// --- unsupported ---
case "ClassDeclaration":
// ~
case "WithStatement":
default:
stmtPath
(Statement Path)์ ๋
ธ๋ ํ์
์ ๋ฐ๋ผ ๋ค์ํ ์ฒ๋ฆฌ๋ฅผ ํ๋ค.
lower
ํจ์์ ์ํด์ ํธ์ถ๋ ๋, stmtPath
๋ BlockStatement
๊ฐ ๋ค์ด์ฌ ๊ฒ์ด๋ค. ๊ทธ๋ผ ์์๋ฅผ ํตํด์ ์ดํด๋ณด์.
// ์์
function complexExample(x) {
let result = 0;
if (x > 0) {
result = x * 2;
} else {
result = x * 3;
}
return result;
}
ํจ์ complexExample
์ body๋ BlockStatement
์ด๋ค. ์ด BlockStatement
๋ฅผ lowerStatement
ํจ์์ ๋๊ฒจ๋ณด์.
case "BlockStatement": {
const stmt = stmtPath as NodePath<t.BlockStatement>;
const statements = stmt.get("body");
stmtPath
๋ฅผ BlockStatement
๋ก ํ์
์บ์คํ
ํ๊ณ , body
๋ฅผ ์ถ์ถํ๋ค.
body
๋ NodePath<t.Statement>[]
ํ์
์ผ๋ก, ๋ธ๋ก๋ฌธ ๋ด์ ๊ฐ ๋ฌธ์ฅ์ ๋ํ๋ธ๋ค.
for (const s of statements) {
lowerStatement(builder, s);
}
statements๋ค์ ์ํ ํ๋ฉด์, lowerStatement
ํจ์๋ฅผ ์ฌ๊ท์ ์ผ๋ก ํธ์ถํ๋ค.
blockStatment ์ฒ๋ฆฌ ์ค์ ์ฌ๊ท์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ๊ณผ์ ๋ง๊ณ ๋ ๋ค๋ฅธ ๊ณผ์ ์ด ํ๋ ๋ ์๋ค.
โconstโ ๋ณ์ ์ ์ธ์ Hoisting ์ฒ๋ฆฌ
๋จผ์ ์ด๋ค ๋ฐ์ธ๋ฉ์ด ์ ์ธ๋๊ธฐ ์ ์ ์ฐธ์กฐ๋ ๊ฒฝ์ฐ ํธ์ด์คํ
๋ ์ ์๋์ง ํ์ธํ ๋ค,
ํด๋น ์ ์ธ์ ์ฐธ์กฐ๋๋ ๊ฐ์ฅ ๋น ๋ฅธ ์ง์ (์ฆ, ๋ฐ๋ก ์์ ์ต์์ ๋ฌธ)์ผ๋ก ์ปจํ
์คํธ ๋ณ์๋ก ํธ์ด์คํ
ํ๋ค.\
const hoistableIdentifiers: Set<t.Identifier> = new Set();
for (const [, binding] of Object.entries(stmt.scope.bindings)) {
// refs to params are always valid / never need to be hoisted
if (binding.kind !== "param") {
hoistableIdentifiers.add(binding.identifier);
}
}
hoistableIdentifiers
๋ผ๋ Set
์ ์์ฑํ๊ณ , stmt.scope.bindings
๋ฅผ ์ํํ๋ฉฐ
identifier ๋ค์ hoistableIdentifiers
์ ์ถ๊ฐํ๋ค.
stmt.scope.bindings๋ ํ์ฌ ๋ธ๋ก์ ๋ฐ์ธ๋ฉ ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ค.
๋ถ๋ชจ๋ ์์ ๋ธ๋ก์ ๋ฐ์ธ๋ฉ ์ ๋ณด๋ ํฌํจํ์ง ์๋๋ค.
์ด๋, const
, let
, var
์ด hoistableIdentifiers
์ ์ถ๊ฐ๋๋ค.
๊ฐ ๋ฌธ์ ์ํํ ๋, ํธ์ด์คํ
์ด ํ์ํ ์์๋ค์ ๋ฃ์ willHoist
๋ผ๋ Set
์ ์์ฑํ๋ค.
for (const s of statements) {
const willHoist = new Set<NodePath<t.Identifier>>();
// ...
}
ํจ์ ์ปจํ
์คํธ์ ๊น์ด๋ฅผ ์ถ์ ํ๋ค. ์๋ณ์ ์ฐธ์กฐ๊ฐ ๋ด๋ถ ํจ์์์ ๋ฐ์ํ๋์ง ์ถ์ ํ๊ธฐ ์ํด์์ด๋ค.
traverse
๋ฅผ ํตํด ํ์ํ๋ฉด์ FunctionExpression
, FunctionDeclaration
, ArrowFunctionExpression
, ObjectMethod
๋
ธ๋๋ฅผ ๋ง๋๋ฉด ํจ์ ๊น์ด๋ฅผ ์ฆ๊ฐ์ํค๊ณ , ๋
ธ๋๋ฅผ ๋น ์ ธ๋์ค๋ฉด ํจ์ ๊น์ด๋ฅผ ๊ฐ์์ํจ๋ค.
let fnDepth = s.isFunctionDeclaration() ? 1 : 0;
const withFunctionContext = {
enter: (): void => {
fnDepth++; // ํจ์ ๊น์ด ์ฆ๊ฐ
},
exit: (): void => {
fnDepth--; // ํจ์ ๊น์ด ๊ฐ์
},
};
s.traverse({
FunctionExpression: withFunctionContext, // ํจ์ ํํ์
FunctionDeclaration: withFunctionContext, // ํจ์ ์ ์ธ์
ArrowFunctionExpression: withFunctionContext, // ํ์ดํ ํจ์
ObjectMethod: withFunctionContext, // ๊ฐ์ฒด ๋ฉ์๋
// ...
});
๋ฌธ์ ์ํํ๋ฉด์, ์๋ณ์๋ฅผ ์ฐพ๋๋ค.
์๋ณ์๊ฐ ์ฐธ์กฐ๋์ง ์๊ฑฐ๋, ๋ถ๋ชจ๊ฐ ํ ๋น ํํ์์ด ์๋ ๊ฒฝ์ฐ, ๋์ด๊ฐ๋ค.
์๋ณ์๊ฐ ์ฐธ์กฐ๋๊ณ hoistableIdentifiers
์ ์์ผ๋ฉฐ, ํจ์ ๊น์ด๊ฐ 0๋ณด๋ค ํฌ๊ฑฐ๋ ๋ฐ์ธ๋ฉ์ด hoisted
์ธ ๊ฒฝ์ฐ, willHoist
์ ์ถ๊ฐํ๋ค.
s.traverse({
// ...
Identifier(id: NodePath<t.Identifier>) {
const id2 = id;
if (
!id2.isReferencedIdentifier() &&
// isReferencedIdentifier is broken and returns false for reassignments
id.parent.type !== "AssignmentExpression"
) {
return;
}
const binding = id.scope.getBinding(id.node.name);
/*
* ์๋ณ์ ์ ์ธ์ ํธ์ด์คํธํ ์ ์๋ ๊ฒฝ์ฐ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
* 1. ์ฐธ์กฐ๊ฐ ๋ด๋ถ ํจ์ ๋ด์์ ๋ฐ์ํ๋ ๊ฒฝ์ฐ
* ๋๋
* 2. ์ ์ธ ์์ฒด๊ฐ ํธ์ด์คํธ ๊ฐ๋ฅํ ๊ฒฝ์ฐ
*/
if (
binding != null &&
hoistableIdentifiers.has(binding.identifier) &&
(fnDepth > 0 || binding.kind === "hoisted")
) {
willHoist.add(id);
}
},
});
์ดํ ๋ค์ ๋
ธ๋๋ฅผ ์ํํ๋ฉด์, hoistableIdentifiers
์ ์๋ ์๋ณ์๋ฅผ ์ญ์ ํ๋ค.
s.traverse({
Identifier(path: NodePath<t.Identifier>) {
if (hoistableIdentifiers.has(path.node)) {
hoistableIdentifiers.delete(path.node);
}
},
});
willHost
๋ฅผ ์ํํ๋ฉด์, identifier
๋ฅผ resolveIdentifier
๋ฅผ ํตํด ๊ณ ์ ํ ์๋ณ์๋ก ๋ณํํ๊ณ ,
lowerValueToTemporary
ํจ์ ํตํด ์์๋ณ์์ DeclareContext
๋ฅผ ์์ฑํ๊ณ builder์ pushํ๋ค.
์ดํ ์ ์ญ ํ๊ฒฝ์ #contextIdentifiers
, #hoistedIdentifiers
์ ์ถ๊ฐํ๋ค.
// Hoist declarations that need it to the earliest point where they are needed
for (const id of willHoist) {
const binding = stmt.scope.getBinding(id.node.name);
if (builder.environment.isHoistedIdentifier(binding.identifier)) {
// Already hoisted
continue;
}
const identifier = builder.resolveIdentifier(id);
const place: Place = {
effect: Effect.Unknown,
identifier: identifier.identifier,
kind: "Identifier",
reactive: false,
loc: id.node.loc ?? GeneratedSource,
};
lowerValueToTemporary(builder, {
kind: "DeclareContext",
lvalue: {
kind: InstructionKind.HoistedConst,
place,
},
loc: id.node.loc ?? GeneratedSource,
});
builder.environment.addHoistedIdentifier(binding.identifier);
๋์ค์ ์ฝ๋ ์์ฑ ์ ์ DeclareContext
๋ฅผ ์ ๊ฑฐํ๊ณ ์ฐ๊ด๋ StoreContext
๋ฅผ ๋ค์ ๋ณํํ์ฌ ์๋ ์์ค ์ฝ๋๋ฅผ ๋ณต์ํ๋ค.
๋ค๋ฅธ ์ข
๋ฅ์ ์ ์ธ์ ๋ํ ํธ์ด์คํ
์ ํฅํ ๊ตฌํ๋ ์์ ์ด๋ค.
ํธ์ด์คํ (Hoisting)์ ํ๋ ์ด์ ๊ฐ ๋ญ๊น?
๊ทธ๋ฐ๋ฐ, ์ด๋ฒ์ ์ดํด๋ณธ ๊ฒ์ ์ปดํ์ผ๋ฌ๊ฐ ํ๋ ํธ์ด์คํ
์ด๋ค.
const ํธ์ด์คํ
์ ํด์ฃผ๋ ์ด์ ๋, ๋ญ๊น? ์์์ ํจ๊ป ์ดํด๋ณด์. ํ
์คํธ ์ฝ๋๋ฅผ ํตํด ๋ณด๋ฉด ์์ง์ ์์๋ฅผ ์ป์ ์ ์๋ค. /src/__tests__/fixtures/compiler
๋ก ์ด๋ํด๋ณด์.
๊ฐ๊ฒฐํด๋ณด์ด๋ ์ฝ๋ ํ๋๋ฅผ ์ค์๋ค. โhoisting-simple-const-declaration.expected.mdโ ํ์ผ์ ์ด์ด๋ณด์.
function hoisting() {
const foo = () => {
return bar + baz;
};
const bar = 3;
const baz = 2;
return foo(); // OK: called outside of TDZ for bar/baz
}
๊ฐ๋จํ ํธ์ด์คํ ์์ ์ด๋ค. ์ด๊ฑธ HIR๋ก ๋ณํํ๋ฉด ์๋์ ๊ฐ์ด ๋ณํ๋ค.
function hoisting
bb0 (block):
[1] <unknown> $1 = DeclareContext HoistedConst <unknown> bar$0
[2] <unknown> $3 = DeclareContext HoistedConst <unknown> baz$2
[3] <unknown> $4 = LoadContext <unknown> bar$0
[4] <unknown> $5 = LoadContext <unknown> baz$2
[5] <unknown> $10 = Function @deps[<unknown> $4,<unknown> $5] @context[<unknown> bar$0,<unknown> baz$2] @effects[]:
bb1 (block):
[1] <unknown> $6 = LoadContext <unknown> bar$0
[2] <unknown> $7 = LoadContext <unknown> baz$2
[3] <unknown> $8 = Binary <unknown> $6 + <unknown> $7
[4] Return <unknown> $8
[6] <unknown> $12 = StoreLocal Const <unknown> foo$11 = <unknown> $10
[7] <unknown> $13 = 3
[8] <unknown> $14 = StoreContext Reassign <unknown> bar$0 = <unknown> $13
[9] <unknown> $15 = 2
[10] <unknown> $16 = StoreContext Reassign <unknown> baz$2 = <unknown> $15
[11] <unknown> $17 = LoadLocal <unknown> foo$11
[12] <unknown> $18 = Call <unknown> $17()
[13] Return <unknown> $18
๋งจ ์์DeclareContext HoistedConst
๋ฅผ ํตํด bar
, baz
๊ฐ ํธ์ด์คํ
๋์์์ ์ ์ ์๋ค.
ํ ์ฌ๊ธฐ๊น์ง ๋ดค์๋ ๋ฑํ ๋ญ๊ฐ ์๋ฟ๋ ๊ฒ์ด ์๋ค.
๊ทธ๋ ๋ค๋ฉด ์ด์ ํธ์ด์คํ ์ ๋ค ๋๊ณ ์์ฑํด๋ณด์. ์ ๋ถ ์ฃผ์์ฒ๋ฆฌ ํ๊ณ ์ปดํ์ผ๋ฌ๋ฅผ ๋ค์ ๋๋ ค๋ณด์๋ค.
// ๐ด
case "BlockStatement": {
const stmt = stmtPath as NodePath<t.BlockStatement>;
const statements = stmt.get("body");
// const hoistableIdentifiers: Set<t.Identifier> = new Set();
// for (const [, binding] of Object.entries(stmt.scope.bindings)) {
// // refs to params are always valid / never need to be hoisted
// if (binding.kind !== "param") {
// hoistableIdentifiers.add(binding.identifier);
// }
// }
for (const s of statements) {
// const willHoist = new Set<NodePath<t.Identifier>>();
// /*
// * If we see a hoistable identifier before its declaration, it should be hoisted just
// * before the statement that references it.
// */
// let fnDepth = s.isFunctionDeclaration() ? 1 : 0;
// const withFunctionContext = {
// enter: (): void => {
// fnDepth++;
// },
// exit: (): void => {
// fnDepth--;
// },
// ...
lowerStatement(builder, s);
}
์ฐ์ ์ด๋ฐ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
Todo: [hoisting] EnterSSA: Expected identifier to be defined before being used. \
Identifier bar$0 is undefined (5:5)
๋ญ๊ฐ ๋ฌ๋ผ์ก๋ค. bar$0
์ด ์ ์๋๊ธฐ ์ ์ ์ฌ์ฉ๋์๋ค๋ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
HIR ์ฝ๋๋ ์ดํด๋ณด์. ์ด๋ค ์ฐจ์ด๊ฐ ์๊ฒผ์๊น?
function hoisting
bb0 (block):
[1] <unknown> $1 = LoadLocal <unknown> bar$0
[2] <unknown> $3 = LoadLocal <unknown> baz$2
[3] <unknown> $8 = Function @deps[<unknown> $1,<unknown> $3] @context[<unknown> bar$0,<unknown> baz$2] @effects[]:
bb1 (block):
[1] <unknown> $4 = LoadLocal <unknown> bar$0
[2] <unknown> $5 = LoadLocal <unknown> baz$2
[3] <unknown> $6 = Binary <unknown> $4 + <unknown> $5
[4] Return <unknown> $6
[4] <unknown> $10 = StoreLocal Const <unknown> foo$9 = <unknown> $8
[5] <unknown> $11 = 3
[6] <unknown> $12 = StoreLocal Const <unknown> bar$0 = <unknown> $11
[7] <unknown> $13 = 2
[8] <unknown> $14 = StoreLocal Const <unknown> baz$2 = <unknown> $13
[9] <unknown> $15 = LoadLocal <unknown> foo$9
[10] <unknown> $16 = Call <unknown> $15()
[11] Return <unknown> $16
์คโฆ DeclareContext HoistedConst
๊ฐ ์ฌ๋ผ์ก๋ค. ์ด๊ฑด ๋น์ฐํ๊ฒ ์ง. ํธ์ด์คํ
์ ํ์ง ์์์ผ๋๊น.
๊ทธ๋ก์ธํด [1] ์์ ์ ์๋๊ธฐ ์ ์ LoadLocal
์ด ๋ฐ์ํ์ฌ ์๋ฌ๊ฐ ๋ฐ์ํ ๊ฒ์ด๋ค. ๋ค๋ฆ๊ฒ [6], [8],์์ StoreLocal Const
๋ก ์ ์๋์์ง๋ง, ์ด๋ฏธ ์ฌ์ฉ๋์๊ธฐ ๋๋ฌธ์ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
javascript๊ฐ ์คํ๋๋ ํ๊ฒฝ์์๋ ์์ง์ด ํธ์ด์คํ ์ ํด์ฃผ์ง๋ง, HIR๋ก ๋ณํ๋๊ณ ๋๋ฉด ๋ค๋ฅธ ์ธ์ด๋ก ๋ณํ๋ ๊ฒ์ด๋ ๋ค๋ฆ์๋ค. ๊ทธ๋ ๊ธฐ์, ์ต๋ํ javascript๋ฅผ ๋ชจ๋ธ๋งํ์ฌ์ผ ํ๋ ๊ฒ์ด์ง ์์๊น ์ถ๋ค. ๊ทธ์ธ์๋ ์ด๋ก์ธํด DCE๋ Const Propagation ๋ฑ ๋ค๋ฅธ ์ต์ ํ ๊ณผ์ ์์๋ ๋์ค์ ํ์ฉํ ์ ์์ง ์์๊น ์ถ์ ์๊ฐ์ ๋จ๊ฒจ๋ณธ๋ค.
๊ทธ๋์ ๊ถ๊ธํด์ Compiler ๊ฐ๋ฐ์ lauren ์๊ฒ ๋ฌผ์ด๋ณด์๋ค. ์ง๋ฌธ์ ๋์ง๊ณ ๋์์ผ ๋จธ์ฑํ๊ฒ ์์ ์ฝ๋๋ฅผ ์ฐพ์๋ณด๊ฒ ๋์๊ณ ๊ตฌํ์ด ํ์ฐ์ ์ด์์์ ์๊ฒ ๋์๋ค.
(์ง๋ฌธ ์์ชฝ์ด ํ๋ ์๋ฆฌ๊ธดํ๋๋ฐ)
itโs more because itโs a todo ๐คฃ const hoisting was the most straightforward to support, and at the moment itโs not used in any other passes
โ lauren ๋์ (@potetotes) June 9, 2024
์๋ฐ ์น์ ํ ๋ต๋ณ์ ๋ฐ์๋ค. ๊ถ๊ธํ ๋ ๋ฐ๋ก ์ง๋ฌธ ํด๋ณผ ์ ์๋ ์ฉ๊ธฐ๊ฐ(?) ์กฐ๊ธ ์๊ธด ๊ฒ ๊ฐ๋ค.
const hoisting์ด ๊ฐ์ฅ ๊ฐ๋จํ๊ฒ ์ง์ํ ์ ์๋ ํํ์๊ธฐ์, ๋จผ์ ๊ตฌํ ๋์๊ณ , ์์ง ๋ค๋ฅธ ์ต์ ํ ๊ณผ์ ์์ ์ฌ์ฉ๋์ง ์์๋ค๊ณ ํ๋ค.
๊ทธ๋ ๋ค๋ฉด, ์ด์ ๋ค์ lowerStatement
ํจ์๋ก ๋์๊ฐ์, ๋ค๋ฅธ ๋ถ๋ถ์ ์ดํด๋ณด๋๋ก ํ์.
์ด์ ์ฌ๊ท์ ์ผ๋ก ๋๋ฉด์ ๊ฐ์ผ์ด์ค์ ๋ง๋ lowering์ ์ํํ ๊ฒ์ด๋ค. ์ด ๊ฒ์ ๋ค ์ ๊ณ ์๋
ธ๋ผ๋ฉด, ์ฝ๋ ์ด๋ ์ง๋ฃจํ ๊ฒ ๊ฐ์์. ๋ค์์ ์งค๋งํ๊ฒ ๋ช๊ฐ๋ง ์ดํด๋ณด๊ฑฐ๋ ์ด๋ฒ ๊ธ์ ๋์ค์ ์ฒจ๋ถํด๋๊ฒ ๋ค.
์จ๋ ์ฌ๊ท์ ์ผ๋ก body ๋ถ๋ถ์ lowering ํ๊ณ ๋๋ฉด ์๋๋ก ๋น ์ ธ๋์ค๊ฒ ๋๋ค.
builder.terminate(
{
kind: "return",
loc: GeneratedSource,
value: lowerValueToTemporary(builder, {
kind: "Primitive",
value: undefined,
loc: GeneratedSource,
}),
id: makeInstructionId(0),
},
null
);
return Ok({
id,
params,
fnType: parent == null ? env.fnType : "Other",
returnType: null, // TODO: extract the actual return type node if present
body: builder.build(),
context,
generator: func.node.generator === true,
async: func.node.async === true,
loc: func.node.loc ?? GeneratedSource,
env,
effects: null,
directives,
});
builder.terminate
๋ฅผ ํตํด return
์ ์์ฑํ๊ณ , builder.build()
๋ฅผ ํตํด ์์ฑํ์ฌ ๋ฐํํ๋ค.
๊ทธ๋ ๊ฒ HIRFunction
์ ๋ฐํํ๊ฒ ๋๋ค.
const hir = lower(func, env).unwrap();
HIR๋กค ๋ณํํ๋ ๊ณผ์ ์์ builder๊ฐ ๋ง์ ์ญํ ์ ํ๋๋ฐ, ์ด๋ ๊น๊ฒ ์ดํด๋ณด์ง ๋ชปํ๋ค. ์ด๋ ๋ค์์ ๋ ๊ตฌ์ฒด์ ์ผ๋ก ์ดํด๋ณด๋๋ก ํ๊ฒ ๋ค.
๋ง๋ฌด๋ฆฌ
์ด๋ฒ ๊ธ์์๋ lower
ํจ์๋ฅผ ์ดํด๋ณด์๋ค.
lower
ํจ์๋ ๋ฐ๋ฒจ AST๋ก ๋ถํฐHIRFunction
์ ๋ฐํํ๋ ํจ์์ด๋ค.- ํจ์์ ์ธ์, ์๋ณ์, ๋ณธ๋ฌธ์ ์ถ์ถํ๊ณ
HIRBuilder
๋ฅผ ํตํดInstruction
๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค. const
๋ณ์ ์ ์ธ์ ํธ์ด์คํ ์ ์ฒ๋ฆฌํ๋ค.lowerStatement
ํจ์๋ฅผ ํตํด ๊ฐ์ข ๋ฌธ์ ์ฒ๋ฆฌํ๋ค.lower
ํจ์๋ ์ฌ๊ท์ ์ผ๋ก ํธ์ถ๋๋ฉฐ, ํจ์์ ๋ณธ๋ฌธ์ ์ฒ๋ฆฌํ๊ณHIRFunction
์ ๋ฐํํ๋ค.
๊ทธ๋ผ ์๋ !
์ด์ฉํ ํด
์ฐธ๊ณ ์๋ฃ