Skip to content

Commit 11d3a2d

Browse files
olwangclaude
andcommitted
perf(reg-vm): make DeepCopy elision actually fire — self-veto fix + DeepCopyElided marker (Phase 2 v2)
Phase 2 v1 (0027b85) was sound but INERT: `deepcopy_instr_forces_keep` routed a param's own prologue `DeepCopy{root}` through the default arm, where the exhaustive `deepcopy_collect_regs` sees the tainted root and votes keep — so every deep-copied param vetoed its OWN elision and nothing was ever elidable (measured 0% win). Two fixes: 1. Self-veto: treat `DeepCopy`/`DeepCopyElided` as read-only-safe in `deepcopy_instr_forces_keep` — a copy is the taint SEED, not a use. 2. Marker-preserving plumbing: the v1 self-`Move` rewrite erased the `DeepCopy` marker the native tier seeds its flat-param ABI + soundness analysis from (native already lowers DeepCopy->Nop; it needs the marker, not the copy), which regressed native_call_flat_int/float_param telemetry. Instead, add a new `RegInstr::DeepCopyElided { reg }`: the elision rewrites elidable `DeepCopy` -> `DeepCopyElided` (same slot, index-safe). The interpreter no-ops it (shares the caller's Rc — the win); native treats it IDENTICALLY to DeepCopy at every site (passes.rs seed/operand/dst/no-source groups, translate.rs Nop/read/allowed, mod.rs prologue+tier-0 eligibility, tier.rs scalar exec) so the marker/ABI are unchanged. Exhaustive matches compile-enforce full coverage (no wildcards). Also lands the v2 intrinsic classifier `deepcopy_intrinsic_class` (PureFreshReader / AliasReturner / default Keep): CallIntrinsic/CallTypedIntrinsic reads no longer force keep, and AliasReturner results propagate taint arg->dst. Broadens elision to intrinsic-read params (List.get/len lower to dedicated RegInstrs already handled; the classifier covers first/find/slice/values/etc.). No mutator/escaper intrinsics exist (mutation is via dedicated RegInstrs), so the safe set is verified read-only. Behind RSS_VM_ELIDE_DEEPCOPY (default OFF): flag-off byte-identical (455/0, 33/0 — DeepCopyElided is only produced under the flag). Flag-ON: runtime 455/0 (incl. native_call_flat_int/float_param telemetry intact AND native_store_reload_mutate_non_mut_heap_param_does_not_leak copy correctly kept), differential 33/0 @ PROPTEST_CASES=1000. Kernel deepcopy_read_param.rss: 2.5s -> 0.17s (~14x), output identical. Default-flip deferred pending an extended soak. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent cb48ffb commit 11d3a2d

6 files changed

Lines changed: 211 additions & 25 deletions

File tree

crates/rsscript/src/reg_vm/exec.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,10 @@ impl RegVm {
277277
let copied = deep_copy_value(self.reg(base + *reg));
278278
self.set_reg(base + *reg, copied);
279279
}
280+
RegInstr::DeepCopyElided { .. } => {
281+
// Elided by the `RSS_VM_ELIDE_DEEPCOPY` pass: the copy is provably redundant, so
282+
// share the caller's `Rc` in place (skip the deep copy). This is the win.
283+
}
280284
RegInstr::LoadString { dst, value } => {
281285
self.set_reg(base + *dst, VmValue::String(Rc::clone(value)));
282286
}

crates/rsscript/src/reg_vm/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,12 @@ fn optimize_self_tail_calls(function: &mut RegFunction, function_id: usize) {
546546
let entry = function
547547
.code
548548
.iter()
549-
.position(|instr| !matches!(instr, RegInstr::DeepCopy { .. }))
549+
.position(|instr| {
550+
!matches!(
551+
instr,
552+
RegInstr::DeepCopy { .. } | RegInstr::DeepCopyElided { .. }
553+
)
554+
})
550555
.unwrap_or(0);
551556

552557
// Apply every site. Appends rebind blocks at the tail; existing indices are
@@ -601,7 +606,7 @@ fn instr_reads_register(instr: &RegInstr, reg: Reg) -> bool {
601606
| RegInstr::UnwrapSome { src, .. }
602607
| RegInstr::UnwrapVariantValue { src, .. }
603608
| RegInstr::AwaitJoin { src, .. } => *src == reg,
604-
RegInstr::DeepCopy { reg: r } => *r == reg,
609+
RegInstr::DeepCopy { reg: r } | RegInstr::DeepCopyElided { reg: r } => *r == reg,
605610
RegInstr::GetField { base, .. } | RegInstr::GetFieldSlot { base, .. } => *base == reg,
606611
RegInstr::SetField { base, value, .. } | RegInstr::SetFieldSlot { base, value, .. } => {
607612
*base == reg || *value == reg
@@ -669,6 +674,7 @@ fn jit_supported_instruction(instr: &RegInstr) -> bool {
669674
| RegInstr::LoadString { .. }
670675
| RegInstr::Move { .. }
671676
| RegInstr::DeepCopy { .. }
677+
| RegInstr::DeepCopyElided { .. }
672678
| RegInstr::Manage { .. }
673679
| RegInstr::GetField { .. }
674680
| RegInstr::SetField { .. }

crates/rsscript/src/reg_vm/model.rs

Lines changed: 177 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,15 @@ pub(crate) enum RegInstr {
581581
DeepCopy {
582582
reg: Reg,
583583
},
584+
/// Marker-preserving elided `DeepCopy`: produced ONLY by the compile-time elision pass
585+
/// (`RSS_VM_ELIDE_DEEPCOPY`) in place of a `DeepCopy` proven redundant. The INTERPRETER
586+
/// treats it as a no-op (share the caller's `Rc`, skip the copy — this is the win), while
587+
/// EVERY native-tier site treats it BYTE-IDENTICALLY to `DeepCopy` (soundness seed, flat-param
588+
/// ABI marker, tier-0 eligibility, `Nop` lowering). Keeping the marker — rather than rewriting
589+
/// to a self-`Move` — is what lets the interp elide the copy without perturbing native tiering.
590+
DeepCopyElided {
591+
reg: Reg,
592+
},
584593
Manage {
585594
dst: Reg,
586595
src: Reg,
@@ -1894,7 +1903,9 @@ fn deepcopy_collect_regs(instr: &RegInstr, out: &mut Vec<Reg>) {
18941903
out.push(*dst);
18951904
out.push(*src);
18961905
}
1897-
RegInstr::DeepCopy { reg } | RegInstr::ResourceDrop { resource: reg } => out.push(*reg),
1906+
RegInstr::DeepCopy { reg }
1907+
| RegInstr::DeepCopyElided { reg }
1908+
| RegInstr::ResourceDrop { resource: reg } => out.push(*reg),
18981909
RegInstr::NativeGuardClosureId { closure, .. } => out.push(*closure),
18991910
RegInstr::SetFieldSlot {
19001911
dst, base, value, ..
@@ -2197,6 +2208,116 @@ fn deepcopy_collect_regs(instr: &RegInstr, out: &mut Vec<Reg>) {
21972208
}
21982209
}
21992210

2211+
/// Taint classification of a collection-read `RegIntrinsic` for `DeepCopy` elision. This is a
2212+
/// WHITELIST: only intrinsics VERIFIED (against their `intrinsics/{list,map,set,deque}.rs` impl)
2213+
/// to (a) never `borrow_mut` an arg and (b) never store an arg into `self.streams`/`self.channels`
2214+
/// or resource state are classified; everything else is [`IntrinsicTaintClass::Keep`].
2215+
#[derive(Clone, Copy, PartialEq, Eq)]
2216+
enum IntrinsicTaintClass {
2217+
/// Reads its collection args and returns a FRESH scalar/bool/collection-of-scalars whose
2218+
/// contents never alias an arg's inner `Rc`. Safe: the call keeps no copy and needs no taint
2219+
/// propagation (the result can never be a live alias of the param).
2220+
PureFreshReader,
2221+
/// Reads its args, but the RESULT shares an arg's inner `Rc` (an element, a subview, or a
2222+
/// fresh collection holding cloned heap elements). The ARGS are read-only-safe, but taint MUST
2223+
/// propagate arg→dst (see `deepcopy_elidable_param_regs`) so a later mutation/store/return of
2224+
/// the aliased result correctly pins the copy.
2225+
AliasReturner,
2226+
/// Not proven read-only-and-non-storing: conservatively force-keep if it touches a tainted
2227+
/// register. Covers Tier-3 (channels/streams/IO/resource-pool/tensor/json/string/…) and every
2228+
/// variant not explicitly whitelisted below. This is the DEFAULT arm — soundness rests on it.
2229+
Keep,
2230+
}
2231+
2232+
/// Classify a `RegIntrinsic` for `DeepCopy`-elision taint tracking. EXPLICIT match with a
2233+
/// conservative `Keep` default: an intrinsic is trusted only when its impl has been verified
2234+
/// non-mutating and non-storing. Collection READS that lower to dedicated `RegInstr`s
2235+
/// (`ListGet`/`ListLen`/`MapGet`) are NOT here — they are handled directly in
2236+
/// `deepcopy_instr_forces_keep`/`deepcopy_elidable_param_regs`.
2237+
fn deepcopy_intrinsic_class(intrinsic: RegIntrinsic) -> IntrinsicTaintClass {
2238+
use IntrinsicTaintClass::{AliasReturner, PureFreshReader};
2239+
match intrinsic {
2240+
// ---- PureFreshReader: fresh scalar/bool/collection-of-scalars, no arg aliasing. ----
2241+
// List
2242+
RegIntrinsic::ListIsEmpty
2243+
| RegIntrinsic::ListContains
2244+
| RegIntrinsic::ListAny
2245+
| RegIntrinsic::ListContainsValue
2246+
| RegIntrinsic::ListCountWhere
2247+
| RegIntrinsic::ListSum
2248+
| RegIntrinsic::ListMin
2249+
| RegIntrinsic::ListMax
2250+
| RegIntrinsic::ListJoin
2251+
| RegIntrinsic::ListConsume
2252+
| RegIntrinsic::ListNew
2253+
// Map
2254+
| RegIntrinsic::MapContainsKey
2255+
| RegIntrinsic::MapLen
2256+
| RegIntrinsic::MapIsEmpty
2257+
| RegIntrinsic::MapKeys
2258+
| RegIntrinsic::MapForEach
2259+
| RegIntrinsic::MapNew
2260+
// Set / SortedSet / SortedMap
2261+
| RegIntrinsic::SetContains
2262+
| RegIntrinsic::SetIsEmpty
2263+
| RegIntrinsic::SetLen
2264+
| RegIntrinsic::SetIsSubset
2265+
| RegIntrinsic::SetToList
2266+
| RegIntrinsic::SetNew
2267+
| RegIntrinsic::SortedSetContains
2268+
| RegIntrinsic::SortedSetIsEmpty
2269+
| RegIntrinsic::SortedSetLen
2270+
| RegIntrinsic::SortedSetNew
2271+
| RegIntrinsic::SortedMapContainsKey
2272+
| RegIntrinsic::SortedMapIsEmpty
2273+
| RegIntrinsic::SortedMapLen
2274+
| RegIntrinsic::SortedMapKeys
2275+
| RegIntrinsic::SortedMapNew
2276+
// Deque
2277+
| RegIntrinsic::DequeIsEmpty
2278+
| RegIntrinsic::DequeLen
2279+
| RegIntrinsic::DequeNew => PureFreshReader,
2280+
2281+
// ---- AliasReturner: result shares an arg's inner `Rc`; propagate taint arg→dst. ----
2282+
// List
2283+
RegIntrinsic::ListFirst
2284+
| RegIntrinsic::ListLast
2285+
| RegIntrinsic::ListFind
2286+
| RegIntrinsic::ListSlice
2287+
| RegIntrinsic::ListTake
2288+
| RegIntrinsic::ListSkip
2289+
| RegIntrinsic::ListReverse
2290+
| RegIntrinsic::ListEnumerate
2291+
| RegIntrinsic::ListPartition
2292+
| RegIntrinsic::ListZip
2293+
| RegIntrinsic::ListFlatten
2294+
| RegIntrinsic::ListFlatMap
2295+
| RegIntrinsic::ListDedup
2296+
| RegIntrinsic::ListGroupBy
2297+
| RegIntrinsic::ListTryFold
2298+
// Map
2299+
| RegIntrinsic::MapGetOrDefault
2300+
| RegIntrinsic::MapValues
2301+
| RegIntrinsic::MapFilter
2302+
| RegIntrinsic::MapFold
2303+
| RegIntrinsic::MapMapValues
2304+
| RegIntrinsic::MapMerge
2305+
| RegIntrinsic::MapTryFold
2306+
// Set / SortedSet / SortedMap
2307+
| RegIntrinsic::SetDifference
2308+
| RegIntrinsic::SetIntersection
2309+
| RegIntrinsic::SetUnion
2310+
| RegIntrinsic::SortedSetToList
2311+
| RegIntrinsic::SortedMapGet
2312+
| RegIntrinsic::SortedMapValues
2313+
// Deque
2314+
| RegIntrinsic::DequeToList => AliasReturner,
2315+
2316+
// Everything else (Tier-3 and any unclassified variant): conservative keep.
2317+
_ => IntrinsicTaintClass::Keep,
2318+
}
2319+
}
2320+
22002321
/// WHITELIST verdict for one instruction: does it force the DeepCopy'd param's copy to be
22012322
/// KEPT (⇒ NOT elidable)? `tainted[r]` marks every register that aliases the param's inner
22022323
/// `Rc`. This is the INTERPRETER-safe (whitelist) counterpart of the native blacklist
@@ -2213,10 +2334,14 @@ fn deepcopy_collect_regs(instr: &RegInstr, out: &mut Vec<Reg>) {
22132334
/// because the callee isolates its own returns under this same scheme.
22142335
/// * A small set of pure, non-aliasing SCALAR/fresh producers (integer arithmetic/compare,
22152336
/// `ListLen`, `StringConcat`, discriminant `Match*`, `Jump*`, `Load*`) → safe.
2337+
/// * Collection-read intrinsics (`CallIntrinsic`/`CallTypedIntrinsic`), per
2338+
/// `deepcopy_intrinsic_class`: `PureFreshReader`/`AliasReturner` → safe (args read-only;
2339+
/// result-aliasing is handled by taint propagation in `deepcopy_elidable_param_regs`);
2340+
/// `Keep` (Tier-3 / unclassified) → keep if a tainted register is touched.
22162341
/// * EVERYTHING ELSE (stores into aggregates, `Return`, `MakeClosure` capture, `SpawnTask`,
2217-
/// `Manage`, `CallIntrinsic`, every unlisted mutator, …) → keep if it references ANY tainted
2218-
/// register. This default is what makes the analysis a whitelist: an instruction is only
2219-
/// trusted when explicitly classified read-only-safe above.
2342+
/// `Manage`, every unlisted mutator, …) → keep if it references ANY tainted register. This
2343+
/// default is what makes the analysis a whitelist: an instruction is only trusted when
2344+
/// explicitly classified read-only-safe above.
22202345
fn deepcopy_instr_forces_keep(instr: &RegInstr, tainted: &[bool], n_regs: usize) -> bool {
22212346
let is_t = |r: Reg| r < n_regs && tainted[r];
22222347
// In-place mutation of a tainted heap receiver — the direct leak.
@@ -2237,6 +2362,27 @@ fn deepcopy_instr_forces_keep(instr: &RegInstr, tainted: &[bool], n_regs: usize)
22372362
| RegInstr::DequePopFront { .. }
22382363
| RegInstr::DequePopBack { .. } => false,
22392364

2365+
// A `DeepCopy` (or an already-elided one) is the taint SEED, not a use: it reads its
2366+
// register and yields a FRESH copy, never mutating through nor escaping the arg's `Rc`.
2367+
// It must never be the reason to keep a copy — in particular the prologue `DeepCopy` of
2368+
// the root under analysis must not veto its own elision.
2369+
RegInstr::DeepCopy { .. } | RegInstr::DeepCopyElided { .. } => false,
2370+
2371+
// Collection-read intrinsics, classified by `deepcopy_intrinsic_class`:
2372+
// * PureFreshReader / AliasReturner → args are read-only; a PureFreshReader result is
2373+
// fresh, and an AliasReturner result's aliasing is handled by taint propagation in
2374+
// `deepcopy_elidable_param_regs` (so a later mutation/escape of the result pins the
2375+
// copy there). Either way this CALL neither mutates nor escapes a tainted arg → safe.
2376+
// * Keep (Tier-3 / unclassified) → conservatively keep if it touches a tainted register
2377+
// (dst OR any arg), matching the former default-arm behavior.
2378+
RegInstr::CallIntrinsic { dst, intrinsic, args }
2379+
| RegInstr::CallTypedIntrinsic {
2380+
dst, intrinsic, args, ..
2381+
} => match deepcopy_intrinsic_class(*intrinsic) {
2382+
IntrinsicTaintClass::PureFreshReader | IntrinsicTaintClass::AliasReturner => false,
2383+
IntrinsicTaintClass::Keep => is_t(*dst) || args.iter().any(|&r| is_t(r)),
2384+
},
2385+
22402386
// Calls: a tainted `mut` arg is mutated by-reference and leaks to our caller. `read`
22412387
// args are safe (the callee deep-copies or has itself proven the param read-only), and
22422388
// the `closure` receiver is only invoked, not mutated.
@@ -2332,6 +2478,25 @@ fn deepcopy_elidable_param_regs(code: &[RegInstr], n_regs: usize) -> std::collec
23322478
changed = true;
23332479
}
23342480
}
2481+
// AliasReturner intrinsics: the result shares an arg's inner `Rc`, so taint flows
2482+
// from ANY tainted arg to `dst` (conservative). This makes a downstream mutation
2483+
// or escape of the aliased result force the copy to be kept in
2484+
// `deepcopy_instr_forces_keep`. PureFreshReader/Keep intrinsics do not propagate
2485+
// (a fresh result cannot alias; a Keep result already vetoes elision directly).
2486+
if let RegInstr::CallIntrinsic { dst, args, intrinsic }
2487+
| RegInstr::CallTypedIntrinsic {
2488+
dst, args, intrinsic, ..
2489+
} = instr
2490+
{
2491+
if *dst < n_regs
2492+
&& !tainted[*dst]
2493+
&& deepcopy_intrinsic_class(*intrinsic) == IntrinsicTaintClass::AliasReturner
2494+
&& args.iter().any(|&r| r < n_regs && tainted[r])
2495+
{
2496+
tainted[*dst] = true;
2497+
changed = true;
2498+
}
2499+
}
23352500
}
23362501
}
23372502
if !code
@@ -2425,21 +2590,20 @@ impl RegUnit {
24252590
lowerer.emit(RegInstr::Return { src: unit });
24262591
// Compile-time `DeepCopy` elision (gated behind `RSS_VM_ELIDE_DEEPCOPY`). Analyze
24272592
// the fully-lowered body and neutralize the prologue `DeepCopy` of every parameter
2428-
// proven never mutated-through-alias and never escaping. Neutralize IN PLACE (a
2429-
// self-`Move`, a cheap shallow `Rc` clone) rather than removing the instruction,
2430-
// because jump/branch targets are ABSOLUTE instruction indices — dropping an
2431-
// instruction would shift them. When the flag is OFF this block is skipped and the
2432-
// lowering is byte-identical to before.
2593+
// proven never mutated-through-alias and never escaping. Rewrite IN PLACE to a
2594+
// `DeepCopyElided` (same slot, so jump/branch targets — ABSOLUTE instruction indices —
2595+
// stay valid) rather than removing the instruction. `DeepCopyElided` is a NO-OP in the
2596+
// interpreter (the win: share the caller's `Rc`, skip the copy) but is treated
2597+
// BYTE-IDENTICALLY to `DeepCopy` at every native-tier site, so native tiering/soundness
2598+
// is unperturbed. When the flag is OFF this block is skipped and the lowering is
2599+
// byte-identical to before.
24332600
if elide_deepcopy_enabled() {
24342601
let n_regs = lowerer.function.regs;
24352602
let elidable = deepcopy_elidable_param_regs(&lowerer.function.code, n_regs);
24362603
for instr in lowerer.function.code.iter_mut() {
24372604
if let RegInstr::DeepCopy { reg } = instr {
24382605
if elidable.contains(reg) {
2439-
*instr = RegInstr::Move {
2440-
dst: *reg,
2441-
src: *reg,
2442-
};
2606+
*instr = RegInstr::DeepCopyElided { reg: *reg };
24432607
}
24442608
}
24452609
}

crates/rsscript/src/reg_vm/native/passes.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1394,7 +1394,11 @@ impl NativeRegionEffects {
13941394
{
13951395
return true;
13961396
}
1397-
RegInstr::DeepCopy { reg } if *reg < n_regs && alias[*reg] => return true,
1397+
RegInstr::DeepCopy { reg } | RegInstr::DeepCopyElided { reg }
1398+
if *reg < n_regs && alias[*reg] =>
1399+
{
1400+
return true;
1401+
}
13981402
RegInstr::Move { dst, src }
13991403
if *dst < n_regs && *src < n_regs && alias[*dst] && alias[*src] =>
14001404
{
@@ -1686,6 +1690,7 @@ fn native_instr_semantics(instr: &RegInstr) -> NativeInstrSemantics {
16861690
RegInstr::Move { src, .. }
16871691
| RegInstr::Manage { src, .. }
16881692
| RegInstr::DeepCopy { reg: src }
1693+
| RegInstr::DeepCopyElided { reg: src }
16891694
| RegInstr::UnwrapSome { src, .. }
16901695
| RegInstr::UnwrapVariantValue { src, .. }
16911696
| RegInstr::AwaitJoin { src, .. } => S(vec![*src]),
@@ -1784,6 +1789,7 @@ fn native_instr_semantics(instr: &RegInstr) -> NativeInstrSemantics {
17841789
| RegInstr::LoadBool { dst, .. }
17851790
| RegInstr::Move { dst, .. }
17861791
| RegInstr::DeepCopy { reg: dst, .. }
1792+
| RegInstr::DeepCopyElided { reg: dst, .. }
17871793
| RegInstr::AddInt { dst, .. }
17881794
| RegInstr::SubInt { dst, .. }
17891795
| RegInstr::MulInt { dst, .. }
@@ -1839,6 +1845,7 @@ fn native_instr_semantics(instr: &RegInstr) -> NativeInstrSemantics {
18391845
| RegInstr::NativeGuardClosureId { .. }
18401846
| RegInstr::ResourceDrop { .. }
18411847
| RegInstr::DeepCopy { .. }
1848+
| RegInstr::DeepCopyElided { .. }
18421849
| RegInstr::Return { .. } => S(vec![]),
18431850
RegInstr::LoadUnit { dst }
18441851
| RegInstr::LoadInt { dst, .. }
@@ -1935,6 +1942,7 @@ fn native_instr_semantics(instr: &RegInstr) -> NativeInstrSemantics {
19351942
| RegInstr::LoadString { .. }
19361943
| RegInstr::Move { .. }
19371944
| RegInstr::DeepCopy { .. }
1945+
| RegInstr::DeepCopyElided { .. }
19381946
| RegInstr::AddInt { .. }
19391947
| RegInstr::SubInt { .. }
19401948
| RegInstr::MulInt { .. }
@@ -2215,7 +2223,7 @@ pub(in crate::reg_vm) fn native_deepcopy_param_unsoundly_mutated(
22152223
let mut tainted = vec![false; n_regs];
22162224
let mut any = false;
22172225
for instr in code {
2218-
if let RegInstr::DeepCopy { reg } = instr {
2226+
if let RegInstr::DeepCopy { reg } | RegInstr::DeepCopyElided { reg } = instr {
22192227
// Seed only HEAP roots that are not proven immutable. The `native_is_heap_reg`
22202228
// gate matters for a generic `read T` param: the lowerer emits `DeepCopy` for it
22212229
// unconditionally, but when `T` instantiates to a scalar the register is typed
@@ -2418,6 +2426,7 @@ pub(in crate::reg_vm) fn native_offset_regs(instr: &RegInstr, b: usize) -> Optio
24182426
src: src + b,
24192427
},
24202428
RegInstr::DeepCopy { reg } => RegInstr::DeepCopy { reg: reg + b },
2429+
RegInstr::DeepCopyElided { reg } => RegInstr::DeepCopyElided { reg: reg + b },
24212430
RegInstr::GetFieldSlot { dst, base, slot } => RegInstr::GetFieldSlot {
24222431
dst: dst + b,
24232432
base: base + b,
@@ -3543,7 +3552,7 @@ pub(in crate::reg_vm) fn subset_or_option_reads(instr: &RegInstr) -> Option<Vec<
35433552
| RegInstr::RuntimeError { .. }
35443553
| RegInstr::LoadNone { .. } => vec![],
35453554
RegInstr::Move { src, .. } => vec![*src],
3546-
RegInstr::DeepCopy { reg } => vec![*reg],
3555+
RegInstr::DeepCopy { reg } | RegInstr::DeepCopyElided { reg } => vec![*reg],
35473556
RegInstr::AddInt { lhs, rhs, .. }
35483557
| RegInstr::SubInt { lhs, rhs, .. }
35493558
| RegInstr::MulInt { lhs, rhs, .. }
@@ -7179,7 +7188,7 @@ pub(in crate::reg_vm) fn native_scalar_replace_variants_in_region(
71797188
// `DeepCopy` of a VAR register (e.g. from a `read`/param-marshalling of a
71807189
// heap variant): a no-op once the variant is scalar-replaced (tag/payload
71817190
// are copied by value). Allowed here; dropped in the rewrite.
7182-
RegInstr::DeepCopy { reg } if var[*reg] => {}
7191+
RegInstr::DeepCopy { reg } | RegInstr::DeepCopyElided { reg } if var[*reg] => {}
71837192
RegInstr::Move { src, .. } if var[*src] => {}
71847193
other => {
71857194
let reads = subset_or_option_reads(other)?;
@@ -7494,7 +7503,8 @@ pub(in crate::reg_vm) fn native_scalar_replace_variants_in_region(
74947503
});
74957504
}
74967505
// `DeepCopy` of a scalar-replaced variant: drop it (scalars copy by value).
7497-
RegInstr::DeepCopy { reg } if region && var[*reg] => {}
7506+
RegInstr::DeepCopy { reg } | RegInstr::DeepCopyElided { reg } if region && var[*reg] => {
7507+
}
74987508
// Copy-through, remapping jump/match targets.
74997509
RegInstr::Jump { target }
75007510
| RegInstr::JumpIfBool { target, .. }

0 commit comments

Comments
 (0)