Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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