From 5920a1d9482a289f7d2057237faf831c1fef0369 Mon Sep 17 00:00:00 2001 From: Alan Egerton Date: Mon, 29 Nov 2021 12:55:00 +0000 Subject: [PATCH] Avoid cloning refcounted types during folding --- compiler/rustc_middle/src/lib.rs | 1 + .../rustc_middle/src/ty/structural_impls.rs | 73 +++++++++++++++++-- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_middle/src/lib.rs b/compiler/rustc_middle/src/lib.rs index 66d1ae1420a..9d8588c9a8e 100644 --- a/compiler/rustc_middle/src/lib.rs +++ b/compiler/rustc_middle/src/lib.rs @@ -33,6 +33,7 @@ #![feature(derive_default_enum)] #![feature(discriminant_kind)] #![feature(exhaustive_patterns)] +#![feature(get_mut_unchecked)] #![feature(if_let_guard)] #![feature(map_first_last)] #![feature(never_type)] diff --git a/compiler/rustc_middle/src/ty/structural_impls.rs b/compiler/rustc_middle/src/ty/structural_impls.rs index 30ed008a5de..28dc9767b78 100644 --- a/compiler/rustc_middle/src/ty/structural_impls.rs +++ b/compiler/rustc_middle/src/ty/structural_impls.rs @@ -13,6 +13,7 @@ use rustc_index::vec::{Idx, IndexVec}; use std::fmt; +use std::mem::ManuallyDrop; use std::ops::ControlFlow; use std::rc::Rc; use std::sync::Arc; @@ -732,11 +733,41 @@ impl<'tcx, T, E> TypeFoldable<'tcx> for Result { impl<'tcx, T: TypeFoldable<'tcx>> TypeFoldable<'tcx> for Rc { fn try_super_fold_with>( - self, + mut self, folder: &mut F, ) -> Result { - // FIXME: Reuse the `Rc` here. - (*self).clone().try_fold_with(folder).map(Rc::new) + // We merely want to replace the contained `T`, if at all possible, + // so that we don't needlessly allocate a new `Rc` or indeed clone + // the contained type. + unsafe { + // First step is to ensure that we have a unique reference to + // the contained type, which `Rc::make_mut` will accomplish (by + // allocating a new `Rc` and cloning the `T` only if required). + // This is done *before* casting to `Rc>` so that + // panicking during `make_mut` does not leak the `T`. + Rc::make_mut(&mut self); + + // Casting to `Rc>` is safe because `ManuallyDrop` + // is `repr(transparent)`. + let ptr = Rc::into_raw(self).cast::>(); + let mut unique = Rc::from_raw(ptr); + + // Call to `Rc::make_mut` above guarantees that `unique` is the + // sole reference to the contained value, so we can avoid doing + // a checked `get_mut` here. + let slot = Rc::get_mut_unchecked(&mut unique); + + // Semantically move the contained type out from `unique`, fold + // it, then move the folded value back into `unique`. Should + // folding fail, `ManuallyDrop` ensures that the "moved-out" + // value is not re-dropped. + let owned = ManuallyDrop::take(slot); + let folded = owned.try_fold_with(folder)?; + *slot = ManuallyDrop::new(folded); + + // Cast back to `Rc`. + Ok(Rc::from_raw(Rc::into_raw(unique).cast())) + } } fn super_visit_with>(&self, visitor: &mut V) -> ControlFlow { @@ -746,11 +777,41 @@ fn super_visit_with>(&self, visitor: &mut V) -> ControlFlow impl<'tcx, T: TypeFoldable<'tcx>> TypeFoldable<'tcx> for Arc { fn try_super_fold_with>( - self, + mut self, folder: &mut F, ) -> Result { - // FIXME: Reuse the `Arc` here. - (*self).clone().try_fold_with(folder).map(Arc::new) + // We merely want to replace the contained `T`, if at all possible, + // so that we don't needlessly allocate a new `Arc` or indeed clone + // the contained type. + unsafe { + // First step is to ensure that we have a unique reference to + // the contained type, which `Arc::make_mut` will accomplish (by + // allocating a new `Arc` and cloning the `T` only if required). + // This is done *before* casting to `Arc>` so that + // panicking during `make_mut` does not leak the `T`. + Arc::make_mut(&mut self); + + // Casting to `Arc>` is safe because `ManuallyDrop` + // is `repr(transparent)`. + let ptr = Arc::into_raw(self).cast::>(); + let mut unique = Arc::from_raw(ptr); + + // Call to `Arc::make_mut` above guarantees that `unique` is the + // sole reference to the contained value, so we can avoid doing + // a checked `get_mut` here. + let slot = Arc::get_mut_unchecked(&mut unique); + + // Semantically move the contained type out from `unique`, fold + // it, then move the folded value back into `unique`. Should + // folding fail, `ManuallyDrop` ensures that the "moved-out" + // value is not re-dropped. + let owned = ManuallyDrop::take(slot); + let folded = owned.try_fold_with(folder)?; + *slot = ManuallyDrop::new(folded); + + // Cast back to `Arc`. + Ok(Arc::from_raw(Arc::into_raw(unique).cast())) + } } fn super_visit_with>(&self, visitor: &mut V) -> ControlFlow {