Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add rspack_cacheable lib #8156

Merged
merged 3 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
342 changes: 267 additions & 75 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,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
25 changes: 25 additions & 0 deletions crates/rspack_cacheable/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
edition = "2021"
license = "MIT"
name = "rspack_cacheable"
version = "0.1.0"

[features]
noop = []

[dependencies]
camino = { workspace = true }
dashmap = { workspace = true }
hashlink = { workspace = true }
indexmap = { workspace = true }
inventory = { workspace = true }
json = { workspace = true }
lightningcss_rs = { 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 }
65 changes: 65 additions & 0 deletions crates/rspack_cacheable/src/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
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) {}

/// A context wrapper that provides shared context methods
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)
jerrykingxyz marked this conversation as resolved.
Show resolved Hide resolved
}

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),
}
}
}
86 changes: 86 additions & 0 deletions crates/rspack_cacheable/src/deserialize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
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 {
BoxedError(BoxedError),
MessageError(&'static str),
DynCheckBytesNotRegister,
NoContext,
UnsupportedField,
}

impl std::fmt::Display for DeserializeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BoxedError(error) => error.fmt(f),
Self::MessageError(msg) => {
write!(f, "{}", msg)
}
Self::DynCheckBytesNotRegister => {
write!(f, "cacheable_dyn check bytes not register")
}
Self::NoContext => {
write!(f, "no context")
}
Self::UnsupportedField => {
write!(f, "unsupported field")
}
}
}
}

impl std::error::Error for DeserializeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::BoxedError(error) => error.source(),
_ => None,
}
}
}

impl Trace for DeserializeError {
fn trace<R>(self, trace: R) -> Self
where
R: std::fmt::Debug + std::fmt::Display + Send + Sync + 'static,
{
Self::BoxedError(BoxedError::trace(BoxedError::new(self), trace))
}
}

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

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

/// Transform bytes to struct
///
/// This function implementation refers to rkyv::from_bytes and
/// add custom error and context support
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,
)
}
174 changes: 174 additions & 0 deletions crates/rspack_cacheable/src/dyn/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
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
});
Loading
Loading