diff --git a/.cargo/config.toml b/.cargo/config.toml index 6d8f379aadfc..a20e20d2971e 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -73,4 +73,4 @@ rustflags = [ linker = "arm-linux-gnueabihf-gcc" [target.wasm32-unknown-unknown] -rustflags = ["-Zshare-generics=y", "--cfg", 'getrandom_backend="wasm_js"'] \ No newline at end of file +rustflags = ["-Zshare-generics=y", "--cfg", 'getrandom_backend="wasm_js"'] diff --git a/Cargo.lock b/Cargo.lock index 21d9bacf7da0..6dc53a67fa51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9218,6 +9218,7 @@ dependencies = [ "once_cell", "parking_lot", "pin-project-lite", + "pot", "rayon", "regex", "rustc-hash 2.1.1", @@ -9237,6 +9238,7 @@ dependencies = [ "turbo-tasks-macros", "turbo-tasks-malloc", "unsize", + "unty", ] [[package]] @@ -9258,7 +9260,6 @@ dependencies = [ "lmdb-rkv", "once_cell", "parking_lot", - "pot", "rand 0.9.0", "regex", "ringmap", @@ -9272,6 +9273,7 @@ dependencies = [ "thread_local", "tokio", "tracing", + "turbo-bincode", "turbo-persistence", "turbo-rcstr", "turbo-tasks", diff --git a/Cargo.toml b/Cargo.toml index c2b800457f2f..857020e0b32b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -360,7 +360,6 @@ preset_env_base = "6.0.0" # General Deps -bincode = { version = "2.0.1", features = ["serde"] } chromiumoxide = { version = "0.5.4", features = [ "tokio-runtime", ], default-features = false } @@ -374,6 +373,7 @@ allsorts = { version = "0.14.0", default-features = false, features = [ ] } anyhow = "1.0.100" async-trait = "0.1.64" +bincode = { version = "2.0.1", features = ["serde"] } bitfield = "0.18.0" byteorder = "1.5.0" bytes = "1.1.0" @@ -393,12 +393,13 @@ either = "1.9.0" erased-serde = "0.4.5" flate2 = "1.0.28" futures = "0.3.31" -futures-util = "0.3.31" futures-retry = "0.6.0" +futures-util = "0.3.31" hashbrown = "0.14.5" image = { version = "0.25.8", default-features = false } indexmap = "2.7.1" indoc = "2.0.0" +inventory = "0.3.21" itertools = "0.10.5" lightningcss = { version = "1.0.0-alpha.68", features = [ "serde", @@ -443,11 +444,10 @@ ringmap = "0.1.3" roaring = "0.10.10" rstest = "0.16.0" rustc-hash = "2.1.1" -twox-hash = { version = "2.1.0", features = ["xxhash64", "xxhash3_128"] } semver = "1.0.16" serde = { version = "1.0.217", features = ["derive"] } -serde_json = "1.0.138" serde_bytes = "0.11.15" +serde_json = "1.0.138" serde_path_to_error = "0.1.16" serde_qs = "0.13.0" serde_with = "3.12.0" @@ -458,18 +458,19 @@ smallvec = { version = "1.15.1", features = [ "const_new", "impl_bincode", ] } -swc_sourcemap = "9.3.4" -strsim = "0.11.1" shrink-to-fit = "0.2.10" +strsim = "0.11.1" +swc_sourcemap = "9.3.4" syn = "2.0.100" tempfile = "3.20.0" -thread_local = "1.1.8" thiserror = "1.0.48" +thread_local = "1.1.8" tokio = "1.43.0" tokio-util = { version = "0.7.13", features = ["io", "rt"] } tracing = "0.1.37" tracing-subscriber = "0.3.16" triomphe = { git = "https://github.com/sokra/triomphe", branch = "sokra/unstable" } +twox-hash = { version = "2.1.0", features = ["xxhash64", "xxhash3_128"] } unsize = "1.1.0" unty = "0.0.4" url = "2.2.2" @@ -478,7 +479,6 @@ uuid = "1.18.1" vergen = { version = "9.0.6", features = ["cargo"] } vergen-gitcl = { version = "1.0.8", features = ["cargo"] } webbrowser = "1.0.6" -inventory = "0.3.21" [patch.crates-io] bincode = { git = "https://github.com/bgw/bincode.git", branch = "bgw/patches" } diff --git a/crates/next-core/src/next_config.rs b/crates/next-core/src/next_config.rs index 0c0c5d8a79cd..4b6a14e7d108 100644 --- a/crates/next-core/src/next_config.rs +++ b/crates/next-core/src/next_config.rs @@ -2204,9 +2204,10 @@ impl NextConfig { /// A subset of ts/jsconfig that next.js implicitly /// interops with. #[turbo_tasks::value(serialization = "custom", eq = "manual")] -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, Encode, Decode)] #[serde(rename_all = "camelCase")] pub struct JsConfig { + #[bincode(with = "turbo_bincode::serde_json")] compiler_options: Option, } diff --git a/turbopack/crates/turbo-tasks-backend/Cargo.toml b/turbopack/crates/turbo-tasks-backend/Cargo.toml index 1aee93cb86c5..7404fcb2d9b8 100644 --- a/turbopack/crates/turbo-tasks-backend/Cargo.toml +++ b/turbopack/crates/turbo-tasks-backend/Cargo.toml @@ -42,7 +42,6 @@ indexmap = { workspace = true } lmdb-rkv = { version = "0.14.0", optional = true } once_cell = { workspace = true } parking_lot = { workspace = true } -pot = "3.0.0" rand = { workspace = true } ringmap = { workspace = true, features = ["serde"] } rustc-hash = { workspace = true } @@ -53,6 +52,7 @@ smallvec = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } thread_local = { workspace = true } +turbo-bincode = { workspace = true } turbo-persistence = { workspace = true } turbo-rcstr = { workspace = true } turbo-tasks = { workspace = true } diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs index 6451b4bfe2e4..37bb7c036a35 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs @@ -1057,6 +1057,9 @@ impl TurboTasksBackendInner { (meta, data) }; let process = |task_id: TaskId, (meta, data): (Option>, Option>)| { + // TODO: perf: Instead of returning a `Vec` of individually allocated `SmallVec`s, it'd + // be better to append everything to a flat per-task or per-shard `Vec`, and have + // each `serialize` call return `(start_idx, end_idx)`. ( task_id, meta.map(|d| self.backing_storage.serialize(task_id, &d)), diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs index 2a61df315cf1..0fe60ff56cb9 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs @@ -10,10 +10,10 @@ use std::{ }; use anyhow::Result; +use bincode::{Decode, Encode}; use indexmap::map::Entry; use ringmap::RingSet; use rustc_hash::{FxBuildHasher, FxHashMap}; -use serde::{Deserialize, Serialize, Serializer, ser::SerializeSeq}; use smallvec::{SmallVec, smallvec}; #[cfg(any( feature = "trace_aggregation_update", @@ -166,9 +166,11 @@ impl ComputeDirtyAndCleanUpdateResult { } } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Encode, Decode, Clone, Debug)] pub struct InnerOfUppersHasNewFollowersJob { + #[bincode(with = "turbo_bincode::smallvec")] pub upper_ids: TaskIdVec, + #[bincode(with = "turbo_bincode::smallvec")] pub new_follower_ids: TaskIdVec, } @@ -178,9 +180,11 @@ impl From for AggregationUpdateJob { } } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Encode, Decode, Clone, Debug)] pub struct InnerOfUppersLostFollowersJob { + #[bincode(with = "turbo_bincode::smallvec")] pub upper_ids: TaskIdVec, + #[bincode(with = "turbo_bincode::smallvec")] pub lost_follower_ids: TaskIdVec, } @@ -190,7 +194,7 @@ impl From for AggregationUpdateJob { } } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Encode, Decode, Clone, Debug)] pub struct AggregatedDataUpdateJob { pub upper_ids: TaskIdVec, pub update: AggregatedDataUpdate, @@ -203,7 +207,7 @@ impl From for AggregationUpdateJob { } /// A job in the job queue for updating something in the aggregated graph. -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Encode, Decode, Clone, Debug)] pub enum AggregationUpdateJob { /// Update the aggregation number of a task. This might result in balancing needed to update /// "upper" and "follower" edges. @@ -252,17 +256,27 @@ pub enum AggregationUpdateJob { collectible_type: turbo_tasks::TraitTypeId, }, /// Increases the active counter of the task - #[serde(skip)] - IncreaseActiveCount { task: TaskId }, + IncreaseActiveCount { + // TODO: bgw: Add a way to skip the entire enum variant in bincode (generating an error + // upon attempted serialization) similar to #[serde(skip)] on variants + #[bincode(skip, default = "unreachable_decode")] + task: TaskId, + }, /// Increases the active counters of the tasks - #[serde(skip)] - IncreaseActiveCounts { task_ids: TaskIdVec }, + IncreaseActiveCounts { + #[bincode(skip, default = "unreachable_decode")] + task_ids: TaskIdVec, + }, /// Decreases the active counter of the task - #[serde(skip)] - DecreaseActiveCount { task: TaskId }, + DecreaseActiveCount { + #[bincode(skip, default = "unreachable_decode")] + task: TaskId, + }, /// Decreases the active counters of the tasks - #[serde(skip)] - DecreaseActiveCounts { task_ids: TaskIdVec }, + DecreaseActiveCounts { + #[bincode(skip, default = "unreachable_decode")] + task_ids: TaskIdVec, + }, /// Balances the edges of the graph. This checks if the graph invariant is still met for this /// edge and coverts a upper edge to a follower edge or vice versa. Balancing might triggers /// more changes to the structure. @@ -271,6 +285,10 @@ pub enum AggregationUpdateJob { Noop, } +fn unreachable_decode() -> T { + unreachable!("AggregatedDataUpdateJob variant should not have been encoded, cannot decode") +} + impl AggregationUpdateJob { pub fn data_update( task: &mut impl TaskGuard, @@ -291,9 +309,10 @@ impl AggregationUpdateJob { } } -#[derive(Default, Serialize, Deserialize, Clone, Copy, Debug)] +#[derive(Default, Encode, Decode, Clone, Copy, Debug)] +#[bincode(decode_bounds = "T: Default", borrow_decode_bounds = "T: Default")] pub struct SessionDependent { - #[serde(skip, default)] + #[bincode(skip)] pub value: T, } @@ -312,7 +331,7 @@ impl Deref for SessionDependent { } /// Aggregated data update. -#[derive(Default, Serialize, Deserialize, Clone, Debug)] +#[derive(Default, Encode, Decode, Clone, Debug)] pub struct AggregatedDataUpdate { /// One of the inner tasks has changed its dirty state or aggregated dirty state. /// (task id, dirty update, current session clean update) @@ -649,21 +668,21 @@ impl AggregatedDataUpdate { } /// An aggregation number update job that is enqueued. -#[derive(Serialize, Deserialize, Clone)] +#[derive(Encode, Decode, Clone)] struct AggregationNumberUpdate { base_aggregation_number: u32, distance: Option, #[cfg(feature = "trace_aggregation_update")] - #[serde(skip, default)] + #[bincode(skip, default)] span: Option, } /// An aggregated data update job that is enqueued. See `AggregatedDataUpdate`. -#[derive(Serialize, Deserialize, Clone)] +#[derive(Encode, Decode, Clone)] struct AggregationUpdateJobItem { job: AggregationUpdateJob, #[cfg(feature = "trace_aggregation_update")] - #[serde(skip, default)] + #[bincode(skip, default)] span: Option, } @@ -692,12 +711,12 @@ struct AggregationUpdateJobGuard { } /// A balancing job that is enqueued. See `balance_edge`. -#[derive(Serialize, Deserialize, Clone)] +#[derive(Encode, Decode, Clone)] struct BalanceJob { upper_id: TaskId, task_id: TaskId, #[cfg(feature = "trace_aggregation_update")] - #[serde(skip, default)] + #[bincode(skip, default)] span: Option, } @@ -728,11 +747,11 @@ impl PartialEq for BalanceJob { impl Eq for BalanceJob {} /// An optimization job that is enqueued. See `optimize_task`. -#[derive(Serialize, Deserialize, Clone)] +#[derive(Encode, Decode, Clone)] struct OptimizeJob { task_id: TaskId, #[cfg(feature = "trace_aggregation_update")] - #[serde(skip, default)] + #[bincode(skip, default)] span: Option, } @@ -761,11 +780,11 @@ impl PartialEq for OptimizeJob { impl Eq for OptimizeJob {} /// A job to find and schedule dirty tasks that is enqueued. See `find_and_schedule_dirty`. -#[derive(Serialize, Deserialize, Clone)] +#[derive(Encode, Decode, Clone)] struct FindAndScheduleJob { task_id: TaskId, #[cfg(feature = "trace_find_and_schedule")] - #[serde(skip, default)] + #[bincode(skip, default)] span: Option, } @@ -793,42 +812,73 @@ impl PartialEq for FindAndScheduleJob { impl Eq for FindAndScheduleJob {} -/// Serializes the jobs in the queue. This is used to filter out transient jobs during -/// serialization. -fn serialize_jobs( - jobs: &VecDeque, - serializer: S, -) -> Result { - let mut seq = serializer.serialize_seq(Some(jobs.len()))?; - for job in jobs { - match job.job { - AggregationUpdateJob::IncreaseActiveCount { .. } - | AggregationUpdateJob::IncreaseActiveCounts { .. } - | AggregationUpdateJob::DecreaseActiveCount { .. } - | AggregationUpdateJob::DecreaseActiveCounts { .. } => { - seq.serialize_element(&AggregationUpdateJobItem { - job: AggregationUpdateJob::Noop, - #[cfg(feature = "trace_aggregation_update")] - span: None, - })?; - } - _ => { - seq.serialize_element(job)?; +/// Encodes the jobs in the queue. This is used to filter out transient jobs during encoding. +mod encode_jobs { + use bincode::{ + de::{BorrowDecoder, Decoder}, + enc::Encoder, + error::{DecodeError, EncodeError}, + }; + + use super::*; + + pub fn encode( + jobs: &VecDeque, + encoder: &mut E, + ) -> Result<(), EncodeError> { + usize::encode(&jobs.len(), encoder)?; + for job in jobs { + match job.job { + AggregationUpdateJob::IncreaseActiveCount { .. } + | AggregationUpdateJob::IncreaseActiveCounts { .. } + | AggregationUpdateJob::DecreaseActiveCount { .. } + | AggregationUpdateJob::DecreaseActiveCounts { .. } => { + AggregationUpdateJobItem { + job: AggregationUpdateJob::Noop, + #[cfg(feature = "trace_aggregation_update")] + span: None, + } + .encode(encoder)?; + } + _ => { + job.encode(encoder)?; + } } } + Ok(()) + } + + pub fn decode>( + decoder: &mut D, + ) -> Result, DecodeError> { + let len = usize::decode(decoder)?; + let mut jobs = VecDeque::with_capacity(len); + for _ in 0..len { + jobs.push_back(Decode::decode(decoder)?); + } + Ok(jobs) + } + + pub fn borrow_decode<'de, Context, D: BorrowDecoder<'de, Context = Context>>( + decoder: &mut D, + ) -> Result, DecodeError> { + decode(decoder) } - seq.end() } /// A queue for aggregation update jobs. -#[derive(Default, Serialize, Deserialize, Clone)] +#[derive(Default, Encode, Decode, Clone)] pub struct AggregationUpdateQueue { - #[serde(serialize_with = "serialize_jobs")] + #[bincode(with = "encode_jobs")] jobs: VecDeque, + #[bincode(with = "turbo_bincode::indexmap")] number_updates: FxIndexMap, done_number_updates: FxHashMap, + #[bincode(with = "turbo_bincode::ringset")] find_and_schedule: FxRingSet, + #[bincode(with = "turbo_bincode::ringset")] balance_queue: FxRingSet, + #[bincode(with = "turbo_bincode::ringset")] optimize_queue: FxRingSet, } diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs index e5794f4b12ff..e18873d38280 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs @@ -1,7 +1,7 @@ use std::mem::take; +use bincode::{Decode, Encode}; use rustc_hash::FxHashSet; -use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use turbo_tasks::TaskId; @@ -23,7 +23,7 @@ use crate::{ data::{CachedDataItemKey, CellRef, CollectibleRef, CollectiblesRef}, }; -#[derive(Serialize, Deserialize, Clone, Default)] +#[derive(Encode, Decode, Clone, Default)] pub enum CleanupOldEdgesOperation { RemoveEdges { task_id: TaskId, @@ -38,7 +38,7 @@ pub enum CleanupOldEdgesOperation { // TODO Add aggregated edge } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Encode, Decode, Clone)] pub enum OutdatedEdge { Child(TaskId), Collectible(CollectibleRef, i32), diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/connect_child.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/connect_child.rs index 15f532755765..cbfc93a6d5f6 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/connect_child.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/connect_child.rs @@ -1,4 +1,4 @@ -use serde::{Deserialize, Serialize}; +use bincode::{Decode, Encode}; use turbo_tasks::{TaskExecutionReason, TaskId}; use crate::{ @@ -12,7 +12,7 @@ use crate::{ data::{CachedDataItem, CachedDataItemKey, InProgressState, InProgressStateInner}, }; -#[derive(Serialize, Deserialize, Clone, Default)] +#[derive(Encode, Decode, Clone, Default)] #[allow(clippy::large_enum_variant)] pub enum ConnectChildOperation { UpdateAggregation { diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/invalidate.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/invalidate.rs index b1541b509cf3..2383da6ee391 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/invalidate.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/invalidate.rs @@ -1,4 +1,4 @@ -use serde::{Deserialize, Serialize}; +use bincode::{Decode, Encode}; use smallvec::SmallVec; use turbo_tasks::{TaskExecutionReason, TaskId}; @@ -19,7 +19,7 @@ use crate::{ }, }; -#[derive(Serialize, Deserialize, Clone, Default)] +#[derive(Encode, Decode, Clone, Default)] #[allow(clippy::large_enum_variant)] pub enum InvalidateOperation { MakeDirty { diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs index 32c0e45d3f75..75d4d4265e8b 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs @@ -13,7 +13,7 @@ use std::{ sync::{Arc, atomic::Ordering}, }; -use serde::{Deserialize, Serialize}; +use bincode::{Decode, Encode}; use turbo_tasks::{ CellId, FxIndexMap, KeyValuePair, TaskId, TurboTasksBackendApi, TypedSharedReference, }; @@ -32,11 +32,7 @@ use crate::{ }; pub trait Operation: - Serialize - + for<'de> Deserialize<'de> - + Default - + TryFrom - + Into + Encode + Decode<()> + Default + TryFrom + Into { fn execute(self, ctx: &mut impl ExecuteContext); } @@ -786,7 +782,7 @@ macro_rules! impl_operation { }; } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Encode, Decode, Clone)] pub enum AnyOperation { ConnectChild(connect_child::ConnectChildOperation), Invalidate(invalidate::InvalidateOperation), diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs index 3a5b50b80c14..fbdf9e2ff294 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/update_cell.rs @@ -1,6 +1,6 @@ use std::mem::take; -use serde::{Deserialize, Serialize}; +use bincode::{Decode, Encode}; use smallvec::SmallVec; #[cfg(not(feature = "verify_determinism"))] use turbo_tasks::backend::VerificationMode; @@ -20,7 +20,7 @@ use crate::{ data::{CachedDataItem, CachedDataItemKey, CellRef}, }; -#[derive(Serialize, Deserialize, Clone, Default)] +#[derive(Encode, Decode, Clone, Default)] #[allow(clippy::large_enum_variant)] pub enum UpdateCellOperation { InvalidateWhenCellDependency { diff --git a/turbopack/crates/turbo-tasks-backend/src/data.rs b/turbopack/crates/turbo-tasks-backend/src/data.rs index b2c97a107910..a88854f0b26a 100644 --- a/turbopack/crates/turbo-tasks-backend/src/data.rs +++ b/turbopack/crates/turbo-tasks-backend/src/data.rs @@ -1,3 +1,4 @@ +use bincode::{Decode, Encode}; use rustc_hash::FxHashSet; use serde::{Deserialize, Serialize}; use turbo_tasks::{ @@ -32,25 +33,25 @@ macro_rules! transient_traits { }; } -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Encode, Decode)] pub struct CellRef { pub task: TaskId, pub cell: CellId, } -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Encode, Decode)] pub struct CollectibleRef { pub collectible_type: TraitTypeId, pub cell: CellRef, } -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Encode, Decode)] pub struct CollectiblesRef { pub task: TaskId, pub collectible_type: TraitTypeId, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub enum OutputValue { Cell(CellRef), Output(TaskId), @@ -140,7 +141,7 @@ transient_traits!(ActivenessState); impl Eq for ActivenessState {} -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Encode, Decode, PartialEq, Eq)] pub enum Dirtyness { Dirty, SessionDependent, @@ -205,14 +206,14 @@ impl InProgressCellState { } } -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, Encode, Decode)] pub struct AggregationNumber { pub base: u32, pub distance: u32, pub effective: u32, } -#[derive(Debug, Clone, KeyValuePair, Serialize, Deserialize)] +#[derive(Debug, Clone, KeyValuePair, Encode, Decode)] pub enum CachedDataItem { // Output Output { @@ -227,8 +228,10 @@ pub enum CachedDataItem { Dirty { value: Dirtyness, }, - #[serde(skip)] CurrentSessionClean { + // TODO: bgw: Add a way to skip the entire enum variant in bincode (generating an error + // upon attempted serialization) similar to #[serde(skip)] on variants + #[bincode(skip, default = "unreachable_decode")] value: (), }, @@ -243,9 +246,10 @@ pub enum CachedDataItem { cell: CellId, value: TypedSharedReference, }, - #[serde(skip)] TransientCellData { + #[bincode(skip, default = "unreachable_decode")] cell: CellId, + #[bincode(skip, default = "unreachable_decode")] value: SharedReference, }, CellTypeMaxIndex { @@ -301,9 +305,10 @@ pub enum CachedDataItem { task: TaskId, value: i32, }, - #[serde(skip)] AggregatedCurrentSessionCleanContainer { + #[bincode(skip, default = "unreachable_decode")] task: TaskId, + #[bincode(skip, default = "unreachable_decode")] value: i32, }, AggregatedCollectible { @@ -313,8 +318,8 @@ pub enum CachedDataItem { AggregatedDirtyContainerCount { value: i32, }, - #[serde(skip)] AggregatedCurrentSessionCleanContainerCount { + #[bincode(skip, default = "unreachable_decode")] value: i32, }, @@ -330,43 +335,52 @@ pub enum CachedDataItem { }, // Transient Root Type - #[serde(skip)] Activeness { + #[bincode(skip, default = "unreachable_decode")] value: ActivenessState, }, // Transient In Progress state - #[serde(skip)] InProgress { + #[bincode(skip, default = "unreachable_decode")] value: InProgressState, }, - #[serde(skip)] InProgressCell { + #[bincode(skip, default = "unreachable_decode")] cell: CellId, + #[bincode(skip, default = "unreachable_decode")] value: InProgressCellState, }, - #[serde(skip)] OutdatedCollectible { + #[bincode(skip, default = "unreachable_decode")] collectible: CollectibleRef, + #[bincode(skip, default = "unreachable_decode")] value: i32, }, - #[serde(skip)] OutdatedOutputDependency { + #[bincode(skip, default = "unreachable_decode")] target: TaskId, + #[bincode(skip, default = "unreachable_decode")] value: (), }, - #[serde(skip)] OutdatedCellDependency { + #[bincode(skip, default = "unreachable_decode")] target: CellRef, + #[bincode(skip, default = "unreachable_decode")] value: (), }, - #[serde(skip)] OutdatedCollectiblesDependency { + #[bincode(skip, default = "unreachable_decode")] target: CollectiblesRef, + #[bincode(skip, default = "unreachable_decode")] value: (), }, } +fn unreachable_decode() -> T { + unreachable!("CachedDataItem variant should not have been encoded, cannot decode") +} + impl CachedDataItem { pub fn cell_data( is_serializable_cell_content: bool, diff --git a/turbopack/crates/turbo-tasks-backend/src/kv_backing_storage.rs b/turbopack/crates/turbo-tasks-backend/src/kv_backing_storage.rs index 09601bf3fc23..6966a70d66b8 100644 --- a/turbopack/crates/turbo-tasks-backend/src/kv_backing_storage.rs +++ b/turbopack/crates/turbo-tasks-backend/src/kv_backing_storage.rs @@ -6,8 +6,9 @@ use std::{ }; use anyhow::{Context, Result, anyhow}; -use serde::{Deserialize, Serialize}; -use smallvec::SmallVec; +use turbo_bincode::{ + TurboBincodeBuffer, turbo_bincode_decode, turbo_bincode_encode, turbo_bincode_encode_into, +}; use turbo_tasks::{ TaskId, backend::CachedTaskType, @@ -33,42 +34,6 @@ use crate::{ utils::chunked_vec::ChunkedVec, }; -const POT_CONFIG: pot::Config = pot::Config::new().compatibility(pot::Compatibility::V4); - -fn pot_serialize_small_vec(value: &T) -> pot::Result> { - struct SmallVecWrite<'l>(&'l mut SmallVec<[u8; 16]>); - impl std::io::Write for SmallVecWrite<'_> { - #[inline] - fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.0.extend_from_slice(buf); - Ok(buf.len()) - } - - #[inline] - fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { - self.0.extend_from_slice(buf); - Ok(()) - } - - #[inline] - fn flush(&mut self) -> std::io::Result<()> { - Ok(()) - } - } - - let mut output = SmallVec::new(); - POT_CONFIG.serialize_into(value, SmallVecWrite(&mut output))?; - Ok(output) -} - -fn pot_ser_symbol_map() -> pot::ser::SymbolMap { - pot::ser::SymbolMap::new().with_compatibility(pot::Compatibility::V4) -} - -fn pot_de_symbol_list<'l>() -> pot::de::SymbolList<'l> { - pot::de::SymbolList::new() -} - const META_KEY_OPERATIONS: u32 = 0; const META_KEY_NEXT_FREE_TASK_ID: u32 = 1; @@ -279,14 +244,14 @@ impl BackingStorageSealed else { return Ok(Vec::new()); }; - let operations = deserialize_with_good_error(operations.borrow())?; + let operations = turbo_bincode_decode(operations.borrow())?; Ok(operations) } get(&self.inner.database).context("Unable to read uncompleted operations from database") } - fn serialize(&self, task: TaskId, data: &Vec) -> Result> { - serialize(task, data) + fn serialize(&self, task: TaskId, data: &Vec) -> Result { + encode_task_data(task, data) } fn save_snapshot( @@ -299,8 +264,8 @@ impl BackingStorageSealed I: Iterator< Item = ( TaskId, - Option>, - Option>, + Option, + Option, ), > + Send + Sync, @@ -308,6 +273,9 @@ impl BackingStorageSealed let _span = tracing::info_span!("save snapshot", operations = operations.len()).entered(); let mut batch = self.inner.database.write_batch()?; + // these buffers should be large, because they're temporary and re-used. + const INITIAL_ENCODE_BUFFER_CAPACITY: usize = 1024; + // Start organizing the updates in parallel match &mut batch { &mut WriteBatch::Concurrent(ref batch, _) => { @@ -337,19 +305,20 @@ impl BackingStorageSealed items = task_cache_updates.iter().map(|m| m.len()).sum::() ) .entered(); - let result = parallel::map_collect_owned::<_, _, Result>>( + let max_task_id = parallel::map_collect_owned::<_, _, Result>>( task_cache_updates, |updates| { let _span = _span.clone().entered(); let mut max_task_id = 0; - let mut task_type_bytes = Vec::new(); + // Re-use the same buffer across every `serialize_task_type` call in + // this chunk. `ConcurrentWriteBatch::put` will copy the data out of + // this buffer into smaller exact-sized vecs. + let mut task_type_bytes = + TurboBincodeBuffer::with_capacity(INITIAL_ENCODE_BUFFER_CAPACITY); for (task_type, task_id) in updates { - serialize_task_type( - &task_type, - &mut task_type_bytes, - Some(task_id), - )?; + task_type_bytes.clear(); + encode_task_type(&task_type, &mut task_type_bytes, Some(task_id))?; let task_id: u32 = *task_id; batch @@ -374,7 +343,7 @@ impl BackingStorageSealed "Unable to write task cache {task_id} => {task_type:?}" ) })?; - max_task_id = max_task_id.max(task_id + 1); + max_task_id = max_task_id.max(task_id); } Ok(max_task_id) @@ -383,7 +352,7 @@ impl BackingStorageSealed .into_iter() .max() .unwrap_or(0); - next_task_id = next_task_id.max(result); + next_task_id = next_task_id.max(max_task_id + 1); } save_infra::, T::ConcurrentWriteBatch<'_>>( @@ -430,9 +399,13 @@ impl BackingStorageSealed items = task_cache_updates.iter().map(|m| m.len()).sum::() ) .entered(); - let mut task_type_bytes = Vec::new(); + // Re-use the same buffer across every `serialize_task_type` call. + // `ConcurrentWriteBatch::put` will copy the data out of this buffer into + // smaller exact-sized vecs. + let mut task_type_bytes = + TurboBincodeBuffer::with_capacity(INITIAL_ENCODE_BUFFER_CAPACITY); for (task_type, task_id) in task_cache_updates.into_iter().flatten() { - serialize_task_type(&task_type, &mut task_type_bytes, Some(task_id))?; + encode_task_type(&task_type, &mut task_type_bytes, Some(task_id))?; let task_id = *task_id; batch @@ -489,8 +462,8 @@ impl BackingStorageSealed tx: &D::ReadTransaction<'_>, task_type: &CachedTaskType, ) -> Result> { - let mut task_type_bytes = Vec::new(); - serialize_task_type(task_type, &mut task_type_bytes, None)?; + let mut task_type_bytes = TurboBincodeBuffer::new(); + encode_task_type(task_type, &mut task_type_bytes, None)?; let Some(bytes) = database.get(tx, KeySpace::ForwardTaskCache, &task_type_bytes)? else { return Ok(None); @@ -528,7 +501,7 @@ impl BackingStorageSealed else { return Ok(None); }; - Ok(Some(deserialize_with_good_error(bytes.borrow())?)) + Ok(Some(turbo_bincode_decode(bytes.borrow())?)) } inner .with_tx(tx, |tx| lookup(&inner.database, tx, task_id)) @@ -560,7 +533,7 @@ impl BackingStorageSealed else { return Ok(Vec::new()); }; - let result: Vec = deserialize_with_good_error(bytes.borrow())?; + let result: Vec = turbo_bincode_decode(bytes.borrow())?; Ok(result) } inner @@ -607,95 +580,91 @@ where WriteBuffer::Borrowed(IntKey::new(META_KEY_NEXT_FREE_TASK_ID).as_ref()), WriteBuffer::Borrowed(&next_task_id.to_le_bytes()), ) - .with_context(|| anyhow!("Unable to write next free task id"))?; + .context("Unable to write next free task id")?; } { let _span = tracing::trace_span!("update operations", operations = operations.len()).entered(); - let operations = pot_serialize_small_vec(&operations) - .with_context(|| anyhow!("Unable to serialize operations"))?; + let operations = + turbo_bincode_encode(&operations).context("Unable to serialize operations")?; batch .put( KeySpace::Infra, WriteBuffer::Borrowed(IntKey::new(META_KEY_OPERATIONS).as_ref()), WriteBuffer::SmallVec(operations), ) - .with_context(|| anyhow!("Unable to write operations"))?; + .context("Unable to write operations")?; } batch.flush(KeySpace::Infra)?; Ok(()) } -// DO NOT REMOVE THE `inline(never)` ATTRIBUTE! -// `pot` uses the pointer address of `&'static str` to deduplicate Symbols. -// If this function is inlined into multiple different callsites it might inline the Serialize -// implementation too, which can pull a `&'static str` from another crate into this crate. -// Since string deduplication between crates is not guaranteed, it can lead to behavior changes due -// to the pointer addresses. This can lead to lookup path and store path creating different -// serialization of the same task type, which breaks task cache lookups. -#[inline(never)] -fn serialize_task_type( +fn encode_task_type( task_type: &CachedTaskType, - mut task_type_bytes: &mut Vec, + buffer: &mut TurboBincodeBuffer, task_id: Option, ) -> Result<()> { - task_type_bytes.clear(); - POT_CONFIG - .serialize_into(task_type, &mut task_type_bytes) - .with_context(|| { + // DO NOT REMOVE THE `inline(never)` ATTRIBUTE! + // CachedTaskType's `Encode`/`Decode` implementations use `pot` internally for `TaskInput`s. + // TODO: remove `serde` and `pot`, make `TaskInput: Encode + Decode`. + // + // `pot` uses the pointer address of `&'static str` to deduplicate Symbols. + // If this function is inlined into multiple different callsites it might inline the Serialize + // implementation too, which can pull a `&'static str` from another crate into this crate. + // Since string deduplication between crates is not guaranteed, it can lead to behavior changes + // due to the pointer addresses. This can lead to lookup path and store path creating different + // serialization of the same task type, which breaks task cache lookups. + #[inline(never)] + fn encode_once_into( + task_type: &CachedTaskType, + buffer: &mut TurboBincodeBuffer, + task_id: Option, + ) -> Result<()> { + turbo_bincode_encode_into(task_type, buffer).with_context(|| { if let Some(task_id) = task_id { - anyhow!("Unable to serialize task {task_id} cache key {task_type:?}") + format!("Unable to serialize task {task_id} cache key {task_type:?}") } else { - anyhow!("Unable to serialize task cache key {task_type:?}") + format!("Unable to serialize task cache key {task_type:?}") } - })?; - #[cfg(feature = "verify_serialization")] - { - let deserialize: Result = serde_path_to_error::deserialize( - &mut pot_de_symbol_list().deserializer_for_slice(&*task_type_bytes)?, - ); + }) + } + + debug_assert!(buffer.is_empty()); + encode_once_into(task_type, buffer, task_id)?; + + if cfg!(feature = "verify_serialization") { + macro_rules! println_and_panic { + ($($tt:tt)*) => { + println!($($tt)*); + panic!($($tt)*); + }; + } + let deserialize: Result = turbo_bincode_decode(buffer); match deserialize { Err(err) => { - println!( - "Task type would not be deserializable {task_id:?}: {err:?}\n{task_type:#?}" - ); - panic!("Task type would not be deserializable {task_id:?}: {err:?}"); + println_and_panic!("Task type would not be deserializable:\n{err:?}"); } Ok(task_type2) => { if &task_type2 != task_type { - println!( - "Task type would not round-trip {task_id:?}:\noriginal: \ - {task_type:#?}\nround-tripped: {task_type2:#?}" - ); - panic!( + println_and_panic!( "Task type would not round-trip {task_id:?}:\noriginal: \ {task_type:#?}\nround-tripped: {task_type2:#?}" ); } - let mut bytes2 = Vec::new(); - let result2 = POT_CONFIG.serialize_into(&task_type2, &mut bytes2); - match result2 { + let mut buffer2 = TurboBincodeBuffer::new(); + match encode_once_into(&task_type2, &mut buffer2, task_id) { Err(err) => { - println!( - "Task type would not be serializable the second time {task_id:?}: \ - {err:?}\n{task_type2:#?}" - ); - panic!( - "Task type would not be serializable the second time {task_id:?}: \ - {err:?}\n{task_type2:#?}" + println_and_panic!( + "Task type would not be serializable the second time:\n{err:?}" ); } Ok(()) => { - if bytes2 != *task_type_bytes { - println!( + if buffer2 != *buffer { + println_and_panic!( "Task type would not serialize to the same bytes the second time \ {task_id:?}:\noriginal: {:x?}\nsecond: {:x?}\n{task_type2:#?}", - task_type_bytes, bytes2 - ); - panic!( - "Task type would not serialize to the same bytes the second time \ - {task_id:?}:\noriginal: {:x?}\nsecond: {:x?}\n{task_type2:#?}", - task_type_bytes, bytes2 + buffer, + buffer2 ); } } @@ -703,6 +672,7 @@ fn serialize_task_type( } } } + Ok(()) } @@ -722,8 +692,8 @@ where I: Iterator< Item = ( TaskId, - Option>, - Option>, + Option, + Option, ), > + Send + Sync, @@ -762,63 +732,47 @@ where }) } -fn serialize(task: TaskId, data: &Vec) -> Result> { - Ok(match pot_serialize_small_vec(data) { - #[cfg(not(feature = "verify_serialization"))] - Ok(value) => value, - _ => { - let mut error = Ok(()); - let mut data = data.clone(); - data.retain(|item| { - let mut buf = Vec::::new(); - let mut symbol_map = pot_ser_symbol_map(); - let mut serializer = symbol_map.serializer_for(&mut buf).unwrap(); - if let Err(err) = serde_path_to_error::serialize(&item, &mut serializer) { - if item.is_optional() { - #[cfg(feature = "verify_serialization")] - println!( - "Skipping non-serializable optional item for {task}: {item:?} due to \ - {err}" - ); - } else { - error = Err(err).context({ - anyhow!("Unable to serialize data item for {task}: {item:?}") - }); - } - false - } else { - #[cfg(feature = "verify_serialization")] - { - let deserialize: Result = - serde_path_to_error::deserialize( - &mut pot_de_symbol_list().deserializer_for_slice(&buf).unwrap(), - ); - if let Err(err) = deserialize { - println!( - "Data item would not be deserializable {task}: {err:?}\n{item:?}" - ); - return false; - } - } - true - } - }); - error?; +fn encode_task_data(task: TaskId, data: &Vec) -> Result { + let orig_result = turbo_bincode_encode(data); + if !cfg!(feature = "verify_serialization") + && let Ok(value) = orig_result + { + return Ok(value); + } - pot_serialize_small_vec(&data) - .with_context(|| anyhow!("Unable to serialize data items for {task}: {data:#?}"))? + let mut error = Ok(()); + let mut filtered_data = data.clone(); + filtered_data.retain(|item| match turbo_bincode_encode(&item) { + Ok(buf) => { + if cfg!(feature = "verify_serialization") { + let deserialized = turbo_bincode_decode::(&buf); + if let Err(err) = deserialized { + println!("Data item would not be deserializable {task}: {err:?}\n{item:?}"); + return false; + } + } + true } - }) -} + Err(err) => { + if item.is_optional() { + if cfg!(feature = "verify_serialization") { + println!( + "Skipping non-encodable optional item for {task}: {item:?} due to {err}" + ); + } + } else { + error = + Err(err).context(format!("Unable to encode data item for {task}: {item:?}")); + } + false + } + }); + error?; -fn deserialize_with_good_error<'de, T: Deserialize<'de>>(data: &'de [u8]) -> Result { - match POT_CONFIG.deserialize(data) { - Ok(value) => Ok(value), - Err(error) => serde_path_to_error::deserialize::<'_, _, T>( - &mut pot_de_symbol_list().deserializer_for_slice(data)?, - ) - .map_err(anyhow::Error::from) - .and(Err(error.into())) - .context("Deserialization failed"), - } + (if filtered_data.len() == data.len() { + orig_result + } else { + turbo_bincode_encode(&filtered_data) + }) + .with_context(|| format!("Unable to serialize data items for {task}: {filtered_data:#?}")) } diff --git a/turbopack/crates/turbo-tasks-macros/src/derive/key_value_pair_macro.rs b/turbopack/crates/turbo-tasks-macros/src/derive/key_value_pair_macro.rs index 9802fd467fa8..97aed767f2ac 100644 --- a/turbopack/crates/turbo-tasks-macros/src/derive/key_value_pair_macro.rs +++ b/turbopack/crates/turbo-tasks-macros/src/derive/key_value_pair_macro.rs @@ -591,9 +591,9 @@ fn field_declarations(fields: &[Vec<&syn::Field>]) -> Vec]) -> Vec]) -> Vec TokenStream { let PrimitiveInput { ty, - bincode_wrappers: _, + bincode_wrappers, } = parse_macro_input!(input as PrimitiveInput); let Some(ident) = get_type_ident(&ty) else { @@ -38,9 +40,18 @@ pub fn primitive(input: TokenStream) -> TokenStream { }; let name = global_name(quote!(stringify!(#ty))); - // TODO: https://github.com/vercel/next.js/pull/86338 -- switch to bincode, use bincode wrapper - let new_value_type = quote! { - turbo_tasks::ValueType::new_with_any_serialization::<#ty>(#name); + let new_value_type = if let Some(bincode_wrappers) = bincode_wrappers { + let BincodeWrappers { + encode_ty, + decode_ty, + } = bincode_wrappers; + quote! { + turbo_tasks::ValueType::new_with_bincode_wrappers::<#ty, #encode_ty, #decode_ty>(#name) + } + } else { + quote! { + turbo_tasks::ValueType::new_with_bincode::<#ty>(#name) + } }; let value_type_and_register = value_type_and_register( diff --git a/turbopack/crates/turbo-tasks-macros/src/value_macro.rs b/turbopack/crates/turbo-tasks-macros/src/value_macro.rs index 3792881043e2..82c725a500f2 100644 --- a/turbopack/crates/turbo-tasks-macros/src/value_macro.rs +++ b/turbopack/crates/turbo-tasks-macros/src/value_macro.rs @@ -348,7 +348,7 @@ pub fn value(args: TokenStream, input: TokenStream) -> TokenStream { }, SerializationMode::Auto | SerializationMode::Custom => { quote! { - turbo_tasks::ValueType::new_with_any_serialization::<#ident>(#name) + turbo_tasks::ValueType::new_with_bincode::<#ident>(#name) } } }; diff --git a/turbopack/crates/turbo-tasks/Cargo.toml b/turbopack/crates/turbo-tasks/Cargo.toml index 4c5ff7e480ac..74f5641bd7ab 100644 --- a/turbopack/crates/turbo-tasks/Cargo.toml +++ b/turbopack/crates/turbo-tasks/Cargo.toml @@ -33,6 +33,7 @@ erased-serde = { workspace = true } event-listener = "5.4.0" futures = { workspace = true } indexmap = { workspace = true, features = ["serde"] } +inventory = { workspace = true } once_cell = { workspace = true } parking_lot = { workspace = true, features = ["serde"]} pin-project-lite = { workspace = true } @@ -41,7 +42,7 @@ regex = { workspace = true } rustc-hash = { workspace = true } serde = { workspace = true, features = ["rc", "derive"] } serde_json = { workspace = true } -shrink-to-fit = { workspace=true,features = ["indexmap", "serde_json", "smallvec", "nightly"] } +shrink-to-fit = { workspace = true, features = ["indexmap", "serde_json", "smallvec", "nightly"] } smallvec = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } @@ -55,7 +56,8 @@ turbo-tasks-hash = { workspace = true } turbo-tasks-macros = { workspace = true } turbo-tasks-malloc = { workspace = true } unsize = { workspace = true } -inventory = { workspace = true } +unty = { workspace = true } +pot = "3.0.0" [dev-dependencies] criterion = { workspace = true, features = ["async_tokio"] } diff --git a/turbopack/crates/turbo-tasks/src/backend.rs b/turbopack/crates/turbo-tasks/src/backend.rs index 2113ea7c89e3..0b800d2a9b8c 100644 --- a/turbopack/crates/turbo-tasks/src/backend.rs +++ b/turbopack/crates/turbo-tasks/src/backend.rs @@ -10,9 +10,13 @@ use std::{ use anyhow::{Result, anyhow}; use auto_hash_map::AutoMap; +use bincode::{ + Decode, Encode, + error::{DecodeError, EncodeError}, +}; use rustc_hash::FxHasher; -use serde::{Deserialize, Serialize}; use tracing::Span; +use turbo_bincode::{TurboBincodeDecoder, TurboBincodeEncoder}; use turbo_rcstr::RcStr; use crate::{ @@ -99,110 +103,31 @@ impl Display for CachedTaskType { } mod ser { - use std::any::Any; - - use serde::{ - Deserialize, Deserializer, Serialize, Serializer, - de::{self}, - ser::{SerializeSeq, SerializeTuple}, + use bincode::{ + de::{Decoder, read::Reader}, + enc::Encoder, }; + use serde::{Deserialize, Deserializer, Serialize, Serializer, ser::SerializeSeq}; use super::*; - impl Serialize for TypedCellContent { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - let value_type = registry::get_value_type(self.0); - let serializable = if let Some(value) = &self.1.0 { - value_type.any_as_serializable(&value.0) - } else { - None - }; - let mut state = serializer.serialize_tuple(3)?; - state.serialize_element(&self.0)?; - if let Some(serializable) = serializable { - state.serialize_element(&true)?; - state.serialize_element(serializable)?; - } else { - state.serialize_element(&false)?; - state.serialize_element(&())?; - } - state.end() - } - } - - impl<'de> Deserialize<'de> for TypedCellContent { - fn deserialize(deserializer: D) -> std::result::Result - where - D: Deserializer<'de>, - { - struct Visitor; + const POT_CONFIG: pot::Config = pot::Config::new().compatibility(pot::Compatibility::V4); - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = TypedCellContent; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!(formatter, "a valid TypedCellContent") - } - - fn visit_seq(self, mut seq: A) -> std::result::Result - where - A: de::SeqAccess<'de>, - { - let value_type: ValueTypeId = seq - .next_element()? - .ok_or_else(|| de::Error::invalid_length(0, &self))?; - let has_value: bool = seq - .next_element()? - .ok_or_else(|| de::Error::invalid_length(1, &self))?; - if has_value { - let seed = registry::get_value_type(value_type) - .get_any_deserialize_seed() - .ok_or_else(|| { - de::Error::custom("Value type doesn't support deserialization") - })?; - let value = seq - .next_element_seed(seed)? - .ok_or_else(|| de::Error::invalid_length(2, &self))?; - let arc = triomphe::Arc::::from(value); - Ok(TypedCellContent( - value_type, - CellContent(Some(SharedReference(arc))), - )) - } else { - let () = seq - .next_element()? - .ok_or_else(|| de::Error::invalid_length(2, &self))?; - Ok(TypedCellContent(value_type, CellContent(None))) - } - } - } - - deserializer.deserialize_tuple(2, Visitor) - } + struct FunctionAndArgBorrowed<'a> { + native_fn: &'static NativeFunction, + arg: &'a dyn MagicAny, } - - enum FunctionAndArg<'a> { - Owned { - native_fn: &'static NativeFunction, - arg: Box, - }, - Borrowed { - native_fn: &'static NativeFunction, - arg: &'a dyn MagicAny, - }, + struct FunctionAndArgOwned { + native_fn: &'static NativeFunction, + arg: Box, } - impl Serialize for FunctionAndArg<'_> { - fn serialize(&self, serializer: S) -> std::result::Result + impl Serialize for FunctionAndArgBorrowed<'_> { + fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let FunctionAndArg::Borrowed { native_fn, arg } = self else { - unreachable!(); - }; + let Self { native_fn, arg } = self; let mut state = serializer.serialize_seq(Some(2))?; state.serialize_element(®istry::get_function_id(native_fn))?; let arg = *arg; @@ -212,17 +137,17 @@ mod ser { } } - impl<'de> Deserialize<'de> for FunctionAndArg<'de> { + impl<'de> Deserialize<'de> for FunctionAndArgOwned { fn deserialize>(deserializer: D) -> Result { struct Visitor; impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = FunctionAndArg<'de>; + type Value = FunctionAndArgOwned; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!(formatter, "a valid FunctionAndArg") + write!(formatter, "a valid FunctionAndArgOwned") } - fn visit_seq(self, mut seq: A) -> std::result::Result + fn visit_seq(self, mut seq: A) -> Result where A: serde::de::SeqAccess<'de>, { @@ -234,64 +159,65 @@ mod ser { let arg = seq .next_element_seed(seed)? .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?; - Ok(FunctionAndArg::Owned { native_fn, arg }) + Ok(FunctionAndArgOwned { native_fn, arg }) } } deserializer.deserialize_seq(Visitor) } } - impl Serialize for CachedTaskType { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: ser::Serializer, - { - let CachedTaskType { - native_fn, - this, - arg, - } = self; - let mut s = serializer.serialize_tuple(2)?; - s.serialize_element(&FunctionAndArg::Borrowed { - native_fn, - arg: &**arg, - })?; - s.serialize_element(this)?; - s.end() + // HACK: We don't yet require `TaskInput: Encode + Decode`, so use a pot serializer for the + // function arguments, and bincode for everything else. + impl Encode for CachedTaskType { + fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { + struct BincodeWriterWrapper(W); + impl std::io::Write for BincodeWriterWrapper { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.write_all(buf)?; + Ok(buf.len()) + } + fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { + self.0.write(buf).map_err(std::io::Error::other) + } + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } + } + let function_and_arg = FunctionAndArgBorrowed { + native_fn: self.native_fn, + arg: &*self.arg, + }; + POT_CONFIG + .serialize_into( + &function_and_arg, + &mut BincodeWriterWrapper(encoder.writer()), + ) + .map_err(|e| EncodeError::OtherString(e.to_string()))?; + Encode::encode(&self.this, encoder) } } - impl<'de> Deserialize<'de> for CachedTaskType { - fn deserialize>(deserializer: D) -> Result { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = CachedTaskType; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!(formatter, "a valid PersistentTaskType") + impl Decode for CachedTaskType { + fn decode>(decoder: &mut D) -> Result { + struct BincodeReaderWrapper(R); + impl std::io::Read for BincodeReaderWrapper { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.read_exact(buf)?; + Ok(buf.len()) } - - fn visit_seq(self, mut seq: A) -> std::result::Result - where - A: serde::de::SeqAccess<'de>, - { - let FunctionAndArg::Owned { native_fn, arg } = seq - .next_element()? - .ok_or_else(|| serde::de::Error::invalid_length(0, &self))? - else { - unreachable!(); - }; - let this = seq - .next_element()? - .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?; - Ok(CachedTaskType { - native_fn, - this, - arg, - }) + fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> { + self.0.read(buf).map_err(std::io::Error::other) } } - deserializer.deserialize_tuple(2, Visitor) + let FunctionAndArgOwned { native_fn, arg } = POT_CONFIG + .deserialize_from(BincodeReaderWrapper(decoder.reader())) + .map_err(|e| DecodeError::OtherString(e.to_string()))?; + let this: Option = Decode::decode(decoder)?; + Ok(CachedTaskType { + native_fn, + this, + arg, + }) } } } @@ -349,6 +275,37 @@ impl TypedCellContent { pub fn into_untyped(self) -> CellContent { self.1 } + + pub fn encode(&self, enc: &mut TurboBincodeEncoder) -> Result<(), EncodeError> { + let Self(type_id, content) = self; + let value_type = registry::get_value_type(*type_id); + type_id.encode(enc)?; + if let Some(bincode) = value_type.bincode { + if let Some(reference) = &content.0 { + true.encode(enc)?; + bincode.0(&*reference.0, enc)?; + Ok(()) + } else { + false.encode(enc)?; + Ok(()) + } + } else { + Ok(()) + } + } + + pub fn decode(dec: &mut TurboBincodeDecoder) -> Result { + let type_id = ValueTypeId::decode(dec)?; + let value_type = registry::get_value_type(type_id); + if let Some(bincode) = value_type.bincode { + let is_some = bool::decode(dec)?; + if is_some { + let reference = bincode.1(dec)?; + return Ok(TypedCellContent(type_id, CellContent(Some(reference)))); + } + } + Ok(TypedCellContent(type_id, CellContent(None))) + } } impl From for TypedCellContent { @@ -403,9 +360,9 @@ pub type TaskCollectiblesMap = AutoMap, // Structurally and functionally similar to Cow<&'static, str> but explicitly notes the importance // of non-static strings potentially containing PII (Personal Identifiable Information). -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq)] pub enum TurboTasksExecutionErrorMessage { - PIISafe(Cow<'static, str>), + PIISafe(#[bincode(with = "turbo_bincode::owned_cow")] Cow<'static, str>), NonPIISafe(String), } @@ -418,13 +375,13 @@ impl Display for TurboTasksExecutionErrorMessage { } } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] pub struct TurboTasksError { pub message: TurboTasksExecutionErrorMessage, pub source: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] pub struct TurboTaskContextError { pub task: RcStr, #[cfg(feature = "task_id_details")] @@ -432,7 +389,7 @@ pub struct TurboTaskContextError { pub source: Option, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq)] pub enum TurboTasksExecutionError { Panic(Arc), Error(Arc), diff --git a/turbopack/crates/turbo-tasks/src/capture_future.rs b/turbopack/crates/turbo-tasks/src/capture_future.rs index ba86a33b1091..2396caa4710b 100644 --- a/turbopack/crates/turbo-tasks/src/capture_future.rs +++ b/turbopack/crates/turbo-tasks/src/capture_future.rs @@ -8,8 +8,8 @@ use std::{ }; use anyhow::Result; +use bincode::{Decode, Encode}; use pin_project_lite::pin_project; -use serde::{Deserialize, Serialize}; use crate::{backend::TurboTasksExecutionErrorMessage, panic_hooks::LAST_ERROR_LOCATION}; @@ -26,7 +26,7 @@ impl> CaptureFuture { } } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] pub struct TurboTasksPanic { pub message: TurboTasksExecutionErrorMessage, pub location: Option, diff --git a/turbopack/crates/turbo-tasks/src/id.rs b/turbopack/crates/turbo-tasks/src/id.rs index c2510ca98d01..777f43ddc440 100644 --- a/turbopack/crates/turbo-tasks/src/id.rs +++ b/turbopack/crates/turbo-tasks/src/id.rs @@ -73,6 +73,7 @@ macro_rules! define_id { type Target = $primitive; fn deref(&self) -> &Self::Target { + // SAFETY: `NonZero` is guaranteed to have the same layout as `T` unsafe { transmute_copy(&&self.id) } } } diff --git a/turbopack/crates/turbo-tasks/src/magic_any.rs b/turbopack/crates/turbo-tasks/src/magic_any.rs index 57235e54467d..316fd1201d20 100644 --- a/turbopack/crates/turbo-tasks/src/magic_any.rs +++ b/turbopack/crates/turbo-tasks/src/magic_any.rs @@ -108,41 +108,3 @@ impl<'de> DeserializeSeed<'de> for MagicAnyDeserializeSeed { (self.functor)(&mut deserializer).map_err(serde::de::Error::custom) } } - -type AnyDeserializeSeedFunctor = fn( - &mut dyn erased_serde::Deserializer<'_>, -) -> Result, erased_serde::Error>; - -#[derive(Clone, Copy)] -pub struct AnyDeserializeSeed { - functor: AnyDeserializeSeedFunctor, -} - -impl AnyDeserializeSeed { - pub fn new() -> Self - where - T: for<'de> Deserialize<'de> + Any + Send + Sync + 'static, - { - fn deserialize Deserialize<'de> + Send + Sync + 'static>( - deserializer: &mut dyn erased_serde::Deserializer<'_>, - ) -> Result, erased_serde::Error> { - let value: T = erased_serde::deserialize(deserializer)?; - Ok(Box::new(value)) - } - Self { - functor: deserialize::, - } - } -} - -impl<'de> DeserializeSeed<'de> for AnyDeserializeSeed { - type Value = Box; - - fn deserialize(self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let mut deserializer = ::erase(deserializer); - (self.functor)(&mut deserializer).map_err(serde::de::Error::custom) - } -} diff --git a/turbopack/crates/turbo-tasks/src/primitives.rs b/turbopack/crates/turbo-tasks/src/primitives.rs index 51bf5f8afc0d..9557d20efaac 100644 --- a/turbopack/crates/turbo-tasks/src/primitives.rs +++ b/turbopack/crates/turbo-tasks/src/primitives.rs @@ -44,8 +44,6 @@ __turbo_tasks_internal_primitive!(Duration); __turbo_tasks_internal_primitive!(Vec); __turbo_tasks_internal_primitive!(Vec); -// TODO: use this in https://github.com/vercel/next.js/pull/86338 -#[allow(dead_code)] struct JsonValueEncodeWrapper<'a>(&'a serde_json::Value); impl ManualEncodeWrapper for JsonValueEncodeWrapper<'_> { @@ -62,8 +60,6 @@ impl Encode for JsonValueEncodeWrapper<'_> { } } -// TODO: use this in https://github.com/vercel/next.js/pull/86338 -#[allow(dead_code)] struct JsonValueDecodeWrapper(serde_json::Value); impl ManualDecodeWrapper for JsonValueDecodeWrapper { diff --git a/turbopack/crates/turbo-tasks/src/raw_vc.rs b/turbopack/crates/turbo-tasks/src/raw_vc.rs index b97e08b2e5da..2b8553eb135f 100644 --- a/turbopack/crates/turbo-tasks/src/raw_vc.rs +++ b/turbopack/crates/turbo-tasks/src/raw_vc.rs @@ -211,7 +211,7 @@ impl RawVc { task, index, ReadCellOptions { - is_serializable_cell_content: value_type.is_serializable(), + is_serializable_cell_content: value_type.bincode.is_some(), final_read_hint: false, tracking: ReadTracking::default(), }, @@ -467,7 +467,7 @@ impl Future for ReadRawVcFuture { if this.is_serializable_cell_content_unknown { let value_type = registry::get_value_type(index.type_id); this.read_cell_options.is_serializable_cell_content = - value_type.is_serializable(); + value_type.bincode.is_some(); } let read_result = tt.try_read_task_cell(task, index, this.read_cell_options); diff --git a/turbopack/crates/turbo-tasks/src/task/shared_reference.rs b/turbopack/crates/turbo-tasks/src/task/shared_reference.rs index 6f86a718f8d1..76575845246a 100644 --- a/turbopack/crates/turbo-tasks/src/task/shared_reference.rs +++ b/turbopack/crates/turbo-tasks/src/task/shared_reference.rs @@ -6,11 +6,20 @@ use std::{ }; use anyhow::Result; -use serde::{Deserialize, Serialize, ser::SerializeTuple}; +use bincode::{ + Decode, Encode, + de::Decoder, + enc::Encoder, + error::{DecodeError, EncodeError}, + impl_borrow_decode, +}; +use turbo_bincode::{ + TurboBincodeDecoder, TurboBincodeEncoder, turbo_bincode_decode, turbo_bincode_encode, +}; use unsize::CoerceUnsize; use crate::{ - ValueTypeId, registry, + ValueType, ValueTypeId, registry, triomphe_utils::{coerce_to_any_send_sync, downcast_triomphe_arc}, }; @@ -55,8 +64,101 @@ impl TypedSharedReference { pub fn into_untyped(self) -> SharedReference { self.reference } + + fn encode(&self, enc: &mut TurboBincodeEncoder) -> Result<(), EncodeError> { + let Self { type_id, reference } = self; + let value_type = registry::get_value_type(*type_id); + if let Some(bincode) = value_type.bincode { + type_id.encode(enc)?; + bincode.0(&*reference.0, enc)?; + Ok(()) + } else { + Err(EncodeError::OtherString(format!( + "{:?} is not serializable", + value_type.global_name + ))) + } + } + + fn decode(dec: &mut TurboBincodeDecoder) -> Result { + let type_id = ValueTypeId::decode(dec)?; + let value_type = registry::get_value_type(type_id); + if let Some(bincode) = value_type.bincode { + let reference = bincode.1(dec)?; + Ok(Self { type_id, reference }) + } else { + #[cold] + fn not_deserializable(value_type: &ValueType) -> DecodeError { + DecodeError::OtherString(format!("{value_type} is not deserializable")) + } + Err(not_deserializable(value_type)) + } + } } +impl Encode for TypedSharedReference { + fn encode<'a, E: Encoder>(&self, encoder: &'a mut E) -> Result<(), EncodeError> { + let maybe_turbo_encoder = if unty::type_equal::() { + // SAFETY: Transmute is safe because `&mut E` is `&mut TurboBincodeEncoder`: + // - `unty::type_equal::()` does not check lifetimes, but does + // check the type and layout, so we know those are correct. + // - The transmuted encoder cannot escape this function, and we know that the lifetime + // of `'f` is at least as long as the function. + // - Lifetimes don't change layout. This is not guaranteed, but if this assumption is + // broken, we'd have a different type id, `type_equal` would return `false` and we'd + // fall back to a slower codepath, and wouldn't violate memory safety. + // - Two mutable references have the same layout and alignment when they reference + // exactly the same type. + // - The explicit lifetime ('a) avoids creating an implitly unbounded lifetime. + Ok(unsafe { std::mem::transmute::<&'a mut E, &'a mut TurboBincodeEncoder>(encoder) }) + } else { + Err(encoder) + }; + match maybe_turbo_encoder { + Ok(turbo_encoder) => TypedSharedReference::encode(self, turbo_encoder), + Err(generic_encoder) => { + // The underlying `SharedReference` can only be serialized using + // `TurboBincodeEncoder` because the encoder function pointer cannot take type + // parameters. This is okay, because we expect any hot codepaths to use + // `TurboBincodeEncoder`. + // + // Create a `TurboBincodeEncoder` and encode this as a nested byte array. We must + // redundantly store a size here, otherwise we won't be able to determine what size + // buffer to use for `TurboBincodeReader`. + let buffer = turbo_bincode_encode(self)?; + buffer.encode(generic_encoder) + } + } + } +} + +impl Decode for TypedSharedReference { + fn decode<'a, D: Decoder>(decoder: &mut D) -> Result { + let maybe_turbo_decoder = if unty::type_equal::() { + // SAFETY: See notes on the `Encode::encode` implementation above. + Ok(unsafe { std::mem::transmute::<&mut D, &mut TurboBincodeDecoder<'a>>(decoder) }) + } else { + Err(decoder) + }; + match maybe_turbo_decoder { + Ok(turbo_decoder) => TypedSharedReference::decode(turbo_decoder), + Err(generic_decoder) => { + // The underlying `SharedReference` can only be deserialized using + // `TurboBincodeDecoder` because the decoder function pointer cannot take type + // parameters. This is okay, because we expect any hot codepaths to use + // `TurboBincodeDecoder`. + // + // Decode the nested byte array that was created during encoding, then use a + // `TurboBincodeDecoder` to decode the contents. + let buffer: Vec = Decode::decode(generic_decoder)?; + turbo_bincode_decode(&buffer) + } + } + } +} + +impl_borrow_decode!(TypedSharedReference); + impl Deref for TypedSharedReference { type Target = SharedReference; @@ -98,30 +200,6 @@ impl Debug for SharedReference { } } -impl Serialize for TypedSharedReference { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let TypedSharedReference { - type_id: ty, - reference: SharedReference(arc), - } = self; - let value_type = registry::get_value_type(*ty); - if let Some(serializable) = value_type.any_as_serializable(arc) { - let mut t = serializer.serialize_tuple(2)?; - t.serialize_element(ty)?; - t.serialize_element(serializable)?; - t.end() - } else { - Err(serde::ser::Error::custom(format!( - "{:?} is not serializable", - registry::get_value_type(*ty).global_name - ))) - } - } -} - impl Display for SharedReference { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "untyped value") @@ -137,54 +215,3 @@ impl Display for TypedSharedReference { ) } } - -impl<'de> Deserialize<'de> for TypedSharedReference { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = TypedSharedReference; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a serializable shared reference") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: serde::de::SeqAccess<'de>, - { - if let Some(type_id) = seq.next_element()? { - let value_type = registry::get_value_type(type_id); - if let Some(seed) = value_type.get_any_deserialize_seed() { - if let Some(value) = seq.next_element_seed(seed)? { - let arc = triomphe::Arc::::from(value); - Ok(TypedSharedReference { - type_id, - reference: SharedReference(arc), - }) - } else { - Err(serde::de::Error::invalid_length( - 1, - &"tuple with type and value", - )) - } - } else { - Err(serde::de::Error::custom(format!( - "{value_type} is not deserializable" - ))) - } - } else { - Err(serde::de::Error::invalid_length( - 0, - &"tuple with type and value", - )) - } - } - } - - deserializer.deserialize_tuple(2, Visitor) - } -} diff --git a/turbopack/crates/turbo-tasks/src/trait_ref.rs b/turbopack/crates/turbo-tasks/src/trait_ref.rs index e583a2a4fb4f..1d921c058418 100644 --- a/turbopack/crates/turbo-tasks/src/trait_ref.rs +++ b/turbopack/crates/turbo-tasks/src/trait_ref.rs @@ -1,7 +1,6 @@ use std::{fmt::Debug, future::Future, marker::PhantomData}; use anyhow::Result; -use serde::{Deserialize, Serialize}; use crate::{ Vc, VcValueTrait, @@ -57,21 +56,6 @@ impl std::hash::Hash for TraitRef { } } -impl Serialize for TraitRef { - fn serialize(&self, serializer: S) -> Result { - self.shared_reference.serialize(serializer) - } -} - -impl<'de, T> Deserialize<'de> for TraitRef { - fn deserialize>(deserializer: D) -> Result { - Ok(Self { - shared_reference: TypedSharedReference::deserialize(deserializer)?, - _t: PhantomData, - }) - } -} - impl std::ops::Deref for TraitRef> where Box: VcValueTrait, diff --git a/turbopack/crates/turbo-tasks/src/value_type.rs b/turbopack/crates/turbo-tasks/src/value_type.rs index 217ac346b954..38e560bd7472 100644 --- a/turbopack/crates/turbo-tasks/src/value_type.rs +++ b/turbopack/crates/turbo-tasks/src/value_type.rs @@ -5,19 +5,23 @@ use std::{ }; use auto_hash_map::{AutoMap, AutoSet}; -use bincode::{Decode, Encode}; -use serde::{Deserialize, Serialize}; +use bincode::{ + Decode, Encode, + error::{DecodeError, EncodeError}, +}; use tracing::Span; +use turbo_bincode::{TurboBincodeDecoder, TurboBincodeEncoder}; use crate::{ - RawVc, VcValueType, id::TraitTypeId, macro_helpers::NativeFunction, - magic_any::AnyDeserializeSeed, registry, task::shared_reference::TypedSharedReference, - vc::VcCellMode, + RawVc, SharedReference, VcValueType, id::TraitTypeId, macro_helpers::NativeFunction, registry, + task::shared_reference::TypedSharedReference, vc::VcCellMode, }; -type AnySerializationFn = fn(&(dyn Any + Sync + Send)) -> &dyn erased_serde::Serialize; type RawCellFactoryFn = fn(TypedSharedReference) -> RawVc; +type AnyEncodeFn = fn(&dyn Any, &mut TurboBincodeEncoder<'_>) -> Result<(), EncodeError>; +type AnyDecodeFn = fn(&mut TurboBincodeDecoder<'_>) -> Result; + // TODO this type need some refactoring when multiple languages are added to // turbo-task In this case a trait_method might be of a different function type. // It probably need to be a Vc. @@ -37,14 +41,14 @@ pub struct ValueType { /// List of trait methods available trait_methods: AutoMap<&'static TraitMethod, &'static NativeFunction>, - /// Functors for serialization - any_serialization: Option<(AnySerializationFn, AnyDeserializeSeed)>, + /// Functions to convert to write the type to a buffer or read it from a buffer. + pub bincode: Option<(AnyEncodeFn, AnyDecodeFn)>, /// An implementation of - /// [`VcCellMode::raw_cell`][crate::vc::cell_mode::VcCellMode::raw_cell]. + /// [`VcCellMode::raw_cell`][crate::vc::VcCellMode::raw_cell]. /// /// Allows dynamically constructing a cell using the type id. Used inside of - /// [`RawVc`] where we have a type id, but not the concrete type `T` of + /// [`TraitRef`][crate::TraitRef] where we have a type id, but not the concrete type `T` of /// `Vc`. /// /// Because we allow resolving `Vc`, it's otherwise not possible @@ -87,81 +91,98 @@ impl Display for ValueType { } } -pub fn any_as_serialize( - this: &(dyn Any + Send + Sync), -) -> &dyn erased_serde::Serialize { - if let Some(r) = this.downcast_ref::() { - return r; +pub fn any_as_encode(this: &dyn Any) -> &T { + if let Some(enc) = this.downcast_ref::() { + return enc; } - panic!( - "any_as_serialize::<{}> called with invalid type", + unreachable!( + "any_as_encode::<{}> called with invalid type", type_name::() ); } -// TODO: use this in https://github.com/vercel/next.js/pull/86338 -#[allow(dead_code)] pub trait ManualEncodeWrapper: Encode { type Value; + // this uses RPIT to avoid some lifetime problems fn new<'a>(value: &'a Self::Value) -> impl Encode + 'a; } -// TODO: use this in https://github.com/vercel/next.js/pull/86338 -#[allow(dead_code)] pub trait ManualDecodeWrapper: Decode<()> { type Value; + fn inner(self) -> Self::Value; } impl ValueType { - /// This is internally used by `#[turbo_tasks::value]` + /// This is internally used by [`#[turbo_tasks::value]`][crate::value]. pub fn new(global_name: &'static str) -> Self { - Self { - name: std::any::type_name::(), + Self::new_inner::(global_name, None) + } + + /// This is internally used by [`#[turbo_tasks::value]`][crate::value]. + pub fn new_with_bincode>( + global_name: &'static str, + ) -> Self { + Self::new_inner::( global_name, - traits: AutoSet::new(), - trait_methods: AutoMap::new(), - any_serialization: None, - raw_cell: >::raw_cell, - } + Some(( + |this, enc| { + T::encode(any_as_encode::(this), enc)?; + Ok(()) + }, + |dec| { + let val = T::decode(dec)?; + Ok(SharedReference::new(triomphe::Arc::new(val))) + }, + )), + ) } - /// This is internally used by `#[turbo_tasks::value]` - pub fn new_with_any_serialization< - T: VcValueType + Any + Serialize + for<'de> Deserialize<'de>, + /// This is used internally by [`turbo_tasks_macros::primitive`] to encode/decode foreign types + /// that cannot implement the [`bincode`] traits due to the [orphan rules]. + /// + /// This is done by constructing wrapper types that implement the bincode traits on behalf of + /// the wrapped type. + /// + /// [orphan rules]: https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules + pub fn new_with_bincode_wrappers< + T: VcValueType, + E: ManualEncodeWrapper, + D: ManualDecodeWrapper, >( global_name: &'static str, + ) -> Self { + Self::new_inner::( + global_name, + Some(( + |this, enc| { + E::new(any_as_encode::(this)).encode(enc)?; + Ok(()) + }, + |dec| { + let val = D::inner(D::decode(dec)?); + Ok(SharedReference::new(triomphe::Arc::new(val))) + }, + )), + ) + } + + // Helper for other constructor functions + fn new_inner( + global_name: &'static str, + bincode: Option<(AnyEncodeFn, AnyDecodeFn)>, ) -> Self { Self { name: std::any::type_name::(), global_name, traits: AutoSet::new(), trait_methods: AutoMap::new(), - any_serialization: Some((any_as_serialize::, AnyDeserializeSeed::new::())), + bincode, raw_cell: >::raw_cell, } } - pub fn any_as_serializable<'a>( - &self, - arc: &'a triomphe::Arc, - ) -> Option<&'a dyn erased_serde::Serialize> { - if let Some(s) = self.any_serialization { - Some((s.0)(&**arc)) - } else { - None - } - } - - pub fn is_serializable(&self) -> bool { - self.any_serialization.is_some() - } - - pub fn get_any_deserialize_seed(&self) -> Option { - self.any_serialization.map(|s| s.1) - } - pub(crate) fn register_trait_method( &mut self, trait_method: &'static TraitMethod,