Fix Array#at and similar cases to capture if receiver is mutable

The previous PR helped me realize we weren't handling Array#at correctly. If the 
receiver is a mutable value its effect should be Capture and the lvalue effect 
needs to be Store. This PR updates the definition for Array#at to make the 
receiver Capture, and then updates inference to automatically set the lvalue 
effect to Store if _any_ argument (or the receiver) was Capture.
This commit is contained in:
Joe Savona
2023-11-29 12:05:25 -08:00
parent 75c7fdcbd0
commit 7da906d648
4 changed files with 29 additions and 20 deletions
@@ -203,7 +203,7 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
positionalParams: [Effect.Read],
restParam: null,
returnType: { kind: "Poly" },
calleeEffect: Effect.Read,
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
@@ -809,6 +809,7 @@ function inferBlock(
signature !== null ? getFunctionEffects(instrValue, signature) : null;
const returnValueKind =
signature !== null ? signature.returnValueKind : ValueKind.Mutable;
let hasCaptureArgument = false;
for (let i = 0; i < instrValue.args.length; i++) {
const arg = instrValue.args[i];
const place = arg.kind === "Identifier" ? arg : arg.place;
@@ -817,16 +818,20 @@ function inferBlock(
} else {
state.reference(place, Effect.ConditionallyMutate);
}
hasCaptureArgument ||= place.effect === Effect.Capture;
}
if (signature !== null) {
state.reference(instrValue.callee, signature.calleeEffect);
} else {
state.reference(instrValue.callee, Effect.ConditionallyMutate);
}
hasCaptureArgument ||= instrValue.callee.effect === Effect.Capture;
state.initialize(instrValue, returnValueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect = Effect.ConditionallyMutate;
instr.lvalue.effect = hasCaptureArgument
? Effect.Store
: Effect.ConditionallyMutate;
continue;
}
case "MethodCall": {
@@ -871,6 +876,7 @@ function inferBlock(
signature !== null ? getFunctionEffects(instrValue, signature) : null;
const returnValueKind =
signature !== null ? signature.returnValueKind : ValueKind.Mutable;
let hasCaptureArgument = false;
for (let i = 0; i < instrValue.args.length; i++) {
const arg = instrValue.args[i];
const place = arg.kind === "Identifier" ? arg : arg.place;
@@ -883,16 +889,20 @@ function inferBlock(
} else {
state.reference(place, Effect.ConditionallyMutate);
}
hasCaptureArgument ||= place.effect === Effect.Capture;
}
if (signature !== null) {
state.reference(instrValue.receiver, signature.calleeEffect);
} else {
state.reference(instrValue.receiver, Effect.ConditionallyMutate);
}
hasCaptureArgument ||= instrValue.receiver.effect === Effect.Capture;
state.initialize(instrValue, returnValueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect = Effect.ConditionallyMutate;
instr.lvalue.effect = hasCaptureArgument
? Effect.Store
: Effect.ConditionallyMutate;
continue;
}
case "PropertyStore": {
@@ -21,18 +21,18 @@ function Component(props) {
import { unstable_useMemoCache as useMemoCache } from "react"; // x's mutable range should extend to `mutate(y)`
function Component(props) {
const $ = useMemoCache(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [42, {}];
$[0] = t0;
const $ = useMemoCache(2);
let x;
if ($[0] !== props.b) {
x = [42, {}];
const idx = foo(props.b);
const y = x.at(idx);
mutate(y);
$[0] = props.b;
$[1] = x;
} else {
t0 = $[0];
x = $[1];
}
const x = t0;
const idx = foo(props.b);
const y = x.at(idx);
mutate(y);
return x;
}
@@ -23,16 +23,15 @@ export const FIXTURE_ENTRYPOINT = {
import { unstable_useMemoCache as useMemoCache } from "react";
function foo() {
const $ = useMemoCache(1);
let t0;
let a;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [[1]];
$[0] = t0;
a = [[1]];
const first = a.at(0);
first.set(0, 2);
$[0] = a;
} else {
t0 = $[0];
a = $[0];
}
const a = t0;
const first = a.at(0);
first.set(0, 2);
return a;
}