Skip to content

Commit

Permalink
Register macro expansion capabilities
Browse files Browse the repository at this point in the history
commit-id:3dc2a19c
  • Loading branch information
maciektr committed Mar 18, 2024
1 parent 46ddf1c commit e4acac3
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 23 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 8 additions & 10 deletions plugins/cairo-lang-macro-attributes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
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
1 change: 1 addition & 0 deletions plugins/cairo-lang-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
82 changes: 81 additions & 1 deletion plugins/cairo-lang-macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<HashMap<String, ExpansionFunc>> = 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,
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 @@ -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 {
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 @@ -3,6 +3,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 @@ -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() }
}
Expand Down Expand Up @@ -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] }
Expand Down Expand Up @@ -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] }
Expand Down Expand Up @@ -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() }
}
"#},
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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() }
}
Expand Down

0 comments on commit e4acac3

Please sign in to comment.