From b3cfd4b13c95a2beb6ee7a0aa6b0f72752b492aa Mon Sep 17 00:00:00 2001 From: Kamen Mladenov Date: Mon, 10 Feb 2025 13:23:04 +0200 Subject: docs(guests_macro): Add detailed documentation comments --- guests_macro/Cargo.toml | 1 + guests_macro/src/lib.rs | 36 +++++++++++++++++++++++- guests_macro/src/parse_fn.rs | 66 ++++++++++++++++++++++++++++++++++---------- 3 files changed, 87 insertions(+), 16 deletions(-) diff --git a/guests_macro/Cargo.toml b/guests_macro/Cargo.toml index 4a2e538..6173b91 100644 --- a/guests_macro/Cargo.toml +++ b/guests_macro/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "guests_macro" +description = "Helper macros for guests and wrapper_macros" version = "0.1.0" edition = "2021" diff --git a/guests_macro/src/lib.rs b/guests_macro/src/lib.rs index 469a2da..30bfec7 100644 --- a/guests_macro/src/lib.rs +++ b/guests_macro/src/lib.rs @@ -2,11 +2,45 @@ use std::{ fs::File, io::Write }; use proc_macro::TokenStream; mod parse_fn; +/// Create an `entrypoint_expr` macro inside the guest program. This will be +/// used inside the ZKVM guest program to call the ZKVM guest wrapper's +/// `make_wrapper` macro with entrypoint function type. +/// +/// The overarching goal is to call `make_wrapper` with an input, which +/// contains the function definition. This can only happen inside the guest, +/// since that is the only place where we have the definition (syntactically). +/// But you can only pass data to macros by "inlining" it as macro arguments +/// (i.e. macros work on syntax, so creating a variable wouldn't work). Also, +/// the `make_wrapper` definition doesn't exist in the guest, but the ZKVM +/// wrapper crate. For these reasons we create a macro which invokes +/// `make_wrapper` with the proper arguments. +/// +/// # Usage +/// +/// Inside your guest (under guests directory) add an attribute above your main +/// (entrypoint/start) function. It takes no arguments. +/// +/// ```rust +/// #[guests_macro::proving_entrypoint] +/// fn main(...) -> ... { ..... } +/// ``` +/// +/// # Example output +/// +/// ```rust +/// #[macro_export] +/// macro_rules! entrypoint_expr { +/// () => { +/// make_wrapper!{fn main(...) -> ...} +/// }; +/// } +/// ``` #[proc_macro_attribute] pub fn proving_entrypoint(_: TokenStream, mut item: TokenStream) -> TokenStream { let (name, args, ret) = parse_fn::split_fn(&item); - // Put the file in zkVMs-benchmarks/guests/ + // We also need to pass some type information to the host program compile-time. + // Put it in the file guests/type.txt. let mut output = File::create("../type.txt").unwrap(); writeln!(output, "{}", &format!("{args}").replace('\n', " ")); write!(output, "{}", &format!("{ret}").replace('\n', " ")); diff --git a/guests_macro/src/parse_fn.rs b/guests_macro/src/parse_fn.rs index c5006a8..87b0073 100644 --- a/guests_macro/src/parse_fn.rs +++ b/guests_macro/src/parse_fn.rs @@ -1,7 +1,16 @@ +//! A "library" with procedural macro functions for parsing and handling +//! function definitions. +//! +//! As of writing, exporting procedural macro functions is not supported. +//! Therefore, it should be used in ZKVM wrapper_macro crates, via a [mod path +//! attribute](https://doc.rust-lang.org/reference/items/modules.html#the-path-attribute). + use proc_macro::{ TokenStream, TokenTree, Delimiter, Spacing, Group }; -/// Input: "fn name(...) -> ... { ... }" -/// Output: "name", "(...)", "..." +/// Split function definition into triplet of name, arguments and output types. +/// +/// **Input:** "fn name(...) -> ... { ..... }" +/// **Output:** "name", "(...)", "..." pub fn split_fn(item: &TokenStream) -> (TokenStream, TokenStream, TokenStream) { let item = item.clone().into_iter(); @@ -44,8 +53,11 @@ pub fn split_fn(item: &TokenStream) -> (TokenStream, TokenStream, TokenStream) { (name, args, ret) } -/// Input: "(p1 : t1, p2: t2, ...)" -/// Output: vec!["p1 : t1", "p2: t2", ...] +/// Split arguments group into a vector of each argument with it's associated +/// type. +/// +/// **Input:** "(p1 : t1, p2 : t2, ...)" +/// **Output:** vec!["p1 : t1", "p2 : t2", ...] pub fn args_split(item: &TokenStream) -> Vec { let contents; if let TokenTree::Group(group) = item.clone().into_iter().next().unwrap() { @@ -62,6 +74,9 @@ pub fn args_split(item: &TokenStream) -> Vec { for tt in contents { match tt { TokenTree::Punct(ref punct) => match punct.as_char() { + // < and > do **not** form TokenTree groups, however their + // usage is like that of a group. Hence, we need extra + // logic to skip them. '<' => angle_level += 1, '>' => angle_level -= 1, ',' => if angle_level == 0 { @@ -83,8 +98,13 @@ pub fn args_split(item: &TokenStream) -> Vec { args } -/// Input: "(p1 : t1, p2: t2, ...)" -/// Output: vec!["p1 : t1", "p2: t2", ...], vec!["p1 : t1", "p2: t2", ...] +/// Like `args_split`, however two vectors are returned: the first for public +/// arguments (and their types) and the second for private ones. +/// +/// `public` is a vector of argument names. +/// +/// **Input:** "(p1 : t1, p2: t2, ...)", vec!["p3", "p4", ...] +/// **Output:** vec!["p1 : t1", "p2: t2", ...], vec!["p3 : t3", "p4: t4", ...] pub fn args_split_public(item: &TokenStream, public: &Vec<&String>) -> (Vec, Vec) { let all_args = args_split(item); let public_args: Vec = all_args @@ -99,15 +119,18 @@ pub fn args_split_public(item: &TokenStream, public: &Vec<&String>) -> (Vec (Vec, Vec) { let contents; if let TokenTree::Group(group) = item.clone().into_iter().next().unwrap() { contents = group.stream().into_iter(); } else { - unreachable!(); + unreachable!("Item passed to args_divide is not a group: \"{item}\""); } let mut patterns = Vec::new(); @@ -119,11 +142,15 @@ pub fn args_divide(item: &TokenStream) -> (Vec, Vec) { for tt in contents { match tt { TokenTree::Punct(ref punct) => { + // Ignore "::" if punct.spacing() == Spacing::Joint && punct.as_char() == ':' { ignore_next = true; } else if !ignore_next { match punct.as_char() { + // < and > do **not** form TokenTree groups, however their + // usage is like that of a group. Hence, we need extra + // logic to skip them. '<' => angle_level += 1, '>' => angle_level -= 1, ':' => { @@ -153,8 +180,13 @@ pub fn args_divide(item: &TokenStream) -> (Vec, Vec) { (patterns, types) } -/// Input: "(p1 : t1, p2: t2, ...)" -/// Output: (vec![p1, p2, ...], vec![t1, t2, ...]), (vec![p1, p2, ...], vec![t1, t2, ...]) +/// Like `args_divide`, however two tuples of vectors are returned: the first +/// for public arguments and types, and the second for private ones. +/// +/// `public` is a vector of argument names. +/// +/// **Input:** "(p1 : t1, p2: t2, ...)", vec!["p3", "p4", ...] +/// **Output:** (vec!["p1", "p2", ...], vec!["t1", "t2", ...]), (vec!["p3", "p4", ...], vec!["t3", "t4", ...]) pub fn args_divide_public(item: &TokenStream, public: &Vec<&String>) -> ((Vec, Vec), (Vec, Vec)) { let (patterns, types) = args_divide(item); @@ -173,15 +205,19 @@ pub fn args_divide_public(item: &TokenStream, public: &Vec<&String>) -> ((Vec (TokenStream, TokenStream) { let (patterns, types) = args_divide(&item); (group_streams(&patterns), group_streams(&types)) } -/// Input: vec![p1, p2, ...] -/// Output: "(p1, p2, ...)" +/// Transform a vector of elements into a (TokenTree) group of elements +/// +/// **Input:** vec!["p1", "p2", ...] +/// **Output:** "(p1, p2, ...)" pub fn group_streams(patterns: &Vec) -> TokenStream { let mut inner_ts = TokenStream::new(); inner_ts.extend(patterns.clone().into_iter().flat_map(|i| [",".parse().unwrap(), i]).skip(1)); -- cgit v1.2.3