Skip to main content

rustc_builtin_macros/
autodiff.rs

1//! This module contains the implementation of the `#[autodiff]` attribute.
2//! Currently our linter isn't smart enough to see that each import is used in one of the two
3//! configs (autodiff enabled or disabled), so we have to add cfg's to each import.
4//! FIXME(ZuseZ4): Remove this once we have a smarter linter.
5
6mod llvm_enzyme {
7    use std::str::FromStr;
8    use std::string::String;
9
10    use rustc_ast::expand::autodiff_attrs::{
11        DiffActivity, DiffMode, valid_input_activity, valid_ret_activity, valid_ty_for_activity,
12    };
13    use rustc_ast::token::{IdentKind, Lit, LitKind, Token, TokenKind};
14    use rustc_ast::tokenstream::*;
15    use rustc_ast::visit::AssocCtxt::*;
16    use rustc_ast::{
17        self as ast, FnRetTy, FnSig, GenericArg, GenericParamKind, Generics, ItemKind,
18        MetaItemInner, PatKind, TyKind, Visibility,
19    };
20    use rustc_attr_ir::RustcAutodiff;
21    use rustc_expand::base::{Annotatable, ExtCtxt};
22    use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym};
23    use thin_vec::{ThinVec, thin_vec};
24    use tracing::{debug, trace};
25
26    use crate::diagnostics;
27
28    pub(crate) fn outer_normal_attr(
29        kind: &Box<rustc_ast::NormalAttr>,
30        id: rustc_ast::AttrId,
31        span: Span,
32    ) -> rustc_ast::Attribute {
33        let style = rustc_ast::AttrStyle::Outer;
34        let kind = rustc_ast::AttrKind::Normal(kind.clone());
35        rustc_ast::Attribute { kind, id, style, span }
36    }
37
38    // If we have a default `()` return type or explicitley `()` return type,
39    // then we often can skip doing some work.
40    fn has_ret(ty: &FnRetTy) -> bool {
41        match ty {
42            FnRetTy::Ty(ty) => !ty.kind.is_unit(),
43            FnRetTy::Default(_) => false,
44        }
45    }
46    fn first_ident(x: &MetaItemInner) -> rustc_span::Ident {
47        if let Some(l) = x.lit() {
48            match l.kind {
49                ast::LitKind::Int(val, _) => {
50                    // get an Ident from a lit
51                    return rustc_span::Ident::from_str(val.get().to_string().as_str());
52                }
53                _ => {}
54            }
55        }
56
57        let segments = &x.meta_item().unwrap().path.segments;
58        if !(segments.len() == 1) {
    ::core::panicking::panic("assertion failed: segments.len() == 1")
};assert!(segments.len() == 1);
59        segments[0].ident
60    }
61
62    fn name(x: &MetaItemInner) -> String {
63        first_ident(x).name.to_string()
64    }
65
66    fn width(x: &MetaItemInner) -> Option<u128> {
67        let lit = x.lit()?;
68        match lit.kind {
69            ast::LitKind::Int(x, _) => Some(x.get()),
70            _ => None,
71        }
72    }
73
74    // Get information about the function the macro is applied to
75    fn extract_item_info(iitem: &Box<ast::Item>) -> Option<(Visibility, FnSig, Ident, Generics)> {
76        match &iitem.kind {
77            ItemKind::Fn(ast::Fn { sig, ident, generics, .. }) => {
78                Some((iitem.vis.clone(), sig.clone(), *ident, generics.clone()))
79            }
80            _ => None,
81        }
82    }
83
84    pub(crate) fn from_ast(
85        ecx: &mut ExtCtxt<'_>,
86        meta_item: &ThinVec<MetaItemInner>,
87        has_ret: bool,
88        mode: DiffMode,
89    ) -> RustcAutodiff {
90        let dcx = ecx.sess.dcx();
91
92        // Now we check, whether the user wants autodiff in batch/vector mode, or scalar mode.
93        // If he doesn't specify an integer (=width), we default to scalar mode, thus width=1.
94        let mut first_activity = 1;
95
96        let width = if let [_, x, ..] = &meta_item[..]
97            && let Some(x) = width(x)
98        {
99            first_activity = 2;
100            match x.try_into() {
101                Ok(x) => x,
102                Err(_) => {
103                    dcx.emit_err(diagnostics::AutoDiffInvalidWidth {
104                        span: meta_item[1].span(),
105                        width: x,
106                    });
107                    return RustcAutodiff::error();
108                }
109            }
110        } else {
111            1
112        };
113
114        let mut activities: Vec<DiffActivity> = ::alloc::vec::Vec::new()vec![];
115        let mut errors = false;
116        for x in &meta_item[first_activity..] {
117            let activity_str = name(x);
118            let res = DiffActivity::from_str(&activity_str);
119            match res {
120                Ok(x) => activities.push(x),
121                Err(_) => {
122                    dcx.emit_err(diagnostics::AutoDiffUnknownActivity {
123                        span: x.span(),
124                        act: activity_str,
125                    });
126                    errors = true;
127                }
128            };
129        }
130        if errors {
131            return RustcAutodiff::error();
132        }
133
134        // If a return type exist, we need to split the last activity,
135        // otherwise we return None as placeholder.
136        let (ret_activity, input_activity) = if has_ret {
137            let Some((last, rest)) = activities.split_last() else {
138                {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("should not be reachable because we counted the number of activities previously")));
};unreachable!(
139                    "should not be reachable because we counted the number of activities previously"
140                );
141            };
142            (last, rest)
143        } else {
144            (&DiffActivity::None, activities.as_slice())
145        };
146
147        RustcAutodiff {
148            mode,
149            width,
150            ret_activity: *ret_activity,
151            input_activity: input_activity.iter().cloned().collect(),
152        }
153    }
154
155    fn meta_item_inner_to_ts(t: &MetaItemInner, ts: &mut Vec<TokenTree>) {
156        let comma = Token::new(TokenKind::Comma, Span::default());
157        let val = first_ident(t);
158        let t = Token::from_ast_ident(val);
159        ts.push(TokenTree::Token(t, Spacing::Joint));
160        ts.push(TokenTree::Token(comma, Spacing::Alone));
161    }
162
163    pub(crate) fn expand_forward(
164        ecx: &mut ExtCtxt<'_>,
165        expand_span: Span,
166        meta_item: &ast::MetaItem,
167        item: Annotatable,
168    ) -> Vec<Annotatable> {
169        expand_with_mode(ecx, expand_span, meta_item, item, DiffMode::Forward)
170    }
171
172    pub(crate) fn expand_reverse(
173        ecx: &mut ExtCtxt<'_>,
174        expand_span: Span,
175        meta_item: &ast::MetaItem,
176        item: Annotatable,
177    ) -> Vec<Annotatable> {
178        expand_with_mode(ecx, expand_span, meta_item, item, DiffMode::Reverse)
179    }
180
181    /// We expand the autodiff macro to generate a new placeholder function which passes
182    /// type-checking and can be called by users. The exact signature of the generated function
183    /// depends on the configuration provided by the user, but here is an example:
184    ///
185    /// ```
186    /// #[autodiff(cos_box, Reverse, Duplicated, Active)]
187    /// fn sin(x: &Box<f32>) -> f32 {
188    ///     f32::sin(**x)
189    /// }
190    /// ```
191    /// which becomes expanded to:
192    /// ```
193    /// #[rustc_autodiff]
194    /// fn sin(x: &Box<f32>) -> f32 {
195    ///     f32::sin(**x)
196    /// }
197    /// #[rustc_autodiff(Reverse, Duplicated, Active)]
198    /// fn cos_box(x: &Box<f32>, dx: &mut Box<f32>, dret: f32) -> f32 {
199    ///     std::intrinsics::autodiff(sin::<> as fn(..) -> .., cos_box::<>, (x, dx, dret))
200    /// }
201    /// ```
202    /// FIXME(ZuseZ4): Once autodiff is enabled by default, make this a doc comment which is checked
203    /// in CI.
204    pub(crate) fn expand_with_mode(
205        ecx: &mut ExtCtxt<'_>,
206        expand_span: Span,
207        meta_item: &ast::MetaItem,
208        mut item: Annotatable,
209        mode: DiffMode,
210    ) -> Vec<Annotatable> {
211        let dcx = ecx.sess.dcx();
212
213        // first get information about the annotable item: visibility, signature, name and generic
214        // parameters.
215        // these will be used to generate the differentiated version of the function
216        let Some((vis, sig, primal, generics, is_impl)) = (match &item {
217            Annotatable::Item(iitem) => {
218                extract_item_info(iitem).map(|(v, s, p, g)| (v, s, p, g, false))
219            }
220            Annotatable::Stmt(stmt) => match &stmt.kind {
221                ast::StmtKind::Item(iitem) => {
222                    extract_item_info(iitem).map(|(v, s, p, g)| (v, s, p, g, false))
223                }
224                _ => None,
225            },
226            Annotatable::AssocItem(assoc_item, _ctxt @ (Impl { of_trait: _ } | Trait)) => {
227                match &assoc_item.kind {
228                    ast::AssocItemKind::Fn(ast::Fn { sig, ident, generics, .. }) => {
229                        Some((assoc_item.vis.clone(), sig.clone(), *ident, generics.clone(), true))
230                    }
231                    _ => None,
232                }
233            }
234            _ => None,
235        }) else {
236            dcx.emit_err(diagnostics::AutoDiffInvalidApplication { span: item.span() });
237            return ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [item]))vec![item];
238        };
239
240        let meta_item_vec: ThinVec<MetaItemInner> = match meta_item.kind {
241            ast::MetaItemKind::List(ref vec) => vec.clone(),
242            _ => {
243                dcx.emit_err(diagnostics::AutoDiffMissingConfig { span: item.span() });
244                return ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [item]))vec![item];
245            }
246        };
247
248        let has_ret = has_ret(&sig.decl.output);
249
250        // create TokenStream from vec elemtents:
251        // meta_item doesn't have a .tokens field
252        let mut ts: Vec<TokenTree> = ::alloc::vec::Vec::new()vec![];
253        if meta_item_vec.is_empty() {
254            // At the bare minimum, we need a fnc name.
255            dcx.emit_err(diagnostics::AutoDiffMissingConfig { span: item.span() });
256            return ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [item]))vec![item];
257        }
258
259        let mode_symbol = match mode {
260            DiffMode::Forward => sym::Forward,
261            DiffMode::Reverse => sym::Reverse,
262            _ => {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("Unsupported mode: {0:?}", mode)));
}unreachable!("Unsupported mode: {:?}", mode),
263        };
264
265        // Insert mode token
266        let mode_token =
267            Token::new(TokenKind::Ident(mode_symbol, IdentKind::Normal), Span::default());
268        ts.insert(0, TokenTree::Token(mode_token, Spacing::Joint));
269        ts.insert(
270            1,
271            TokenTree::Token(Token::new(TokenKind::Comma, Span::default()), Spacing::Alone),
272        );
273
274        // Now, if the user gave a width (vector aka batch-mode ad), then we copy it.
275        // If it is not given, we default to 1 (scalar mode).
276        let start_position;
277        let kind = LitKind::Integer;
278        let symbol;
279        if meta_item_vec.len() >= 2
280            && let Some(width) = width(&meta_item_vec[1])
281        {
282            start_position = 2;
283            symbol = Symbol::intern(&width.to_string());
284        } else {
285            start_position = 1;
286            symbol = sym::integer(1);
287        }
288
289        let l = Lit { kind, symbol, suffix: None };
290        let t = Token::new(TokenKind::Literal(l), Span::default());
291        let comma = Token::new(TokenKind::Comma, Span::default());
292        ts.push(TokenTree::Token(t, Spacing::Joint));
293        ts.push(TokenTree::Token(comma, Spacing::Alone));
294
295        for t in meta_item_vec.clone()[start_position..].iter() {
296            meta_item_inner_to_ts(t, &mut ts);
297        }
298
299        if !has_ret {
300            // We don't want users to provide a return activity if the function doesn't return anything.
301            // For simplicity, we just add a dummy token to the end of the list.
302            let t = Token::new(TokenKind::Ident(sym::None, IdentKind::Normal), Span::default());
303            ts.push(TokenTree::Token(t, Spacing::Joint));
304            ts.push(TokenTree::Token(comma, Spacing::Alone));
305        }
306        // We remove the last, trailing comma.
307        ts.pop();
308        let ts = TokenStream::from_iter(ts);
309
310        let x = from_ast(ecx, &meta_item_vec, has_ret, mode);
311        if !x.is_active() {
312            // We encountered an error, so we return the original item.
313            // This allows us to potentially parse other attributes.
314            return ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [item]))vec![item];
315        }
316        let span = ecx.with_def_site_ctxt(expand_span);
317
318        let d_sig = gen_enzyme_decl(ecx, &sig, &x, span);
319
320        let d_body = ecx.block(
321            span,
322            {
    let len = [()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(call_autodiff(ecx, primal, first_ident(&meta_item_vec[0]), span,
            &sig, &d_sig, &generics, is_impl));
    vec
}thin_vec![call_autodiff(
323                ecx,
324                primal,
325                first_ident(&meta_item_vec[0]),
326                span,
327                &sig,
328                &d_sig,
329                &generics,
330                is_impl,
331            )],
332        );
333
334        // The first element of it is the name of the function to be generated
335        let d_fn = ecx.item_fn(d_sig, first_ident(&meta_item_vec[0]), generics, Some(d_body));
336        let mut rustc_ad_attr =
337            Box::new(ast::NormalAttr::from_ident(Ident::with_dummy_span(sym::rustc_autodiff)));
338
339        let ts2 = ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [TokenTree::Token(Token::new(TokenKind::Ident(sym::never,
                            IdentKind::Normal), span), Spacing::Joint)]))vec![TokenTree::Token(
340            Token::new(TokenKind::Ident(sym::never, IdentKind::Normal), span),
341            Spacing::Joint,
342        )];
343        let never_arg = ast::DelimArgs {
344            dspan: DelimSpan::from_single(span),
345            delim: ast::token::Delimiter::Parenthesis,
346            tokens: TokenStream::from_iter(ts2),
347        };
348        let inline_item = ast::AttrItem {
349            unsafety: ast::Safety::Default,
350            path: ast::Path::from_ident(Ident::with_dummy_span(sym::inline)),
351            args: ast::AttrArgs::Delimited(never_arg),
352            span: DUMMY_SP,
353        };
354        let inline_never_attr = Box::new(ast::NormalAttr { item: inline_item, tokens: None });
355        let new_id = ecx.sess.psess.attr_id_generator.mk_attr_id();
356        let attr = outer_normal_attr(&rustc_ad_attr, new_id, span);
357        let new_id = ecx.sess.psess.attr_id_generator.mk_attr_id();
358        let inline_never = outer_normal_attr(&inline_never_attr, new_id, span);
359
360        // We're avoid duplicating the attribute `#[rustc_autodiff]`.
361        fn same_attribute(attr: &ast::AttrKind, item: &ast::AttrKind) -> bool {
362            match (attr, item) {
363                (ast::AttrKind::Normal(a), ast::AttrKind::Normal(b)) => {
364                    let a = &a.item.path;
365                    let b = &b.item.path;
366                    a.segments.iter().eq_by(&b.segments, |a, b| a.ident == b.ident)
367                }
368                _ => false,
369            }
370        }
371
372        let mut has_inline_never = false;
373
374        // Don't add it multiple times:
375        let orig_annotatable = match item {
376            Annotatable::Item(ref mut iitem) => {
377                if !iitem.attrs.iter().any(|a| same_attribute(&a.kind, &attr.kind)) {
378                    iitem.attrs.push(attr);
379                }
380                if iitem.attrs.iter().any(|a| same_attribute(&a.kind, &inline_never.kind)) {
381                    has_inline_never = true;
382                }
383                Annotatable::Item(iitem.clone())
384            }
385            Annotatable::AssocItem(ref mut assoc_item, ctxt @ (Impl { .. } | Trait)) => {
386                if !assoc_item.attrs.iter().any(|a| same_attribute(&a.kind, &attr.kind)) {
387                    assoc_item.attrs.push(attr);
388                }
389                if assoc_item.attrs.iter().any(|a| same_attribute(&a.kind, &inline_never.kind)) {
390                    has_inline_never = true;
391                }
392                Annotatable::AssocItem(assoc_item.clone(), ctxt)
393            }
394            Annotatable::Stmt(ref mut stmt) => {
395                match stmt.kind {
396                    ast::StmtKind::Item(ref mut iitem) => {
397                        if !iitem.attrs.iter().any(|a| same_attribute(&a.kind, &attr.kind)) {
398                            iitem.attrs.push(attr);
399                        }
400                        if iitem.attrs.iter().any(|a| same_attribute(&a.kind, &inline_never.kind)) {
401                            has_inline_never = true;
402                        }
403                    }
404                    _ => {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("stmt kind checked previously")));
}unreachable!("stmt kind checked previously"),
405                };
406
407                Annotatable::Stmt(stmt.clone())
408            }
409            _ => {
410                {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("annotatable kind checked previously")));
}unreachable!("annotatable kind checked previously")
411            }
412        };
413        // Now update for d_fn
414        rustc_ad_attr.item.args = rustc_ast::AttrArgs::Delimited(rustc_ast::DelimArgs {
415            dspan: DelimSpan::dummy(),
416            delim: rustc_ast::token::Delimiter::Parenthesis,
417            tokens: ts,
418        });
419
420        let new_id = ecx.sess.psess.attr_id_generator.mk_attr_id();
421        let d_attr = outer_normal_attr(&rustc_ad_attr, new_id, span);
422
423        // If the source function has the `#[inline(never)]` attribute, we'll also add it to the diff function
424        let mut d_attrs = {
    let len = [()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(d_attr);
    vec
}thin_vec![d_attr];
425
426        if has_inline_never {
427            d_attrs.push(inline_never);
428        }
429
430        let d_annotatable = match &item {
431            Annotatable::AssocItem(_, ctxt) => {
432                let assoc_item = ast::AssocItemKind::Fn(d_fn);
433                let d_fn = Box::new(ast::AssocItem {
434                    attrs: d_attrs,
435                    id: ast::DUMMY_NODE_ID,
436                    span,
437                    vis,
438                    kind: assoc_item,
439                    tokens: None,
440                });
441                Annotatable::AssocItem(d_fn, *ctxt)
442            }
443            Annotatable::Item(_) => {
444                let mut d_fn = ecx.item(span, d_attrs, ItemKind::Fn(d_fn));
445                d_fn.vis = vis;
446
447                Annotatable::Item(d_fn)
448            }
449            Annotatable::Stmt(_) => {
450                let mut d_fn = ecx.item(span, d_attrs, ItemKind::Fn(d_fn));
451                d_fn.vis = vis;
452                Annotatable::Stmt(Box::new(ecx.stmt_item(span, d_fn)))
453            }
454            _ => {
455                {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("item kind checked previously")));
}unreachable!("item kind checked previously")
456            }
457        };
458
459        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [orig_annotatable, d_annotatable]))vec![orig_annotatable, d_annotatable]
460    }
461
462    // shadow arguments (the extra ones which were not in the original (primal) function), in reverse mode must be
463    // mutable references or ptrs, because Enzyme will write into them.
464    fn assure_mut_ref(ty: &ast::Ty) -> ast::Ty {
465        let mut ty = ty.clone();
466        match ty.kind {
467            TyKind::Ptr(_, ref mut mutbl) => {
468                *mutbl = ast::Mutability::Mut;
469            }
470            TyKind::Ref(_, _, ref mut mutbl) => {
471                *mutbl = ast::Mutability::Mut;
472            }
473            _ => {
474                {
    ::core::panicking::panic_fmt(format_args!("unsupported type: {0:?}", ty));
};panic!("unsupported type: {:?}", ty);
475            }
476        }
477        ty
478    }
479
480    // Generate `autodiff` intrinsic call
481    // ```
482    // std::intrinsics::autodiff(source as fn(..) -> .., diff, (args))
483    // ```
484    fn call_autodiff(
485        ecx: &ExtCtxt<'_>,
486        primal: Ident,
487        diff: Ident,
488        span: Span,
489        p_sig: &FnSig,
490        d_sig: &FnSig,
491        generics: &Generics,
492        is_impl: bool,
493    ) -> rustc_ast::Stmt {
494        let primal_path_expr = gen_turbofish_expr(ecx, primal, generics, span, is_impl);
495
496        let self_ty = || ecx.ty_self(DUMMY_SP);
497        let fn_ptr_params: ThinVec<ast::Param> = p_sig
498            .decl
499            .inputs
500            .iter()
501            .map(|param| {
502                let ty = match &param.ty.kind {
503                    TyKind::ImplicitSelf => self_ty(),
504                    TyKind::Ref(lt, inner_ty, mutbl)
505                        if #[allow(non_exhaustive_omitted_patterns)] match inner_ty.kind {
    TyKind::ImplicitSelf => true,
    _ => false,
}matches!(inner_ty.kind, TyKind::ImplicitSelf) =>
506                    {
507                        ecx.ty(span, TyKind::Ref(*lt, self_ty(), *mutbl))
508                    }
509                    TyKind::Ptr(inner_ty, mutbl)
510                        if #[allow(non_exhaustive_omitted_patterns)] match inner_ty.kind {
    TyKind::ImplicitSelf => true,
    _ => false,
}matches!(inner_ty.kind, TyKind::ImplicitSelf) =>
511                    {
512                        ecx.ty(span, TyKind::Ptr(self_ty(), *mutbl))
513                    }
514                    _ => param.ty.clone(),
515                };
516                ast::Param {
517                    attrs: ast::AttrVec::new(),
518                    ty,
519                    pat: Box::new(ecx.pat_wild(span)),
520                    id: ast::DUMMY_NODE_ID,
521                    span,
522                    is_placeholder: false,
523                    has_const_keyword: false,
524                }
525            })
526            .collect();
527        let fn_ptr_ty = ecx.ty(
528            span,
529            TyKind::FnPtr(Box::new(ast::FnPtrTy {
530                safety: p_sig.header.safety,
531                ext: p_sig.header.ext,
532                generic_params: ThinVec::new(),
533                decl: Box::new(ast::FnDecl {
534                    inputs: fn_ptr_params,
535                    output: p_sig.decl.output.clone(),
536                }),
537                decl_span: span,
538            })),
539        );
540        let primal_fn_ptr = ecx.expr(span, ast::ExprKind::Cast(primal_path_expr, fn_ptr_ty));
541
542        let diff_path_expr = gen_turbofish_expr(ecx, diff, generics, span, is_impl);
543
544        let tuple_expr = ecx.expr_tuple(
545            span,
546            d_sig
547                .decl
548                .inputs
549                .iter()
550                .map(|arg| match arg.pat.kind {
551                    PatKind::Ident(_, ident, _) => ecx.expr_path(ecx.path_ident(span, ident)),
552                    _ => ::core::panicking::unimplemented_default_msg()unimplemented!(),
553                })
554                .collect::<ThinVec<_>>(),
555        );
556
557        let call_expr = ecx.expr_call_intrinsic(
558            span,
559            sym::autodiff,
560            {
    let len = [(), (), ()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(primal_fn_ptr);
    vec.push(diff_path_expr);
    vec.push(tuple_expr);
    vec
}thin_vec![primal_fn_ptr, diff_path_expr, tuple_expr],
561        );
562
563        ecx.stmt_expr(call_expr)
564    }
565
566    // Generate turbofish expression from fn name and generics
567    // Given `foo` and `<A, B, C>` params, gen `foo::<A, B, C>`
568    // We use this expression when passing primal and diff function to the autodiff intrinsic
569    fn gen_turbofish_expr(
570        ecx: &ExtCtxt<'_>,
571        ident: Ident,
572        generics: &Generics,
573        span: Span,
574        is_impl: bool,
575    ) -> Box<ast::Expr> {
576        let generic_args = generics
577            .params
578            .iter()
579            .filter_map(|p| match &p.kind {
580                GenericParamKind::Type { .. } => {
581                    let path = ast::Path::from_ident(p.ident);
582                    let ty = ecx.ty_path(path);
583                    Some(GenericArg::Type(ty))
584                }
585                GenericParamKind::Const { .. } => {
586                    let anon_const = ecx.const_ident(span, p.ident);
587                    Some(GenericArg::Const(anon_const))
588                }
589                GenericParamKind::Lifetime => None,
590            })
591            .collect::<Vec<_>>();
592
593        let idents = if is_impl { &[Ident::new(kw::SelfUpper, span), ident][..] } else { &[ident] };
594        let path = ecx.path_all(span, idents, generic_args);
595
596        ecx.expr_path(path)
597    }
598
599    // Generate the new function declaration. Const arguments are kept as is. Duplicated arguments must
600    // be pointers or references. Those receive a shadow argument, which is a mutable reference/pointer.
601    // Active arguments must be scalars. Their shadow argument is added to the return type (and will be
602    // zero-initialized by Enzyme).
603    // Each argument of the primal function (and the return type if existing) must be annotated with an
604    // activity.
605    //
606    // Error handling: If the user provides an invalid configuration (incorrect numbers, types, or
607    // both), we emit an error and return the original signature. This allows us to continue parsing.
608    // FIXME(Sa4dUs): make individual activities' span available so errors
609    // can point to only the activity instead of the entire attribute
610    fn gen_enzyme_decl(
611        ecx: &ExtCtxt<'_>,
612        sig: &ast::FnSig,
613        x: &RustcAutodiff,
614        span: Span,
615    ) -> ast::FnSig {
616        let dcx = ecx.sess.dcx();
617        let has_ret = has_ret(&sig.decl.output);
618        let sig_args = sig.decl.inputs.len() + if has_ret { 1 } else { 0 };
619        let num_activities = x.input_activity.len() + if x.has_ret_activity() { 1 } else { 0 };
620        if sig_args != num_activities {
621            dcx.emit_err(diagnostics::AutoDiffInvalidNumberActivities {
622                span,
623                expected: sig_args,
624                found: num_activities,
625            });
626            // This is not the right signature, but we can continue parsing.
627            return sig.clone();
628        }
629        if !(sig.decl.inputs.len() == x.input_activity.len()) {
    ::core::panicking::panic("assertion failed: sig.decl.inputs.len() == x.input_activity.len()")
};assert!(sig.decl.inputs.len() == x.input_activity.len());
630        if !(has_ret == x.has_ret_activity()) {
    ::core::panicking::panic("assertion failed: has_ret == x.has_ret_activity()")
};assert!(has_ret == x.has_ret_activity());
631        let mut d_decl = sig.decl.clone();
632        let mut d_inputs = ThinVec::new();
633        let mut act_ret = ThinVec::new();
634
635        // We have two loops, a first one just to check the activities and types and possibly report
636        // multiple errors in one compilation session.
637        let mut errors = false;
638        for (arg, activity) in sig.decl.inputs.iter().zip(x.input_activity.iter()) {
639            if !valid_input_activity(x.mode, *activity) {
640                dcx.emit_err(diagnostics::AutoDiffInvalidApplicationModeAct {
641                    span,
642                    mode: x.mode.to_string(),
643                    act: activity.to_string(),
644                });
645                errors = true;
646            }
647            if !valid_ty_for_activity(&arg.ty, *activity) {
648                dcx.emit_err(diagnostics::AutoDiffInvalidTypeForActivity {
649                    span: arg.ty.span,
650                    act: activity.to_string(),
651                });
652                errors = true;
653            }
654        }
655
656        if has_ret && !valid_ret_activity(x.mode, x.ret_activity) {
657            dcx.emit_err(diagnostics::AutoDiffInvalidRetAct {
658                span,
659                mode: x.mode.to_string(),
660                act: x.ret_activity.to_string(),
661            });
662            // We don't set `errors = true` to avoid annoying type errors relative
663            // to the expanded macro type signature
664        }
665
666        if errors {
667            // This is not the right signature, but we can continue parsing.
668            return sig.clone();
669        }
670
671        let unsafe_activities = x
672            .input_activity
673            .iter()
674            .any(|&act| #[allow(non_exhaustive_omitted_patterns)] match act {
    DiffActivity::DuplicatedOnly | DiffActivity::DualOnly => true,
    _ => false,
}matches!(act, DiffActivity::DuplicatedOnly | DiffActivity::DualOnly));
675        for (arg, activity) in sig.decl.inputs.iter().zip(x.input_activity.iter()) {
676            d_inputs.push(arg.clone());
677            match activity {
678                DiffActivity::Active => {
679                    act_ret.push(arg.ty.clone());
680                    // if width =/= 1, then push [arg.ty; width] to act_ret
681                }
682                DiffActivity::ActiveOnly => {
683                    // We will add the active scalar to the return type.
684                    // This is handled later.
685                }
686                DiffActivity::Duplicated | DiffActivity::DuplicatedOnly => {
687                    for i in 0..x.width {
688                        let mut shadow_arg = arg.clone();
689                        // We += into the shadow in reverse mode.
690                        *shadow_arg.ty = assure_mut_ref(&arg.ty);
691                        let old_name = if let PatKind::Ident(_, ident, _) = arg.pat.kind {
692                            ident.name
693                        } else {
694                            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/2d20b42b883dcfc014f3c2c56081eeb6091c2179/compiler/rustc_builtin_macros/src/autodiff.rs:694",
                        "rustc_builtin_macros::autodiff::llvm_enzyme",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/2d20b42b883dcfc014f3c2c56081eeb6091c2179/compiler/rustc_builtin_macros/src/autodiff.rs"),
                        ::tracing_core::__macro_support::Option::Some(694u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_builtin_macros::autodiff::llvm_enzyme"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("{0:#?}",
                                                    &shadow_arg.pat) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("{:#?}", &shadow_arg.pat);
695                            { ::core::panicking::panic_fmt(format_args!("not an ident?")); };panic!("not an ident?");
696                        };
697                        let name = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("d{0}_{1}", old_name, i))
    })format!("d{}_{}", old_name, i);
698                        *shadow_arg.pat = ecx.pat_name(shadow_arg.pat.span, Symbol::intern(&name));
699                        d_inputs.push(shadow_arg);
700                    }
701                }
702                DiffActivity::Dual
703                | DiffActivity::DualOnly
704                | DiffActivity::Dualv
705                | DiffActivity::DualvOnly => {
706                    // the *v variants get lowered to enzyme_dupv and enzyme_dupnoneedv, which cause
707                    // Enzyme to not expect N arguments, but one argument (which is instead larger).
708                    let iterations =
709                        if #[allow(non_exhaustive_omitted_patterns)] match activity {
    DiffActivity::Dualv | DiffActivity::DualvOnly => true,
    _ => false,
}matches!(activity, DiffActivity::Dualv | DiffActivity::DualvOnly) {
710                            1
711                        } else {
712                            x.width
713                        };
714                    for i in 0..iterations {
715                        let mut shadow_arg = arg.clone();
716                        let old_name = if let PatKind::Ident(_, ident, _) = arg.pat.kind {
717                            ident.name
718                        } else {
719                            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/2d20b42b883dcfc014f3c2c56081eeb6091c2179/compiler/rustc_builtin_macros/src/autodiff.rs:719",
                        "rustc_builtin_macros::autodiff::llvm_enzyme",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/2d20b42b883dcfc014f3c2c56081eeb6091c2179/compiler/rustc_builtin_macros/src/autodiff.rs"),
                        ::tracing_core::__macro_support::Option::Some(719u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_builtin_macros::autodiff::llvm_enzyme"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("{0:#?}",
                                                    &shadow_arg.pat) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("{:#?}", &shadow_arg.pat);
720                            { ::core::panicking::panic_fmt(format_args!("not an ident?")); };panic!("not an ident?");
721                        };
722                        let name = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("b{0}_{1}", old_name, i))
    })format!("b{}_{}", old_name, i);
723
724                        *shadow_arg.pat = ecx.pat_name(shadow_arg.pat.span, Symbol::intern(&name));
725                        d_inputs.push(shadow_arg);
726                    }
727                }
728                DiffActivity::Const => {
729                    // Nothing to do here.
730                }
731                DiffActivity::None | DiffActivity::FakeActivitySize(_) => {
732                    { ::core::panicking::panic_fmt(format_args!("Should not happen")); };panic!("Should not happen");
733                }
734            }
735        }
736
737        let active_only_ret = x.ret_activity == DiffActivity::ActiveOnly;
738        if active_only_ret {
739            if !x.mode.is_rev() {
    ::core::panicking::panic("assertion failed: x.mode.is_rev()")
};assert!(x.mode.is_rev());
740        }
741
742        // If we return a scalar in the primal and the scalar is active,
743        // then add it as last arg to the inputs.
744        if x.mode.is_rev() {
745            match x.ret_activity {
746                DiffActivity::Active | DiffActivity::ActiveOnly => {
747                    let ty = match d_decl.output {
748                        FnRetTy::Ty(ref ty) => ty.clone(),
749                        FnRetTy::Default(span) => {
750                            {
    ::core::panicking::panic_fmt(format_args!("Did not expect Default ret ty: {0:?}",
            span));
};panic!("Did not expect Default ret ty: {:?}", span);
751                        }
752                    };
753                    let shadow_arg = ecx.param(ty.span, sym::dret, ty);
754                    d_inputs.push(shadow_arg);
755                }
756                _ => {}
757            }
758        }
759        d_decl.inputs = d_inputs;
760
761        if x.mode.is_fwd() {
762            let ty = match d_decl.output {
763                FnRetTy::Ty(ref ty) => ty.clone(),
764                FnRetTy::Default(span) => {
765                    // We want to return std::hint::black_box(()).
766                    let ty = ecx.ty_unit(span);
767                    d_decl.output = FnRetTy::Ty(ty.clone());
768                    if !#[allow(non_exhaustive_omitted_patterns)] match x.ret_activity {
            DiffActivity::None => true,
            _ => false,
        } {
    ::core::panicking::panic("assertion failed: matches!(x.ret_activity, DiffActivity::None)")
};assert!(matches!(x.ret_activity, DiffActivity::None));
769                    // this won't be used below, so any type would be fine.
770                    ty
771                }
772            };
773
774            if #[allow(non_exhaustive_omitted_patterns)] match x.ret_activity {
    DiffActivity::Dual | DiffActivity::Dualv => true,
    _ => false,
}matches!(x.ret_activity, DiffActivity::Dual | DiffActivity::Dualv) {
775                let kind = if x.width == 1 || #[allow(non_exhaustive_omitted_patterns)] match x.ret_activity {
    DiffActivity::Dualv => true,
    _ => false,
}matches!(x.ret_activity, DiffActivity::Dualv) {
776                    // Dual can only be used for f32/f64 ret.
777                    // In that case we return now a tuple with two floats.
778                    TyKind::Tup({
    let len = [(), ()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(ty.clone());
    vec.push(ty.clone());
    vec
}thin_vec![ty.clone(), ty.clone()])
779                } else {
780                    // We have to return [T; width+1], +1 for the primal return.
781                    let anon_const = rustc_ast::AnonConst {
782                        id: ast::DUMMY_NODE_ID,
783                        value: ecx.expr_usize(span, 1 + x.width as usize),
784                    };
785                    TyKind::Array(ty.clone(), anon_const)
786                };
787                let ty = Box::new(rustc_ast::Ty { kind, id: ty.id, span: ty.span });
788                d_decl.output = FnRetTy::Ty(ty);
789            }
790            if #[allow(non_exhaustive_omitted_patterns)] match x.ret_activity {
    DiffActivity::DualOnly | DiffActivity::DualvOnly => true,
    _ => false,
}matches!(x.ret_activity, DiffActivity::DualOnly | DiffActivity::DualvOnly) {
791                // No need to change the return type,
792                // we will just return the shadow in place of the primal return.
793                // However, if we have a width > 1, then we don't return -> T, but -> [T; width]
794                if x.width > 1 {
795                    let anon_const = rustc_ast::AnonConst {
796                        id: ast::DUMMY_NODE_ID,
797                        value: ecx.expr_usize(span, x.width as usize),
798                    };
799                    let kind = TyKind::Array(ty.clone(), anon_const);
800                    let ty = Box::new(rustc_ast::Ty { kind, id: ty.id, span: ty.span });
801                    d_decl.output = FnRetTy::Ty(ty);
802                }
803            }
804        }
805
806        // If we use ActiveOnly, drop the original return value.
807        d_decl.output =
808            if active_only_ret { FnRetTy::Default(span) } else { d_decl.output.clone() };
809
810        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/2d20b42b883dcfc014f3c2c56081eeb6091c2179/compiler/rustc_builtin_macros/src/autodiff.rs:810",
                        "rustc_builtin_macros::autodiff::llvm_enzyme",
                        ::tracing::Level::TRACE,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/2d20b42b883dcfc014f3c2c56081eeb6091c2179/compiler/rustc_builtin_macros/src/autodiff.rs"),
                        ::tracing_core::__macro_support::Option::Some(810u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_builtin_macros::autodiff::llvm_enzyme"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::TRACE <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("act_ret: {0:?}",
                                                    act_ret) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};trace!("act_ret: {:?}", act_ret);
811
812        // If we have an active input scalar, add it's gradient to the
813        // return type. This might require changing the return type to a
814        // tuple.
815        if act_ret.len() > 0 {
816            let ret_ty = match d_decl.output {
817                FnRetTy::Ty(ref ty) => {
818                    if !active_only_ret {
819                        act_ret.insert(0, ty.clone());
820                    }
821                    let kind = TyKind::Tup(act_ret);
822                    Box::new(rustc_ast::Ty { kind, id: ty.id, span: ty.span })
823                }
824                FnRetTy::Default(span) => {
825                    if act_ret.len() == 1 {
826                        act_ret[0].clone()
827                    } else {
828                        ecx.ty(span, TyKind::Tup(act_ret))
829                    }
830                }
831            };
832            d_decl.output = FnRetTy::Ty(ret_ty);
833        }
834
835        let mut d_header = sig.header;
836        if unsafe_activities {
837            d_header.safety = rustc_ast::Safety::Unsafe(span);
838        }
839        let d_sig = FnSig { header: d_header, decl: d_decl, span };
840        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/2d20b42b883dcfc014f3c2c56081eeb6091c2179/compiler/rustc_builtin_macros/src/autodiff.rs:840",
                        "rustc_builtin_macros::autodiff::llvm_enzyme",
                        ::tracing::Level::TRACE,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/2d20b42b883dcfc014f3c2c56081eeb6091c2179/compiler/rustc_builtin_macros/src/autodiff.rs"),
                        ::tracing_core::__macro_support::Option::Some(840u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_builtin_macros::autodiff::llvm_enzyme"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::TRACE <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("Generated signature: {0:?}",
                                                    d_sig) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};trace!("Generated signature: {:?}", d_sig);
841        d_sig
842    }
843}
844
845pub(crate) use llvm_enzyme::{expand_forward, expand_reverse};