Skip to content

Commit

Permalink
refactor(common): Bikeshed plugin apis (#5120)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwonoj authored Jul 6, 2022
1 parent 64ccb58 commit 2ba8b39
Show file tree
Hide file tree
Showing 19 changed files with 290 additions and 258 deletions.
20 changes: 10 additions & 10 deletions crates/swc/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ impl RustPlugins {
use std::{path::PathBuf, sync::Arc};

use anyhow::Context;
use swc_common::{plugin::Serialized, FileName};
use swc_common::{plugin::PluginSerializedBytes, FileName};
use swc_ecma_loader::resolve::Resolve;

// swc_plugin_macro will not inject proxy to the comments if comments is empty
Expand All @@ -92,7 +92,7 @@ impl RustPlugins {
inner: self.comments.clone(),
},
|| {
let mut serialized = Serialized::serialize(&n)?;
let mut serialized = PluginSerializedBytes::try_serialize(&n)?;

// Run plugin transformation against current program.
// We do not serialize / deserialize between each plugin execution but
Expand All @@ -111,11 +111,11 @@ impl RustPlugins {

let config_json = serde_json::to_string(&p.1)
.context("Failed to serialize plugin config as json")
.and_then(|value| Serialized::serialize(&value))?;
.and_then(|value| PluginSerializedBytes::try_serialize(&value))?;

let context_json = serde_json::to_string(&self.plugin_context)
.context("Failed to serialize plugin context as json")
.and_then(|value| Serialized::serialize(&value))?;
.and_then(|value| PluginSerializedBytes::try_serialize(&value))?;

let resolved_path = self
.resolver
Expand Down Expand Up @@ -152,7 +152,7 @@ impl RustPlugins {

// Plugin transformation is done. Deserialize transformed bytes back
// into Program
Serialized::deserialize(&serialized)
serialized.deserialize()
},
)
}
Expand All @@ -162,7 +162,7 @@ impl RustPlugins {
use std::{path::PathBuf, sync::Arc};

use anyhow::Context;
use swc_common::{plugin::Serialized, FileName};
use swc_common::{plugin::PluginSerializedBytes, FileName};
use swc_ecma_loader::resolve::Resolve;

let should_enable_comments_proxy = self.comments.is_some();
Expand All @@ -172,17 +172,17 @@ impl RustPlugins {
inner: self.comments.clone(),
},
|| {
let mut serialized = Serialized::serialize(&n)?;
let mut serialized = PluginSerializedBytes::try_serialize(&n)?;

if let Some(plugins) = &self.plugins {
for p in plugins {
let config_json = serde_json::to_string(&p.1)
.context("Failed to serialize plugin config as json")
.and_then(|value| Serialized::serialize(&value))?;
.and_then(|value| PluginSerializedBytes::try_serialize(&value))?;

let context_json = serde_json::to_string(&self.plugin_context)
.context("Failed to serialize plugin context as json")
.and_then(|value| Serialized::serialize(&value))?;
.and_then(|value| PluginSerializedBytes::try_serialize(&value))?;

serialized = swc_plugin_runner::apply_transform_plugin(
&p.0,
Expand All @@ -197,7 +197,7 @@ impl RustPlugins {
}
}

Serialized::deserialize(&serialized)
serialized.deserialize()
},
)
}
Expand Down
175 changes: 89 additions & 86 deletions crates/swc_common/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,48 +41,38 @@ pub enum PluginError {
Serialize(String),
}

/// Wraps internal representation of serialized data. Consumers should not
/// rely on specific details of byte format struct contains: it is
/// strictly implementation detail which can change anytime.
pub struct Serialized {
field: rkyv::AlignedVec,
/// Wraps internal representation of serialized data for exchanging data between
/// plugin to the host. Consumers should not rely on specific details of byte
/// format struct contains: it is strict implementation detail which can
/// change anytime.
pub struct PluginSerializedBytes {
pub(crate) field: rkyv::AlignedVec,
}

#[cfg(feature = "plugin-base")]
impl Serialized {
pub fn new_for_plugin(bytes: &[u8], len: i32) -> Serialized {
let mut vec = rkyv::AlignedVec::with_capacity(
len.try_into()
.expect("Cannot determine size of the serialized bytes"),
);
vec.extend_from_slice(bytes);
Serialized { field: vec }
impl PluginSerializedBytes {
/**
* Constructs an instance from already serialized byte
* slices.
*/
pub fn from_slice(bytes: &[u8]) -> PluginSerializedBytes {
let mut field = rkyv::AlignedVec::new();
field.extend_from_slice(bytes);
PluginSerializedBytes { field }
}

pub fn from(vec: rkyv::AlignedVec) -> Serialized {
Serialized { field: vec }
}

/// Not an actual trait Into impl: simple wrapper to deserialize<T>:expect()
pub fn into<T>(self) -> T
where
T: rkyv::Archive,
T::Archived: rkyv::Deserialize<T, rkyv::de::deserializers::SharedDeserializeMap>,
{
Serialized::deserialize(&self).expect("Should able to deserialize")
}

#[allow(clippy::should_implement_trait)]
pub fn as_ref(&self) -> &rkyv::AlignedVec {
&self.field
}

pub fn serialize<W>(t: &W) -> Result<Serialized, Error>
/**
* Constructs an instance from given struct by serializing it.
*
* This is sort of mimic TryFrom behavior, since we can't use generic
* to implement TryFrom trait
*/
pub fn try_serialize<W>(t: &W) -> Result<Self, Error>
where
W: rkyv::Serialize<rkyv::ser::serializers::AllocSerializer<512>>,
{
rkyv::to_bytes::<_, 512>(t)
.map(Serialized::from)
.map(|field| PluginSerializedBytes { field })
.map_err(|err| match err {
rkyv::ser::serializers::CompositeSerializerError::SerializerError(e) => e.into(),
rkyv::ser::serializers::CompositeSerializerError::ScratchSpaceError(e) => {
Expand All @@ -94,73 +84,86 @@ impl Serialized {
})
}

pub fn deserialize<W>(bytes: &Serialized) -> Result<W, Error>
/*
* Internal fn to constructs an instance from raw bytes ptr.
*/
fn from_raw_ptr(
raw_allocated_ptr: *const u8,
raw_allocated_ptr_len: usize,
) -> PluginSerializedBytes {
let raw_ptr_bytes =
unsafe { std::slice::from_raw_parts(raw_allocated_ptr, raw_allocated_ptr_len) };

PluginSerializedBytes::from_slice(raw_ptr_bytes)
}

pub fn as_slice(&self) -> &[u8] {
self.field.as_slice()
}

pub fn as_ptr(&self) -> (*const u8, usize) {
(self.field.as_ptr(), self.field.len())
}

pub fn deserialize<W>(&self) -> Result<W, Error>
where
W: rkyv::Archive,
W::Archived: rkyv::Deserialize<W, rkyv::de::deserializers::SharedDeserializeMap>,
{
use anyhow::Context;
use rkyv::Deserialize;

let bytes = &bytes.field;
let archived = unsafe { rkyv::archived_root::<W>(&bytes[..]) };
let archived = unsafe { rkyv::archived_root::<W>(&self.field[..]) };

archived
.deserialize(&mut rkyv::de::deserializers::SharedDeserializeMap::new())
.with_context(|| format!("failed to deserialize `{}`", type_name::<W>()))
}
}

/// Convenient wrapper to Serialized::* to construct actual struct from raw
/// ptr. This is common workflow on both of runtime (host / plugin) to
/// deserialize struct from allocated / copied ptr.
///
/// # Safety
/// This is naturally unsafe by constructing bytes slice from raw ptr.
pub unsafe fn deserialize_from_ptr<W>(
raw_allocated_ptr: *const u8,
raw_allocated_ptr_len: i32,
) -> Result<W, Error>
where
W: rkyv::Archive,
W::Archived: rkyv::Deserialize<W, rkyv::de::deserializers::SharedDeserializeMap>,
{
// Create serialized bytes slice from ptr
let raw_ptr_bytes = unsafe {
std::slice::from_raw_parts(raw_allocated_ptr, raw_allocated_ptr_len.try_into()?)
};

let serialized = Serialized::new_for_plugin(raw_ptr_bytes, raw_allocated_ptr_len);
Serialized::deserialize(&serialized)
}

/// Deserialize `Fallible` struct from raw ptr. This is similar to
/// `deserialize_from_ptr` but for the struct requires bounds to the
/// SharedSerializeRegistry which cannot be Infallible. Internally this does
/// not call deserialize with Infallible deserializer, use
/// SharedDeserializeMap instead.
///
/// # Safety
/// This is unsafe by construting bytes slice from raw ptr also deserialize
/// it without slice bound check.
pub unsafe fn deserialize_from_ptr_fallible<W>(
raw_allocated_ptr: *const u8,
raw_allocated_ptr_len: i32,
) -> Result<W, Error>
where
W: rkyv::Archive,
W::Archived: rkyv::Deserialize<W, rkyv::de::deserializers::SharedDeserializeMap>,
{
// Create serialized bytes slice from ptr
let raw_ptr_bytes = unsafe {
std::slice::from_raw_parts(raw_allocated_ptr, raw_allocated_ptr_len.try_into()?)
};

let serialized = Serialized::new_for_plugin(raw_ptr_bytes, raw_allocated_ptr_len);
/// Simple wrapper around constructing PluginSerializedBytes from raw
/// ptr to call deserialize to support common workflow on both of runtime
/// (host / plugin) to instantiate a struct from allocated / copied ptr.
///
/// # Safety
/// This is naturally unsafe by constructing bytes slice from raw ptr.
pub unsafe fn deserialize_from_ptr<W>(
raw_allocated_ptr: *const u8,
raw_allocated_ptr_len: i32,
) -> Result<W, Error>
where
W: rkyv::Archive,
W::Archived: rkyv::Deserialize<W, rkyv::de::deserializers::SharedDeserializeMap>,
{
let serialized =
PluginSerializedBytes::from_raw_ptr(raw_allocated_ptr, raw_allocated_ptr_len as usize);

serialized.deserialize()
}

unsafe {
rkyv::from_bytes_unchecked(serialized.as_ref())
.map_err(|err| Error::msg("Failed to deserialize given ptr"))
}
/// Deserialize `Fallible` struct from raw ptr. This is similar to
/// `deserialize_from_ptr` but for the struct requires bounds to the
/// SharedSerializeRegistry which cannot be Infallible. Internally this does
/// not call deserialize with Infallible deserializer, use
/// SharedDeserializeMap instead.
///
/// # Safety
/// This is unsafe by construting bytes slice from raw ptr also deserialize
/// it without slice bound check.
pub unsafe fn deserialize_from_ptr_into_fallible<W>(
raw_allocated_ptr: *const u8,
raw_allocated_ptr_len: i32,
) -> Result<W, Error>
where
W: rkyv::Archive,
W::Archived: rkyv::Deserialize<W, rkyv::de::deserializers::SharedDeserializeMap>,
{
let serialized =
PluginSerializedBytes::from_raw_ptr(raw_allocated_ptr, raw_allocated_ptr_len as usize);

unsafe {
rkyv::from_bytes_unchecked(&serialized.field)
.map_err(|err| Error::msg("Failed to deserialize given ptr"))
}
}

Expand Down
32 changes: 14 additions & 18 deletions crates/swc_common/src/syntax_pos/hygiene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,10 @@ impl Mark {
pub fn is_descendant_of(mut self, ancestor: Mark) -> bool {
// This code path executed inside of the guest memory context.
// In here, preallocate memory for the context.
let serialized = crate::plugin::Serialized::serialize(&MutableMarkContext(0, 0, 0))
.expect("Should be serializable");
let serialized_ref = serialized.as_ref();
let ptr = serialized_ref.as_ptr();
let len = serialized_ref.len();
let serialized =
crate::plugin::PluginSerializedBytes::try_serialize(&MutableMarkContext(0, 0, 0))
.expect("Should be serializable");
let (ptr, len) = serialized.as_ptr();

// Calling host proxy fn. Inside of host proxy, host will
// write the result into allocated context in the guest memory space.
Expand All @@ -193,7 +192,7 @@ impl Mark {

// Deserialize result, assign / return values as needed.
let context: MutableMarkContext = unsafe {
crate::plugin::Serialized::deserialize_from_ptr(
crate::plugin::deserialize_from_ptr(
ptr,
len.try_into().expect("Should able to convert ptr length"),
)
Expand All @@ -220,18 +219,17 @@ impl Mark {
#[allow(unused_mut, unused_assignments)]
#[cfg(all(feature = "plugin-mode", target_arch = "wasm32"))]
pub fn least_ancestor(mut a: Mark, mut b: Mark) -> Mark {
let serialized = crate::plugin::Serialized::serialize(&MutableMarkContext(0, 0, 0))
.expect("Should be serializable");
let serialized_ref = serialized.as_ref();
let ptr = serialized_ref.as_ptr();
let len = serialized_ref.len();
let serialized =
crate::plugin::PluginSerializedBytes::try_serialize(&MutableMarkContext(0, 0, 0))
.expect("Should be serializable");
let (ptr, len) = serialized.as_ptr();

unsafe {
__mark_least_ancestor(a.0, b.0, ptr as _);
}

let context: MutableMarkContext = unsafe {
crate::plugin::Serialized::deserialize_from_ptr(
crate::plugin::deserialize_from_ptr(
ptr,
len.try_into().expect("Should able to convert ptr length"),
)
Expand Down Expand Up @@ -386,18 +384,16 @@ impl SyntaxContext {
#[cfg(all(feature = "plugin-mode", target_arch = "wasm32"))]
pub fn remove_mark(&mut self) -> Mark {
let context = MutableMarkContext(0, 0, 0);
let serialized =
crate::plugin::Serialized::serialize(&context).expect("Should be serializable");
let serialized_ref = serialized.as_ref();
let ptr = serialized_ref.as_ptr();
let len = serialized_ref.len();
let serialized = crate::plugin::PluginSerializedBytes::try_serialize(&context)
.expect("Should be serializable");
let (ptr, len) = serialized.as_ptr();

unsafe {
__syntax_context_remove_mark_proxy(self.0, ptr as _);
}

let context: MutableMarkContext = unsafe {
crate::plugin::Serialized::deserialize_from_ptr(
crate::plugin::deserialize_from_ptr(
ptr,
len.try_into().expect("Should able to convert ptr length"),
)
Expand Down
13 changes: 5 additions & 8 deletions crates/swc_plugin/src/handler.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use swc_common::{plugin::Serialized, sync::OnceCell};
use swc_common::{plugin::PluginSerializedBytes, sync::OnceCell};

use crate::pseudo_scoped_key::PseudoScopedKey;

Expand All @@ -18,16 +18,13 @@ pub struct PluginDiagnosticsEmitter;
impl swc_common::errors::Emitter for PluginDiagnosticsEmitter {
#[cfg_attr(not(target_arch = "wasm32"), allow(unused))]
fn emit(&mut self, db: &swc_common::errors::DiagnosticBuilder<'_>) {
let diag =
Serialized::serialize(&*db.diagnostic).expect("Should able to serialize Diagnostic");
let diag_ref = diag.as_ref();

let ptr = diag_ref.as_ptr() as i32;
let len = diag_ref.len() as i32;
let diag = PluginSerializedBytes::try_serialize(&*db.diagnostic)
.expect("Should able to serialize Diagnostic");
let (ptr, len) = diag.as_ptr();

#[cfg(target_arch = "wasm32")] // Allow testing
unsafe {
__emit_diagnostics(ptr, len);
__emit_diagnostics(ptr as i32, len as i32);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/swc_plugin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Reexports
pub use swc_common::{
chain,
plugin::{PluginError, Serialized},
plugin::{deserialize_from_ptr, PluginError, PluginSerializedBytes},
};

pub mod comments {
Expand Down
Loading

1 comment on commit 2ba8b39

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: 2ba8b39 Previous: 0e4a03c Ratio
es/full/minify/libraries/antd 1793059441 ns/iter (± 19419381) 1772791159 ns/iter (± 78467817) 1.01
es/full/minify/libraries/d3 458002080 ns/iter (± 8174077) 466364990 ns/iter (± 51301912) 0.98
es/full/minify/libraries/echarts 1767255416 ns/iter (± 27022818) 1816227662 ns/iter (± 217842083) 0.97
es/full/minify/libraries/jquery 105031772 ns/iter (± 1123954) 129542528 ns/iter (± 47596669) 0.81
es/full/minify/libraries/lodash 135658774 ns/iter (± 2026988) 160789279 ns/iter (± 7215569) 0.84
es/full/minify/libraries/moment 58412659 ns/iter (± 1688631) 69011659 ns/iter (± 3230079) 0.85
es/full/minify/libraries/react 19025866 ns/iter (± 624079) 20433374 ns/iter (± 2123365) 0.93
es/full/minify/libraries/terser 649137328 ns/iter (± 8763536) 646052205 ns/iter (± 11160829) 1.00
es/full/minify/libraries/three 595689678 ns/iter (± 8260417) 596606779 ns/iter (± 87425911) 1.00
es/full/minify/libraries/typescript 3794969811 ns/iter (± 41264280) 3783381764 ns/iter (± 377411365) 1.00
es/full/minify/libraries/victory 793618184 ns/iter (± 13120719) 765078488 ns/iter (± 46436322) 1.04
es/full/minify/libraries/vue 162883003 ns/iter (± 4093929) 157639120 ns/iter (± 13193365) 1.03
es/full/codegen/es3 33390 ns/iter (± 2164) 32674 ns/iter (± 700) 1.02
es/full/codegen/es5 33520 ns/iter (± 1437) 32421 ns/iter (± 808) 1.03
es/full/codegen/es2015 33348 ns/iter (± 2453) 32668 ns/iter (± 6188) 1.02
es/full/codegen/es2016 33027 ns/iter (± 1400) 32498 ns/iter (± 1061) 1.02
es/full/codegen/es2017 33145 ns/iter (± 1905) 32438 ns/iter (± 1815) 1.02
es/full/codegen/es2018 33363 ns/iter (± 1405) 32522 ns/iter (± 4958) 1.03
es/full/codegen/es2019 32743 ns/iter (± 1013) 32714 ns/iter (± 921) 1.00
es/full/codegen/es2020 33653 ns/iter (± 2063) 32345 ns/iter (± 600) 1.04
es/full/all/es3 197204060 ns/iter (± 6765255) 208943121 ns/iter (± 28156658) 0.94
es/full/all/es5 185450433 ns/iter (± 8415012) 190442358 ns/iter (± 56673963) 0.97
es/full/all/es2015 148429370 ns/iter (± 8675163) 167122727 ns/iter (± 46598022) 0.89
es/full/all/es2016 146390049 ns/iter (± 4563200) 163782319 ns/iter (± 40752940) 0.89
es/full/all/es2017 145679691 ns/iter (± 4869048) 165281731 ns/iter (± 23282599) 0.88
es/full/all/es2018 143298322 ns/iter (± 4795651) 165276077 ns/iter (± 31520258) 0.87
es/full/all/es2019 143258353 ns/iter (± 3914855) 163802190 ns/iter (± 36834062) 0.87
es/full/all/es2020 138333891 ns/iter (± 6273466) 148630830 ns/iter (± 28040043) 0.93
es/full/parser 711277 ns/iter (± 32187) 728682 ns/iter (± 23592) 0.98
es/full/base/fixer 29251 ns/iter (± 1188) 29644 ns/iter (± 1111) 0.99
es/full/base/resolver_and_hygiene 90854 ns/iter (± 10016) 90341 ns/iter (± 3965) 1.01
serialization of ast node 224 ns/iter (± 18) 217 ns/iter (± 4) 1.03
serialization of serde 239 ns/iter (± 23) 230 ns/iter (± 14) 1.04

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.