Skip to content

Commit

Permalink
feat: rspack cacheable
Browse files Browse the repository at this point in the history
  • Loading branch information
jerrykingxyz committed Oct 17, 2024
1 parent a97e105 commit e2eee98
Show file tree
Hide file tree
Showing 63 changed files with 4,243 additions and 1 deletion.
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,11 @@ napi = { package = "napi-h", version = "=2.16.1" }
napi-build = { version = "2" }
napi-derive = { version = "2" }

# Serialize and Deserialize
inventory = { version = "0.1" }
rkyv = { version = "=0.8.8" }

# Must be pinned with the same swc versions
#rkyv = { version = "=0.7.44" } # synced with swc wasm plugin
swc_config = { version = "=1.0.0" }
swc_core = { version = "=1.0.0", default-features = false }
swc_ecma_minifier = { version = "=1.0.0", default-features = false }
Expand Down
22 changes: 22 additions & 0 deletions crates/rspack_cacheable/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
edition = "2021"
license = "MIT"
name = "rspack_cacheable"
version = "0.1.0"

[dependencies]
camino = { workspace = true }
dashmap = { workspace = true }
hashlink = { workspace = true }
indexmap = { workspace = true }
inventory = { workspace = true }
json = { workspace = true }
lightningcss = { workspace = true }
once_cell = { workspace = true }
rkyv = { workspace = true }
rspack_macros = { path = "../rspack_macros" }
rspack_resolver = { workspace = true }
rspack_sources = { workspace = true }
serde_json = { workspace = true }
swc_core = { workspace = true, features = ["ecma_ast"] }
ustr = { workspace = true }
64 changes: 64 additions & 0 deletions crates/rspack_cacheable/src/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use std::{any::Any, ptr::NonNull};

use rkyv::{
de::{ErasedPtr, Pooling, PoolingState},
ser::{sharing::SharingState, Sharing},
};

use crate::{DeserializeError, SerializeError};

const CONTEXT_ADDR: usize = 0;
unsafe fn default_drop(_: ErasedPtr) {}

pub struct ContextGuard<'a> {
context: &'a dyn Any,
}

impl<'a> ContextGuard<'a> {
pub fn new(context: &'a dyn Any) -> Self {
Self { context }
}

pub fn add_to_sharing<S: Sharing<SerializeError>>(
&self,
sharing: &mut S,
) -> Result<(), SerializeError> {
sharing.start_sharing(CONTEXT_ADDR);
sharing.finish_sharing(CONTEXT_ADDR, self as *const _ as usize)
}

pub fn sharing_context<S: Sharing<SerializeError>>(
sharing: &'a mut S,
) -> Result<&'a dyn Any, SerializeError> {
match sharing.start_sharing(CONTEXT_ADDR) {
SharingState::Finished(addr) => {
let guard: &Self = unsafe { &*(addr as *const Self) };
Ok(guard.context)
}
_ => Err(SerializeError::NoContext),
}
}

pub fn add_to_pooling<P: Pooling<DeserializeError>>(
&self,
pooling: &mut P,
) -> Result<(), DeserializeError> {
unsafe {
let ctx_ptr = ErasedPtr::new(NonNull::new_unchecked(self as *const _ as *mut ()));
pooling.start_pooling(CONTEXT_ADDR);
pooling.finish_pooling(CONTEXT_ADDR, ctx_ptr, default_drop)
}
}

pub fn pooling_context<P: Pooling<DeserializeError>>(
pooling: &'a mut P,
) -> Result<&'a dyn Any, DeserializeError> {
match pooling.start_pooling(CONTEXT_ADDR) {
PoolingState::Finished(ptr) => {
let guard: &Self = unsafe { &*(ptr.data_address() as *const Self) };
Ok(guard.context)
}
_ => Err(DeserializeError::NoContext),
}
}
}
71 changes: 71 additions & 0 deletions crates/rspack_cacheable/src/deserialize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use std::any::Any;

use rkyv::{
access,
api::{deserialize_using, high::HighValidator},
bytecheck::CheckBytes,
de::Pool,
rancor::{BoxedError, Source, Strategy, Trace},
Archive, Deserialize,
};

use crate::context::ContextGuard;

#[derive(Debug)]
pub enum DeserializeError {
RkyvError(BoxedError),
DynCheckBytesNotRegister,
// A deserialize failed occurred
DeserializeFailed(&'static str),
NoContext,
UnsupportedField,
}

impl std::fmt::Display for DeserializeError {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// write!(f, "{}", self.inner)?;
Ok(())
}
}

impl std::error::Error for DeserializeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
// self.inner.source()
todo!()
}
}

impl Trace for DeserializeError {
fn trace<R>(self, _trace: R) -> Self
where
R: std::fmt::Debug + std::fmt::Display + Send + Sync + 'static,
{
todo!()
// Self::RkyvError()
// inner: self.inner.trace(trace),
// }
}
}

impl Source for DeserializeError {
fn new<T: std::error::Error + Send + Sync + 'static>(source: T) -> Self {
Self::RkyvError(BoxedError::new(source))
}
}

pub type Validator<'a> = HighValidator<'a, DeserializeError>;
pub type Deserializer = Strategy<Pool, DeserializeError>;

pub fn from_bytes<T, C: Any>(bytes: &[u8], context: &C) -> Result<T, DeserializeError>
where
T: Archive,
T::Archived: for<'a> CheckBytes<Validator<'a>> + Deserialize<T, Deserializer>,
{
let guard = ContextGuard::new(context);
let mut deserializer = Pool::default();
guard.add_to_pooling(&mut deserializer)?;
deserialize_using(
access::<T::Archived, DeserializeError>(bytes)?,
&mut deserializer,
)
}
175 changes: 175 additions & 0 deletions crates/rspack_cacheable/src/dyn/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
use core::marker::PhantomData;
use std::{
collections::HashMap,
hash::{Hash, Hasher},
};

use inventory;
use rkyv::{
bytecheck::{CheckBytes, StructCheckContext},
ptr_meta::{DynMetadata, Pointee},
rancor::{Fallible, Trace},
traits::NoUndef,
Archived, Portable, SerializeUnsized,
};

pub mod validation;
use crate::{DeserializeError, Deserializer, SerializeError, Serializer};

/// A trait object that can be archived.
pub trait SerializeDyn {
/// Writes the value to the serializer and returns the position it was written to.
fn serialize_dyn(&self, serializer: &mut Serializer) -> Result<usize, SerializeError>;
}

impl<T> SerializeDyn for T
where
T: for<'a> SerializeUnsized<Serializer<'a>>,
{
fn serialize_dyn(&self, serializer: &mut Serializer) -> Result<usize, SerializeError> {
self.serialize_unsized(serializer)
}
}

/// A trait object that can be deserialized.
///
/// See [`SerializeDyn`] for more information.
pub trait DeserializeDyn<T: Pointee + ?Sized> {
/// Deserializes this value into the given out pointer.
fn deserialize_dyn(
&self,
deserializer: &mut Deserializer,
out: *mut T,
) -> Result<(), DeserializeError>;

/// Returns the pointer metadata for the deserialized form of this type.
fn deserialized_pointer_metadata(&self) -> DynMetadata<T>;
}

/// The archived version of `DynMetadata`.
pub struct ArchivedDynMetadata<T: ?Sized> {
dyn_id: Archived<u64>,
phantom: PhantomData<T>,
}

impl<T: ?Sized> Default for ArchivedDynMetadata<T> {
fn default() -> Self {
Self {
dyn_id: Archived::<u64>::from_native(0),
phantom: PhantomData::default(),
}
}
}
impl<T: ?Sized> Hash for ArchivedDynMetadata<T> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
Hash::hash(&self.dyn_id, state);
}
}
impl<T: ?Sized> PartialEq for ArchivedDynMetadata<T> {
#[inline]
fn eq(&self, other: &ArchivedDynMetadata<T>) -> bool {
self.dyn_id == other.dyn_id
}
}
impl<T: ?Sized> Eq for ArchivedDynMetadata<T> {}
impl<T: ?Sized> PartialOrd for ArchivedDynMetadata<T> {
#[inline]
fn partial_cmp(&self, other: &ArchivedDynMetadata<T>) -> Option<::core::cmp::Ordering> {
Some(self.dyn_id.cmp(&other.dyn_id))
}
}
impl<T: ?Sized> Ord for ArchivedDynMetadata<T> {
#[inline]
fn cmp(&self, other: &ArchivedDynMetadata<T>) -> ::core::cmp::Ordering {
self.dyn_id.cmp(&other.dyn_id)
}
}
impl<T: ?Sized> Clone for ArchivedDynMetadata<T> {
fn clone(&self) -> ArchivedDynMetadata<T> {
*self
}
}
impl<T: ?Sized> Copy for ArchivedDynMetadata<T> {}
impl<T: ?Sized> Unpin for ArchivedDynMetadata<T> {}
unsafe impl<T: ?Sized> Sync for ArchivedDynMetadata<T> {}
unsafe impl<T: ?Sized> Send for ArchivedDynMetadata<T> {}
unsafe impl<T: ?Sized> NoUndef for ArchivedDynMetadata<T> {}
unsafe impl<T: ?Sized> Portable for ArchivedDynMetadata<T> {}
unsafe impl<T: ?Sized, C> CheckBytes<C> for ArchivedDynMetadata<T>
where
C: Fallible + ?Sized,
C::Error: Trace,
Archived<u64>: CheckBytes<C>,
PhantomData<T>: CheckBytes<C>,
{
unsafe fn check_bytes(
value: *const Self,
context: &mut C,
) -> ::core::result::Result<(), C::Error> {
Archived::<u64>::check_bytes(&raw const (*value).dyn_id, context).map_err(|e| {
C::Error::trace(
e,
StructCheckContext {
struct_name: "ArchivedDynMetadata",
field_name: "dyn_id",
},
)
})?;
PhantomData::<T>::check_bytes(&raw const (*value).phantom, context).map_err(|e| {
C::Error::trace(
e,
StructCheckContext {
struct_name: "ArchivedDynMetadata",
field_name: "phantom",
},
)
})?;
Ok(())
}
}

impl<T: ?Sized> ArchivedDynMetadata<T> {
pub fn new(dyn_id: u64) -> Self {
Self {
dyn_id: Archived::<u64>::from_native(dyn_id),
phantom: PhantomData,
}
}

/// Returns the pointer metadata for the trait object this metadata refers
/// to.
pub fn lookup_metadata(&self) -> DynMetadata<T> {
unsafe {
std::mem::transmute(
*DYN_REGISTRY
.get(&self.dyn_id.to_native())
.expect("attempted to get vtable for an unregistered impl"),
)
}
}
}

pub struct DynEntry {
dyn_id: u64,
vtable: usize,
}

impl DynEntry {
pub fn new(dyn_id: u64, vtable: usize) -> Self {
Self { dyn_id, vtable }
}
}

inventory::collect!(DynEntry);

static DYN_REGISTRY: std::sync::LazyLock<HashMap<u64, usize>> = std::sync::LazyLock::new(|| {
let mut result = HashMap::default();
for entry in inventory::iter::<DynEntry> {
let old_value = result.insert(entry.dyn_id, entry.vtable);
if old_value.is_some() {
panic!("cacheable_dyn init global REGISTRY error, duplicate implementation.")
}
}
result
});
49 changes: 49 additions & 0 deletions crates/rspack_cacheable/src/dyn/validation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::collections::HashMap;

use rkyv::bytecheck::CheckBytes;

use crate::{DeserializeError, Validator};

type CheckBytesDyn = unsafe fn(*const u8, &mut Validator<'_>) -> Result<(), DeserializeError>;

/// # Safety
///
/// Run T::check_bytes
pub unsafe fn default_check_bytes_dyn<T>(
bytes: *const u8,
context: &mut Validator<'_>,
) -> Result<(), DeserializeError>
where
T: for<'a> CheckBytes<Validator<'a>>,
{
T::check_bytes(bytes.cast(), context)
}

pub struct CheckBytesEntry {
vtable: usize,
check_bytes_dyn: CheckBytesDyn,
}

impl CheckBytesEntry {
#[doc(hidden)]
pub fn new(vtable: usize, check_bytes_dyn: CheckBytesDyn) -> Self {
Self {
vtable,
check_bytes_dyn,
}
}
}

inventory::collect!(CheckBytesEntry);

pub static CHECK_BYTES_REGISTRY: std::sync::LazyLock<HashMap<usize, CheckBytesDyn>> =
std::sync::LazyLock::new(|| {
let mut result = HashMap::default();
for entry in inventory::iter::<CheckBytesEntry> {
let old_value = result.insert(entry.vtable, entry.check_bytes_dyn);
if old_value.is_some() {
panic!("vtable conflict, a trait implementation was likely added twice (but it's possible there was a hash collision)")
}
}
result
});
Loading

0 comments on commit e2eee98

Please sign in to comment.