@@ -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.
22202345fn 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 }
0 commit comments