boxify_macro/
lib.rs

1//! Proc-macro crate for `boxify` crate.
2//! Please see [`boxify`](../boxify) for more information.
3
4use std::str::FromStr;
5
6use litrs::{BoolLit, Literal};
7use proc_macro2::{Span, TokenStream};
8use quote::{quote, quote_spanned, ToTokens};
9use syn::{
10    parse_quote, parse_quote_spanned,
11    spanned::Spanned,
12    visit_mut::{self, VisitMut},
13    Expr, ExprCall, ExprPath, ExprStruct, Token,
14};
15
16use crate::expr_helpers::ExprCallExt;
17
18extern crate proc_macro;
19
20mod expr_helpers;
21
22/// Places the given value on the heap, like [`Box::new`], but without creating it on the stack first.
23/// This is useful for values that are too big to be created on the stack.
24///
25/// # Examples
26///
27/// ```rust,ignore
28/// use boxify::boxify;
29///
30/// let b = boxify!([42u32; 1024 * 1024 * 1024]);
31/// assert_eq!(b[0], 42);
32/// ```
33#[proc_macro]
34pub fn boxify(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
35    let value_to_box = syn::parse_macro_input!(item as Expr);
36
37    boxify_impl(value_to_box).into()
38}
39
40fn boxify_impl(value_to_box: Expr) -> TokenStream {
41    if let Some(zero_alloc) = zero_alloc_special_handling(&value_to_box) {
42        return zero_alloc;
43    }
44
45    let validate_fields = validate_fields(value_to_box.clone());
46
47    let final_value_ptr = parse_quote! {
48        __boxify_final_value_ptr
49    };
50    let instantiation_code = match &value_to_box {
51        // listing them here explicitly in order to throw an error for any other type
52        // since only these here are allocated directly on the heap right now
53        Expr::Struct(_) | Expr::Repeat(_) | Expr::Tuple(_) | Expr::Call(_) | Expr::Path(_) => {
54            fill_ptr(&final_value_ptr, &value_to_box)
55        }
56        _ => unimplemented!("Unsupported input type"),
57    };
58
59    quote! {{
60        let mut __boxify_final_value = ::boxify::new_box_uninit_typed(#validate_fields);
61        let __boxify_final_value_ptr = __boxify_final_value.as_mut_ptr();
62
63        #instantiation_code
64
65        // SAFETY: We just filled the memory with a valid value
66        unsafe { ::boxify::assume_init(__boxify_final_value) }
67    }}
68}
69
70/// Special handling for `[0; N]` arrays, which can be allocated with `new_box_zeroed`.
71fn zero_alloc_special_handling(value_to_box: &Expr) -> Option<TokenStream> {
72    if let Expr::Repeat(array) = value_to_box {
73        let value_expr = &*array.expr;
74        // parse the literal to check if it's all zero bytes
75        if let Ok(literal) = Literal::parse(value_expr.to_token_stream().to_string()) {
76            let is_zero = match &literal {
77                Literal::Integer(i) if i.value::<u128>() == Some(0) => true,
78                Literal::Bool(BoolLit::False) => true,
79                Literal::Float(f) if f64::from_str(f.number_part()) == Ok(0f64) => true,
80                Literal::Char(c) if c.value() == '\0' => true,
81                Literal::Byte(b) if b.value() == 0 => true,
82                _ => false,
83            };
84
85            // if the array is all zero, we can just allocate a zeroed memory chunk
86            if is_zero {
87                return Some(quote! {
88                    // SAFETY: We checked above that the array value should be all zero bytes
89                    unsafe {
90                        // using the `_typed` version here for proper type inference
91                        ::boxify::new_box_zeroed_typed(::boxify::TypeInferer::new(|| {
92                            #array
93                        }))
94                    }
95                });
96            }
97        }
98    }
99    None
100}
101
102/// Outputs code that creates a value of the same type as its input
103/// without taking ownership of the input.
104struct CloneType;
105
106impl VisitMut for CloneType {
107    fn visit_field_value_mut(&mut self, v: &mut syn::FieldValue) {
108        if v.colon_token.is_none() {
109            // need to set the colon token here, otherwise syn will not generate the expression
110            v.colon_token = Some(Token![:](v.span()));
111        }
112
113        let expr = &v.expr;
114        // Replace the expression with a clone of the expression.
115        v.expr = parse_quote! {
116            // SAFETY: we never execute this code,
117            // we just use it for type checking / inference
118            unsafe { ::boxify::clone(&#expr) }
119        };
120        visit_mut::visit_field_value_mut(self, v);
121    }
122}
123
124/// Generates code that validates that all fields of a struct were provided.
125fn validate_fields(mut expr: Expr) -> proc_macro2::TokenStream {
126    if let Expr::Struct(ExprStruct {
127        dot2_token: Some(dotdot),
128        ..
129    }) = &mut expr
130    {
131        return syn::Error::new(dotdot.span(), "Struct update syntax is not supported")
132            .into_compile_error();
133    }
134
135    CloneType.visit_expr_mut(&mut expr);
136
137    // Wrap this in a `TypeInferer` to prevent misuse
138    quote! {
139        ::boxify::TypeInferer::new(||
140        {
141            #expr
142        })
143    }
144}
145
146/// Validates that the given expression is not a function call of the form `function(...)`.
147///
148/// This is needed to distinguish between function calls and struct instantiations and
149/// cause a compile error for the former.
150fn validate_not_fn(expr: &ExprCall) -> TokenStream {
151    // clone the call params to avoid capturing outside variables
152    let mut clone = expr.clone();
153    CloneType.visit_expr_call_mut(&mut clone);
154
155    // create a match pattern that matches the given call expression
156    // example: `Tuple(a, b, c)` -> `Tuple(_, _, _)`
157    // this will cause a compiler error if the expression is a function call,
158    // since function calls cannot be match patterns (and that's exactly what we want)
159    let mut match_expr = expr.clone();
160    match_expr.replace_params(parse_quote! { _ });
161
162    quote_spanned! {expr.span()=> {
163        ::boxify::TypeInferer::new(||
164        {
165            // tuple structs can be matched
166            match #clone {
167                #match_expr => {}
168            }
169        });
170    }}
171}
172
173/// Fills a pointer with a value by matching on the value and choosing the
174/// appropriate method to fill the pointer.
175/// This is needed to be able to introduce special-handling for arrays and
176/// potentially big structs.
177fn fill_ptr(ptr: &Expr, value: &Expr) -> proc_macro2::TokenStream {
178    match value {
179        // Expr::Array(_array) => {
180        //     todo!("array literals are currently not supported");
181        // }
182        Expr::Repeat(array) => fill_array(ptr, array),
183        Expr::Struct(strct) => fill_struct_fields(ptr, strct),
184        Expr::Tuple(tuple) => fill_tuple(ptr, tuple.span(), &tuple.elems),
185        Expr::Call(call) => {
186            if let Expr::Path(ExprPath { path, .. }) = &*call.func {
187                let ident = path.segments.last().expect("empty path not supported");
188                let first_char = ident
189                    .ident
190                    .to_string()
191                    .chars()
192                    .next()
193                    .expect("empty ident not supported");
194                if first_char.is_uppercase() {
195                    // we assume it's a struct instantiation
196                    // but we need to make sure it's not a function call (otherwise we'd generate invalid code)
197                    let validate_not_fn_call = validate_not_fn(call);
198                    let fill_code = fill_tuple(ptr, call.span(), &call.args);
199
200                    quote! {{
201                        #validate_not_fn_call
202                        #fill_code
203                    }}
204                } else {
205                    // assume it's a function call
206                    // there is currently no way to know for sure, but the worst that can happen here is that we
207                    // allocate the contents of a tuple struct on the stack. Not ideal, but not UB.
208
209                    quote! {
210                        // unsafe { #ptr.write(#value); }
211                        unsafe { #ptr.write_unaligned(#value); }
212                    }
213                }
214            } else {
215                unimplemented!("Function calls are not supported")
216            }
217        }
218        e => {
219            // fallback to creating the value on the stack and writing it to
220            // the pointer from there
221            quote! {
222                // unsafe { #ptr.write(#e); }
223                unsafe { #ptr.write_unaligned(#e); }
224            }
225        }
226    }
227}
228
229/// Fills a struct by filling all its fields.
230fn fill_struct_fields(strct_ptr: &Expr, strct: &syn::ExprStruct) -> proc_macro2::TokenStream {
231    let instantiation_codes = strct.fields.iter().map(|field| {
232        let ident = &field.member;
233        let expr = &field.expr;
234
235        let field_ptr = parse_quote! {
236            ::core::ptr::addr_of_mut!((*#strct_ptr).#ident)
237        };
238        fill_ptr(&field_ptr, expr)
239    });
240    quote! {
241        #(#instantiation_codes);*
242    }
243}
244
245fn fill_array(ptr: &Expr, array: &syn::ExprRepeat) -> proc_macro2::TokenStream {
246    let value = &*array.expr;
247    quote! {
248        // SAFETY: We only call this on uninitialized memory
249        unsafe { ::boxify::fill_array(#ptr, #value); }
250    }
251}
252
253/// Fills a tuple by filling all its elements.
254fn fill_tuple(
255    ptr: &Expr,
256    span: Span,
257    elems: &syn::punctuated::Punctuated<Expr, syn::token::Comma>,
258) -> proc_macro2::TokenStream {
259    let instantiation_codes = elems.iter().enumerate().map(|(index, value)| {
260        let index = syn::Index::from(index);
261        let field_ptr = parse_quote_spanned! {value.span()=>
262            ::core::ptr::addr_of_mut!((*#ptr).#index)
263        };
264        fill_ptr(&field_ptr, value)
265    });
266    quote_spanned! {span=>
267        #(#instantiation_codes);*
268    }
269}
270
271#[cfg(test)]
272mod tests {
273    use syn::ExprStruct;
274
275    use super::*;
276
277    #[test]
278    fn test_validate_fields() {
279        let expr = parse_quote! {
280            Parent {
281                child: Child {
282                    value: 42,
283                    grand_child: GrandChild {
284                        vec: v,
285                        huge_array: [42; 1024 * 1024 * 1024],
286                    },
287                },
288            }
289        };
290        let tokens = validate_fields(expr);
291
292        let expected: TokenStream = quote! {
293            ::boxify::TypeInferer::new(|| {
294                Parent {
295                    child: unsafe { ::boxify::clone(&Child {
296                        value: unsafe { ::boxify::clone(&42) },
297                        grand_child: unsafe { ::boxify::clone(&GrandChild {
298                            vec: unsafe { ::boxify::clone(&v) },
299                            huge_array: unsafe { ::boxify::clone(&[42; 1024 * 1024 * 1024]) },
300                        }) },
301                    }) },
302                }
303            })
304        };
305        assert_eq!(tokens.to_string(), expected.to_string());
306    }
307
308    #[test]
309    fn boxify_short_form() {
310        let mut a: ExprStruct = parse_quote!(Foo { a });
311        let mut b: ExprStruct = parse_quote!(Foo { a: a });
312
313        let expected: ExprStruct = parse_quote!(Foo {
314            a: unsafe { ::boxify::clone(&a) }
315        });
316
317        CloneType.visit_expr_struct_mut(&mut a);
318        CloneType.visit_expr_struct_mut(&mut b);
319
320        assert_eq!(
321            a.to_token_stream().to_string(),
322            expected.to_token_stream().to_string()
323        );
324        assert_eq!(
325            b.to_token_stream().to_string(),
326            expected.to_token_stream().to_string()
327        );
328    }
329
330    #[test]
331    fn trait_fn_call() {
332        let e: Expr = parse_quote!(La {
333            ma: -42i128,
334            na: E {
335                f: &[42u64; 10],
336                g: -42i32,
337                h: false,
338                i: &[42u64; 10],
339                j: String::from("a string"),
340                __phantom: ::core::marker::PhantomData::<&()>
341            },
342            __phantom: ::core::marker::PhantomData::<&()>
343        });
344
345        boxify_impl(e);
346    }
347}