Skip to content

Commit 3a32319

Browse files
authored
Turbopack: bincode: Add traits for types that require TurboBincodeEncoder or TurboBincodeDecoder (#86633)
**Example usage:** See `CachedTaskType`​ in `turbopack/crates/turbo-tasks/src/backend.rs`​ on #86631 for an example of how this is used. That needs to call the encoder/decoder function pointer stored on `native_fn.arg_meta.bincode` with a concrete `TurboBincodeEncoder`/`TurboBincodeDecoder`, so it uses this trait. --- For `SharedReference` (value cell contents) and `TaskInput`s, we need to generate function pointers to functions that can encode/decode the type. Those generated functions cannot contain generics, so they require `TurboBincodeEncoder` or `TurboBincodeDecoder<()>` as arguments. That's okay because we don't want to use multiple encoders/decoders anyways, because that would be really bad for binary size and compilation time. However, `SharedReference` and `TaskInput`s are wrapped in other container types that need to be encoded/decoded (e.g. `TypedSharedReference`. In these cases, the wrapper types need to be able to implement `Encode`/`Decode`, but can't because of the fixed `Encoder`/`Decoder` implementations. So this provides a way to do that, by panicking at runtime if the type doesn't match. This uses the `unty` crate which is part of the bincode project: https://github.com/bincode-org/unty
1 parent f9fdf6e commit 3a32319

File tree

5 files changed

+119
-0
lines changed

5 files changed

+119
-0
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,7 @@ tracing = "0.1.37"
470470
tracing-subscriber = "0.3.16"
471471
triomphe = { git = "https://github.com/sokra/triomphe", branch = "sokra/unstable" }
472472
unsize = "1.1.0"
473+
unty = "0.0.4"
473474
url = "2.2.2"
474475
urlencoding = "2.1.2"
475476
uuid = "1.18.1"

turbopack/crates/turbo-bincode/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ ringmap = { workspace = true }
1919
serde = { workspace = true }
2020
serde_json = { workspace = true }
2121
smallvec = { workspace = true }
22+
unty = { workspace = true }

turbopack/crates/turbo-bincode/src/lib.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
#[doc(hidden)]
2+
pub mod macro_helpers;
3+
14
use std::ptr::copy_nonoverlapping;
25

36
use ::smallvec::SmallVec;
@@ -115,6 +118,57 @@ impl Reader for TurboBincodeReader<'_> {
115118
}
116119
}
117120

121+
/// Represents a type that can only be encoded with a [`TurboBincodeEncoder`].
122+
///
123+
/// All traits implementing this must also implement the more generic [`Encode`] trait, but they
124+
/// should panic if any other encoder is used.
125+
///
126+
/// Use [`impl_encode_for_turbo_bincode_encode`] to automatically implement the [`Encode`] trait
127+
/// from this one.
128+
pub trait TurboBincodeEncode: Encode {
129+
fn encode(&self, encoder: &mut TurboBincodeEncoder) -> Result<(), EncodeError>;
130+
}
131+
132+
/// Represents a type that can only be decoded with a [`TurboBincodeDecoder`] and an empty `()`
133+
/// context.
134+
///
135+
/// All traits implementing this must also implement the more generic [`Decode`] trait, but they
136+
/// should panic if any other encoder is used.
137+
///
138+
/// Use [`impl_decode_for_turbo_bincode_decode`] to automatically implement the [`Decode`] trait
139+
/// from this one.
140+
pub trait TurboBincodeDecode<Context>: Decode<Context> {
141+
fn decode(decoder: &mut TurboBincodeDecoder) -> Result<Self, DecodeError>;
142+
}
143+
144+
#[macro_export]
145+
macro_rules! impl_encode_for_turbo_bincode_encode {
146+
($ty:ty) => {
147+
impl $crate::macro_helpers::bincode::Encode for $ty {
148+
fn encode<'a, E: $crate::macro_helpers::bincode::enc::Encoder>(
149+
&self,
150+
encoder: &'a mut E,
151+
) -> ::std::result::Result<(), $crate::macro_helpers::bincode::error::EncodeError> {
152+
$crate::macro_helpers::encode_for_turbo_bincode_encode_impl(self, encoder)
153+
}
154+
}
155+
};
156+
}
157+
158+
#[macro_export]
159+
macro_rules! impl_decode_for_turbo_bincode_decode {
160+
($ty:ty) => {
161+
impl<Context> $crate::macro_helpers::bincode::Decode<Context> for $ty {
162+
fn decode<D: $crate::macro_helpers::bincode::de::Decoder<Context = Context>>(
163+
decoder: &mut D,
164+
) -> ::std::result::Result<Self, $crate::macro_helpers::bincode::error::DecodeError>
165+
{
166+
$crate::macro_helpers::decode_for_turbo_bincode_decode_impl(decoder)
167+
}
168+
}
169+
};
170+
}
171+
118172
pub mod indexmap {
119173
use std::hash::{BuildHasher, Hash};
120174

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use std::{any::type_name, mem::transmute};
2+
3+
pub use bincode;
4+
use bincode::{
5+
de::Decoder,
6+
enc::Encoder,
7+
error::{DecodeError, EncodeError},
8+
};
9+
10+
use crate::{TurboBincodeDecode, TurboBincodeDecoder, TurboBincodeEncode, TurboBincodeEncoder};
11+
12+
#[track_caller]
13+
pub fn encode_for_turbo_bincode_encode_impl<'a, T: TurboBincodeEncode, E: Encoder>(
14+
value: &T,
15+
encoder: &'a mut E,
16+
) -> Result<(), EncodeError> {
17+
let encoder = if unty::type_equal::<E, TurboBincodeEncoder>() {
18+
// SAFETY: Transmute is safe because `&mut E` is `&mut TurboBincodeEncoder`:
19+
// - `unty::type_equal::<E, TurboBincodeEncoder>()` does not check lifetimes, but does check
20+
// the type and layout, so we know those are correct.
21+
// - The transmuted encoder cannot escape this function, and we know that the lifetime of
22+
// `'a` is at least as long as the function.
23+
// - Lifetimes don't change layout. This is not strictly guaranteed, but if this assumption
24+
// is broken, we'd have a different type id (type ids are derived from layout
25+
// information), `type_equal` would return `false`, and we'd panic instead of violating
26+
// memory safety.
27+
// - Two mutable references have the same layout and alignment when they reference exactly
28+
// the same type.
29+
// - The explicit lifetime ('a) avoids creating an implitly unbounded lifetime.
30+
unsafe { transmute::<&'a mut E, &'a mut TurboBincodeEncoder>(encoder) }
31+
} else {
32+
unreachable!(
33+
"{} implements TurboBincodeEncode, but was called with a {} encoder implementation",
34+
type_name::<T>(),
35+
type_name::<E>(),
36+
)
37+
};
38+
TurboBincodeEncode::encode(value, encoder)
39+
}
40+
41+
#[track_caller]
42+
pub fn decode_for_turbo_bincode_decode_impl<
43+
'a,
44+
Context,
45+
T: TurboBincodeDecode<Context>,
46+
D: Decoder<Context = Context>,
47+
>(
48+
decoder: &'a mut D,
49+
) -> Result<T, DecodeError> {
50+
let decoder = if unty::type_equal::<D, TurboBincodeDecoder>() {
51+
// SAFETY: See notes on the `Encode::encode` implementation on
52+
// `encode_for_turbo_bincode_encode_impl`.
53+
unsafe { transmute::<&'a mut D, &'a mut TurboBincodeDecoder<'a>>(decoder) }
54+
} else {
55+
unreachable!(
56+
"{} implements TurboBincodeDecode, but was called with a {} decoder implementation",
57+
type_name::<T>(),
58+
type_name::<D>(),
59+
)
60+
};
61+
TurboBincodeDecode::decode(decoder)
62+
}

0 commit comments

Comments
 (0)