@samitouri / QOS-React-2 / commits / 687ad02e43

Dont merge if scope output is not guaranteed to change

As noted earlier in the stack and in chat, there are some cases where the output of a reactive scope is not guaranteed to change just because its inputs did. Consider a function `foo(x) { return x < 10 }`. If x was 0 and changes to 1, the result of `foo(x)` won't change. For code such as `[foo(x)]`, then, merging the scope for `t0 = foo(x)` and `t1 = [t0]` into a single `t1 = [foo(x)]` could cause us to invalidate `t1` unnecessarily. For example, x changing from 0 to 1 would allocate a new array of `[true]` even though the value didn't meaningfully change. This is the second category of merging, where we merge scopes A and B if the outputs of A are the inputs to B. With this change, we only do this type of merge if the outputs of the first scope are known to invalidate whenever the input does. We're conservative about this, and only consider function expressions, arrays, object, and jsx to always invalidate. Function calls _may_ invalidate, but as w the `foo()` example here they also may not. Note that this is purely about optimization and not correctness. We could always merge in this case (per earlier in the stack) but that might invalidate more often than we would like.

Joe Savona committed Sep 27, 2023 at 14:04 UTC 687ad02e4352fc339e8ee0d028c8ddee309bd584
14 files changed +319 -131
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/MergeConsecutiveScopes.ts
+63 -7
@@ -11,6 +11,7 @@ import {
11 Place,
12 ReactiveBlock,
13 ReactiveFunction,
14 + ReactiveInstruction,
15 ReactiveScope,
16 ReactiveScopeBlock,
17 ReactiveScopeDependency,
@@ -161,7 +162,7 @@ class Transform extends ReactiveFunctionTransform<void> {
162 } else {
163 if (
164 currentScope !== null &&
164 - canMergeScopes(currentScope.scope.scope, instr.scope) &&
165 + canMergeScopes(currentScope.scope, instr) &&
166 // If there are intermediate instructions, we can only merge the scopes
167 // if those intermediate instructions are all used by the second scope.
168 // if not, merging them would make those values unavailable to subsequent
@@ -243,24 +244,33 @@ function areLValuesLastUsedByScope(
244 return true;
245 }
246
246 -function canMergeScopes(a: ReactiveScope, b: ReactiveScope): boolean {
247 +function canMergeScopes(a: ReactiveScopeBlock, b: ReactiveScopeBlock): boolean {
248 // Don't merge scopes with reassignments
248 - if (a.reassignments.size !== 0 || b.reassignments.size !== 0) {
249 + if (a.scope.reassignments.size !== 0 || b.scope.reassignments.size !== 0) {
250 return false;
251 }
251 - if (areEqualDependencies(a.dependencies, b.dependencies)) {
252 + // Merge scopes whose dependencies are identical
253 + if (areEqualDependencies(a.scope.dependencies, b.scope.dependencies)) {
254 return true;
255 }
256 + // Merge scopes where the outputs of the previous scope are the inputs
257 + // of the subsequent scope. Note that the output of a scope is not
258 + // guaranteed to change when its inputs change, for example `foo(x)`
259 + // may not change when `x` changes, for example `foo(x) { return x < 10}`
260 + // will not change as x changes from 0 -> 1.
261 + // Therefore we check that the outputs of the previous scope are of a type
262 + // that is guaranteed to invalidate with its inputs, and only merge in this case.
263 if (
264 areEqualDependencies(
265 new Set(
257 - [...a.declarations.values()].map((declaration) => ({
266 + [...a.scope.declarations.values()].map((declaration) => ({
267 identifier: declaration.identifier,
268 path: [],
269 }))
270 ),
262 - b.dependencies
263 - )
271 + b.scope.dependencies
272 + ) &&
273 + scopeAlwaysInvalidatesOnDependencyChanges(a)
274 ) {
275 return true;
276 }
@@ -295,3 +305,49 @@ function areEqualDependencies(
305 function areEqualPaths(a: Array<string>, b: Array<string>): boolean {
306 return a.length === b.length && a.every((item, ix) => item === b[ix]);
307 }
308 +
309 +function scopeAlwaysInvalidatesOnDependencyChanges(
310 + scope: ReactiveScopeBlock
311 +): boolean {
312 + const visitor = new DeclarationTypeVisitor(scope.scope);
313 + visitor.visitScope(scope, undefined);
314 + return visitor.alwaysInvalidatesOnInputChange;
315 +}
316 +
317 +class DeclarationTypeVisitor extends ReactiveFunctionVisitor<void> {
318 + scope: ReactiveScope;
319 + alwaysInvalidatesOnInputChange: boolean = false;
320 +
321 + constructor(scope: ReactiveScope) {
322 + super();
323 + this.scope = scope;
324 + }
325 +
326 + override visitInstruction(
327 + instruction: ReactiveInstruction,
328 + state: void
329 + ): void {
330 + this.traverseInstruction(instruction, state);
331 + if (
332 + instruction.lvalue === null ||
333 + !this.scope.declarations.has(instruction.lvalue.identifier.id)
334 + ) {
335 + // no lvalue or this instruction isn't directly constructing a
336 + // scope output value, skip
337 + return;
338 + }
339 + switch (instruction.value.kind) {
340 + case "FunctionExpression":
341 + case "ArrayExpression":
342 + case "JsxExpression":
343 + case "JsxFragment":
344 + case "ObjectExpression": {
345 + // These instruction types *always* allocate. If they execute
346 + // they will produce a new value, triggering downstream reactive
347 + // updates
348 + this.alwaysInvalidatesOnInputChange = true;
349 + break;
350 + }
351 + }
352 + }
353 +}
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/allocating-primitive-as-dep-nested-scope.expect.md
+25 -12
@@ -24,31 +24,44 @@ import { unstable_useMemoCache as useMemoCache } from "react"; // bar(props.b) i
24 // Correctness:
25 // - y depends on either bar(props.b) or bar(props.b) + 1
26 function AllocatingPrimitiveAsDepNested(props) {
27 - const $ = useMemoCache(5);
27 + const $ = useMemoCache(9);
28 const c_0 = $[0] !== props.b;
29 const c_1 = $[1] !== props.a;
30 - let t2;
30 + let x;
31 + let y;
32 if (c_0 || c_1) {
32 - const x = {};
33 + x = {};
34 mutate(x);
35 const t0 = bar(props.b) + 1;
35 - const c_3 = $[3] !== t0;
36 + const c_4 = $[4] !== t0;
37 let t1;
37 - if (c_3) {
38 + if (c_4) {
39 t1 = foo(t0);
39 - $[3] = t0;
40 - $[4] = t1;
40 + $[4] = t0;
41 + $[5] = t1;
42 } else {
42 - t1 = $[4];
43 + t1 = $[5];
44 }
44 - const y = t1;
45 + y = t1;
46 mutate(x, props.a);
46 - t2 = [x, y];
47 $[0] = props.b;
48 $[1] = props.a;
49 - $[2] = t2;
49 + $[2] = x;
50 + $[3] = y;
51 + } else {
52 + x = $[2];
53 + y = $[3];
54 + }
55 + const c_6 = $[6] !== x;
56 + const c_7 = $[7] !== y;
57 + let t2;
58 + if (c_6 || c_7) {
59 + t2 = [x, y];
60 + $[6] = x;
61 + $[7] = y;
62 + $[8] = t2;
63 } else {
51 - t2 = $[2];
64 + t2 = $[8];
65 }
66 return t2;
67 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/array-access-assignment.expect.md
+25 -12
@@ -24,33 +24,46 @@ export const FIXTURE_ENTRYPOINT = {
24 ```javascript
25 import { unstable_useMemoCache as useMemoCache } from "react";
26 function foo(a, b, c) {
27 - const $ = useMemoCache(6);
27 + const $ = useMemoCache(10);
28 const c_0 = $[0] !== a;
29 const c_1 = $[1] !== b;
30 const c_2 = $[2] !== c;
31 - let t1;
31 + let x;
32 + let z;
33 if (c_0 || c_1 || c_2) {
33 - const x = [a];
34 - const c_4 = $[4] !== b;
34 + x = [a];
35 + const c_5 = $[5] !== b;
36 let t0;
36 - if (c_4) {
37 + if (c_5) {
38 t0 = [null, b];
38 - $[4] = b;
39 - $[5] = t0;
39 + $[5] = b;
40 + $[6] = t0;
41 } else {
41 - t0 = $[5];
42 + t0 = $[6];
43 }
44 const y = t0;
44 - const z = [[], [], [c]];
45 + z = [[], [], [c]];
46 x[0] = y[1];
47 z[0][0] = x[0];
47 - t1 = [x, z];
48 $[0] = a;
49 $[1] = b;
50 $[2] = c;
51 - $[3] = t1;
51 + $[3] = x;
52 + $[4] = z;
53 + } else {
54 + x = $[3];
55 + z = $[4];
56 + }
57 + const c_7 = $[7] !== x;
58 + const c_8 = $[8] !== z;
59 + let t1;
60 + if (c_7 || c_8) {
61 + t1 = [x, z];
62 + $[7] = x;
63 + $[8] = z;
64 + $[9] = t1;
65 } else {
53 - t1 = $[3];
66 + t1 = $[9];
67 }
68 return t1;
69 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/array-at-effect.expect.md
+29 -20
@@ -22,38 +22,47 @@ import { unstable_useMemoCache as useMemoCache } from "react"; // arrayInstance.
22 // - read on receiver
23 // - mutate on lvalue
24 function ArrayAtTest(props) {
25 - const $ = useMemoCache(7);
25 + const $ = useMemoCache(9);
26 const c_0 = $[0] !== props.x;
27 let t0;
28 if (c_0) {
29 - t0 = [foo(props.x)];
29 + t0 = foo(props.x);
30 $[0] = props.x;
31 $[1] = t0;
32 } else {
33 t0 = $[1];
34 }
35 - const arr = t0;
36 - const c_2 = $[2] !== props.y;
37 - const c_3 = $[3] !== arr;
38 - let t2;
39 - if (c_2 || c_3) {
40 - const c_5 = $[5] !== props.y;
41 - let t1;
42 - if (c_5) {
43 - t1 = bar(props.y);
44 - $[5] = props.y;
45 - $[6] = t1;
35 + const c_2 = $[2] !== t0;
36 + let t1;
37 + if (c_2) {
38 + t1 = [t0];
39 + $[2] = t0;
40 + $[3] = t1;
41 + } else {
42 + t1 = $[3];
43 + }
44 + const arr = t1;
45 + const c_4 = $[4] !== props.y;
46 + const c_5 = $[5] !== arr;
47 + let t3;
48 + if (c_4 || c_5) {
49 + const c_7 = $[7] !== props.y;
50 + let t2;
51 + if (c_7) {
52 + t2 = bar(props.y);
53 + $[7] = props.y;
54 + $[8] = t2;
55 } else {
47 - t1 = $[6];
56 + t2 = $[8];
57 }
49 - t2 = arr.at(t1);
50 - $[2] = props.y;
51 - $[3] = arr;
52 - $[4] = t2;
58 + t3 = arr.at(t2);
59 + $[4] = props.y;
60 + $[5] = arr;
61 + $[6] = t3;
62 } else {
54 - t2 = $[4];
63 + t3 = $[6];
64 }
56 - const result = t2;
65 + const result = t3;
66 return result;
67 }
68
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/codegen-emit-make-read-only.expect.md
+15 -8
@@ -23,22 +23,29 @@ import { makeReadOnly } from "react-forget-runtime";
23 import { unstable_useMemoCache as useMemoCache } from "react"; // @enableEmitFreeze true
24
25 function MyComponentName(props) {
26 - const $ = useMemoCache(3);
26 + const $ = useMemoCache(5);
27 const c_0 = $[0] !== props.a;
28 const c_1 = $[1] !== props.b;
29 - let y;
29 + let x;
30 if (c_0 || c_1) {
31 - const x = {};
31 + x = {};
32 foo(x, props.a);
33 foo(x, props.b);
34 -
35 - y = [];
36 - y.push(x);
34 $[0] = props.a;
35 $[1] = props.b;
39 - $[2] = __DEV__ ? makeReadOnly(y, "MyComponentName") : y;
36 + $[2] = __DEV__ ? makeReadOnly(x, "MyComponentName") : x;
37 + } else {
38 + x = $[2];
39 + }
40 + const c_3 = $[3] !== x;
41 + let y;
42 + if (c_3) {
43 + y = [];
44 + y.push(x);
45 + $[3] = x;
46 + $[4] = __DEV__ ? makeReadOnly(y, "MyComponentName") : y;
47 } else {
41 - y = $[2];
48 + y = $[4];
49 }
50 return y;
51 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/fbt-template-string-same-scope.expect.md
+14 -9
@@ -29,7 +29,7 @@ import { unstable_useMemoCache as useMemoCache } from "react";
29 import fbt from "fbt";
30
31 export function Component(props) {
32 - const $ = useMemoCache(2);
32 + const $ = useMemoCache(4);
33 let count = 0;
34 if (props.items) {
35 count = props.items.length;
@@ -37,19 +37,24 @@ export function Component(props) {
37 const c_0 = $[0] !== count;
38 let t0;
39 if (c_0) {
40 - t0 = (
41 - <View>
42 - {fbt._("for {count} experiences", [fbt._param("count", count)], {
43 - hk: "nmYpm",
44 - })}
45 - </View>
46 - );
40 + t0 = fbt._("for {count} experiences", [fbt._param("count", count)], {
41 + hk: "nmYpm",
42 + });
43 $[0] = count;
44 $[1] = t0;
45 } else {
46 t0 = $[1];
47 }
52 - return t0;
48 + const c_2 = $[2] !== t0;
49 + let t1;
50 + if (c_2) {
51 + t1 = <View>{t0}</View>;
52 + $[2] = t0;
53 + $[3] = t1;
54 + } else {
55 + t1 = $[3];
56 + }
57 + return t1;
58 }
59
60 ```
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement.expect.md
+14 -7
@@ -22,20 +22,27 @@ export const FIXTURE_ENTRYPOINT = {
22 ```javascript
23 import { unstable_useMemoCache as useMemoCache } from "react";
24 function Component(props) {
25 - const $ = useMemoCache(2);
25 + const $ = useMemoCache(4);
26 const c_0 = $[0] !== props;
27 - let t0;
27 + let items;
28 if (c_0) {
29 - const items = [];
29 + items = [];
30 for (const key in props) {
31 items.push(<div key={key}>{key}</div>);
32 }
33 -
34 - t0 = <div>{items}</div>;
33 $[0] = props;
36 - $[1] = t0;
34 + $[1] = items;
35 + } else {
36 + items = $[1];
37 + }
38 + const c_2 = $[2] !== items;
39 + let t0;
40 + if (c_2) {
41 + t0 = <div>{items}</div>;
42 + $[2] = items;
43 + $[3] = t0;
44 } else {
38 - t0 = $[1];
45 + t0 = $[3];
46 }
47 return t0;
48 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/phi-type-inference-array-push.expect.md
+20 -8
@@ -32,12 +32,13 @@ export const FIXTURE_ENTRYPOINT = {
32 ```javascript
33 import { unstable_useMemoCache as useMemoCache } from "react"; // @debug
34 function Component(props) {
35 - const $ = useMemoCache(2);
35 + const $ = useMemoCache(6);
36 const c_0 = $[0] !== props;
37 - let t0;
37 + let x;
38 + let y;
39 if (c_0) {
39 - const x = {};
40 - let y = undefined;
40 + x = {};
41 + y = undefined;
42 if (props.cond) {
43 y = [props.value];
44 } else {
@@ -45,12 +46,23 @@ function Component(props) {
46 }
47
48 y.push(x);
48 -
49 - t0 = [x, y];
49 $[0] = props;
51 - $[1] = t0;
50 + $[1] = x;
51 + $[2] = y;
52 + } else {
53 + x = $[1];
54 + y = $[2];
55 + }
56 + const c_3 = $[3] !== x;
57 + const c_4 = $[4] !== y;
58 + let t0;
59 + if (c_3 || c_4) {
60 + t0 = [x, y];
61 + $[3] = x;
62 + $[4] = y;
63 + $[5] = t0;
64 } else {
53 - t0 = $[1];
65 + t0 = $[5];
66 }
67 return t0;
68 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/primitive-as-dep-nested-scope.expect.md
+25 -12
@@ -26,31 +26,44 @@ import { unstable_useMemoCache as useMemoCache } from "react"; // props.b + 1 is
26 // Correctness:
27 // y depends on either props.b or props.b + 1
28 function PrimitiveAsDepNested(props) {
29 - const $ = useMemoCache(5);
29 + const $ = useMemoCache(9);
30 const c_0 = $[0] !== props.b;
31 const c_1 = $[1] !== props.a;
32 - let t2;
32 + let x;
33 + let y;
34 if (c_0 || c_1) {
34 - const x = {};
35 + x = {};
36 mutate(x);
37 const t0 = props.b + 1;
37 - const c_3 = $[3] !== t0;
38 + const c_4 = $[4] !== t0;
39 let t1;
39 - if (c_3) {
40 + if (c_4) {
41 t1 = foo(t0);
41 - $[3] = t0;
42 - $[4] = t1;
42 + $[4] = t0;
43 + $[5] = t1;
44 } else {
44 - t1 = $[4];
45 + t1 = $[5];
46 }
46 - const y = t1;
47 + y = t1;
48 mutate(x, props.a);
48 - t2 = [x, y];
49 $[0] = props.b;
50 $[1] = props.a;
51 - $[2] = t2;
51 + $[2] = x;
52 + $[3] = y;
53 + } else {
54 + x = $[2];
55 + y = $[3];
56 + }
57 + const c_6 = $[6] !== x;
58 + const c_7 = $[7] !== y;
59 + let t2;
60 + if (c_6 || c_7) {
61 + t2 = [x, y];
62 + $[6] = x;
63 + $[7] = y;
64 + $[8] = t2;
65 } else {
53 - t2 = $[2];
66 + t2 = $[8];
67 }
68 return t2;
69 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reactive-scopes.expect.md
+14 -7
@@ -26,24 +26,31 @@ export const FIXTURE_ENTRYPOINT = {
26 ```javascript
27 import { unstable_useMemoCache as useMemoCache } from "react";
28 function f(a, b) {
29 - const $ = useMemoCache(3);
29 + const $ = useMemoCache(5);
30 const c_0 = $[0] !== a.length;
31 const c_1 = $[1] !== b;
32 - let t0;
32 + let x;
33 if (c_0 || c_1) {
34 - const x = [];
34 + x = [];
35 if (a.length === 1) {
36 if (b) {
37 x.push(b);
38 }
39 }
40 -
41 - t0 = <div>{x}</div>;
40 $[0] = a.length;
41 $[1] = b;
44 - $[2] = t0;
42 + $[2] = x;
43 + } else {
44 + x = $[2];
45 + }
46 + const c_3 = $[3] !== x;
47 + let t0;
48 + if (c_3) {
49 + t0 = <div>{x}</div>;
50 + $[3] = x;
51 + $[4] = t0;
52 } else {
46 - t0 = $[2];
53 + t0 = $[4];
54 }
55 return t0;
56 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reactivity-analysis-interleaved-reactivity.expect.md
+15 -11
@@ -35,33 +35,37 @@ export const FIXTURE_ENTRYPOINT = {
35 ```javascript
36 import { unstable_useMemoCache as useMemoCache } from "react";
37 function Component(props) {
38 - const $ = useMemoCache(5);
38 + const $ = useMemoCache(6);
39 const c_0 = $[0] !== props.b;
40 let a;
41 - let t0;
41 if (c_0) {
42 a = {};
43 const b = [];
44 b.push(props.b);
45 a.a = null;
47 -
48 - t0 = [a];
46 $[0] = props.b;
47 $[1] = a;
51 - $[2] = t0;
48 } else {
49 a = $[1];
54 - t0 = $[2];
50 + }
51 + const c_2 = $[2] !== a;
52 + let t0;
53 + if (c_2) {
54 + t0 = [a];
55 + $[2] = a;
56 + $[3] = t0;
57 + } else {
58 + t0 = $[3];
59 }
60 const c = t0;
57 - const c_3 = $[3] !== a;
61 + const c_4 = $[4] !== a;
62 let t1;
59 - if (c_3) {
63 + if (c_4) {
64 t1 = [c, a];
61 - $[3] = a;
62 - $[4] = t1;
65 + $[4] = a;
66 + $[5] = t1;
67 } else {
64 - t1 = $[4];
68 + t1 = $[5];
69 }
70 return t1;
71 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps-join-uncond-scopes-cond-deps.expect.md
+24 -12
@@ -52,30 +52,42 @@ import { unstable_useMemoCache as useMemoCache } from "react"; // This tests an
52 // }
53
54 function TestJoinCondDepsInUncondScopes(props) {
55 - const $ = useMemoCache(4);
55 + const $ = useMemoCache(8);
56 const c_0 = $[0] !== props.a.b;
57 - let t0;
57 + let x;
58 + let y;
59 if (c_0) {
59 - const y = {};
60 - const c_2 = $[2] !== props;
61 - let x;
62 - if (c_2) {
60 + y = {};
61 + const c_3 = $[3] !== props;
62 + if (c_3) {
63 x = {};
64 if (foo) {
65 mutate1(x, props.a.b);
66 }
67 - $[2] = props;
68 - $[3] = x;
67 + $[3] = props;
68 + $[4] = x;
69 } else {
70 - x = $[3];
70 + x = $[4];
71 }
72
73 mutate2(y, props.a.b);
74 - t0 = [x, y];
74 $[0] = props.a.b;
76 - $[1] = t0;
75 + $[1] = x;
76 + $[2] = y;
77 + } else {
78 + x = $[1];
79 + y = $[2];
80 + }
81 + const c_5 = $[5] !== x;
82 + const c_6 = $[6] !== y;
83 + let t0;
84 + if (c_5 || c_6) {
85 + t0 = [x, y];
86 + $[5] = x;
87 + $[6] = y;
88 + $[7] = t0;
89 } else {
78 - t0 = $[1];
90 + t0 = $[7];
91 }
92 return t0;
93 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/temporary-accessed-outside-scope.expect.md
+18 -3
@@ -15,19 +15,34 @@ function Component(props) {
15 ```javascript
16 import { unstable_useMemoCache as useMemoCache } from "react";
17 function Component(props) {
18 - const $ = useMemoCache(2);
18 + const $ = useMemoCache(6);
19 const c_0 = $[0] !== props;
20 let t0;
21 + let t1;
22 if (c_0) {
23 const maybeMutable = new MaybeMutable();
24 const x = props;
24 - t0 = [x, maybeMutate(maybeMutable)];
25 + t0 = x;
26 + t1 = maybeMutate(maybeMutable);
27 $[0] = props;
28 $[1] = t0;
29 + $[2] = t1;
30 } else {
31 t0 = $[1];
32 + t1 = $[2];
33 }
30 - return t0;
34 + const c_3 = $[3] !== t0;
35 + const c_4 = $[4] !== t1;
36 + let t2;
37 + if (c_3 || c_4) {
38 + t2 = [t0, t1];
39 + $[3] = t0;
40 + $[4] = t1;
41 + $[5] = t2;
42 + } else {
43 + t2 = $[5];
44 + }
45 + return t2;
46 }
47
48 ```
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/temporary-property-load-accessed-outside-scope.expect.md
+18 -3
@@ -15,19 +15,34 @@ function Component(props) {
15 ```javascript
16 import { unstable_useMemoCache as useMemoCache } from "react";
17 function Component(props) {
18 - const $ = useMemoCache(2);
18 + const $ = useMemoCache(6);
19 const c_0 = $[0] !== props.value;
20 let t0;
21 + let t1;
22 if (c_0) {
23 const maybeMutable = new MaybeMutable();
24 const x = props.value;
24 - t0 = [x, maybeMutate(maybeMutable)];
25 + t0 = x;
26 + t1 = maybeMutate(maybeMutable);
27 $[0] = props.value;
28 $[1] = t0;
29 + $[2] = t1;
30 } else {
31 t0 = $[1];
32 + t1 = $[2];
33 }
30 - return t0;
34 + const c_3 = $[3] !== t0;
35 + const c_4 = $[4] !== t1;
36 + let t2;
37 + if (c_3 || c_4) {
38 + t2 = [t0, t1];
39 + $[3] = t0;
40 + $[4] = t1;
41 + $[5] = t2;
42 + } else {
43 + t2 = $[5];
44 + }
45 + return t2;
46 }
47
48 ```