Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Register macro expansion capabilities #1195

Merged
merged 1 commit into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 20 additions & 16 deletions plugins/cairo-lang-macro-attributes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,20 @@ use syn::{parse_macro_input, ItemFn};
#[proc_macro_attribute]
pub fn attribute_macro(_args: TokenStream, input: TokenStream) -> TokenStream {
let item: ItemFn = parse_macro_input!(input as ItemFn);
let original_item_name = item.sig.ident.to_string();
let item = hide_name(item);
let item_name = &item.sig.ident;
maciektr marked this conversation as resolved.
Show resolved Hide resolved
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,
}
}
#[::cairo_lang_macro::linkme::distributed_slice(::cairo_lang_macro::MACRO_DEFINITIONS_SLICE)]
#[linkme(crate = ::cairo_lang_macro::linkme)]
static MACRO_DEFINITIONS_SLICE_DESERIALIZE: ::cairo_lang_macro::ExpansionDefinition =
::cairo_lang_macro::ExpansionDefinition{
name: #original_item_name,
kind: ::cairo_lang_macro::ExpansionKind::Attr,
fun: #item_name,
};
};
TokenStream::from(expanded)
}
Expand All @@ -50,12 +50,8 @@ pub fn attribute_macro(_args: TokenStream, input: TokenStream) -> TokenStream {
/// # Safety
#[proc_macro_attribute]
pub fn aux_data_collection_callback(_args: TokenStream, input: TokenStream) -> TokenStream {
let mut item: ItemFn = parse_macro_input!(input as ItemFn);
// Rename item to hide it from the macro source code.
let id = short_hash(item.sig.ident.to_string());
let item_name = format!("{}_{}", item.sig.ident, id);
item.sig.ident = syn::Ident::new(item_name.as_str(), item.sig.ident.span());

let item: ItemFn = parse_macro_input!(input as ItemFn);
let item = hide_name(item);
let item_name = &item.sig.ident;
let expanded = quote! {
#item
Expand All @@ -66,3 +62,11 @@ pub fn aux_data_collection_callback(_args: TokenStream, input: TokenStream) -> T
};
TokenStream::from(expanded)
}

/// Rename item to hide it from the macro source code.
fn hide_name(mut item: ItemFn) -> ItemFn {
let id = short_hash(item.sig.ident.to_string());
let item_name = format!("{}_{}", item.sig.ident, id);
item.sig.ident = syn::Ident::new(item_name.as_str(), item.sig.ident.span());
item
}
11 changes: 11 additions & 0 deletions plugins/cairo-lang-macro-stable/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<StableExpansion>;

/// Token stream.
///
/// This struct implements FFI-safe stable ABI.
Expand Down
84 changes: 83 additions & 1 deletion plugins/cairo-lang-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,95 @@ pub use cairo_lang_macro_attributes::*;
pub use linkme;

use cairo_lang_macro_stable::ffi::StableSlice;
use cairo_lang_macro_stable::{StableAuxData, StableProcMacroResult};
use cairo_lang_macro_stable::{StableAuxData, StableExpansionsList, StableProcMacroResult};
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.
#[doc(hidden)]
#[linkme::distributed_slice]
pub static MACRO_DEFINITIONS_SLICE: [ExpansionDefinition];

/// 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]
#[doc(hidden)]
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]
#[doc(hidden)]
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]
#[doc(hidden)]
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 = MACRO_DEFINITIONS_SLICE
.iter()
.find_map(|m| {
if m.name == item_name.as_str() {
Some(m.fun)
} else {
None
}
})
.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,
Expand Down
33 changes: 30 additions & 3 deletions plugins/cairo-lang-macro/src/types/conversion.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -351,6 +354,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 {
Expand Down
33 changes: 33 additions & 0 deletions plugins/cairo-lang-macro/src/types/expansions.rs
Original file line number Diff line number Diff line change
@@ -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
}
}
}
3 changes: 3 additions & 0 deletions plugins/cairo-lang-macro/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ use std::fmt::Display;
use std::vec::IntoIter;

mod conversion;
mod expansions;

pub use expansions::*;

#[derive(Debug)]
pub enum ProcMacroResult {
Expand Down
11 changes: 9 additions & 2 deletions scarb/src/compiler/plugin/proc_macro/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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<StableAuxData>) -> StableSlice<StableAuxData>;

Expand Down
14 changes: 7 additions & 7 deletions scarb/tests/build_cairo_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,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() }
}
Expand Down Expand Up @@ -271,7 +271,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] }
Expand Down Expand Up @@ -318,7 +318,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] }
Expand Down Expand Up @@ -365,7 +365,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() }
}
"#},
Expand Down Expand Up @@ -412,7 +412,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()
Expand Down Expand Up @@ -471,7 +471,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()
Expand Down Expand Up @@ -542,7 +542,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() }
}
Expand Down
Loading