From 75c7fdcbd0e73a846899e69b13062c82f0fac468 Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Wed, 29 Nov 2023 10:46:44 -0800 Subject: [PATCH] MutableIfOperandsAreMutable flag handles mutation via capturing There was one missing piece to the optimization from the previous PR: Array#map can return an alias to the receiver in its output, which means that mutations of the result have to be treated as mutations of the receiver. This means we need to use a Capture effect on the receiver. If that doesn't get downgraded to a Read bc the value was immutable, we then also need to make the lvalue effect a Store (so that InferMutableRanges actually looks at it for aliasing). --- .../src/Inference/InferReferenceEffects.ts | 9 ++-- ...n-mutating-lambda-mutated-result.expect.md | 50 +++++++++++++++++++ ...rray-non-mutating-lambda-mutated-result.js | 14 ++++++ 3 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/array-map-mutable-array-non-mutating-lambda-mutated-result.expect.md create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/array-map-mutable-array-non-mutating-lambda-mutated-result.js diff --git a/compiler/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts b/compiler/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts index 278ab358ed..974e6b83a3 100644 --- a/compiler/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts +++ b/compiler/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts @@ -851,16 +851,19 @@ function inferBlock( ) { /* * None of the args are mutable or mutate their params, we can downgrade to - * treating as all reads + * treating as all reads (except that the receiver may be captured) */ for (const arg of instrValue.args) { const place = arg.kind === "Identifier" ? arg : arg.place; state.reference(place, Effect.Read); } - state.reference(instrValue.receiver, Effect.Read); + state.reference(instrValue.receiver, Effect.Capture); state.initialize(instrValue, signature.returnValueKind); state.define(instr.lvalue, instrValue); - instr.lvalue.effect = Effect.ConditionallyMutate; + instr.lvalue.effect = + instrValue.receiver.effect === Effect.Capture + ? Effect.Store + : Effect.ConditionallyMutate; continue; } diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/array-map-mutable-array-non-mutating-lambda-mutated-result.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/array-map-mutable-array-non-mutating-lambda-mutated-result.expect.md new file mode 100644 index 0000000000..d9aef7904e --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/array-map-mutable-array-non-mutating-lambda-mutated-result.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +function Component(props) { + const x = [{}]; + const y = x.map((item) => { + return item; + }); + y[0].flag = true; + return [x, y]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{}], + isComponent: false, +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + const x = [{}]; + const y = x.map((item) => item); + y[0].flag = true; + t0 = [x, y]; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{}], + isComponent: false, +}; + +``` + +### Eval output +(kind: ok) [[{"flag":true}],["[[ cyclic ref *2 ]]"]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/array-map-mutable-array-non-mutating-lambda-mutated-result.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/array-map-mutable-array-non-mutating-lambda-mutated-result.js new file mode 100644 index 0000000000..d048fe444d --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/array-map-mutable-array-non-mutating-lambda-mutated-result.js @@ -0,0 +1,14 @@ +function Component(props) { + const x = [{}]; + const y = x.map((item) => { + return item; + }); + y[0].flag = true; + return [x, y]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{}], + isComponent: false, +};