|
| 1 | +# React Compiler Knowledge Base |
| 2 | + |
| 3 | +This document contains knowledge about the React Compiler gathered during development sessions. It serves as a reference for understanding the codebase architecture and key concepts. |
| 4 | + |
| 5 | +## Project Structure |
| 6 | + |
| 7 | +- `packages/babel-plugin-react-compiler/` - Main compiler package |
| 8 | + - `src/HIR/` - High-level Intermediate Representation types and utilities |
| 9 | + - `src/Inference/` - Effect inference passes (aliasing, mutation, etc.) |
| 10 | + - `src/Validation/` - Validation passes that check for errors |
| 11 | + - `src/Entrypoint/Pipeline.ts` - Main compilation pipeline with pass ordering |
| 12 | + - `src/__tests__/fixtures/compiler/` - Test fixtures |
| 13 | + - `error.*.js` - Fixtures that should produce compilation errors |
| 14 | + - `*.expect.md` - Expected output for each fixture |
| 15 | + |
| 16 | +## Running Tests |
| 17 | + |
| 18 | +```bash |
| 19 | +# Run all tests |
| 20 | +yarn snap |
| 21 | + |
| 22 | +# Run tests matching a pattern |
| 23 | +# Example: yarn snap -p 'error.*' |
| 24 | +yarn snap -p <pattern> |
| 25 | + |
| 26 | +# Run a single fixture in debug mode. Use the path relative to the __tests__/fixtures/compiler directory |
| 27 | +# For each step of compilation, outputs the step name and state of the compiled program |
| 28 | +# Example: yarn snap -p simple.js -d |
| 29 | +yarn snap -p <file-basename> -d |
| 30 | + |
| 31 | +# Update fixture outputs (also works with -p) |
| 32 | +yarn snap -u |
| 33 | +``` |
| 34 | + |
| 35 | +## Version Control |
| 36 | + |
| 37 | +This repository uses Sapling (`sl`) for version control. Unlike git, Sapling does not require explicitly adding files to the staging area. |
| 38 | + |
| 39 | +```bash |
| 40 | +# Check status |
| 41 | +sl status |
| 42 | + |
| 43 | +# Commit all changes |
| 44 | +sl commit -m "Your commit message" |
| 45 | + |
| 46 | +# Commit with multi-line message using heredoc |
| 47 | +sl commit -m "$(cat <<'EOF' |
| 48 | +Summary line |
| 49 | +
|
| 50 | +Detailed description here |
| 51 | +EOF |
| 52 | +)" |
| 53 | +``` |
| 54 | + |
| 55 | +## Key Concepts |
| 56 | + |
| 57 | +### HIR (High-level Intermediate Representation) |
| 58 | + |
| 59 | +The compiler converts source code to HIR for analysis. Key types in `src/HIR/HIR.ts`: |
| 60 | + |
| 61 | +- **HIRFunction** - A function being compiled |
| 62 | + - `body.blocks` - Map of BasicBlocks |
| 63 | + - `context` - Captured variables from outer scope |
| 64 | + - `params` - Function parameters |
| 65 | + - `returns` - The function's return place |
| 66 | + - `aliasingEffects` - Effects that describe the function's behavior when called |
| 67 | + |
| 68 | +- **Instruction** - A single operation |
| 69 | + - `lvalue` - The place being assigned to |
| 70 | + - `value` - The instruction kind (CallExpression, FunctionExpression, LoadLocal, etc.) |
| 71 | + - `effects` - Array of AliasingEffects for this instruction |
| 72 | + |
| 73 | +- **Terminal** - Block terminators (return, branch, etc.) |
| 74 | + - `effects` - Array of AliasingEffects |
| 75 | + |
| 76 | +- **Place** - A reference to a value |
| 77 | + - `identifier.id` - Unique IdentifierId |
| 78 | + |
| 79 | +- **Phi nodes** - Join points for values from different control flow paths |
| 80 | + - Located at `block.phis` |
| 81 | + - `phi.place` - The result place |
| 82 | + - `phi.operands` - Map of predecessor block to source place |
| 83 | + |
| 84 | +### AliasingEffects System |
| 85 | + |
| 86 | +Effects describe data flow and operations. Defined in `src/Inference/AliasingEffects.ts`: |
| 87 | + |
| 88 | +**Data Flow Effects:** |
| 89 | +- `Impure` - Marks a place as containing an impure value (e.g., Date.now() result, ref.current) |
| 90 | +- `Capture a -> b` - Value from `a` is captured into `b` (mutable capture) |
| 91 | +- `Alias a -> b` - `b` aliases `a` |
| 92 | +- `ImmutableCapture a -> b` - Immutable capture (like Capture but read-only) |
| 93 | +- `Assign a -> b` - Direct assignment |
| 94 | +- `MaybeAlias a -> b` - Possible aliasing |
| 95 | +- `CreateFrom a -> b` - Created from source |
| 96 | + |
| 97 | +**Mutation Effects:** |
| 98 | +- `Mutate value` - Value is mutated |
| 99 | +- `MutateTransitive value` - Value and transitive captures are mutated |
| 100 | +- `MutateConditionally value` - May mutate |
| 101 | +- `MutateTransitiveConditionally value` - May mutate transitively |
| 102 | + |
| 103 | +**Other Effects:** |
| 104 | +- `Render place` - Place is used in render context (JSX props, component return) |
| 105 | +- `Freeze place` - Place is frozen (made immutable) |
| 106 | +- `Create place` - New value created |
| 107 | +- `CreateFunction` - Function expression created, includes `captures` array |
| 108 | +- `Apply` - Function application with receiver, function, args, and result |
| 109 | + |
| 110 | +### Hook Aliasing Signatures |
| 111 | + |
| 112 | +Located in `src/HIR/Globals.ts`, hooks can define custom aliasing signatures to control how data flows through them. |
| 113 | + |
| 114 | +**Structure:** |
| 115 | +```typescript |
| 116 | +aliasing: { |
| 117 | + receiver: '@receiver', // The hook function itself |
| 118 | + params: ['@param0'], // Named positional parameters |
| 119 | + rest: '@rest', // Rest parameters (or null) |
| 120 | + returns: '@returns', // Return value |
| 121 | + temporaries: [], // Temporary values during execution |
| 122 | + effects: [ // Array of effects to apply when hook is called |
| 123 | + {kind: 'Freeze', value: '@param0', reason: ValueReason.HookCaptured}, |
| 124 | + {kind: 'Assign', from: '@param0', into: '@returns'}, |
| 125 | + ], |
| 126 | +} |
| 127 | +``` |
| 128 | + |
| 129 | +**Common patterns:** |
| 130 | + |
| 131 | +1. **RenderHookAliasing** (useState, useContext, useMemo, useCallback): |
| 132 | + - Freezes arguments (`Freeze @rest`) |
| 133 | + - Marks arguments as render-time (`Render @rest`) |
| 134 | + - Creates frozen return value |
| 135 | + - Aliases arguments to return |
| 136 | + |
| 137 | +2. **EffectHookAliasing** (useEffect, useLayoutEffect, useInsertionEffect): |
| 138 | + - Freezes function and deps |
| 139 | + - Creates internal effect object |
| 140 | + - Captures function and deps into effect |
| 141 | + - Returns undefined |
| 142 | + |
| 143 | +3. **Event handler hooks** (useEffectEvent): |
| 144 | + - Freezes callback (`Freeze @fn`) |
| 145 | + - Aliases input to return (`Assign @fn -> @returns`) |
| 146 | + - NO Render effect (callback not called during render) |
| 147 | + |
| 148 | +**Example: useEffectEvent** |
| 149 | +```typescript |
| 150 | +const UseEffectEventHook = addHook( |
| 151 | + DEFAULT_SHAPES, |
| 152 | + { |
| 153 | + positionalParams: [Effect.Freeze], // Takes one positional param |
| 154 | + restParam: null, |
| 155 | + returnType: {kind: 'Function', ...}, |
| 156 | + calleeEffect: Effect.Read, |
| 157 | + hookKind: 'useEffectEvent', |
| 158 | + returnValueKind: ValueKind.Frozen, |
| 159 | + aliasing: { |
| 160 | + receiver: '@receiver', |
| 161 | + params: ['@fn'], // Name for the callback parameter |
| 162 | + rest: null, |
| 163 | + returns: '@returns', |
| 164 | + temporaries: [], |
| 165 | + effects: [ |
| 166 | + {kind: 'Freeze', value: '@fn', reason: ValueReason.HookCaptured}, |
| 167 | + {kind: 'Assign', from: '@fn', into: '@returns'}, |
| 168 | + // Note: NO Render effect - callback is not called during render |
| 169 | + ], |
| 170 | + }, |
| 171 | + }, |
| 172 | + BuiltInUseEffectEventId, |
| 173 | +); |
| 174 | + |
| 175 | +// Add as both names for compatibility |
| 176 | +['useEffectEvent', UseEffectEventHook], |
| 177 | +['experimental_useEffectEvent', UseEffectEventHook], |
| 178 | +``` |
| 179 | + |
| 180 | +**Key insight:** If a hook is missing an `aliasing` config, it falls back to `DefaultNonmutatingHook` which includes a `Render` effect on all arguments. This can cause false positives for hooks like `useEffectEvent` whose callbacks are not called during render. |
| 181 | + |
| 182 | +### Effect Inference Pipeline |
| 183 | + |
| 184 | +Effects are populated by `InferMutationAliasingEffects` (runs before validation): |
| 185 | + |
| 186 | +1. For `Date.now()`, `Math.random()` etc. - adds `Impure` effect (controlled by `validateNoImpureFunctionsInRender` config) |
| 187 | +2. For `ref.current` access - adds `Impure` effect (controlled by `validateRefAccessDuringRender` config) |
| 188 | +3. For return terminals - adds `Alias` from return value to `fn.returns` |
| 189 | +4. For component/JSX returns - adds `Render` effect |
| 190 | +5. For function expressions - adds `CreateFunction` effect with captures |
| 191 | + |
| 192 | +### Validation: validateNoImpureValuesInRender |
| 193 | + |
| 194 | +Located at `src/Validation/ValidateNoImpureValuesInRender.ts` |
| 195 | + |
| 196 | +**Purpose:** Detect when impure values (refs, Date.now results, etc.) flow into render context. |
| 197 | + |
| 198 | +**Algorithm:** |
| 199 | +1. Track impure values in a Map<IdentifierId, ImpureEffect> |
| 200 | +2. Track functions with impure returns separately (they're not impure values themselves) |
| 201 | +3. Fixed-point iteration over all blocks: |
| 202 | + - Process phi nodes (any impure operand makes result impure) |
| 203 | + - Process instruction effects |
| 204 | + - Process terminal effects |
| 205 | + - Backwards propagation for mutated LoadLocal values |
| 206 | +4. Validate: check all Render effects against impure values |
| 207 | + |
| 208 | +**Key patterns:** |
| 209 | +- `Impure` effect marks the target as impure |
| 210 | +- `Capture/Alias/etc` propagates impurity from source to target |
| 211 | +- `Apply` propagates impurity from args/receiver to result |
| 212 | +- `CreateFunction` propagates impurity from captured values (but NOT from body effects) |
| 213 | +- If a value has both `Render` and `Capture` in same instruction, only error on Render (don't cascade) |
| 214 | + |
| 215 | +**Tracking functions with impure returns:** |
| 216 | +- Separate from the `impure` map (function values aren't impure, just their returns) |
| 217 | +- Populated when analyzing FunctionExpression bodies |
| 218 | +- Used when: |
| 219 | + 1. Calling the function - mark call result as impure |
| 220 | + 2. Capturing the function - mark target as impure (for object.foo = impureFunc cases) |
| 221 | + |
| 222 | +**Backwards propagation:** |
| 223 | +- When `$x = LoadLocal y` and `$x` is mutated with impure content, mark `y` as impure |
| 224 | +- This handles: `const arr = []; arr.push(impure); render(arr)` |
| 225 | + |
| 226 | +## Known Issues / Edge Cases |
| 227 | + |
| 228 | +### Function Outlining |
| 229 | +After `OutlineFunctions` pass, inner functions are replaced with `LoadGlobal(_temp)`. The validation runs BEFORE outlining, so it sees the original FunctionExpression. But be aware that test output shows post-outlining HIR. |
| 230 | + |
| 231 | +### SSA and LoadLocal |
| 232 | +In SSA form, each `LoadLocal` creates a new identifier. When a loaded value is mutated: |
| 233 | +- `$x = LoadLocal y` |
| 234 | +- `mutate($x, impure)` |
| 235 | +- `$z = LoadLocal y` (different from $x!) |
| 236 | +- `render($z)` |
| 237 | + |
| 238 | +The impurity in $x must propagate back to y, then forward to $z. This requires backwards propagation in the fixed-point loop. |
| 239 | + |
| 240 | +## Configuration Flags |
| 241 | + |
| 242 | +In `Environment.ts` / test directives: |
| 243 | +- `validateNoImpureFunctionsInRender` - Enable impure function validation (Date.now, Math.random, etc.) |
| 244 | +- `validateRefAccessDuringRender` - Enable ref access validation |
| 245 | + |
| 246 | +## Debugging Tips |
| 247 | + |
| 248 | +1. Run `yarn snap -p <fixture>` to see full HIR output with effects |
| 249 | +2. Look for `@aliasingEffects=` on FunctionExpressions |
| 250 | +3. Look for `Impure`, `Render`, `Capture` effects on instructions |
| 251 | +4. Check the pass ordering in Pipeline.ts to understand when effects are populated vs validated |
0 commit comments