feat(plugin): Support lookup_char_pos of SourceMap (#4364)

This commit is contained in:
OJ Kwon 2022-04-18 22:25:44 -07:00 committed by Donny/강동윤
parent c769c95ac5
commit f06c862a9f
25 changed files with 397 additions and 149 deletions

8
Cargo.lock generated
View File

@ -2367,9 +2367,9 @@ checksum = "11000e6ba5020e53e7cc26f73b91ae7d5496b4977851479edb66b694c0675c21"
[[package]]
name = "rkyv"
version = "0.7.36"
version = "0.7.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5230ae2981a885590b0dc84e0b24c0ed23ad24f7adc0eb824b26cafa961f7c36"
checksum = "1f08c8062c1fe1253064043b8fc07bfea1b9702b71b4a86c11ea3588183b12e1"
dependencies = [
"bytecheck",
"hashbrown 0.12.0",
@ -2381,9 +2381,9 @@ dependencies = [
[[package]]
name = "rkyv_derive"
version = "0.7.36"
version = "0.7.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fc752d5925dbcb324522f3a4c93193d17f107b2e11810913aa3ad352fa01480"
checksum = "e289706df51226e84814bf6ba1a9e1013112ae29bc7a9878f73fce360520c403"
dependencies = [
"proc-macro2",
"quote",

View File

@ -467,9 +467,11 @@ impl Options {
}
let comments = comments.cloned();
let source_map = cm.clone();
crate::plugin::plugins(
Some(plugin_resolver),
comments,
source_map,
experimental,
plugin_context,
)
@ -494,7 +496,8 @@ impl Options {
swc_plugin_runner::cache::init_plugin_module_cache_once();
let comments = comments.cloned();
crate::plugin::plugins(None, comments, experimental, plugin_context)
let source_map = cm.clone();
crate::plugin::plugins(None, comments, source_map, experimental, plugin_context)
};
#[cfg(not(feature = "plugin"))]

View File

@ -45,6 +45,7 @@ pub struct PluginContext {
pub fn plugins(
resolver: Option<CachingResolver<NodeModulesResolver>>,
comments: Option<swc_common::comments::SingleThreadedComments>,
source_map: std::sync::Arc<swc_common::SourceMap>,
config: crate::config::JscExperimental,
plugin_context: PluginContext,
) -> impl Fold {
@ -52,6 +53,7 @@ pub fn plugins(
RustPlugins {
resolver,
comments,
source_map,
plugins: config.plugins,
plugin_context,
}
@ -67,6 +69,7 @@ struct RustPlugins {
resolver: Option<CachingResolver<NodeModulesResolver>>,
comments: Option<swc_common::comments::SingleThreadedComments>,
plugins: Option<Vec<PluginConfig>>,
source_map: std::sync::Arc<swc_common::SourceMap>,
plugin_context: PluginContext,
}
@ -141,6 +144,7 @@ impl RustPlugins {
config_json,
context_json,
should_enable_comments_proxy,
&self.source_map,
)?;
drop(transform_span_guard);
}
@ -188,6 +192,7 @@ impl RustPlugins {
config_json,
context_json,
should_enable_comments_proxy,
&self.source_map,
)?;
}
}

View File

@ -41,7 +41,7 @@ from_variant = {version = "0.1.3", path = "../from_variant"}
num-bigint = "0.4"
once_cell = "1.10.0"
parking_lot = {version = "0.12.0", optional = true}
rkyv = {version = "0.7.28", optional = true}
rkyv = { version = "0.7.37", optional = true }
rustc-hash = "1.1.0"
serde = {version = "1.0.119", features = ["derive"]}
siphasher = "0.3.9"

View File

@ -126,4 +126,34 @@ impl Serialized {
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);
unsafe {
rkyv::from_bytes_unchecked(serialized.as_ref())
.map_err(|err| Error::msg("Failed to deserialize given ptr"))
}
}
}

View File

@ -24,7 +24,7 @@ rkyv-impl = ["rkyv", "swc_common/rkyv-impl"]
arbitrary = {version = "1", optional = true, features = ["derive"]}
is-macro = "0.2.0"
num-bigint = {version = "0.4", features = ["serde"]}
rkyv = {version = "0.7.28", optional = true}
rkyv = { version = "0.7.37", optional = true }
serde = {version = "1.0.133", features = ["derive"]}
string_enum = {version = "0.3.1", path = "../string_enum"}
swc_atoms = {version = "0.2", path = "../swc_atoms"}

View File

@ -13,6 +13,7 @@ pub mod comments {
pub mod source_map {
pub use swc_common::source_map::{CharPos, Loc, MultiByteChar, NonNarrowChar, SourceFile};
pub use swc_plugin_proxy::PluginSourceMapProxy;
}
pub mod utils {
@ -46,7 +47,7 @@ pub mod environment {
// We don't set target cfg as it'll block macro expansions
// in ide (i.e rust-analyzer) or non-wasm target `cargo check`
pub use swc_plugin_macro::plugin_transform;
use swc_plugin_proxy::PluginCommentsProxy;
use swc_plugin_proxy::{PluginCommentsProxy, PluginSourceMapProxy};
#[cfg(target_arch = "wasm32")]
mod allocation;
#[cfg(target_arch = "wasm32")]
@ -64,6 +65,10 @@ pub struct TransformPluginProgramMetadata {
/// This is a proxy to the actual data lives in the host. Only when plugin
/// attempts to read these it'll ask to the host to get values.
pub comments: Option<PluginCommentsProxy>,
/// Proxy to the sourceMap for the Program passed into plugin.
/// This is a proxy to the actual data lives in the host. Only when plugin
/// attempts to read these it'll ask to the host to get values.
pub source_map: PluginSourceMapProxy,
/// Stringified JSON value for given plugin's configuration.
/// This is readonly. Changing value in plugin doesn't affect host's
/// behavior.

View File

@ -103,6 +103,7 @@ fn handle_func(func: ItemFn) -> TokenStream {
let plugin_comments_proxy = if should_enable_comments_proxy == 1 { Some(swc_plugin::comments::PluginCommentsProxy) } else { None };
let mut metadata = swc_plugin::TransformPluginProgramMetadata {
comments: plugin_comments_proxy,
source_map: swc_plugin::source_map::PluginSourceMapProxy,
plugin_config: config,
transform_context: context
};

View File

@ -18,7 +18,7 @@ plugin-mode = []
[dependencies]
better_scoped_tls = { version = "0.1.0", path = "../better_scoped_tls" }
rkyv = "0.7.36"
rkyv = "0.7.37"
swc_common = { version = "0.17.20", path = "../swc_common", features = [
"plugin-base",
] }

View File

@ -3,6 +3,5 @@ mod plugin_comments_proxy;
#[cfg(feature = "plugin-rt")]
pub use host_comments_storage::{HostCommentsStorage, COMMENTS};
pub use plugin_comments_proxy::CommentsVecPtr;
#[cfg(feature = "plugin-mode")]
pub use plugin_comments_proxy::PluginCommentsProxy;

View File

@ -1,11 +1,13 @@
#[cfg(feature = "plugin-mode")]
use swc_common::plugin::Serialized;
#[cfg(feature = "plugin-mode")]
use swc_common::{
comments::{Comment, Comments},
BytePos,
};
#[cfg(feature = "plugin-mode")]
#[cfg_attr(not(target_arch = "wasm32"), allow(unused))]
use crate::memory_interop::read_returned_result_from_host;
#[cfg(target_arch = "wasm32")]
extern "C" {
fn __copy_comment_to_host_env(bytes_ptr: i32, bytes_ptr_len: i32);
@ -24,10 +26,6 @@ extern "C" {
fn __add_pure_comment_proxy(byte_pos: u32);
}
/// A struct to exchange allocated Vec<Comment> between memory spaces.
#[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
pub struct CommentsVecPtr(pub i32, pub i32);
/// A struct implements `swc_common::comments::Comments` for the plugin.
/// This is a proxy to the host's comments reference while plugin transform
/// runs, does not contain actual data but lazily requests to the host for each
@ -52,7 +50,8 @@ impl PluginCommentsProxy {
{
#[cfg(target_arch = "wasm32")]
{
let serialized = Serialized::serialize(value).expect("Should able to serialize value");
let serialized = swc_common::plugin::Serialized::serialize(value)
.expect("Should able to serialize value");
let serialized_comment_ptr_ref = serialized.as_ref();
unsafe {
// We need to copy PluginCommentProxy's param for add_leading (Comment, or
@ -69,54 +68,6 @@ impl PluginCommentsProxy {
}
}
}
/// Utility wrapper to call host fn which returns a Comment or Vec<Comment>.
#[cfg_attr(not(target_arch = "wasm32"), allow(unused))]
fn read_returned_comments_from_host<F, R>(&self, f: F) -> Option<R>
where
F: FnOnce(i32) -> i32,
R: rkyv::Archive,
R::Archived: rkyv::Deserialize<R, rkyv::Infallible>,
{
// Allocate CommentsVecPtr to get return value from the host
let comments_vec_ptr = CommentsVecPtr(0, 0);
let serialized_comments_vec_ptr = Serialized::serialize(&comments_vec_ptr)
.expect("Should able to serialize CommentsVecPtr");
let serialized_comments_vec_ptr_ref = serialized_comments_vec_ptr.as_ref();
let serialized_comments_vec_ptr_raw_ptr = serialized_comments_vec_ptr_ref.as_ptr();
let serialized_comments_vec_ptr_raw_len = serialized_comments_vec_ptr_ref.len();
#[cfg(target_arch = "wasm32")]
{
let ret = f(serialized_comments_vec_ptr_raw_ptr as _);
// Host fn call completes: by contract in comments_proxy, if return value is 0
// we know there's no value to read. Otherwise, we know host filled in
// CommentsVecPtr to the pointer for the actual value for the
// results.
if ret == 0 {
return None;
}
}
// Now reconstruct CommentsVecPtr to reveal ptr to the allocated
// Vec<Comments>
let comments_vec_ptr: CommentsVecPtr = unsafe {
Serialized::deserialize_from_ptr(
serialized_comments_vec_ptr_raw_ptr,
serialized_comments_vec_ptr_raw_len
.try_into()
.expect("Should able to convert ptr length"),
)
.expect("Should able to deserialize CommentsVecPtr")
};
// Using CommentsVecPtr's value, reconstruct actual Comment, or Vec<Comments>
Some(unsafe {
Serialized::deserialize_from_ptr(comments_vec_ptr.0 as _, comments_vec_ptr.1)
.expect("Returned comments should be serializable")
})
}
}
#[cfg(feature = "plugin-mode")]
@ -160,7 +111,7 @@ impl Comments for PluginCommentsProxy {
fn take_leading(&self, pos: BytePos) -> Option<Vec<Comment>> {
#[cfg(target_arch = "wasm32")]
return self.read_returned_comments_from_host(|serialized_ptr| unsafe {
return read_returned_result_from_host(|serialized_ptr| unsafe {
__take_leading_comments_proxy(pos.0, serialized_ptr)
});
@ -170,7 +121,7 @@ impl Comments for PluginCommentsProxy {
fn get_leading(&self, pos: BytePos) -> Option<Vec<Comment>> {
#[cfg(target_arch = "wasm32")]
return self.read_returned_comments_from_host(|serialized_ptr| unsafe {
return read_returned_result_from_host(|serialized_ptr| unsafe {
__get_leading_comments_proxy(pos.0, serialized_ptr)
});
@ -216,7 +167,7 @@ impl Comments for PluginCommentsProxy {
fn take_trailing(&self, pos: BytePos) -> Option<Vec<Comment>> {
#[cfg(target_arch = "wasm32")]
return self.read_returned_comments_from_host(|serialized_ptr| unsafe {
return read_returned_result_from_host(|serialized_ptr| unsafe {
__take_trailing_comments_proxy(pos.0, serialized_ptr)
});
@ -226,7 +177,7 @@ impl Comments for PluginCommentsProxy {
fn get_trailing(&self, pos: BytePos) -> Option<Vec<Comment>> {
#[cfg(target_arch = "wasm32")]
return self.read_returned_comments_from_host(|serialized_ptr| unsafe {
return read_returned_result_from_host(|serialized_ptr| unsafe {
__get_trailing_comments_proxy(pos.0, serialized_ptr)
});

View File

@ -1,9 +1,10 @@
mod comments;
mod memory_interop;
mod source_map;
pub use comments::CommentsVecPtr;
#[cfg(feature = "plugin-mode")]
pub use comments::PluginCommentsProxy;
#[cfg(feature = "plugin-rt")]
pub use comments::{HostCommentsStorage, COMMENTS};
pub use memory_interop::AllocatedBytesPtr;
#[cfg(feature = "plugin-mode")]
pub use source_map::PluginSourceMapProxy;

View File

@ -0,0 +1,6 @@
mod read_returned_result_from_host;
pub use read_returned_result_from_host::AllocatedBytesPtr;
#[cfg_attr(not(target_arch = "wasm32"), allow(unused))]
pub(crate) use read_returned_result_from_host::{
read_returned_result_from_host, read_returned_result_from_host_fallible,
};

View File

@ -0,0 +1,129 @@
#[cfg_attr(not(target_arch = "wasm32"), allow(unused))]
use swc_common::plugin::Serialized;
/// A struct to exchange allocated data between memory spaces.
#[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
pub struct AllocatedBytesPtr(pub i32, pub i32);
/// Performs an interop while calling host fn to get non-determined size return
/// values from the host. This is based on the contract between host's imported
/// fn, by imported fn allocated memory for the guest space then hand over its
/// ptr and length via a struct. Refer plugin_runner/imported_fn/mod.rs for the
/// detail.
///
/// Returns a struct AllocatedBytesPtr to the ptr for actual return value if
/// host fn allocated return value, None otherwise.
#[cfg_attr(not(target_arch = "wasm32"), allow(unused))]
fn read_returned_result_from_host_inner<F>(f: F) -> Option<AllocatedBytesPtr>
where
F: FnOnce(i32) -> i32,
{
// Allocate AllocatedBytesPtr to get return value from the host
let allocated_bytes_ptr = AllocatedBytesPtr(0, 0);
let serialized_allocated_bytes_ptr = Serialized::serialize(&allocated_bytes_ptr)
.expect("Should able to serialize AllocatedBytesPtr");
let serialized_allocated_bytes_ptr_ref = serialized_allocated_bytes_ptr.as_ref();
let serialized_allocated_bytes_raw_ptr = serialized_allocated_bytes_ptr_ref.as_ptr();
#[cfg(target_arch = "wasm32")]
{
let ret = f(serialized_allocated_bytes_raw_ptr as _);
// Host fn call completes: by contract in host proxy, if return value is 0
// we know there's no value to read. Otherwise, we know host filled in
// AllocatedBytesPtr to the pointer for the actual value for the
// results.
if ret == 0 {
return None;
}
}
// Return reconstructted AllocatedBytesPtr to reveal ptr to the allocated bytes
Some(unsafe {
Serialized::deserialize_from_ptr(
serialized_allocated_bytes_raw_ptr,
serialized_allocated_bytes_ptr_ref
.len()
.try_into()
.expect("Should able to convert ptr length"),
)
.expect("Should able to deserialize AllocatedBytesPtr")
})
}
/// Performs deserialization to the actual return value type from returned ptr.
///
/// This fn is for the Infallible types works for most of the cases.
#[cfg_attr(not(target_arch = "wasm32"), allow(unused))]
pub fn read_returned_result_from_host<F, R>(f: F) -> Option<R>
where
F: FnOnce(i32) -> i32,
R: rkyv::Archive,
R::Archived: rkyv::Deserialize<R, rkyv::Infallible>,
{
let allocated_returned_value_ptr = read_returned_result_from_host_inner(f);
// Using AllocatedBytesPtr's value, reconstruct actual return value
allocated_returned_value_ptr.map(|allocated_returned_value_ptr| unsafe {
Serialized::deserialize_from_ptr(
allocated_returned_value_ptr.0 as _,
allocated_returned_value_ptr.1,
)
.expect("Returned value should be serializable")
})
}
/// Performs deserialization to the actual return value type from returned ptr.
///
/// This behaves same as read_returned_result_from_host, the only difference is
/// this is for the `Fallible` struct to deserialize. If a struct contains
/// shared pointers like Arc, Rc rkyv requires trait bounds to the
/// SharedSerializeRegistry which cannot be infallible.
#[cfg_attr(not(target_arch = "wasm32"), allow(unused))]
pub fn read_returned_result_from_host_fallible<F, R>(f: F) -> Option<R>
where
F: FnOnce(i32) -> i32,
R: rkyv::Archive,
R::Archived: rkyv::Deserialize<R, rkyv::de::deserializers::SharedDeserializeMap>,
{
// Allocate AllocatedBytesPtr to get return value from the host
let allocated_bytes_ptr = AllocatedBytesPtr(0, 0);
let serialized_allocated_bytes_ptr = Serialized::serialize(&allocated_bytes_ptr)
.expect("Should able to serialize AllocatedBytesPtr");
let serialized_allocated_bytes_ptr_ref = serialized_allocated_bytes_ptr.as_ref();
let serialized_allocated_bytes_raw_ptr = serialized_allocated_bytes_ptr_ref.as_ptr();
#[cfg(target_arch = "wasm32")]
{
let ret = f(serialized_allocated_bytes_raw_ptr as _);
// Host fn call completes: by contract in host proxy, if return value is 0
// we know there's no value to read. Otherwise, we know host filled in
// AllocatedBytesPtr to the pointer for the actual value for the
// results.
if ret == 0 {
return None;
}
}
// Now reconstruct AllocatedBytesPtr to reveal ptr to the allocated bytes
let allocated_returned_value_ptr: AllocatedBytesPtr = unsafe {
Serialized::deserialize_from_ptr(
serialized_allocated_bytes_raw_ptr,
serialized_allocated_bytes_ptr_ref
.len()
.try_into()
.expect("Should able to convert ptr length"),
)
.expect("Should able to deserialize AllocatedBytesPtr")
};
// Using AllocatedBytesPtr's value, reconstruct actual return value
Some(unsafe {
Serialized::deserialize_from_ptr_fallible(
allocated_returned_value_ptr.0 as _,
allocated_returned_value_ptr.1,
)
.expect("Returned value should be serializable")
})
}

View File

@ -1,2 +1,3 @@
mod plugin_source_map_proxy;
#[cfg(feature = "plugin-mode")]
pub use plugin_source_map_proxy::PluginSourceMapProxy;

View File

@ -1 +1,32 @@
pub struct PluginSourceMapProxy {}
#[cfg(feature = "plugin-mode")]
use swc_common::{BytePos, Loc};
#[cfg(feature = "plugin-mode")]
#[cfg_attr(not(target_arch = "wasm32"), allow(unused))]
use crate::memory_interop::read_returned_result_from_host_fallible;
#[cfg(target_arch = "wasm32")]
extern "C" {
fn __lookup_char_pos_source_map_proxy(byte_pos: u32, allocated_ret_ptr: i32) -> i32;
}
#[cfg(feature = "plugin-mode")]
#[derive(Debug, Copy, Clone)]
pub struct PluginSourceMapProxy;
/// Subset of SourceMap interface supported in plugin.
/// Unlike `Comments`, this does not fully implement `SourceMap`.
#[cfg(feature = "plugin-mode")]
impl PluginSourceMapProxy {
#[cfg_attr(not(target_arch = "wasm32"), allow(unused))]
pub fn lookup_char_pos(&self, pos: BytePos) -> Loc {
#[cfg(target_arch = "wasm32")]
return read_returned_result_from_host_fallible(|serialized_ptr| unsafe {
__lookup_char_pos_source_map_proxy(pos.0, serialized_ptr)
})
.expect("Host should return Loc");
#[cfg(not(target_arch = "wasm32"))]
unimplemented!("Sourcemap proxy cannot be called in this context")
}
}

View File

@ -6,10 +6,10 @@ use swc_common::{
plugin::Serialized,
BytePos,
};
use swc_plugin_proxy::{CommentsVecPtr, COMMENTS};
use swc_plugin_proxy::COMMENTS;
use wasmer::{LazyInit, Memory, NativeFunc};
use crate::memory_interop::{copy_bytes_into_host, write_into_memory_view};
use crate::memory_interop::{allocate_return_values_into_guest, copy_bytes_into_host};
/// External environment state for imported (declared in host, injected into
/// guest) fn for comments proxy.
@ -99,44 +99,6 @@ where
default
}
/// Set `return` value to pass into guest from functions returning values with
/// non-deterministic size like `Vec<Comment>`. Guest pre-allocates a struct to
/// contain ptr to the value, host in here allocates guest memory for the actual
/// value then returns its ptr with length to the preallocated struct.
fn allocate_return_values_into_guest(
memory: &Memory,
alloc_guest_memory: &NativeFunc<u32, i32>,
allocated_ret_ptr: i32,
serialized_bytes: &Serialized,
) {
let serialized_bytes_len = serialized_bytes.as_ref().len();
let (allocated_ptr, allocated_ptr_len) =
write_into_memory_view(memory, serialized_bytes, |_| {
// In most cases our host-plugin trampoline works in a way that
// plugin pre-allocates
// memory before calling host imported fn. But in case of
// comments return value is Vec<Comments> which
// guest cannot predetermine size to allocate, instead
// let host allocate by calling guest's alloc via attached
// hostenvironment.
alloc_guest_memory
.call(
serialized_bytes_len
.try_into()
.expect("Should be able to convert size"),
)
.expect("Should able to allocate memory in the plugin")
});
// Retuning (allocated_ptr, len) into caller (plugin)
let comment_ptr_serialized =
Serialized::serialize(&CommentsVecPtr(allocated_ptr, allocated_ptr_len))
.expect("Should be serializable");
write_into_memory_view(memory, &comment_ptr_serialized, |_| allocated_ret_ptr);
}
/// Common logics for add_*_comment/comments.
fn add_comments_inner<F>(env: &CommentHostEnvironment, byte_pos: u32, f: F)
where

View File

@ -14,7 +14,7 @@
* get_leading_comments_proxy()get_leading()
*
*
* CommentsVecPtr(ptr, len)
* AllocatedBytesPtr(p,len)
*
*
*
@ -37,13 +37,14 @@
* read, deserialize memory host wrote.
* - In case of `get_leading`, returned value is non-deterministic vec
* (`Vec<Comments>`) guest cannot preallocate with specific length. Instead,
* guest passes a fixed size struct (CommentsVecPtr), once host allocates
* guest passes a fixed size struct (AllocatedBytesPtr), once host allocates
* actual vec into guest it'll write pointer to the vec into the struct.
*/
use std::sync::Arc;
use parking_lot::Mutex;
use swc_common::SourceMap;
use wasmer::{imports, Function, ImportObject, Module};
use crate::{
@ -71,11 +72,14 @@ mod span;
use handler::*;
use hygiene::*;
use self::source_map::{lookup_char_pos_proxy, SourceMapHostEnvironment};
/// Create an ImportObject includes functions to be imported from host to the
/// plugins.
pub(crate) fn build_import_object(
module: &Module,
transform_result: &Arc<Mutex<Vec<u8>>>,
source_map: Arc<SourceMap>,
) -> ImportObject {
let wasmer_store = module.store();
@ -199,6 +203,16 @@ pub(crate) fn build_import_object(
let add_pure_comment_fn_decl = Function::new_native(wasmer_store, add_pure_comment_proxy);
// source_map
let source_map_buffer = Arc::new(Mutex::new(vec![]));
let source_map = Arc::new(Mutex::new(source_map));
let lookup_char_pos_source_map_fn_decl = Function::new_native_with_env(
wasmer_store,
SourceMapHostEnvironment::new(&source_map, &source_map_buffer),
lookup_char_pos_proxy,
);
imports! {
"env" => {
// transform
@ -232,6 +246,8 @@ pub(crate) fn build_import_object(
"__take_trailing_comments_proxy" => take_trailing_comments_fn_decl,
"__get_trailing_comments_proxy" => get_trailing_comments_fn_decl,
"__add_pure_comment_proxy" => add_pure_comment_fn_decl,
// source_map
"__lookup_char_pos_source_map_proxy" =>lookup_char_pos_source_map_fn_decl,
}
}
}

View File

@ -2,9 +2,9 @@ use std::sync::Arc;
use parking_lot::Mutex;
use swc_common::{plugin::Serialized, BytePos, SourceMap};
use wasmer::{LazyInit, Memory};
use wasmer::{LazyInit, Memory, NativeFunc};
use crate::memory_interop::write_into_memory_view;
use crate::memory_interop::allocate_return_values_into_guest;
/// External environment state for imported (declared in host, injected into
/// guest) fn for source map proxy.
@ -12,24 +12,50 @@ use crate::memory_interop::write_into_memory_view;
pub struct SourceMapHostEnvironment {
#[wasmer(export)]
pub memory: wasmer::LazyInit<Memory>,
/// Attached imported fn `__alloc` to the hostenvironment to allow any other
/// imported fn can allocate guest's memory space from host runtime.
#[wasmer(export(name = "__alloc"))]
pub alloc_guest_memory: LazyInit<NativeFunc<u32, i32>>,
pub source_map: Arc<Mutex<Arc<SourceMap>>>,
/// A buffer to non-determined size of return value from the host.
pub mutable_source_map_buffer: Arc<Mutex<Vec<u8>>>,
}
impl SourceMapHostEnvironment {
pub fn new(source_map: &Arc<Mutex<Arc<SourceMap>>>) -> SourceMapHostEnvironment {
pub fn new(
source_map: &Arc<Mutex<Arc<SourceMap>>>,
mutable_source_map_buffer: &Arc<Mutex<Vec<u8>>>,
) -> SourceMapHostEnvironment {
SourceMapHostEnvironment {
memory: LazyInit::default(),
alloc_guest_memory: LazyInit::default(),
source_map: source_map.clone(),
mutable_source_map_buffer: mutable_source_map_buffer.clone(),
}
}
}
pub fn lookup_char_pos_proxy(env: &SourceMapHostEnvironment, byte_pos: u32, allocated_ptr: i32) {
let ret = (env.source_map.lock()).lookup_char_pos(BytePos(byte_pos));
pub fn lookup_char_pos_proxy(
env: &SourceMapHostEnvironment,
byte_pos: u32,
allocated_ret_ptr: i32,
) -> i32 {
if let Some(memory) = env.memory_ref() {
let serialized_bytes = Serialized::serialize(&ret).expect("Should be serializable");
let ret = (env.source_map.lock()).lookup_char_pos(BytePos(byte_pos));
let serialized_loc_bytes = Serialized::serialize(&ret).expect("Should be serializable");
write_into_memory_view(memory, &serialized_bytes, |_| allocated_ptr);
if let Some(alloc_guest_memory) = env.alloc_guest_memory_ref() {
allocate_return_values_into_guest(
memory,
alloc_guest_memory,
allocated_ret_ptr,
&serialized_loc_bytes,
);
1
} else {
0
}
} else {
0
}
}

View File

@ -1,8 +1,8 @@
use std::path::Path;
use std::{path::Path, sync::Arc};
use anyhow::{Context, Error};
use once_cell::sync::Lazy;
use swc_common::plugin::Serialized;
use swc_common::{plugin::Serialized, SourceMap};
use transform_executor::TransformExecutor;
pub mod cache;
@ -13,6 +13,7 @@ mod memory_interop;
mod transform_executor;
// entrypoint fn swc calls to perform its transform via plugin.
#[allow(clippy::too_many_arguments)]
pub fn apply_transform_plugin(
plugin_name: &str,
path: &Path,
@ -21,9 +22,10 @@ pub fn apply_transform_plugin(
config_json: Serialized,
context_json: Serialized,
should_enable_comments_proxy: bool,
source_map: &Arc<SourceMap>,
) -> Result<Serialized, Error> {
(|| -> Result<_, Error> {
let mut transform_tracker = TransformExecutor::new(path, cache)?;
let mut transform_tracker = TransformExecutor::new(path, cache, source_map)?;
let should_enable_comments_proxy = if should_enable_comments_proxy { 1 } else { 0 };
transform_tracker.transform(
&program,

View File

@ -2,6 +2,7 @@ use std::{env, sync::Arc};
use anyhow::{Context, Error};
use parking_lot::Mutex;
use swc_common::SourceMap;
use wasmer::{ChainableNamedResolver, Instance};
use wasmer_wasi::{is_wasi_module, WasiState};
@ -11,13 +12,14 @@ use crate::imported_fn::build_import_object;
pub fn load_plugin(
plugin_path: &std::path::Path,
cache: &once_cell::sync::Lazy<crate::cache::PluginModuleCache>,
source_map: &Arc<SourceMap>,
) -> Result<(Instance, Arc<Mutex<Vec<u8>>>), Error> {
let module = cache.load_module(plugin_path);
return match module {
Ok(module) => {
let transform_result: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(vec![]));
let import_object = build_import_object(&module, &transform_result);
let import_object = build_import_object(&module, &transform_result, source_map.clone());
// Plugin binary can be either wasm32-wasi or wasm32-unknown-unknown.
// Wasi specific env need to be initialized if given module targets wasm32-wasi.

View File

@ -1,5 +1,6 @@
use swc_common::plugin::Serialized;
use wasmer::{Array, Memory, WasmPtr};
use swc_plugin_proxy::AllocatedBytesPtr;
use wasmer::{Array, Memory, NativeFunc, WasmPtr};
#[tracing::instrument(level = "info", skip_all)]
pub fn copy_bytes_into_host(memory: &Memory, bytes_ptr: i32, bytes_ptr_len: i32) -> Vec<u8> {
@ -61,3 +62,41 @@ where
.expect("Should be able to convert to i32"),
)
}
/// Set `return` value to pass into guest from functions returning values with
/// non-deterministic size like `Vec<Comment>`. Guest pre-allocates a struct to
/// contain ptr to the value, host in here allocates guest memory for the actual
/// value then returns its ptr with length to the preallocated struct.
pub fn allocate_return_values_into_guest(
memory: &Memory,
alloc_guest_memory: &NativeFunc<u32, i32>,
allocated_ret_ptr: i32,
serialized_bytes: &Serialized,
) {
let serialized_bytes_len = serialized_bytes.as_ref().len();
let (allocated_ptr, allocated_ptr_len) =
write_into_memory_view(memory, serialized_bytes, |_| {
// In most cases our host-plugin trampoline works in a way that
// plugin pre-allocates
// memory before calling host imported fn. But in case of
// comments return value is Vec<Comments> which
// guest cannot predetermine size to allocate, instead
// let host allocate by calling guest's alloc via attached
// hostenvironment.
alloc_guest_memory
.call(
serialized_bytes_len
.try_into()
.expect("Should be able to convert size"),
)
.expect("Should able to allocate memory in the plugin")
});
// Retuning (allocated_ptr, len) into caller (plugin)
let comment_ptr_serialized =
Serialized::serialize(&AllocatedBytesPtr(allocated_ptr, allocated_ptr_len))
.expect("Should be serializable");
write_into_memory_view(memory, &comment_ptr_serialized, |_| allocated_ret_ptr);
}

View File

@ -2,7 +2,10 @@ use std::sync::Arc;
use anyhow::{anyhow, Error};
use parking_lot::Mutex;
use swc_common::plugin::{PluginError, Serialized};
use swc_common::{
plugin::{PluginError, Serialized},
SourceMap,
};
use wasmer::Instance;
use crate::memory_interop::write_into_memory_view;
@ -24,12 +27,14 @@ pub struct TransformExecutor {
}
impl TransformExecutor {
#[tracing::instrument(level = "info", skip(cache))]
#[tracing::instrument(level = "info", skip(cache, source_map))]
pub fn new(
path: &std::path::Path,
cache: &once_cell::sync::Lazy<crate::cache::PluginModuleCache>,
source_map: &Arc<SourceMap>,
) -> Result<TransformExecutor, Error> {
let (instance, transform_result) = crate::load_plugin::load_plugin(path, cache)?;
let (instance, transform_result) =
crate::load_plugin::load_plugin(path, cache, source_map)?;
let tracker = TransformExecutor {
exported_plugin_transform: instance

View File

@ -95,6 +95,7 @@ fn internal() -> Result<(), Error> {
config,
context,
false,
&cm,
)
.expect("Plugin should apply transform");
@ -144,6 +145,7 @@ fn internal() -> Result<(), Error> {
config,
context,
false,
&cm,
)
.expect("Plugin should apply transform")
});
@ -179,6 +181,7 @@ fn internal() -> Result<(), Error> {
Serialized::serialize(&"{sourceFileName: 'multiple_plugin_test'}".to_string())
.expect("Should serializable"),
false,
&cm,
)
.expect("Plugin should apply transform");
@ -192,6 +195,7 @@ fn internal() -> Result<(), Error> {
Serialized::serialize(&"{sourceFileName: 'multiple_plugin_test2'}".to_string())
.expect("Should serializable"),
false,
&cm,
)
.expect("Plugin should apply transform");

View File

@ -183,6 +183,12 @@ dependencies = [
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "hashbrown"
version = "0.12.0"
@ -209,6 +215,16 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "indexmap"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee"
dependencies = [
"autocfg",
"hashbrown 0.11.2",
]
[[package]]
name = "is-macro"
version = "0.2.0"
@ -480,12 +496,12 @@ dependencies = [
[[package]]
name = "rkyv"
version = "0.7.36"
version = "0.7.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5230ae2981a885590b0dc84e0b24c0ed23ad24f7adc0eb824b26cafa961f7c36"
checksum = "1f08c8062c1fe1253064043b8fc07bfea1b9702b71b4a86c11ea3588183b12e1"
dependencies = [
"bytecheck",
"hashbrown",
"hashbrown 0.12.0",
"ptr_meta",
"rend",
"rkyv_derive",
@ -494,9 +510,9 @@ dependencies = [
[[package]]
name = "rkyv_derive"
version = "0.7.36"
version = "0.7.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fc752d5925dbcb324522f3a4c93193d17f107b2e11810913aa3ad352fa01480"
checksum = "e289706df51226e84814bf6ba1a9e1013112ae29bc7a9878f73fce360520c403"
dependencies = [
"proc-macro2",
"quote",
@ -612,7 +628,7 @@ dependencies = [
[[package]]
name = "swc_common"
version = "0.17.19"
version = "0.17.21"
dependencies = [
"ahash",
"anyhow",
@ -638,7 +654,7 @@ dependencies = [
[[package]]
name = "swc_ecma_ast"
version = "0.73.1"
version = "0.76.0"
dependencies = [
"is-macro",
"num-bigint",
@ -650,9 +666,22 @@ dependencies = [
"unicode-id",
]
[[package]]
name = "swc_ecma_utils"
version = "0.81.0"
dependencies = [
"indexmap",
"once_cell",
"swc_atoms",
"swc_common",
"swc_ecma_ast",
"swc_ecma_visit",
"tracing",
]
[[package]]
name = "swc_ecma_visit"
version = "0.59.0"
version = "0.62.0"
dependencies = [
"num-bigint",
"swc_atoms",
@ -682,7 +711,7 @@ dependencies = [
[[package]]
name = "swc_macros_common"
version = "0.3.3"
version = "0.3.4"
dependencies = [
"pmutil",
"proc-macro2",
@ -692,23 +721,15 @@ dependencies = [
[[package]]
name = "swc_plugin"
version = "0.41.0"
version = "0.47.0"
dependencies = [
"swc_atoms",
"swc_common",
"swc_ecma_ast",
"swc_ecma_utils",
"swc_ecma_visit",
"swc_plugin_comments",
"swc_plugin_macro",
]
[[package]]
name = "swc_plugin_comments"
version = "0.1.0"
dependencies = [
"better_scoped_tls",
"rkyv",
"swc_common",
"swc_plugin_proxy",
]
[[package]]
@ -720,6 +741,15 @@ dependencies = [
"syn",
]
[[package]]
name = "swc_plugin_proxy"
version = "0.1.2"
dependencies = [
"better_scoped_tls",
"rkyv",
"swc_common",
]
[[package]]
name = "swc_visit"
version = "0.3.0"