作为继续我对过程宏各个方面的研究的一部分,我想分享一种扩展其功能的方法。让我提醒您,过程宏允许您向语言添加元编程元素,从而显着简化例程操作,例如序列化或查询处理。宏的核心是编译器插件,它们会在构建使用它们的机架之前进行编译。这样的宏有一些明显的缺点。
- 在IDE中难以支持此类宏。实际上,您需要以某种方式教导代码分析器考虑所有功能,自行编译,加载和执行这些宏。这是一项非常重要的任务。
- 由于宏是自给自足的,彼此之间一无所知,因此无法组合宏,有时这很有用。
作为解决第一个问题,实验也在进行中所有程序的宏汇编成WASM模块,这将有可能在未来完全拒绝编译它们在目标机器上,并在同一时间,以解决他们的IDE支持的问题。
至于第二个问题,在本文中,我将讨论解决该问题的方法。实际上,我们需要一个宏,该宏可以使用属性加载一些其他宏并将它们组合到管道中。在最简单的情况下,您可以简单地想象这样的事情:
假设我们有一些宏TextMessage
,用于显示给定类型的特征ToString
并FromStr
使用某种编解码器作为文本表示形式。不同类型的消息可能具有不同的编解码器,并且它们的完整列表可能会随时间扩展,并且每个编解码器可能具有其自己的唯一属性集。
#[derive(Debug, Serialize, Deserialize, PartialEq, TextMessage)]
#[text_message(codec = "serde_json", params(pretty))]
struct FooMessage {
name: String,
description: String,
value: u64,
}
, . libloading, IDE. , syn
quote
, , .
WASM , . .
, watt WASM , . watt proc-macro2
, . , darling
proc-macro2
, .
, proc-macro2
, WASM
- . , wasmtime, bytecodealliance, , Mozilla, Intel RedHat. wasmtime , , ,
Disclaimer: wasmtime ,
, , . WASM , .
!
, , , WASM , . .
:
pub fn implement_codec(input: TokenStream) -> TokenStream;
, , . TokenStream
, :
pub fn implement_codec(input: &str) -> String;
, ,
, :
, , ! WASM , , , , .
, , , . , , , , , . : .
#[no_mangle]
pub unsafe extern "C" fn toy_alloc(size: i32) -> i32 {
let size_bytes: [u8; 4] = size.to_le_bytes();
let mut buf: Vec<u8> = Vec::with_capacity(size as usize + size_bytes.len());
buf.extend(size_bytes.iter());
to_host_ptr(buf)
}
unsafe fn to_host_ptr(mut buf: Vec<u8>) -> i32 {
let ptr = buf.as_mut_ptr();
mem::forget(buf);
ptr as *mut c_void as usize as i32
}
#[no_mangle]
pub unsafe extern "C" fn toy_free(ptr: i32) {
let ptr = ptr as usize as *mut u8;
let mut size_bytes = [0u8; 4];
ptr.copy_to(size_bytes.as_mut_ptr(), 4);
let size = u32::from_le_bytes(size_bytes) as usize;
Vec::from_raw_parts(ptr, size, size);
}
, , wasm_bindgen
.
WASM . , .
#[no_mangle]
pub unsafe extern "C" fn implement_codec(
item_ptr: i32,
item_len: i32,
) -> i32 {
let item = str_from_raw_parts(item_ptr, item_len);
let item = TokenStream::from_str(&item).expect("Unable to parse item");
let tokens = codec::implement_codec(item);
let out = tokens.to_string();
to_host_buf(out)
}
pub unsafe fn str_from_raw_parts<'a>(ptr: i32, len: i32) -> &'a str {
let slice = std::slice::from_raw_parts(ptr as *const u8, len as usize);
std::str::from_utf8(slice).unwrap()
}
, WASM .
pub struct WasmMacro {
module: Module,
}
impl WasmMacro {
pub fn from_file(file: impl AsRef<Path>) -> anyhow::Result<Self> {
let store = Store::default();
let module = Module::from_file(&store, file)?;
Ok(Self { module })
}
pub fn proc_macro_derive(
&self,
fun: &str,
item: TokenStream,
) -> anyhow::Result<TokenStream> {
let item = item.to_string();
let instance = Instance::new(&self.module, &[])?;
let proc_macro_attribute_fn = instance
.get_export(fun)
.ok_or_else(|| anyhow!("Unable to find `{}` method in the export table", fun))?
.func()
.ok_or_else(|| anyhow!("export {} is not a function", fun))?
.get2::<i32, i32, i32,>()?;
let item_buf = WasmBuf::from_host_buf(&instance, item);
let (item_ptr, item_len) = item_buf.raw_parts();
let ptr = proc_macro_attribute_fn(item_ptr, item_len).unwrap();
let res = WasmBuf::from_raw_ptr(&instance, ptr);
let res_str = std::str::from_utf8(res.as_ref())?;
TokenStream::from_str(&res_str)
.map_err(|_| anyhow!("Unable to parse token stream"))
}
}
WasmBuf
: ,
, toy_alloc
.
, .
struct WasmBuf<'a> {
offset: usize,
len: usize,
instance: &'a Instance,
memory: &'a Memory,
}
const WASM_PTR_LEN: usize = 4;
impl<'a> WasmBuf<'a> {
pub fn new(instance: &'a Instance, len: usize) -> Self {
let memory = Self::get_memory(instance);
let offset = Self::toy_alloc(instance, len);
Self {
offset: offset as usize,
len,
instance,
memory,
}
}
pub fn from_host_buf(instance: &'a Instance, bytes: impl AsRef<[u8]>) -> Self {
let bytes = bytes.as_ref();
let len = bytes.len();
let mut wasm_buf = Self::new(instance, len);
wasm_buf.as_mut().copy_from_slice(bytes);
wasm_buf
}
pub fn from_raw_ptr(instance: &'a Instance, offset: i32) -> Self {
let offset = offset as usize;
let memory = Self::get_memory(instance);
let len = unsafe {
let buf = memory.data_unchecked();
let mut len_bytes = [0; WASM_PTR_LEN];
len_bytes.copy_from_slice(&buf[offset..offset + WASM_PTR_LEN]);
u32::from_le_bytes(len_bytes)
};
Self {
offset,
len: len as usize,
memory,
instance,
}
}
pub fn as_ref(&self) -> &[u8] {
unsafe {
let begin = self.offset + WASM_PTR_LEN;
let end = begin + self.len;
&self.memory.data_unchecked()[begin..end]
}
}
pub fn as_mut(&mut self) -> &mut [u8] {
unsafe {
let begin = self.offset + WASM_PTR_LEN;
let end = begin + self.len;
&mut self.memory.data_unchecked_mut()[begin..end]
}
}
}
, .
impl Drop for WasmBuf<'_> {
fn drop(&mut self) {
Self::toy_free(self.instance, self.len);
}
}
,
WASM , .
#[proc_macro_derive(TextMessage, attributes(text_message))]
pub fn text_message(input: TokenStream) -> TokenStream {
let input: DeriveInput = parse_macro_input!(input);
let attrs = TextMessageAttrs::from_raw(&input.attrs)
.expect("Unable to parse text message attributes.");
let codec_dir = Path::new(&std::env::var("CARGO_MANIFEST_DIR")
.unwrap())
.join("codecs");
let plugin_name = format!("{}_text_codec.wasm", attrs.codec);
let codec_path = codec_dir.join(plugin_name);
let wasm_macro = WasmMacro::from_file(codec_path)
.expect("Unable to load wasm module");
wasm_macro
.proc_macro_derive(
"implement_codec",
input.into_token_stream().into(),
)
.expect("Unable to apply proc_macro_attribute")
}
. , WASM , .
#[derive(Debug, Serialize, Deserialize, PartialEq, TextMessage)]
#[text_message(codec = "serde_json", params(pretty))]
struct FooMessage {
name: String,
description: String,
value: u64,
}
fn main() {
let msg = FooMessage {
name: "Linus Torvalds".to_owned(),
description: "The Linux founder.".to_owned(),
value: 1,
};
let text = msg.to_string();
println!("{}", text);
let msg2 = text.parse().unwrap();
assert_eq!(msg, msg2);
}
到目前为止,它看起来更像是一条面包上的手推车,但是,另一方面,它只是原理本身的一个小而精妙的展示。这样的宏可以扩展。我们不再需要重写原始的程序宏来更改或扩展其行为。而且,如果您将模块注册表用于WASM,则可以分发诸如货箱之类的模块。