Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[turbopack] Make ReadRef using VcCellMode semantics, add VcCellMode::raw_cell API #68467

Merged
merged 2 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions turbopack/crates/turbo-tasks/src/backend.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use std::{
any::Any,
borrow::Cow,
fmt::{self, Debug, Display, Write},
future::Future,
Expand All @@ -22,8 +21,9 @@ use crate::{
raw_vc::CellId,
registry,
trait_helpers::{get_trait_method, has_trait, traits},
triomphe_utils::unchecked_sidecast_triomphe_arc,
FunctionId, RawVc, ReadRef, SharedReference, TaskId, TaskIdProvider, TaskIdSet, TraitRef,
TraitTypeId, ValueTypeId, VcValueTrait, VcValueType,
TraitTypeId, ValueTypeId, VcRead, VcValueTrait, VcValueType,
};

pub enum TaskType {
Expand Down Expand Up @@ -347,11 +347,14 @@ impl Display for CellContent {
}

impl TypedCellContent {
pub fn cast<T: Any + VcValueType>(self) -> Result<ReadRef<T>> {
pub fn cast<T: VcValueType>(self) -> Result<ReadRef<T>> {
let data = self.1 .0.ok_or_else(|| anyhow!("Cell is empty"))?;
let data = data
.downcast()
.downcast::<<T::Read as VcRead<T>>::Repr>()
.map_err(|_err| anyhow!("Unexpected type in cell"))?;
// SAFETY: `T` and `T::Read::Repr` must have equivalent memory representations,
// guaranteed by the unsafe implementation of `VcValueType`.
let data = unsafe { unchecked_sidecast_triomphe_arc(data) };
Ok(ReadRef::new_arc(data))
}

Expand All @@ -374,10 +377,6 @@ impl TypedCellContent {
)
}

pub fn try_cast<T: Any + VcValueType>(self) -> Option<ReadRef<T>> {
Some(ReadRef::new_arc(self.1 .0?.downcast().ok()?))
}

pub fn into_untyped(self) -> CellContent {
self.1
}
Expand Down
2 changes: 1 addition & 1 deletion turbopack/crates/turbo-tasks/src/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl Completion {
// only updates the cell when it is empty (Completion::cell opted-out of
// that via `#[turbo_tasks::value(cell = "new")]`)
let cell = turbo_tasks::macro_helpers::find_cell_by_type(*COMPLETION_VALUE_TYPE_ID);
cell.conditional_update_shared(|old| old.is_none().then_some(Completion));
cell.conditional_update(|old| old.is_none().then_some(Completion));
let raw: RawVc = cell.into();
raw.into()
}
Expand Down
147 changes: 119 additions & 28 deletions turbopack/crates/turbo-tasks/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1522,57 +1522,148 @@ pub struct CurrentCellRef {
index: CellId,
}

type VcReadRepr<T> = <<T as VcValueType>::Read as VcRead<T>>::Repr;

impl CurrentCellRef {
pub fn conditional_update_shared<
T: VcValueType + 'static,
F: FnOnce(Option<&T>) -> Option<T>,
>(
/// Updates the cell if the given `functor` returns a value.
pub fn conditional_update<T>(&self, functor: impl FnOnce(Option<&T>) -> Option<T>)
where
T: VcValueType,
{
self.conditional_update_with_shared_reference(|old_shared_reference| {
let old_ref = old_shared_reference
.and_then(|sr| sr.0.downcast_ref::<VcReadRepr<T>>())
.map(|content| <T::Read as VcRead<T>>::repr_to_value_ref(content));
let new_value = functor(old_ref)?;
Some(SharedReference::new(triomphe::Arc::new(
<T::Read as VcRead<T>>::value_to_repr(new_value),
)))
})
}

/// Updates the cell if the given `functor` returns a `SharedReference`.
pub fn conditional_update_with_shared_reference(
&self,
functor: F,
functor: impl FnOnce(Option<&SharedReference>) -> Option<SharedReference>,
) {
let tt = turbo_tasks();
let content = tt
.read_own_task_cell(self.current_task, self.index)
.ok()
.and_then(|v| v.try_cast::<T>());
let update =
functor(content.as_deref().map(|content| {
<<T as VcValueType>::Read as VcRead<T>>::target_to_value_ref(content)
}));
let cell_content = tt.read_own_task_cell(self.current_task, self.index).ok();
let update = functor(cell_content.as_ref().and_then(|cc| cc.1 .0.as_ref()));
if let Some(update) = update {
tt.update_own_task_cell(
self.current_task,
self.index,
CellContent(Some(SharedReference::new(triomphe::Arc::new(update)))),
)
tt.update_own_task_cell(self.current_task, self.index, CellContent(Some(update)))
}
}

pub fn compare_and_update_shared<T: PartialEq + VcValueType + 'static>(&self, new_content: T) {
self.conditional_update_shared(|old_content| {
if let Some(old_content) = old_content {
if PartialEq::eq(&new_content, old_content) {
/// Replace the current cell's content with `new_value` if the current
/// content is not equal by value with the existing content.
///
/// The comparison happens using the value itself, not the
/// [`VcRead::Target`] of that value.
///
/// Take this example of a custom equality implementation on a transparent
/// wrapper type:
///
/// ```
/// #[turbo_tasks::value(transparent, eq = "manual")]
/// struct Wrapper(Vec<u32>);
///
/// impl PartialEq for Wrapper {
/// fn eq(&self, other: Wrapper) {
/// // Example: order doesn't matter for equality
/// let (mut this, mut other) = (self.clone(), other.clone());
/// this.sort_unstable();
/// other.sort_unstable();
/// this == other
/// }
/// }
///
/// impl Eq for Wrapper {}
/// ```
///
/// Comparisons of `Vc<Wrapper>` used when updating the cell will use
/// `Wrapper`'s custom equality implementation, rather than the one
/// provided by the target (`Vec<u32>`) type.
///
/// However, in most cases, the default derived implementation of
/// `PartialEq` is used which just forwards to the inner value's
/// `PartialEq`.
///
/// If you already have a `SharedReference`, consider calling
/// [`compare_and_update_with_shared_reference`] which can re-use the
/// `SharedReference` object.
pub fn compare_and_update<T>(&self, new_value: T)
where
T: PartialEq + VcValueType,
{
self.conditional_update(|old_value| {
if let Some(old_value) = old_value {
if old_value == &new_value {
return None;
}
}
Some(new_value)
});
}

/// Replace the current cell's content with `new_shared_reference` if the
/// current content is not equal by value with the existing content.
///
/// If you already have a `SharedReference`, this is a faster version of
/// [`CurrentCellRef::compare_and_update`].
///
/// The [`SharedReference`] is expected to use the `<T::Read as
/// VcRead<T>>::Repr` type for its representation of the value.
pub fn compare_and_update_with_shared_reference<T>(&self, new_shared_reference: SharedReference)
where
T: VcValueType + PartialEq,
{
fn extract_sr_value<T: VcValueType>(sr: &SharedReference) -> &T {
<T::Read as VcRead<T>>::repr_to_value_ref(
sr.0.downcast_ref::<VcReadRepr<T>>()
.expect("cannot update SharedReference of different type"),
)
}
self.conditional_update_with_shared_reference(|old_sr| {
if let Some(old_sr) = old_sr {
let old_value: &T = extract_sr_value(old_sr);
let new_value = extract_sr_value(&new_shared_reference);
if old_value == new_value {
return None;
}
}
Some(new_content)
Some(new_shared_reference)
});
}

pub fn update_shared<T: VcValueType + 'static>(&self, new_content: T) {
/// Unconditionally updates the content of the cell.
pub fn update<T>(&self, new_value: T)
where
T: VcValueType,
{
let tt = turbo_tasks();
tt.update_own_task_cell(
self.current_task,
self.index,
CellContent(Some(SharedReference::new(triomphe::Arc::new(new_content)))),
CellContent(Some(SharedReference::new(triomphe::Arc::new(
<T::Read as VcRead<T>>::value_to_repr(new_value),
)))),
)
}

pub fn update_shared_reference(&self, shared_ref: SharedReference) {
/// A faster version of [`Self::update`] if you already have a
/// [`SharedReference`].
///
/// If the passed-in [`SharedReference`] is the same as the existing cell's
/// by identity, no update is performed.
///
/// The [`SharedReference`] is expected to use the `<T::Read as
/// VcRead<T>>::Repr` type for its representation of the value.
pub fn update_with_shared_reference(&self, shared_ref: SharedReference) {
let tt = turbo_tasks();
let content = tt.read_own_task_cell(self.current_task, self.index).ok();
let update = if let Some(TypedCellContent(_, CellContent(shared_ref_exp))) = content {
shared_ref_exp.as_ref().ne(&Some(&shared_ref))
let update = if let Some(TypedCellContent(_, CellContent(Some(shared_ref_exp)))) = content {
// pointer equality (not value equality)
shared_ref_exp != shared_ref
} else {
true
};
Expand Down
11 changes: 6 additions & 5 deletions turbopack/crates/turbo-tasks/src/read_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ use turbo_tasks_hash::DeterministicHash;

use crate::{
debug::{ValueDebugFormat, ValueDebugFormatString},
macro_helpers::find_cell_by_type,
trace::{TraceRawVcs, TraceRawVcsContext},
triomphe_utils::unchecked_sidecast_triomphe_arc,
vc::VcCellMode,
SharedReference, Vc, VcRead, VcValueType,
};

Expand Down Expand Up @@ -244,14 +244,15 @@ where
/// reference.
pub fn cell(read_ref: ReadRef<T>) -> Vc<T> {
let type_id = T::get_value_type_id();
let local_cell = find_cell_by_type(type_id);
// SAFETY: `T` and `T::Read::Repr` must have equivalent memory representations,
// guaranteed by the unsafe implementation of `VcValueType`.
local_cell.update_shared_reference(SharedReference::new(unsafe {
let value = unsafe {
unchecked_sidecast_triomphe_arc::<T, <T::Read as VcRead<T>>::Repr>(read_ref.0)
}));
};
Vc {
node: local_cell.into(),
node: <T::CellMode as VcCellMode<T>>::raw_cell(
SharedReference::new(value).typed(type_id),
),
_t: PhantomData,
}
}
Expand Down
2 changes: 1 addition & 1 deletion turbopack/crates/turbo-tasks/src/trait_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ where
// See Safety clause above.
let TypedSharedReference(ty, shared_ref) = trait_ref.shared_reference;
let local_cell = find_cell_by_type(ty);
local_cell.update_shared_reference(shared_ref);
local_cell.update_with_shared_reference(shared_ref);
let raw_vc: RawVc = local_cell.into();
raw_vc.into()
}
Expand Down
18 changes: 3 additions & 15 deletions turbopack/crates/turbo-tasks/src/vc/cast.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::{marker::PhantomData, mem::ManuallyDrop};
use std::marker::PhantomData;

use anyhow::Result;

use crate::{backend::TypedCellContent, ReadRef, TraitRef, VcRead, VcValueTrait, VcValueType};
use crate::{backend::TypedCellContent, ReadRef, TraitRef, VcValueTrait, VcValueType};

/// Trait defined to share behavior between values and traits within
/// [`ReadRawVcFuture`][crate::ReadRawVcFuture]. See [`VcValueTypeCast`] and
Expand All @@ -27,19 +27,7 @@ where
type Output = ReadRef<T>;

fn cast(content: TypedCellContent) -> Result<Self::Output> {
Ok(
// Safety: the `VcValueType` implementor must guarantee that both `T` and
// `Repr` are #[repr(transparent)].
unsafe {
// Downcast the cell content to the expected representation type, then
// transmute it to the expected type.
// See https://users.rust-lang.org/t/transmute-doesnt-work-on-generic-types/87272/9
std::mem::transmute_copy::<
ManuallyDrop<ReadRef<<T::Read as VcRead<T>>::Repr>>,
Self::Output,
>(&ManuallyDrop::new(content.cast()?))
},
)
content.cast()
}
}

Expand Down
55 changes: 45 additions & 10 deletions turbopack/crates/turbo-tasks/src/vc/cell_mode.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
use std::marker::PhantomData;
use std::{any::type_name, marker::PhantomData};

use super::{read::VcRead, traits::VcValueType};
use crate::{manager::find_cell_by_type, Vc};
use crate::{manager::find_cell_by_type, task::shared_reference::TypedSharedReference, RawVc, Vc};

/// Trait that controls the behavior of `Vc::cell` on a value type basis.
type VcReadTarget<T> = <<T as VcValueType>::Read as VcRead<T>>::Target;
type VcReadRepr<T> = <<T as VcValueType>::Read as VcRead<T>>::Repr;

/// Trait that controls the behavior of [`Vc::cell`] based on the value type's
/// [`VcValueType::CellMode`].
///
/// This trait must remain sealed within this crate.
pub trait VcCellMode<T>
where
T: VcValueType,
{
fn cell(value: <T::Read as VcRead<T>>::Target) -> Vc<T>;
/// Create a new cell.
fn cell(value: VcReadTarget<T>) -> Vc<T>;

/// Create a type-erased [`RawVc`] cell given a pre-existing type-erased
/// [`SharedReference`][crate::task::SharedReference].
///
/// This is used in APIs that already have a `SharedReference`, such as in
/// [`ReadRef::cell`][crate::ReadRef::cell] or in [`Vc::resolve`] when
/// resolving a local [`Vc`]. This avoids unnecessary cloning.
fn raw_cell(value: TypedSharedReference) -> RawVc;
}

/// Mode that always updates the cell's content.
Expand All @@ -22,14 +35,21 @@ impl<T> VcCellMode<T> for VcCellNewMode<T>
where
T: VcValueType,
{
fn cell(inner: <T::Read as VcRead<T>>::Target) -> Vc<T> {
fn cell(inner: VcReadTarget<T>) -> Vc<T> {
let cell = find_cell_by_type(T::get_value_type_id());
cell.update_shared(<T::Read as VcRead<T>>::target_to_repr(inner));
cell.update(<T::Read as VcRead<T>>::target_to_value(inner));
Vc {
node: cell.into(),
_t: PhantomData,
}
}

fn raw_cell(content: TypedSharedReference) -> RawVc {
debug_assert_repr::<T>(&content);
let cell = find_cell_by_type(content.0);
cell.update_with_shared_reference(content.1);
cell.into()
}
}

/// Mode that compares the cell's content with the new value and only updates
Expand All @@ -40,15 +60,30 @@ pub struct VcCellSharedMode<T> {

impl<T> VcCellMode<T> for VcCellSharedMode<T>
where
T: VcValueType,
<T::Read as VcRead<T>>::Repr: PartialEq,
T: VcValueType + PartialEq,
{
fn cell(inner: <T::Read as VcRead<T>>::Target) -> Vc<T> {
fn cell(inner: VcReadTarget<T>) -> Vc<T> {
let cell = find_cell_by_type(T::get_value_type_id());
cell.compare_and_update_shared(<T::Read as VcRead<T>>::target_to_repr(inner));
cell.compare_and_update(<T::Read as VcRead<T>>::target_to_value(inner));
Vc {
node: cell.into(),
_t: PhantomData,
}
}

fn raw_cell(content: TypedSharedReference) -> RawVc {
debug_assert_repr::<T>(&content);
let cell = find_cell_by_type(content.0);
cell.compare_and_update_with_shared_reference::<T>(content.1);
cell.into()
}
}

fn debug_assert_repr<T: VcValueType>(content: &TypedSharedReference) {
debug_assert!(
(*content.1 .0).is::<VcReadRepr<T>>(),
"SharedReference for type {} must use representation type {}",
type_name::<T>(),
type_name::<VcReadRepr<T>>(),
);
}
Loading
Loading