mirror of
https://github.com/swc-project/swc.git
synced 2024-10-04 04:07:18 +03:00
refactor(plugin): Refactor transform executor (#5147)
This commit is contained in:
parent
07ffb76793
commit
e8214babf5
@ -95,8 +95,10 @@ impl RustPlugins {
|
||||
inner: self.comments.clone(),
|
||||
},
|
||||
|| {
|
||||
let span = tracing::span!(tracing::Level::INFO, "serialize_program").entered();
|
||||
let program = VersionedSerializable::new(n);
|
||||
let mut serialized = PluginSerializedBytes::try_serialize(&program)?;
|
||||
let mut serialized_program = PluginSerializedBytes::try_serialize(&program)?;
|
||||
drop(span);
|
||||
|
||||
// Run plugin transformation against current program.
|
||||
// We do not serialize / deserialize between each plugin execution but
|
||||
@ -106,29 +108,6 @@ impl RustPlugins {
|
||||
// transform.
|
||||
if let Some(plugins) = &self.plugins {
|
||||
for p in plugins {
|
||||
let span = tracing::span!(
|
||||
tracing::Level::INFO,
|
||||
"serialize_context",
|
||||
plugin_module = p.0.as_str()
|
||||
);
|
||||
let context_span_guard = span.enter();
|
||||
|
||||
let config_json = serde_json::to_string(&p.1)
|
||||
.context("Failed to serialize plugin config as json")
|
||||
.and_then(|value| {
|
||||
PluginSerializedBytes::try_serialize(&VersionedSerializable::new(
|
||||
value,
|
||||
))
|
||||
})?;
|
||||
|
||||
let context_json = serde_json::to_string(&self.plugin_context)
|
||||
.context("Failed to serialize plugin context as json")
|
||||
.and_then(|value| {
|
||||
PluginSerializedBytes::try_serialize(&VersionedSerializable::new(
|
||||
value,
|
||||
))
|
||||
})?;
|
||||
|
||||
let resolved_path = self
|
||||
.resolver
|
||||
.as_ref()
|
||||
@ -140,31 +119,66 @@ impl RustPlugins {
|
||||
} else {
|
||||
anyhow::bail!("Failed to resolve plugin path: {:?}", resolved_path);
|
||||
};
|
||||
|
||||
let mut transform_plugin_executor =
|
||||
swc_plugin_runner::create_plugin_transform_executor(
|
||||
&path,
|
||||
&swc_plugin_runner::cache::PLUGIN_MODULE_CACHE,
|
||||
&self.source_map,
|
||||
)?;
|
||||
|
||||
let span = tracing::span!(
|
||||
tracing::Level::INFO,
|
||||
"serialize_context",
|
||||
plugin_module = p.0.as_str()
|
||||
);
|
||||
let context_span_guard = span.enter();
|
||||
|
||||
let serialized_config_json = serde_json::to_string(&p.1)
|
||||
.context("Failed to serialize plugin config as json")
|
||||
.and_then(|value| {
|
||||
PluginSerializedBytes::try_serialize(&VersionedSerializable::new(
|
||||
value,
|
||||
))
|
||||
})?;
|
||||
|
||||
let serialized_context_json = serde_json::to_string(&self.plugin_context)
|
||||
.context("Failed to serialize plugin context as json")
|
||||
.and_then(|value| {
|
||||
PluginSerializedBytes::try_serialize(&VersionedSerializable::new(
|
||||
value,
|
||||
))
|
||||
})?;
|
||||
drop(context_span_guard);
|
||||
|
||||
let span = tracing::span!(
|
||||
tracing::Level::INFO,
|
||||
"execute_plugin_runner",
|
||||
plugin_module = p.0.as_str()
|
||||
);
|
||||
let transform_span_guard = span.enter();
|
||||
serialized = swc_plugin_runner::apply_transform_plugin(
|
||||
&p.0,
|
||||
&path,
|
||||
&swc_plugin_runner::cache::PLUGIN_MODULE_CACHE,
|
||||
serialized,
|
||||
config_json,
|
||||
context_json,
|
||||
should_enable_comments_proxy,
|
||||
&self.source_map,
|
||||
)?;
|
||||
drop(transform_span_guard);
|
||||
)
|
||||
.entered();
|
||||
|
||||
serialized_program = transform_plugin_executor
|
||||
.transform(
|
||||
&serialized_program,
|
||||
&serialized_config_json,
|
||||
&serialized_context_json,
|
||||
should_enable_comments_proxy,
|
||||
)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to invoke `{}` as js transform plugin at {}",
|
||||
&p.0,
|
||||
path.display()
|
||||
)
|
||||
})?;
|
||||
drop(span);
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin transformation is done. Deserialize transformed bytes back
|
||||
// into Program
|
||||
serialized.deserialize().map(|v| v.into_inner())
|
||||
serialized_program.deserialize().map(|v| v.into_inner())
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -188,11 +202,11 @@ impl RustPlugins {
|
||||
},
|
||||
|| {
|
||||
let program = VersionedSerializable::new(n);
|
||||
let mut serialized = PluginSerializedBytes::try_serialize(&program)?;
|
||||
let mut serialized_program = PluginSerializedBytes::try_serialize(&program)?;
|
||||
|
||||
if let Some(plugins) = &self.plugins {
|
||||
for p in plugins {
|
||||
let config_json = serde_json::to_string(&p.1)
|
||||
let serialized_config_json = serde_json::to_string(&p.1)
|
||||
.context("Failed to serialize plugin config as json")
|
||||
.and_then(|value| {
|
||||
PluginSerializedBytes::try_serialize(&VersionedSerializable::new(
|
||||
@ -200,7 +214,7 @@ impl RustPlugins {
|
||||
))
|
||||
})?;
|
||||
|
||||
let context_json = serde_json::to_string(&self.plugin_context)
|
||||
let serialized_context_json = serde_json::to_string(&self.plugin_context)
|
||||
.context("Failed to serialize plugin context as json")
|
||||
.and_then(|value| {
|
||||
PluginSerializedBytes::try_serialize(&VersionedSerializable::new(
|
||||
@ -208,20 +222,27 @@ impl RustPlugins {
|
||||
))
|
||||
})?;
|
||||
|
||||
serialized = swc_plugin_runner::apply_transform_plugin(
|
||||
&p.0,
|
||||
&PathBuf::from(&p.0),
|
||||
&swc_plugin_runner::cache::PLUGIN_MODULE_CACHE,
|
||||
serialized,
|
||||
config_json,
|
||||
context_json,
|
||||
should_enable_comments_proxy,
|
||||
&self.source_map,
|
||||
)?;
|
||||
let mut transform_plugin_executor =
|
||||
swc_plugin_runner::create_plugin_transform_executor(
|
||||
&PathBuf::from(&p.0),
|
||||
&swc_plugin_runner::cache::PLUGIN_MODULE_CACHE,
|
||||
&self.source_map,
|
||||
)?;
|
||||
|
||||
serialized_program = transform_plugin_executor
|
||||
.transform(
|
||||
&serialized_program,
|
||||
&serialized_config_json,
|
||||
&serialized_context_json,
|
||||
should_enable_comments_proxy,
|
||||
)
|
||||
.with_context(|| {
|
||||
format!("failed to invoke `{}` as js transform plugin", &p.0)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
serialized.deserialize().map(|v| v.into_inner())
|
||||
serialized_program.deserialize().map(|v| v.into_inner())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -228,7 +228,7 @@ impl VisitMut for TransformVisitor {
|
||||
/// as returning ptr back to host.
|
||||
///
|
||||
/// It is possible to opt out from macro by writing transform fn manually via
|
||||
/// `__plugin_process_impl(
|
||||
/// `__transform_plugin_process_impl(
|
||||
/// ast_ptr: *const u8,
|
||||
/// ast_ptr_len: i32,
|
||||
/// config_str_ptr: *const u8,
|
||||
|
@ -19,7 +19,8 @@ pub fn plugin_transform(
|
||||
|
||||
fn handle_func(func: ItemFn) -> TokenStream {
|
||||
let ident = func.sig.ident.clone();
|
||||
let process_impl_ident = Ident::new("__plugin_process_impl", Span::call_site());
|
||||
let transform_process_impl_ident =
|
||||
Ident::new("__transform_plugin_process_impl", Span::call_site());
|
||||
|
||||
let ret = quote! {
|
||||
#func
|
||||
@ -59,7 +60,7 @@ fn handle_func(func: ItemFn) -> TokenStream {
|
||||
// There are some cases error won't be wrapped up however - for example, we expect
|
||||
// serialization of PluginError itself should succeed.
|
||||
#[no_mangle]
|
||||
pub fn #process_impl_ident(ast_ptr: *const u8, ast_ptr_len: i32, config_str_ptr: *const u8, config_str_ptr_len: i32, context_str_ptr: *const u8, context_str_ptr_len: i32, should_enable_comments_proxy: i32) -> i32 {
|
||||
pub fn #transform_process_impl_ident(ast_ptr: *const u8, ast_ptr_len: i32, config_str_ptr: *const u8, config_str_ptr_len: i32, context_str_ptr: *const u8, context_str_ptr_len: i32, should_enable_comments_proxy: i32) -> i32 {
|
||||
// Reconstruct `Program` & config string from serialized program
|
||||
// Host (SWC) should allocate memory, copy bytes and pass ptr to plugin.
|
||||
let program = unsafe { swc_plugin::deserialize_from_ptr(ast_ptr, ast_ptr_len).map(|v| v.into_inner()) };
|
||||
|
@ -5,8 +5,8 @@
|
||||
/// specifically `SingleThreadedComments`. It doesn't support thread-safety
|
||||
/// which does not allow to be passed into wasmerEnv (HostEnvironment). A scoped
|
||||
/// tls holds inner comments as global, per each transform execution. Refer
|
||||
/// `swc::RustPlugins::apply_transform_plugin` for the responsibility to manage
|
||||
/// actual data.
|
||||
/// `swc_plugin::transform_execytor::TransformExecutor::transform` for the
|
||||
/// responsibility to manage actual data.
|
||||
///
|
||||
/// Should never attempt to use this other than plugin_runner.
|
||||
// TODO: This storage does not support mutable yet
|
||||
|
@ -62,24 +62,32 @@ fn bench_transform(b: &mut Bencher, plugin_dir: &Path) {
|
||||
let program = VersionedSerializable::new(program);
|
||||
let program_ser = PluginSerializedBytes::try_serialize(&program).unwrap();
|
||||
|
||||
let res = swc_plugin_runner::apply_transform_plugin(
|
||||
"test",
|
||||
let mut transform_plugin_executor = swc_plugin_runner::create_plugin_transform_executor(
|
||||
&plugin_dir
|
||||
.join("target")
|
||||
.join("wasm32-unknown-unknown")
|
||||
.join("release")
|
||||
.join("swc_internal_plugin.wasm"),
|
||||
&cache,
|
||||
program_ser,
|
||||
PluginSerializedBytes::try_serialize(&VersionedSerializable::new(String::from("{}")))
|
||||
.unwrap(),
|
||||
PluginSerializedBytes::try_serialize(&VersionedSerializable::new(String::from("{}")))
|
||||
.unwrap(),
|
||||
true,
|
||||
&swc_plugin_runner::cache::PLUGIN_MODULE_CACHE,
|
||||
&cm,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let res = transform_plugin_executor
|
||||
.transform(
|
||||
&program_ser,
|
||||
&PluginSerializedBytes::try_serialize(&VersionedSerializable::new(String::from(
|
||||
"{}",
|
||||
)))
|
||||
.unwrap(),
|
||||
&PluginSerializedBytes::try_serialize(&VersionedSerializable::new(String::from(
|
||||
"{}",
|
||||
)))
|
||||
.unwrap(),
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let _ = black_box(res);
|
||||
})
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
use std::{path::Path, sync::Arc};
|
||||
|
||||
use anyhow::{Context, Error};
|
||||
use anyhow::Error;
|
||||
use once_cell::sync::Lazy;
|
||||
use swc_common::{plugin::PluginSerializedBytes, SourceMap};
|
||||
use swc_common::SourceMap;
|
||||
use transform_executor::TransformExecutor;
|
||||
|
||||
pub mod cache;
|
||||
@ -12,33 +12,18 @@ mod load_plugin;
|
||||
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,
|
||||
/**
|
||||
* Attempt to create a executor to run plugin binaries.
|
||||
* Internally this will try to load binary from given cache which can fail,
|
||||
* returns error in that case.
|
||||
*
|
||||
* Note you CANNOT reuse executor once trasform has been executed: executor
|
||||
* is stateful.
|
||||
*/
|
||||
pub fn create_plugin_transform_executor(
|
||||
path: &Path,
|
||||
cache: &Lazy<cache::PluginModuleCache>,
|
||||
program: PluginSerializedBytes,
|
||||
config_json: PluginSerializedBytes,
|
||||
context_json: PluginSerializedBytes,
|
||||
should_enable_comments_proxy: bool,
|
||||
source_map: &Arc<SourceMap>,
|
||||
) -> Result<PluginSerializedBytes, Error> {
|
||||
(|| -> Result<_, Error> {
|
||||
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,
|
||||
&config_json,
|
||||
&context_json,
|
||||
should_enable_comments_proxy,
|
||||
)
|
||||
})()
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to invoke `{}` as js transform plugin at {}",
|
||||
plugin_name,
|
||||
path.display()
|
||||
)
|
||||
})
|
||||
) -> Result<TransformExecutor, Error> {
|
||||
TransformExecutor::new(path, cache, source_map)
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ impl TransformExecutor {
|
||||
exported_plugin_transform: instance
|
||||
.exports
|
||||
.get_native_function::<(i32, i32, i32, i32, i32, i32, i32), i32>(
|
||||
"__plugin_process_impl",
|
||||
"__transform_plugin_process_impl",
|
||||
)?,
|
||||
exported_plugin_free: instance
|
||||
.exports
|
||||
@ -102,14 +102,26 @@ impl TransformExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check compile-time versions of AST schema between the plugin and
|
||||
* the host. Returns true if it's compatible, false otherwise.
|
||||
*
|
||||
* Host should appropriately handle if plugin is not compatible to the
|
||||
* current runtime.
|
||||
*/
|
||||
pub fn is_transform_schema_compatible(&self) -> bool {
|
||||
todo!("Not supported yet");
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "info", skip_all)]
|
||||
pub fn transform(
|
||||
&mut self,
|
||||
program: &PluginSerializedBytes,
|
||||
config: &PluginSerializedBytes,
|
||||
context: &PluginSerializedBytes,
|
||||
should_enable_comments_proxy: i32,
|
||||
should_enable_comments_proxy: bool,
|
||||
) -> Result<PluginSerializedBytes, Error> {
|
||||
let should_enable_comments_proxy = if should_enable_comments_proxy { 1 } else { 0 };
|
||||
let guest_program_ptr = self.write_bytes_into_guest(program)?;
|
||||
let config_str_ptr = self.write_bytes_into_guest(config)?;
|
||||
let context_str_ptr = self.write_bytes_into_guest(context)?;
|
||||
|
@ -96,18 +96,13 @@ fn internal() -> Result<(), Error> {
|
||||
.expect("Should serializable");
|
||||
|
||||
let cache: Lazy<PluginModuleCache> = Lazy::new(PluginModuleCache::new);
|
||||
let mut plugin_transform_executor =
|
||||
swc_plugin_runner::create_plugin_transform_executor(&path, &cache, &cm)
|
||||
.expect("Should load plugin");
|
||||
|
||||
let program_bytes = swc_plugin_runner::apply_transform_plugin(
|
||||
"internal-test",
|
||||
&path,
|
||||
&cache,
|
||||
program,
|
||||
config,
|
||||
context,
|
||||
false,
|
||||
&cm,
|
||||
)
|
||||
.expect("Plugin should apply transform");
|
||||
let program_bytes = plugin_transform_executor
|
||||
.transform(&program, &config, &context, false)
|
||||
.expect("Plugin should apply transform");
|
||||
|
||||
let program: Program = program_bytes
|
||||
.deserialize()
|
||||
@ -153,17 +148,13 @@ fn internal() -> Result<(), Error> {
|
||||
let cache: Lazy<PluginModuleCache> = Lazy::new(PluginModuleCache::new);
|
||||
|
||||
let _res = HANDLER.set(&handler, || {
|
||||
swc_plugin_runner::apply_transform_plugin(
|
||||
"internal-test",
|
||||
&path,
|
||||
&cache,
|
||||
program,
|
||||
config,
|
||||
context,
|
||||
false,
|
||||
&cm,
|
||||
)
|
||||
.expect("Plugin should apply transform")
|
||||
let mut plugin_transform_executor =
|
||||
swc_plugin_runner::create_plugin_transform_executor(&path, &cache, &cm)
|
||||
.expect("Should load plugin");
|
||||
|
||||
plugin_transform_executor
|
||||
.transform(&program, &config, &context, false)
|
||||
.expect("Plugin should apply transform")
|
||||
});
|
||||
|
||||
Ok(())
|
||||
@ -190,38 +181,44 @@ fn internal() -> Result<(), Error> {
|
||||
.expect("Should serializable");
|
||||
let cache: Lazy<PluginModuleCache> = Lazy::new(PluginModuleCache::new);
|
||||
|
||||
serialized_program = swc_plugin_runner::apply_transform_plugin(
|
||||
"internal-test",
|
||||
&path,
|
||||
&cache,
|
||||
serialized_program,
|
||||
PluginSerializedBytes::try_serialize(&VersionedSerializable::new("{}".to_string()))
|
||||
let mut plugin_transform_executor =
|
||||
swc_plugin_runner::create_plugin_transform_executor(&path, &cache, &cm)
|
||||
.expect("Should load plugin");
|
||||
|
||||
serialized_program = plugin_transform_executor
|
||||
.transform(
|
||||
&serialized_program,
|
||||
&PluginSerializedBytes::try_serialize(&VersionedSerializable::new(
|
||||
"{}".to_string(),
|
||||
))
|
||||
.expect("Should serializable"),
|
||||
PluginSerializedBytes::try_serialize(&VersionedSerializable::new(
|
||||
"{sourceFileName: 'multiple_plugin_test'}".to_string(),
|
||||
))
|
||||
.expect("Should serializable"),
|
||||
false,
|
||||
&cm,
|
||||
)
|
||||
.expect("Plugin should apply transform");
|
||||
&PluginSerializedBytes::try_serialize(&VersionedSerializable::new(
|
||||
"{sourceFileName: 'multiple_plugin_test'}".to_string(),
|
||||
))
|
||||
.expect("Should serializable"),
|
||||
false,
|
||||
)
|
||||
.expect("Plugin should apply transform");
|
||||
|
||||
// TODO: we'll need to apply 2 different plugins
|
||||
serialized_program = swc_plugin_runner::apply_transform_plugin(
|
||||
"internal-test",
|
||||
&path,
|
||||
&cache,
|
||||
serialized_program,
|
||||
PluginSerializedBytes::try_serialize(&VersionedSerializable::new("{}".to_string()))
|
||||
let mut plugin_transform_executor =
|
||||
swc_plugin_runner::create_plugin_transform_executor(&path, &cache, &cm)
|
||||
.expect("Should load plugin");
|
||||
|
||||
serialized_program = plugin_transform_executor
|
||||
.transform(
|
||||
&serialized_program,
|
||||
&PluginSerializedBytes::try_serialize(&VersionedSerializable::new(
|
||||
"{}".to_string(),
|
||||
))
|
||||
.expect("Should serializable"),
|
||||
PluginSerializedBytes::try_serialize(&VersionedSerializable::new(
|
||||
"{sourceFileName: 'multiple_plugin_test2'}".to_string(),
|
||||
))
|
||||
.expect("Should serializable"),
|
||||
false,
|
||||
&cm,
|
||||
)
|
||||
.expect("Plugin should apply transform");
|
||||
&PluginSerializedBytes::try_serialize(&VersionedSerializable::new(
|
||||
"{sourceFileName: 'multiple_plugin_test2'}".to_string(),
|
||||
))
|
||||
.expect("Should serializable"),
|
||||
false,
|
||||
)
|
||||
.expect("Plugin should apply transform");
|
||||
|
||||
let program: Program = serialized_program
|
||||
.deserialize()
|
||||
|
18
tests/rust-plugins/swc_internal_plugin/Cargo.lock
generated
18
tests/rust-plugins/swc_internal_plugin/Cargo.lock
generated
@ -722,7 +722,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_common"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
@ -750,7 +750,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_ast"
|
||||
version = "0.82.0"
|
||||
version = "0.83.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bytecheck",
|
||||
@ -767,7 +767,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_parser"
|
||||
version = "0.109.1"
|
||||
version = "0.110.0"
|
||||
dependencies = [
|
||||
"either",
|
||||
"enum_kind",
|
||||
@ -784,7 +784,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_utils"
|
||||
version = "0.91.0"
|
||||
version = "0.92.0"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"once_cell",
|
||||
@ -798,7 +798,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_visit"
|
||||
version = "0.68.0"
|
||||
version = "0.69.0"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"swc_atoms",
|
||||
@ -810,7 +810,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecmascript"
|
||||
version = "0.176.0"
|
||||
version = "0.178.0"
|
||||
dependencies = [
|
||||
"swc_ecma_ast",
|
||||
"swc_ecma_parser",
|
||||
@ -848,7 +848,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_plugin"
|
||||
version = "0.72.0"
|
||||
version = "0.74.0"
|
||||
dependencies = [
|
||||
"swc_atoms",
|
||||
"swc_common",
|
||||
@ -868,7 +868,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_plugin_proxy"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0"
|
||||
dependencies = [
|
||||
"better_scoped_tls",
|
||||
"bytecheck",
|
||||
@ -887,7 +887,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_visit_macros"
|
||||
version = "0.3.2"
|
||||
version = "0.3.5"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"pmutil",
|
||||
|
@ -29,7 +29,7 @@ impl VisitMut for ConsoleOutputReplacer {
|
||||
/// as returning ptr back to host.
|
||||
///
|
||||
/// It is possible to opt out from macro by writing transform fn manually via
|
||||
/// `__plugin_process_impl(
|
||||
/// `__transform_plugin_process_impl(
|
||||
/// ast_ptr: *const u8,
|
||||
/// ast_ptr_len: i32,
|
||||
/// config_str_ptr: *const u8,
|
||||
|
Loading…
Reference in New Issue
Block a user