Skip to content
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
2 changes: 1 addition & 1 deletion crates/next-build-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ async fn endpoint_write_to_disk_with_apply(
endpoint_write_to_disk(*endpoint)
}

#[turbo_tasks::value(serialization = "none")]
#[turbo_tasks::value(serialization = "skip")]
struct WithEffects {
output_paths: ReadRef<EndpointOutputPaths>,
effects: Effects,
Expand Down
2 changes: 1 addition & 1 deletion crates/next-napi-bindings/src/next_api/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use turbopack_core::{

use crate::next_api::utils::strongly_consistent_catch_collectables;

#[turbo_tasks::value(serialization = "none")]
#[turbo_tasks::value(serialization = "skip")]
pub struct WriteAnalyzeResult {
pub issues: Arc<Vec<ReadRef<PlainIssue>>>,
pub diagnostics: Arc<Vec<ReadRef<PlainDiagnostic>>>,
Expand Down
4 changes: 2 additions & 2 deletions crates/next-napi-bindings/src/next_api/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ async fn issue_filter_from_endpoint(
}
}

#[turbo_tasks::value(serialization = "none")]
#[turbo_tasks::value(serialization = "skip")]
struct WrittenEndpointWithIssues {
written: Option<ReadRef<EndpointOutputPaths>>,
issues: Arc<Vec<ReadRef<PlainIssue>>>,
Expand Down Expand Up @@ -215,7 +215,7 @@ pub fn endpoint_server_changed_subscribe(
)
}

#[turbo_tasks::value(shared, serialization = "none", eq = "manual")]
#[turbo_tasks::value(shared, serialization = "skip", eq = "manual")]
struct EndpointIssuesAndDiags {
changed: Option<ReadRef<Completion>>,
issues: Arc<Vec<ReadRef<PlainIssue>>>,
Expand Down
10 changes: 5 additions & 5 deletions crates/next-napi-bindings/src/next_api/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -965,7 +965,7 @@ impl NapiEntrypoints {
}
}

#[turbo_tasks::value(serialization = "none")]
#[turbo_tasks::value(serialization = "skip")]
struct EntrypointsWithIssues {
entrypoints: Option<ReadRef<EntrypointsOperation>>,
issues: Arc<Vec<ReadRef<PlainIssue>>>,
Expand Down Expand Up @@ -1000,14 +1000,14 @@ fn project_container_entrypoints_operation(
container.entrypoints()
}

#[turbo_tasks::value(serialization = "none")]
#[turbo_tasks::value(serialization = "skip")]
struct OperationResult {
issues: Arc<Vec<ReadRef<PlainIssue>>>,
diagnostics: Arc<Vec<ReadRef<PlainDiagnostic>>>,
effects: Arc<Effects>,
}

#[turbo_tasks::value(serialization = "none")]
#[turbo_tasks::value(serialization = "skip")]
struct AllWrittenEntrypointsWithIssues {
entrypoints: Option<ReadRef<EntrypointsOperation>>,
issues: Arc<Vec<ReadRef<PlainIssue>>>,
Expand Down Expand Up @@ -1800,7 +1800,7 @@ pub fn project_entrypoints_subscribe(
)
}

#[turbo_tasks::value(serialization = "none")]
#[turbo_tasks::value(serialization = "skip")]
struct HmrUpdateWithIssues {
update: ReadRef<Update>,
issues: Arc<Vec<ReadRef<PlainIssue>>>,
Expand Down Expand Up @@ -1945,7 +1945,7 @@ struct HmrChunkNames {
pub chunk_names: Vec<RcStr>,
}

#[turbo_tasks::value(serialization = "none")]
#[turbo_tasks::value(serialization = "skip")]
struct HmrChunkNamesWithIssues {
chunk_names: ReadRef<Vec<RcStr>>,
issues: Arc<Vec<ReadRef<PlainIssue>>>,
Expand Down
131 changes: 131 additions & 0 deletions turbopack/crates/turbo-tasks-backend/src/backend/cell_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
//! Unified cell storage.
//!
//! Every task cell — whether its value type is bincode-serializable, hash-only,
//! derivable, or non-reconstructible — lives in a single `CellData` map keyed
//! by [`CellId`]. The map's bincode impl decides at encode time which entries
//! to persist, by consulting the global [`ValueType`] registry: entries whose
//! value type has no bincode function are omitted from the serialized output.
//!
//! This replaces the older split of `persistent_cell_data` /
//! `transient_cell_data` fields which routed every cell write through an
//! `is_serializable_cell_content: bool` that threaded through ~14 call sites.
//! By keying the bincode decision on the value type itself, the routing
//! collapses to an unconditional insert.
//!
//! The inner value is stored as [`SharedReference`] rather than
//! [`TypedSharedReference`] because the `CellId` key already carries the
//! [`ValueTypeId`] — duplicating it in each map entry would waste memory.
//! Encode / decode recover the value type from the key.

use std::{
hash::BuildHasherDefault,
ops::{Deref, DerefMut},
};

use auto_hash_map::AutoMap;
use bincode::{
Decode, Encode,
error::{DecodeError, EncodeError},
};
use rustc_hash::FxHasher;
use turbo_bincode::{
TurboBincodeDecode, TurboBincodeDecoder, TurboBincodeEncode, TurboBincodeEncoder,
impl_decode_for_turbo_bincode_decode, impl_encode_for_turbo_bincode_encode,
};
use turbo_tasks::{CellId, SharedReference, ShrinkToFit, ValueTypePersistence, registry};

type InnerMap = AutoMap<CellId, SharedReference, BuildHasherDefault<FxHasher>, 1>;

/// Map of cell id → shared reference, with bincode that filters out entries
/// whose value type has no bincode function.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CellData(InnerMap);

impl CellData {
pub fn new() -> Self {
Self::default()
}
}

impl Deref for CellData {
type Target = InnerMap;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl DerefMut for CellData {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

impl ShrinkToFit for CellData {
fn shrink_to_fit(&mut self) {
self.0.shrink_to_fit();
}
}

impl TurboBincodeEncode for CellData {
/// Writes `count-of-persistable-entries` followed by each persistable
/// `(CellId, encoded-value)`. Entries whose value type is `SkipPersist`
/// or `SessionStateful` (no bincode) are skipped; they will be
/// reconstructed on the next task execution after restore.
fn encode(&self, encoder: &mut TurboBincodeEncoder) -> Result<(), EncodeError> {
// First pass: count persistable entries. One extra O(N) iteration over
// the registry — cold path (snapshot time only) and the registry is a
// static array indexed by ValueTypeId, so each lookup is cheap.
let count = self
.0
.iter()
.filter(|(cell, _)| {
matches!(
registry::get_value_type(cell.type_id).persistence,
ValueTypePersistence::Persistable(_, _),
)
})
.count();
count.encode(encoder)?;
// TODO: consider sorting by type_id and delta encoding indices to reduce serialized size
for (cell_id, reference) in self.0.iter() {
let value_type = registry::get_value_type(cell_id.type_id);
let ValueTypePersistence::Persistable(encode_fn, _) = value_type.persistence else {
continue;
};
cell_id.encode(encoder)?;
encode_fn(&*reference.0, encoder)?;
}
Ok(())
}
}

impl<Context> TurboBincodeDecode<Context> for CellData {
/// Reads the count written by [`CellData::encode`] and decodes each
/// `(CellId, SharedReference)` entry by looking up the value type's
/// bincode decode function.
///
/// Missing cell types — or cells whose value type isn't `Persistable` —
/// are a decode error: the encoder filters them out, so they should not
/// appear on the wire.
fn decode(decoder: &mut TurboBincodeDecoder) -> Result<Self, DecodeError> {
let count = usize::decode(decoder)?;
let mut map = InnerMap::with_capacity_and_hasher(count, BuildHasherDefault::default());
for _ in 0..count {
let cell = CellId::decode(decoder)?;
let value_type = registry::get_value_type(cell.type_id);
let ValueTypePersistence::Persistable(_, decode_fn) = value_type.persistence else {
return Err(DecodeError::OtherString(format!(
"cell of type {} has no bincode decoder",
value_type.ty.global_name
)));
};
let reference = decode_fn(decoder)?;
map.insert(cell, reference);
}
Ok(Self(map))
}
}

impl_encode_for_turbo_bincode_encode!(CellData);
impl_decode_for_turbo_bincode_decode!(CellData);
49 changes: 12 additions & 37 deletions turbopack/crates/turbo-tasks-backend/src/backend/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod cell_data;
mod counter_map;
mod operation;
mod storage;
Expand Down Expand Up @@ -857,7 +858,6 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
}

let ReadCellOptions {
is_serializable_cell_content,
tracking,
final_read_hint,
} = options;
Expand All @@ -878,17 +878,17 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
};

let content = if final_read_hint {
task.remove_cell_data(is_serializable_cell_content, cell)
task.remove_cell_data(&cell)
} else {
task.get_cell_data(is_serializable_cell_content, cell)
task.get_cell_data(&cell).cloned()
};
if let Some(content) = content {
if tracking.should_track(false) {
add_cell_dependency(task_id, task, reader, reader_task, cell, tracking.key());
}
return Ok(Ok(TypedCellContent(
cell.type_id,
CellContent(Some(content.reference)),
CellContent(Some(content)),
)));
}

Expand Down Expand Up @@ -2689,35 +2689,18 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
// Note: We do not mark the tasks as dirty here, as these tasks are unused or stale
// anyway and we want to avoid needless re-executions. When the cells become
// used again, they are invalidated from the update cell operation.
// Remove cell data for cells that no longer exist
let to_remove_persistent: Vec<_> = task
.iter_persistent_cell_data()
let to_remove: Vec<_> = task
.iter_cell_data()
.filter_map(|(cell, _)| {
cell_counters
.get(&cell.type_id)
.is_none_or(|start_index| cell.index >= *start_index)
.then_some(*cell)
})
.collect();

// Remove transient cell data for cells that no longer exist
let to_remove_transient: Vec<_> = task
.iter_transient_cell_data()
.filter_map(|(cell, _)| {
cell_counters
.get(&cell.type_id)
.is_none_or(|start_index| cell.index >= *start_index)
.then_some(*cell)
})
.collect();
removed_cell_data.reserve_exact(to_remove_persistent.len() + to_remove_transient.len());
for cell in to_remove_persistent {
if let Some(data) = task.remove_persistent_cell_data(&cell) {
removed_cell_data.push(data.into_untyped());
}
}
for cell in to_remove_transient {
if let Some(data) = task.remove_transient_cell_data(&cell) {
removed_cell_data.reserve_exact(to_remove.len());
for cell in to_remove {
if let Some(data) = task.remove_cell_data(&cell) {
removed_cell_data.push(data);
}
}
Expand Down Expand Up @@ -2904,14 +2887,12 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
&self,
task_id: TaskId,
cell: CellId,
options: ReadCellOptions,
turbo_tasks: &dyn TurboTasksBackendApi<TurboTasksBackend<B>>,
) -> Result<TypedCellContent> {
let mut ctx = self.execute_context(turbo_tasks);
let task = ctx.task(task_id, TaskDataCategory::Data);
if let Some(content) = task.get_cell_data(options.is_serializable_cell_content, cell) {
debug_assert!(content.type_id == cell.type_id, "Cell type ID mismatch");
Ok(CellContent(Some(content.reference)).into_typed(cell.type_id))
if let Some(content) = task.get_cell_data(&cell).cloned() {
Ok(CellContent(Some(content)).into_typed(cell.type_id))
} else {
Ok(CellContent(None).into_typed(cell.type_id))
}
Expand Down Expand Up @@ -3041,7 +3022,6 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
&self,
task_id: TaskId,
cell: CellId,
is_serializable_cell_content: bool,
content: CellContent,
updated_key_hashes: Option<SmallVec<[u64; 2]>>,
content_hash: Option<CellHash>,
Expand All @@ -3052,7 +3032,6 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
task_id,
cell,
content,
is_serializable_cell_content,
updated_key_hashes,
content_hash,
verification_mode,
Expand Down Expand Up @@ -3553,11 +3532,9 @@ impl<B: BackingStorage> Backend for TurboTasksBackend<B> {
&self,
task_id: TaskId,
cell: CellId,
options: ReadCellOptions,
turbo_tasks: &dyn TurboTasksBackendApi<Self>,
) -> Result<TypedCellContent> {
self.0
.try_read_own_task_cell(task_id, cell, options, turbo_tasks)
self.0.try_read_own_task_cell(task_id, cell, turbo_tasks)
}

fn read_task_collectibles(
Expand Down Expand Up @@ -3598,7 +3575,6 @@ impl<B: BackingStorage> Backend for TurboTasksBackend<B> {
&self,
task_id: TaskId,
cell: CellId,
is_serializable_cell_content: bool,
content: CellContent,
updated_key_hashes: Option<SmallVec<[u64; 2]>>,
content_hash: Option<CellHash>,
Expand All @@ -3608,7 +3584,6 @@ impl<B: BackingStorage> Backend for TurboTasksBackend<B> {
self.0.update_task_cell(
task_id,
cell,
is_serializable_cell_content,
content,
updated_key_hashes,
content_hash,
Expand Down
Loading
Loading