Skip to main content

hyperlight_guest_tracing_macro/
lib.rs

1/*
2Copyright 2025  The Hyperlight Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17use proc_macro::TokenStream;
18use quote::quote;
19use syn::{ItemFn, parse_macro_input};
20
21/// A procedural macro attribute for tracing function calls.
22/// This macro will create a trace record when the function is called
23///
24/// The trace record will contain the function name as a string.
25/// Note: This macro is intended to be used with the `hyperlight_guest_tracing` crate.
26#[proc_macro_attribute]
27pub fn trace_function(_attr: TokenStream, item: TokenStream) -> TokenStream {
28    let input_fn = parse_macro_input!(item as ItemFn);
29
30    let fn_name = &input_fn.sig.ident;
31    let fn_name_str = fn_name.to_string();
32    let fn_vis = &input_fn.vis;
33    let fn_sig = &input_fn.sig;
34    let fn_block = &input_fn.block;
35    let fn_attrs = &input_fn.attrs;
36    let fn_output = &input_fn.sig.output;
37
38    // Compose entry/exit messages
39    let entry_msg = format!("> {}", fn_name_str);
40    let _exit_msg = format!("< {}", fn_name_str);
41
42    let expanded = match fn_output {
43        syn::ReturnType::Default => {
44            // No return value (unit)
45            #[cfg(feature = "trace")]
46            quote! {
47                #(#fn_attrs)*
48                #fn_vis #fn_sig {
49                    const _: () = assert!(
50                        #entry_msg.len() <= hyperlight_guest_tracing::MAX_TRACE_MSG_LEN,
51                        "Trace message exceeds the maximum bytes length",
52                    );
53                    ::hyperlight_guest_tracing::create_trace_record(#entry_msg);
54                    // Call the original function body
55                    #fn_block
56                    ::hyperlight_guest_tracing::create_trace_record(#_exit_msg);
57                }
58            }
59            #[cfg(not(feature = "trace"))]
60            quote! {
61                #(#fn_attrs)*
62                #fn_vis #fn_sig {
63                    const _: () = assert!(
64                        #entry_msg.len() <= hyperlight_guest_tracing::MAX_TRACE_MSG_LEN,
65                        "Trace message exceeds the maximum bytes length",
66                    );
67                    #fn_block
68                }
69            }
70        }
71        syn::ReturnType::Type(_, _) => {
72            // Has a return value
73            #[cfg(feature = "trace")]
74            quote! {
75                #(#fn_attrs)*
76                #fn_vis #fn_sig {
77                    const _: () = assert!(
78                        #entry_msg.len() <= hyperlight_guest_tracing::MAX_TRACE_MSG_LEN,
79                        "Trace message exceeds the maximum bytes length",
80                    );
81                    ::hyperlight_guest_tracing::create_trace_record(#entry_msg);
82                    let __trace_result = (|| #fn_block )();
83                    ::hyperlight_guest_tracing::create_trace_record(#_exit_msg);
84                    __trace_result
85                }
86            }
87            #[cfg(not(feature = "trace"))]
88            quote! {
89                #(#fn_attrs)*
90                #fn_vis #fn_sig {
91                    const _: () = assert!(
92                        #entry_msg.len() <= hyperlight_guest_tracing::MAX_TRACE_MSG_LEN,
93                        "Trace message exceeds the maximum bytes length",
94                    );
95                    #fn_block
96                }
97            }
98        }
99    };
100
101    TokenStream::from(expanded)
102}
103
104// Input structure for the trace macro
105struct TraceMacroInput {
106    message: syn::Lit,
107    statement: Option<proc_macro2::TokenStream>,
108}
109
110impl syn::parse::Parse for TraceMacroInput {
111    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
112        let message: syn::Lit = input.parse()?;
113        if !matches!(message, syn::Lit::Str(_)) {
114            return Err(input.error("first argument to trace! must be a string literal"));
115        }
116        if let syn::Lit::Str(ref lit_str) = message
117            && lit_str.value().is_empty()
118        {
119            return Err(input.error("trace message must not be empty"));
120        }
121
122        let statement = if input.peek(syn::Token![,]) {
123            let _: syn::Token![,] = input.parse()?;
124            Some(input.parse()?)
125        } else {
126            None
127        };
128        Ok(TraceMacroInput { message, statement })
129    }
130}
131
132/// This macro creates a trace record with a message, or traces a block with entry/exit records.
133///
134/// When called with an expression or statement as the second argument, it is wrapped in a block,
135/// entry and exit trace records are created at the start and end of block, and the result of the block is returned.
136#[proc_macro]
137pub fn trace(input: TokenStream) -> TokenStream {
138    let parsed = syn::parse_macro_input!(input as TraceMacroInput);
139    let trace_message = match parsed.message {
140        syn::Lit::Str(ref lit_str) => lit_str.value(),
141        _ => unreachable!(),
142    };
143    if let Some(statement) = parsed.statement {
144        let entry_msg = format!("+ {}", trace_message);
145        let _exit_msg = format!("- {}", trace_message);
146        #[cfg(feature = "trace")]
147        let expanded = quote! {
148            {
149                const _: () = assert!(
150                    #entry_msg.len() <= hyperlight_guest_tracing::MAX_TRACE_MSG_LEN,
151                    "Trace message exceeds the maximum bytes length",
152                );
153                ::hyperlight_guest_tracing::create_trace_record(#entry_msg);
154                let __trace_result = #statement;
155                ::hyperlight_guest_tracing::create_trace_record(#_exit_msg);
156                __trace_result
157            }
158        };
159        #[cfg(not(feature = "trace"))]
160        let expanded = quote! {
161            {
162                const _: () = assert!(
163                    #entry_msg.len() <= hyperlight_guest_tracing::MAX_TRACE_MSG_LEN,
164                    "Trace message exceeds the maximum bytes length",
165                );
166                #statement
167            }
168        };
169
170        TokenStream::from(expanded)
171    } else {
172        #[cfg(feature = "trace")]
173        let expanded = quote! {
174            {
175                const _: () = assert!(
176                    #trace_message.len() <= hyperlight_guest_tracing::MAX_TRACE_MSG_LEN,
177                    "Trace message exceeds the maximum bytes length",
178                );
179                ::hyperlight_guest_tracing::create_trace_record(#trace_message);
180            }
181        };
182        #[cfg(not(feature = "trace"))]
183        let expanded = quote! {
184            {
185                const _: () = assert!(
186                    #trace_message.len() <= hyperlight_guest_tracing::MAX_TRACE_MSG_LEN,
187                    "Trace message exceeds the maximum bytes length",
188                );
189            }
190        };
191
192        TokenStream::from(expanded)
193    }
194}
195
196/// This macro flushes the trace buffer, sending any remaining trace records to the host.
197#[proc_macro]
198pub fn flush(_input: TokenStream) -> TokenStream {
199    #[cfg(feature = "trace")]
200    let expanded = quote! {
201        {
202            ::hyperlight_guest_tracing::flush_trace_buffer();
203        }
204    };
205    #[cfg(not(feature = "trace"))]
206    let expanded = quote! {};
207
208    TokenStream::from(expanded)
209}