Skip to main content

rustc_passes/
abi_test.rs

1use rustc_hir::attrs::RustcAbiAttrKind;
2use rustc_hir::def::DefKind;
3use rustc_hir::def_id::LocalDefId;
4use rustc_hir::find_attr;
5use rustc_middle::ty::layout::{FnAbiError, LayoutError};
6use rustc_middle::ty::{self, GenericArgs, Instance, Ty, TyCtxt};
7use rustc_span::{Span, span_bug};
8use rustc_target::callconv::FnAbi;
9
10use super::layout_test::ensure_wf;
11use crate::diagnostics::{AbiInvalidAttribute, AbiNe, AbiOf, UnrecognizedArgument};
12
13pub fn test_abi(tcx: TyCtxt<'_>) {
14    if !tcx.features().rustc_attrs() {
15        // if the `rustc_attrs` feature is not enabled, don't bother testing ABI
16        return;
17    }
18    for id in tcx.hir_crate_items(()).definitions() {
19        let Some((attr_span, attr_kind)) =
20            {
    {
        'done:
            {
            for i in ::rustc_attr_ir::HasAttrs::get_attrs(id, &tcx) {
                #[allow(unused_imports)]
                use ::rustc_attr_ir::AttributeKind::*;
                let i: &::rustc_attr_ir::Attribute = i;
                match i {
                    ::rustc_attr_ir::Attribute::Parsed(RustcAbi {
                        attr_span, kind }) => {
                        break 'done Some((*attr_span, *kind));
                    }
                    ::rustc_attr_ir::Attribute::Unparsed(..) =>
                        {}
                        #[deny(unreachable_patterns)]
                        _ => {}
                }
            }
            None
        }
    }
}find_attr!(tcx, id, RustcAbi{ attr_span, kind } => (*attr_span, *kind))
21        else {
22            continue;
23        };
24        match tcx.def_kind(id) {
25            DefKind::Fn | DefKind::AssocFn => {
26                dump_abi_of_fn_item(tcx, id, attr_span, attr_kind);
27            }
28            DefKind::TyAlias => {
29                dump_abi_of_fn_type(tcx, id, attr_span, attr_kind);
30            }
31            _ => {
32                tcx.dcx().emit_err(AbiInvalidAttribute { span: tcx.def_span(id) });
33            }
34        }
35    }
36}
37
38fn unwrap_fn_abi<'tcx>(
39    abi: Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>>,
40    tcx: TyCtxt<'tcx>,
41    item_def_id: LocalDefId,
42) -> &'tcx FnAbi<'tcx, Ty<'tcx>> {
43    match abi {
44        Ok(abi) => abi,
45        Err(FnAbiError::Layout(layout_error)) => {
46            tcx.dcx().span_fatal(tcx.def_span(item_def_id), layout_error.to_string());
47        }
48    }
49}
50
51fn dump_abi_of_fn_item(
52    tcx: TyCtxt<'_>,
53    item_def_id: LocalDefId,
54    attr_span: Span,
55    attr_kind: RustcAbiAttrKind,
56) {
57    let typing_env = ty::TypingEnv::post_analysis(tcx, item_def_id);
58    let args = GenericArgs::identity_for_item(tcx, item_def_id);
59    let instance = match Instance::try_resolve(tcx, typing_env, item_def_id.into(), args) {
60        Ok(Some(instance)) => instance,
61        Ok(None) => {
62            // Not sure what to do here, but `LayoutError::Unknown` seems reasonable?
63            let ty = tcx.type_of(item_def_id).instantiate_identity().skip_norm_wip();
64            tcx.dcx().span_fatal(tcx.def_span(item_def_id), LayoutError::Unknown(ty).to_string());
65        }
66        Err(_guaranteed) => return,
67    };
68    let abi = unwrap_fn_abi(
69        tcx.fn_abi_of_instance(
70            typing_env.as_query_input((instance, /* extra_args */ ty::List::empty())),
71        ),
72        tcx,
73        item_def_id,
74    );
75
76    // Check out the `#[rustc_abi(..)]` attribute to tell what to dump.
77    // The `..` are the names of fields to dump.
78    match attr_kind {
79        RustcAbiAttrKind::Debug => {
80            let fn_name = tcx.item_name(item_def_id);
81            tcx.dcx().emit_err(AbiOf {
82                span: tcx.def_span(item_def_id),
83                fn_name,
84                // FIXME: using the `Debug` impl here isn't ideal.
85                fn_abi: ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0:#?}", abi))
    })format!("{:#?}", abi),
86            });
87        }
88        _ => {
89            tcx.dcx().emit_err(UnrecognizedArgument { span: attr_span });
90        }
91    }
92}
93
94fn test_abi_eq<'tcx>(abi1: &'tcx FnAbi<'tcx, Ty<'tcx>>, abi2: &'tcx FnAbi<'tcx, Ty<'tcx>>) -> bool {
95    if abi1.conv != abi2.conv
96        || abi1.args.len() != abi2.args.len()
97        || abi1.c_variadic != abi2.c_variadic
98        || abi1.fixed_count != abi2.fixed_count
99        || abi1.can_unwind != abi2.can_unwind
100    {
101        return false;
102    }
103
104    abi1.ret.eq_abi(&abi2.ret)
105        && abi1.args.iter().zip(abi2.args.iter()).all(|(arg1, arg2)| arg1.eq_abi(arg2))
106}
107
108fn dump_abi_of_fn_type(
109    tcx: TyCtxt<'_>,
110    item_def_id: LocalDefId,
111    attr_span: Span,
112    attr_kind: RustcAbiAttrKind,
113) {
114    let typing_env = ty::TypingEnv::post_analysis(tcx, item_def_id);
115    let ty = tcx.type_of(item_def_id).instantiate_identity().skip_norm_wip();
116    let span = tcx.def_span(item_def_id);
117    if !ensure_wf(tcx, typing_env, ty, item_def_id, span) {
118        return;
119    }
120
121    match attr_kind {
122        RustcAbiAttrKind::Debug => {
123            let ty::FnPtr(sig_tys, hdr) = ty.kind() else {
124                bug_impl(Some(attr_span),
    format_args!("`#[rustc_abi(debug)]` on a type alias requires function pointer type"),
    Location::caller());span_bug!(
125                    attr_span,
126                    "`#[rustc_abi(debug)]` on a type alias requires function pointer type"
127                );
128            };
129            let abi =
130                unwrap_fn_abi(
131                    tcx.fn_abi_of_fn_ptr(typing_env.as_query_input((
132                        sig_tys.with(*hdr),
133                        /* extra_args */ ty::List::empty(),
134                    ))),
135                    tcx,
136                    item_def_id,
137                );
138
139            let fn_name = tcx.item_name(item_def_id);
140            tcx.dcx().emit_err(AbiOf { span, fn_name, fn_abi: ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0:#?}", abi))
    })format!("{:#?}", abi) });
141        }
142        RustcAbiAttrKind::AssertEq => {
143            let ty::Tuple(fields) = ty.kind() else {
144                bug_impl(Some(attr_span),
    format_args!("`#[rustc_abi(assert_eq)]` on a type alias requires pair type"),
    Location::caller());span_bug!(
145                    attr_span,
146                    "`#[rustc_abi(assert_eq)]` on a type alias requires pair type"
147                );
148            };
149            let [field1, field2] = ***fields else {
150                bug_impl(Some(attr_span),
    format_args!("`#[rustc_abi(assert_eq)]` on a type alias requires pair type"),
    Location::caller());span_bug!(
151                    attr_span,
152                    "`#[rustc_abi(assert_eq)]` on a type alias requires pair type"
153                );
154            };
155            let ty::FnPtr(sig_tys1, hdr1) = field1.kind() else {
156                bug_impl(Some(attr_span),
    format_args!("`#[rustc_abi(assert_eq)]` on a type alias requires pair of function pointer types"),
    Location::caller());span_bug!(
157                    attr_span,
158                    "`#[rustc_abi(assert_eq)]` on a type alias requires pair of function pointer types"
159                );
160            };
161            let abi1 = unwrap_fn_abi(
162                tcx.fn_abi_of_fn_ptr(typing_env.as_query_input((
163                    sig_tys1.with(*hdr1),
164                    /* extra_args */ ty::List::empty(),
165                ))),
166                tcx,
167                item_def_id,
168            );
169            let ty::FnPtr(sig_tys2, hdr2) = field2.kind() else {
170                bug_impl(Some(attr_span),
    format_args!("`#[rustc_abi(assert_eq)]` on a type alias requires pair of function pointer types"),
    Location::caller());span_bug!(
171                    attr_span,
172                    "`#[rustc_abi(assert_eq)]` on a type alias requires pair of function pointer types"
173                );
174            };
175            let abi2 = unwrap_fn_abi(
176                tcx.fn_abi_of_fn_ptr(typing_env.as_query_input((
177                    sig_tys2.with(*hdr2),
178                    /* extra_args */ ty::List::empty(),
179                ))),
180                tcx,
181                item_def_id,
182            );
183
184            if !test_abi_eq(abi1, abi2) {
185                tcx.dcx().emit_err(AbiNe {
186                    span,
187                    left: ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0:#?}", abi1))
    })format!("{:#?}", abi1),
188                    right: ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0:#?}", abi2))
    })format!("{:#?}", abi2),
189                });
190            }
191        }
192    }
193}