From e4acac3096fe77295fcfafdd95cc099f84085696 Mon Sep 17 00:00:00 2001 From: maciektr Date: Fri, 15 Mar 2024 20:02:32 +0100 Subject: [PATCH] Register macro expansion capabilities commit-id:3dc2a19c --- Cargo.lock | 1 + .../cairo-lang-macro-attributes/src/lib.rs | 18 ++-- plugins/cairo-lang-macro-stable/src/lib.rs | 11 +++ plugins/cairo-lang-macro/Cargo.toml | 1 + plugins/cairo-lang-macro/src/lib.rs | 82 ++++++++++++++++++- .../cairo-lang-macro/src/types/conversion.rs | 33 +++++++- .../cairo-lang-macro/src/types/expansions.rs | 33 ++++++++ plugins/cairo-lang-macro/src/types/mod.rs | 3 + scarb/src/compiler/plugin/proc_macro/ffi.rs | 11 ++- scarb/tests/build_cairo_plugin.rs | 14 ++-- 10 files changed, 184 insertions(+), 23 deletions(-) create mode 100644 plugins/cairo-lang-macro/src/types/expansions.rs diff --git a/Cargo.lock b/Cargo.lock index bdfdaeaee..460bf8c09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -733,6 +733,7 @@ dependencies = [ "cairo-lang-macro-attributes", "cairo-lang-macro-stable", "linkme", + "once_cell", "scarb-stable-hash", ] diff --git a/plugins/cairo-lang-macro-attributes/src/lib.rs b/plugins/cairo-lang-macro-attributes/src/lib.rs index ffcef1d44..0d0c4c073 100644 --- a/plugins/cairo-lang-macro-attributes/src/lib.rs +++ b/plugins/cairo-lang-macro-attributes/src/lib.rs @@ -13,19 +13,17 @@ use syn::{parse_macro_input, ItemFn}; pub fn attribute_macro(_args: TokenStream, input: TokenStream) -> TokenStream { let item: ItemFn = parse_macro_input!(input as ItemFn); let item_name = &item.sig.ident; + let item_name_s = item_name.to_string(); let expanded = quote! { #item - #[no_mangle] - pub unsafe extern "C" fn expand(stable_token_stream: cairo_lang_macro_stable::StableTokenStream) -> cairo_lang_macro_stable::StableResultWrapper { - let token_stream = cairo_lang_macro::TokenStream::from_stable(&stable_token_stream); - let result = #item_name(token_stream); - let result: cairo_lang_macro_stable::StableProcMacroResult = result.into_stable(); - cairo_lang_macro_stable::StableResultWrapper { - input: stable_token_stream, - output: result, - } - } + #[linkme::distributed_slice(cairo_lang_macro::MACRO_DEFINITIONS_SLICE)] + static MACRO_DEFINITIONS_SLICE_DESERIALIZE: cairo_lang_macro::ExpansionDefinition = + cairo_lang_macro::ExpansionDefinition{ + name: #item_name_s, + kind: cairo_lang_macro::ExpansionKind::Attr, + fun: #item_name, + }; }; TokenStream::from(expanded) } diff --git a/plugins/cairo-lang-macro-stable/src/lib.rs b/plugins/cairo-lang-macro-stable/src/lib.rs index c59d25d6b..86793d2a0 100644 --- a/plugins/cairo-lang-macro-stable/src/lib.rs +++ b/plugins/cairo-lang-macro-stable/src/lib.rs @@ -6,6 +6,17 @@ use std::ptr::NonNull; pub mod ffi; +#[repr(C)] +#[derive(Debug)] +pub struct StableExpansion { + pub name: *mut c_char, + pub kind: StableExpansionKind, +} + +pub type StableExpansionKind = NonZeroU8; + +pub type StableExpansionsList = StableSlice; + /// Token stream. /// /// This struct implements FFI-safe stable ABI. diff --git a/plugins/cairo-lang-macro/Cargo.toml b/plugins/cairo-lang-macro/Cargo.toml index a9a9efc56..0b3bd2471 100644 --- a/plugins/cairo-lang-macro/Cargo.toml +++ b/plugins/cairo-lang-macro/Cargo.toml @@ -16,4 +16,5 @@ repository.workspace = true cairo-lang-macro-attributes = { path = "../cairo-lang-macro-attributes" } cairo-lang-macro-stable = { path = "../cairo-lang-macro-stable" } linkme.workspace = true +once_cell.workspace = true scarb-stable-hash = { path = "../../utils/scarb-stable-hash" } diff --git a/plugins/cairo-lang-macro/src/lib.rs b/plugins/cairo-lang-macro/src/lib.rs index 15cfc962b..06e5e0cb6 100644 --- a/plugins/cairo-lang-macro/src/lib.rs +++ b/plugins/cairo-lang-macro/src/lib.rs @@ -1,13 +1,93 @@ pub use cairo_lang_macro_attributes::*; use cairo_lang_macro_stable::ffi::StableSlice; -use cairo_lang_macro_stable::{StableAuxData, StableProcMacroResult}; +use cairo_lang_macro_stable::{StableAuxData, StableExpansionsList, StableProcMacroResult}; use linkme::distributed_slice; +use once_cell::sync::Lazy; +use std::collections::HashMap; +use std::ffi::{c_char, CStr}; use std::slice; mod types; pub use types::*; +#[derive(Clone)] +pub struct ExpansionDefinition { + pub name: &'static str, + pub kind: ExpansionKind, + pub fun: ExpansionFunc, +} + +type ExpansionFunc = fn(TokenStream) -> ProcMacroResult; + +/// Distributed slice for storing procedural macro code expansion capabilities. +/// +/// Each element denotes name of the macro, and the expand function pointer. +#[distributed_slice] +pub static MACRO_DEFINITIONS_SLICE: [ExpansionDefinition]; + +static EXPAND_CALLBACK: Lazy> = Lazy::new(|| { + MACRO_DEFINITIONS_SLICE + .iter() + .map(|m| (m.name.to_string(), m.fun)) + .collect() +}); + +/// This function discovers expansion capabilities defined by the procedural macro. +/// +/// This function needs to be accessible through the FFI interface, +/// of the dynamic library re-exporting it. +/// +/// # Safety +#[no_mangle] +pub unsafe extern "C" fn list_expansions() -> StableExpansionsList { + let list = MACRO_DEFINITIONS_SLICE + .iter() + .map(|m| m.clone().into_stable()) + .collect(); + StableSlice::new(list) +} + +/// Free the memory allocated for the [`StableProcMacroResult`]. +/// +/// This function needs to be accessible through the FFI interface, +/// of the dynamic library re-exporting it. +/// +/// # Safety +#[no_mangle] +pub unsafe extern "C" fn free_expansions_list(list: StableExpansionsList) { + let v = list.into_owned(); + v.into_iter().for_each(|v| { + ExpansionDefinition::free_owned(v); + }); +} + +/// The code expansion callback. +/// +/// This function needs to be accessible through the FFI interface, +/// of the dynamic library re-exporting it. +/// +/// The function will be called for each code expansion by the procedural macro. +/// +/// # Safety +#[no_mangle] +pub unsafe extern "C" fn expand( + item_name: *const c_char, + stable_token_stream: cairo_lang_macro_stable::StableTokenStream, +) -> cairo_lang_macro_stable::StableResultWrapper { + let token_stream = TokenStream::from_stable(&stable_token_stream); + let item_name = CStr::from_ptr(item_name).to_string_lossy().to_string(); + let fun = EXPAND_CALLBACK + .get(item_name.as_str()) + .expect("proc macro not found"); + let result = fun(token_stream); + let result: StableProcMacroResult = result.into_stable(); + cairo_lang_macro_stable::StableResultWrapper { + input: stable_token_stream, + output: result, + } +} + /// Free the memory allocated for the [`StableProcMacroResult`]. /// /// This function needs to be accessible through the FFI interface, diff --git a/plugins/cairo-lang-macro/src/types/conversion.rs b/plugins/cairo-lang-macro/src/types/conversion.rs index ac993f22d..d4b62f59e 100644 --- a/plugins/cairo-lang-macro/src/types/conversion.rs +++ b/plugins/cairo-lang-macro/src/types/conversion.rs @@ -1,8 +1,11 @@ -use crate::{AuxData, Diagnostic, ProcMacroResult, Severity, TokenStream, TokenStreamMetadata}; +use crate::{ + AuxData, Diagnostic, ExpansionDefinition, ProcMacroResult, Severity, TokenStream, + TokenStreamMetadata, +}; use cairo_lang_macro_stable::ffi::StableSlice; use cairo_lang_macro_stable::{ - StableAuxData, StableDiagnostic, StableProcMacroResult, StableSeverity, StableTokenStream, - StableTokenStreamMetadata, + StableAuxData, StableDiagnostic, StableExpansion, StableProcMacroResult, StableSeverity, + StableTokenStream, StableTokenStreamMetadata, }; use std::ffi::{c_char, CStr, CString}; use std::num::NonZeroU8; @@ -349,6 +352,30 @@ impl Severity { } } +impl ExpansionDefinition { + // Convert to FFI-safe representation. + /// + /// # Safety + #[doc(hidden)] + pub fn into_stable(self) -> StableExpansion { + StableExpansion { + name: CString::new(self.name).unwrap().into_raw(), + kind: self.kind.into_stable(), + } + } + + /// Take the ownership of the string. + /// + /// Useful when you need to free the allocated memory. + /// Only use on the same side of FFI-barrier, where the memory has been allocated. + /// + /// # Safety + #[doc(hidden)] + pub unsafe fn free_owned(expansion: StableExpansion) { + let _ = from_raw_cstring(expansion.name); + } +} + // Create a string from a raw pointer to a c_char. // Note that this will free the underlying memory. unsafe fn from_raw_cstring(raw: *mut c_char) -> String { diff --git a/plugins/cairo-lang-macro/src/types/expansions.rs b/plugins/cairo-lang-macro/src/types/expansions.rs new file mode 100644 index 000000000..15d757c58 --- /dev/null +++ b/plugins/cairo-lang-macro/src/types/expansions.rs @@ -0,0 +1,33 @@ +use cairo_lang_macro_stable::StableExpansionKind; +use std::num::NonZeroU8; + +#[derive(Clone)] +pub enum ExpansionKind { + Attr = 1, + Derive = 2, + Inline = 3, +} + +impl ExpansionKind { + /// Convert to FFI-safe representation. + /// # Safety + #[doc(hidden)] + pub fn into_stable(self) -> StableExpansionKind { + NonZeroU8::try_from(self as u8).unwrap() + } + + /// Convert to native Rust representation. + /// + /// # Safety + #[doc(hidden)] + pub unsafe fn from_stable(kind: &StableExpansionKind) -> Self { + if *kind == Self::Attr.into_stable() { + Self::Attr + } else if *kind == Self::Derive.into_stable() { + Self::Derive + } else { + // Note that it defaults to inline for unknown values. + Self::Inline + } + } +} diff --git a/plugins/cairo-lang-macro/src/types/mod.rs b/plugins/cairo-lang-macro/src/types/mod.rs index 9d106f342..c877df284 100644 --- a/plugins/cairo-lang-macro/src/types/mod.rs +++ b/plugins/cairo-lang-macro/src/types/mod.rs @@ -3,6 +3,9 @@ use std::fmt::Display; use std::vec::IntoIter; mod conversion; +mod expansions; + +pub use expansions::*; #[derive(Debug)] pub enum ProcMacroResult { diff --git a/scarb/src/compiler/plugin/proc_macro/ffi.rs b/scarb/src/compiler/plugin/proc_macro/ffi.rs index 436f2c2ac..1e4dd1ba4 100644 --- a/scarb/src/compiler/plugin/proc_macro/ffi.rs +++ b/scarb/src/compiler/plugin/proc_macro/ffi.rs @@ -9,6 +9,7 @@ use cairo_lang_syntax::node::db::SyntaxGroup; use cairo_lang_syntax::node::{ast, TypedSyntaxNode}; use camino::Utf8PathBuf; use libloading::{Library, Symbol}; +use std::ffi::{c_char, CString}; use std::fmt::Debug; use crate::compiler::plugin::proc_macro::compilation::SharedLibraryProvider; @@ -79,9 +80,15 @@ impl ProcMacroInstance { pub(crate) fn generate_code(&self, token_stream: TokenStream) -> ProcMacroResult { // This must be manually freed with call to from_owned_stable. let stable_token_stream = token_stream.into_stable(); + // Allocate proc macro name. + let item_name = CString::new(self.package_id.name.to_string()) + .unwrap() + .into_raw(); // Call FFI interface for code expansion. // Note that `stable_result` has been allocated by the dynamic library. - let stable_result = (self.plugin.vtable.expand)(stable_token_stream); + let stable_result = (self.plugin.vtable.expand)(item_name, stable_token_stream); + // Free proc macro name. + let _ = unsafe { CString::from_raw(item_name) }; // Free the memory allocated by the `stable_token_stream`. // This will call `CString::from_raw` under the hood, to take ownership. unsafe { @@ -113,7 +120,7 @@ impl ProcMacroInstance { } } -type ExpandCode = extern "C" fn(StableTokenStream) -> StableResultWrapper; +type ExpandCode = extern "C" fn(*const c_char, StableTokenStream) -> StableResultWrapper; type FreeResult = extern "C" fn(StableProcMacroResult); type AuxDataCallback = extern "C" fn(StableSlice) -> StableSlice; diff --git a/scarb/tests/build_cairo_plugin.rs b/scarb/tests/build_cairo_plugin.rs index f6b199030..d105ea92a 100644 --- a/scarb/tests/build_cairo_plugin.rs +++ b/scarb/tests/build_cairo_plugin.rs @@ -97,7 +97,7 @@ fn simple_project(t: &impl PathChild) { use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, AuxData}; #[attribute_macro] - pub fn some_macro(token_stream: TokenStream) -> ProcMacroResult { + pub fn some(token_stream: TokenStream) -> ProcMacroResult { let _code = token_stream.to_string(); ProcMacroResult::Leave { diagnostics: Vec::new() } } @@ -272,7 +272,7 @@ fn can_emit_plugin_warning() { use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, Diagnostic}; #[attribute_macro] - pub fn some_macro(token_stream: TokenStream) -> ProcMacroResult { + pub fn some(token_stream: TokenStream) -> ProcMacroResult { let _code = token_stream.to_string(); let diag = Diagnostic::warn("Some warning from macro."); ProcMacroResult::Leave { diagnostics: vec![diag] } @@ -319,7 +319,7 @@ fn can_emit_plugin_error() { use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, Diagnostic}; #[attribute_macro] - pub fn some_macro(token_stream: TokenStream) -> ProcMacroResult { + pub fn some(token_stream: TokenStream) -> ProcMacroResult { let _code = token_stream.to_string(); let diag = Diagnostic::error("Some error from macro."); ProcMacroResult::Leave { diagnostics: vec![diag] } @@ -366,7 +366,7 @@ fn can_remove_original_node() { use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro}; #[attribute_macro] - pub fn some_macro(_: TokenStream) -> ProcMacroResult { + pub fn some(_: TokenStream) -> ProcMacroResult { ProcMacroResult::Remove { diagnostics: Vec::new() } } "#}, @@ -413,7 +413,7 @@ fn can_replace_original_node() { use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro}; #[attribute_macro] - pub fn some_macro(token_stream: TokenStream) -> ProcMacroResult { + pub fn some(token_stream: TokenStream) -> ProcMacroResult { let token_stream = TokenStream::new( token_stream .to_string() @@ -472,7 +472,7 @@ fn can_return_aux_data_from_plugin() { } #[attribute_macro] - pub fn some_macro(token_stream: TokenStream) -> ProcMacroResult { + pub fn some(token_stream: TokenStream) -> ProcMacroResult { let token_stream = TokenStream::new( token_stream .to_string() @@ -543,7 +543,7 @@ fn can_read_token_stream_metadata() { use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro}; #[attribute_macro] - pub fn some_macro(token_stream: TokenStream) -> ProcMacroResult { + pub fn some(token_stream: TokenStream) -> ProcMacroResult { println!("{:?}", token_stream.metadata()); ProcMacroResult::Leave { diagnostics: Vec::new() } }