Skip to main content

rustc_hir_typeck/
intrinsicck.rs

1use hir::HirId;
2use rustc_abi::Primitive::Pointer;
3use rustc_abi::VariantIdx;
4use rustc_data_structures::fx::FxHashSet;
5use rustc_errors::codes::*;
6use rustc_errors::struct_span_code_err;
7use rustc_hir as hir;
8use rustc_index::Idx;
9use rustc_middle::ty::layout::{LayoutError, SizeSkeleton};
10use rustc_middle::ty::offload_meta::is_region_ty;
11use rustc_middle::ty::{self, Ty, TyCtxt, Unnormalized};
12use rustc_span::def_id::LocalDefId;
13use rustc_span::{ErrorGuaranteed, bug};
14use tracing::trace;
15
16/// If the type is `Option<T>`, it will return `T`, otherwise
17/// the type itself. Works on most `Option`-like types.
18fn unpack_option_like<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> {
19    let ty::Adt(def, args) = *ty.kind() else { return ty };
20
21    if def.variants().len() == 2 && !def.repr().c() && def.repr().int.is_none() {
22        let data_idx;
23
24        let one = VariantIdx::new(1);
25        let zero = VariantIdx::ZERO;
26
27        if def.variant(zero).fields.is_empty() {
28            data_idx = one;
29        } else if def.variant(one).fields.is_empty() {
30            data_idx = zero;
31        } else {
32            return ty;
33        }
34
35        if def.variant(data_idx).fields.len() == 1 {
36            return def.variant(data_idx).single_field().ty(tcx, args).skip_norm_wip();
37        }
38    }
39
40    ty
41}
42
43/// Try to display a sensible error with as much information as possible.
44fn skeleton_string<'tcx>(
45    ty: Ty<'tcx>,
46    sk: Result<SizeSkeleton<'tcx>, &'tcx LayoutError<'tcx>>,
47) -> String {
48    match sk {
49        Ok(SizeSkeleton::Pointer { tail, .. }) => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("pointer to `{0}`", tail))
    })format!("pointer to `{tail}`"),
50        Ok(SizeSkeleton::Known(size, _)) => {
51            if let Some(v) = u128::from(size.bytes()).checked_mul(8) {
52                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} bits", v))
    })format!("{v} bits")
53            } else {
54                // `u128` should definitely be able to hold the size of different architectures
55                // larger sizes should be reported as error `are too big for the target architecture`
56                // otherwise we have a bug somewhere
57                ::rustc_span::macros::bug_impl(None,
    format_args!("{0:?} overflow for u128", size), Location::caller())bug!("{:?} overflow for u128", size)
58            }
59        }
60        Err(LayoutError::TooGeneric(bad)) => {
61            if *bad == ty {
62                "this type does not have a fixed size".to_owned()
63            } else {
64                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("size can vary because of {0}",
                bad))
    })format!("size can vary because of {bad}")
65            }
66        }
67        Err(err) => err.to_string(),
68    }
69}
70
71fn check_transmute<'tcx>(
72    tcx: TyCtxt<'tcx>,
73    typing_env: ty::TypingEnv<'tcx>,
74    from: Unnormalized<'tcx, Ty<'tcx>>,
75    to: Unnormalized<'tcx, Ty<'tcx>>,
76    hir_id: HirId,
77) -> Result<(), ErrorGuaranteed> {
78    let span = tcx.hir_span(hir_id);
79    let normalize = |ty: Unnormalized<'tcx, Ty<'tcx>>| -> Result<Ty<'tcx>, ErrorGuaranteed> {
80        tcx.try_normalize_erasing_regions(typing_env, ty).map_err(|err| {
81            let err = LayoutError::NormalizationFailure(ty.skip_normalization(), err);
82            tcx.dcx().span_err(span, err.to_string())
83        })
84    };
85
86    let from = normalize(from)?;
87    let to = normalize(to)?;
88    {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/76456a688bc0ed897fa23a677d4ddb5b3fdb575a/compiler/rustc_hir_typeck/src/intrinsicck.rs:88",
                        "rustc_hir_typeck::intrinsicck", ::tracing::Level::TRACE,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/76456a688bc0ed897fa23a677d4ddb5b3fdb575a/compiler/rustc_hir_typeck/src/intrinsicck.rs"),
                        ::tracing_core::__macro_support::Option::Some(88u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_hir_typeck::intrinsicck"),
                        ::tracing_core::field::FieldSet::new(&[{
                                            const NAME:
                                                ::tracing::__macro_support::FieldName<{
                                                    ::tracing::__macro_support::FieldName::len("from")
                                                }> =
                                                ::tracing::__macro_support::FieldName::new("from");
                                            NAME.as_str()
                                        },
                                        {
                                            const NAME:
                                                ::tracing::__macro_support::FieldName<{
                                                    ::tracing::__macro_support::FieldName::len("to")
                                                }> =
                                                ::tracing::__macro_support::FieldName::new("to");
                                            NAME.as_str()
                                        }], ::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(&::tracing::field::debug(&from)
                                            as &dyn ::tracing::field::Value)),
                                (::tracing::__macro_support::Option::Some(&::tracing::field::debug(&to)
                                            as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};trace!(?from, ?to);
89
90    // Transmutes that are only changing lifetimes are always ok.
91    if from == to {
92        return Ok(());
93    }
94
95    let sk_from = SizeSkeleton::compute(from, tcx, typing_env, span);
96    let sk_to = SizeSkeleton::compute(to, tcx, typing_env, span);
97    {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/76456a688bc0ed897fa23a677d4ddb5b3fdb575a/compiler/rustc_hir_typeck/src/intrinsicck.rs:97",
                        "rustc_hir_typeck::intrinsicck", ::tracing::Level::TRACE,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/76456a688bc0ed897fa23a677d4ddb5b3fdb575a/compiler/rustc_hir_typeck/src/intrinsicck.rs"),
                        ::tracing_core::__macro_support::Option::Some(97u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_hir_typeck::intrinsicck"),
                        ::tracing_core::field::FieldSet::new(&[{
                                            const NAME:
                                                ::tracing::__macro_support::FieldName<{
                                                    ::tracing::__macro_support::FieldName::len("sk_from")
                                                }> =
                                                ::tracing::__macro_support::FieldName::new("sk_from");
                                            NAME.as_str()
                                        },
                                        {
                                            const NAME:
                                                ::tracing::__macro_support::FieldName<{
                                                    ::tracing::__macro_support::FieldName::len("sk_to")
                                                }> =
                                                ::tracing::__macro_support::FieldName::new("sk_to");
                                            NAME.as_str()
                                        }], ::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(&::tracing::field::debug(&sk_from)
                                            as &dyn ::tracing::field::Value)),
                                (::tracing::__macro_support::Option::Some(&::tracing::field::debug(&sk_to)
                                            as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};trace!(?sk_from, ?sk_to);
98
99    // Check for same size using the skeletons.
100    if let Ok(sk_from) = sk_from
101        && let Ok(sk_to) = sk_to
102    {
103        if sk_from.same_size(sk_to) {
104            return Ok(());
105        }
106
107        // Special-case transmuting from `typeof(function)` and
108        // `Option<typeof(function)>` to present a clearer error.
109        let from = unpack_option_like(tcx, from);
110        if let ty::FnDef(..) = from.kind()
111            && let SizeSkeleton::Known(size_to, _) = sk_to
112            && size_to == Pointer(tcx.data_layout.instruction_address_space).size(&tcx)
113        {
114            {
    tcx.sess.dcx().struct_span_err(span,
            ::alloc::__export::must_use({
                    ::alloc::fmt::format(format_args!("can\'t transmute zero-sized type"))
                })).with_code(E0591)
}struct_span_code_err!(tcx.sess.dcx(), span, E0591, "can't transmute zero-sized type")
115                .with_note(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("source type: {0}", from))
    })format!("source type: {from}"))
116                .with_note(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("target type: {0}", to))
    })format!("target type: {to}"))
117                .with_help("cast with `as` to a pointer instead")
118                .emit();
119            return Ok(());
120        }
121    }
122
123    let mut err = {
    tcx.sess.dcx().struct_span_err(span,
            ::alloc::__export::must_use({
                    ::alloc::fmt::format(format_args!("cannot transmute between types of different sizes, or dependently-sized types"))
                })).with_code(E0512)
}struct_span_code_err!(
124        tcx.sess.dcx(),
125        span,
126        E0512,
127        "cannot transmute between types of different sizes, or dependently-sized types"
128    );
129    if from == to {
130        err.note(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}` does not have a fixed size",
                from))
    })format!("`{from}` does not have a fixed size"));
131        Err(err.emit_err())
132    } else {
133        err.note(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("source type: `{0}` ({1})", from,
                skeleton_string(from, sk_from)))
    })format!("source type: `{}` ({})", from, skeleton_string(from, sk_from)));
134        err.note(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("target type: `{0}` ({1})", to,
                skeleton_string(to, sk_to)))
    })format!("target type: `{}` ({})", to, skeleton_string(to, sk_to)));
135        Err(err.emit_err())
136    }
137}
138
139fn contains_nested_offload_region<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool {
140    let mut visited = FxHashSet::default();
141    let mut stack = ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [ty]))vec![ty];
142    let mut is_root = true;
143
144    while let Some(ty) = stack.pop() {
145        if !is_root && is_region_ty(tcx, ty) {
146            return true;
147        }
148        is_root = false;
149
150        if let ty::Adt(def, args) = *ty.kind()
151            && visited.insert(def.did())
152        {
153            stack.extend(def.all_fields().map(|field| field.ty(tcx, args).skip_norm_wip()));
154        }
155        stack.extend(ty.walk().skip(1).filter_map(|arg| arg.as_type()));
156    }
157
158    false
159}
160
161fn check_offload<'tcx>(
162    tcx: TyCtxt<'tcx>,
163    typing_env: ty::TypingEnv<'tcx>,
164    kernel_ty: Ty<'tcx>,
165    args_ty: Ty<'tcx>,
166    ret_ty: Ty<'tcx>,
167    hir_id: HirId,
168) -> Result<(), ErrorGuaranteed> {
169    let span = tcx.hir_span(hir_id);
170    let ty::FnDef(kernel_def_id, kernel_args) = *kernel_ty.kind() else {
171        let guar = tcx
172            .sess
173            .dcx()
174            .struct_span_err(
175                span,
176                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("expected a function item for the offload kernel, found `{0}`",
                kernel_ty))
    })format!("expected a function item for the offload kernel, found `{}`", kernel_ty),
177            )
178            .emit_err();
179        return Err(guar);
180    };
181
182    let kernel_sig =
183        tcx.fn_sig(kernel_def_id).instantiate(tcx, kernel_args.skip_binder()).skip_norm_wip();
184    let kernel_sig = tcx.instantiate_bound_regions_with_erased(kernel_sig);
185
186    let ty::Tuple(tuple_fields) = *args_ty.kind() else {
187        let guar = tcx
188            .sess
189            .dcx()
190            .struct_span_err(
191                span,
192                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("expected a tuple for the offload arguments, found `{0}`",
                args_ty))
    })format!("expected a tuple for the offload arguments, found `{}`", args_ty),
193            )
194            .emit_err();
195        return Err(guar);
196    };
197
198    if kernel_sig.inputs().len() != tuple_fields.len() {
199        let guar = tcx
200            .sess
201            .dcx()
202            .struct_span_err(
203                span,
204                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("offload kernel expects {0} arguments, but {1} arguments were provided",
                kernel_sig.inputs().len(), tuple_fields.len()))
    })format!(
205                    "offload kernel expects {} arguments, but {} arguments were provided",
206                    kernel_sig.inputs().len(),
207                    tuple_fields.len()
208                ),
209            )
210            .emit_err();
211        return Err(guar);
212    }
213
214    let normalize = |ty| {
215        if let Ok(ty) = tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)) {
216            ty
217        } else {
218            Ty::new_error_with_message(
219                tcx,
220                span,
221                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("tried to normalize non-wf type {0:#?} in check_offload",
                ty))
    })format!("tried to normalize non-wf type {ty:#?} in check_offload"),
222            )
223        }
224    };
225
226    let mut result = Ok(());
227
228    for (i, (&input_ty, arg_ty)) in kernel_sig.inputs().iter().zip(tuple_fields.iter()).enumerate()
229    {
230        let norm_input_ty = normalize(input_ty);
231        let norm_arg_ty = normalize(arg_ty);
232
233        if contains_nested_offload_region(tcx, norm_input_ty)
234            || contains_nested_offload_region(tcx, norm_arg_ty)
235        {
236            let guar = tcx
237                .sess
238                .dcx()
239                .struct_span_err(
240                    span,
241                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("offload kernel argument {0} contains a `Region` nested inside another type. Pass the `Region` by value so it can be mapped like a slice",
                i))
    })format!(
242                        "offload kernel argument {i} contains a `Region` nested inside another \
243                        type. Pass the `Region` by value so it can be mapped like a slice"
244                    ),
245                )
246                .emit_err();
247            result = Err(guar);
248        } else if norm_input_ty != norm_arg_ty {
249            let guar = tcx
250                .sess
251                .dcx()
252                .struct_span_err(
253                    span,
254                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("type mismatch in offload kernel argument {0}: expected `{1}`, found `{2}`",
                i, norm_input_ty, norm_arg_ty))
    })format!(
255                        "type mismatch in offload kernel argument {}: expected `{}`, found `{}`",
256                        i, norm_input_ty, norm_arg_ty
257                    ),
258                )
259                .emit_err();
260            result = Err(guar);
261        }
262    }
263
264    let norm_kernel_ret = normalize(kernel_sig.output());
265    let norm_offload_ret = normalize(ret_ty);
266    if norm_kernel_ret != norm_offload_ret {
267        let guar = tcx.sess.dcx().struct_span_err(
268            span,
269            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("offload kernel return type mismatch: kernel returns `{0}`, but offload call expects `{1}`",
                norm_kernel_ret, norm_offload_ret))
    })format!(
270                "offload kernel return type mismatch: kernel returns `{}`, but offload call expects `{}`",
271                norm_kernel_ret, norm_offload_ret
272            )
273        ).emit_err();
274        result = Err(guar);
275    }
276
277    result
278}
279
280pub(crate) fn check_transmutes(tcx: TyCtxt<'_>, owner: LocalDefId) -> Result<(), ErrorGuaranteed> {
281    if !!tcx.is_typeck_child(owner.to_def_id()) {
    ::core::panicking::panic("assertion failed: !tcx.is_typeck_child(owner.to_def_id())")
};assert!(!tcx.is_typeck_child(owner.to_def_id()));
282    let typeck_results = tcx.typeck(owner);
283    if let Some(e) = typeck_results.tainted_by_errors {
284        return Err(e);
285    };
286
287    let typing_env = ty::TypingEnv::codegen(tcx, owner);
288    let mut result = Ok(());
289    for &(from, to, hir_id) in &typeck_results.transmutes_to_check {
290        let (to, from) = ty::set_aliases_to_non_rigid(tcx, (to, from)).unzip();
291        result = result.and(check_transmute(tcx, typing_env, from, to, hir_id));
292    }
293    result
294}
295
296pub(crate) fn check_offloads(tcx: TyCtxt<'_>, owner: LocalDefId) -> Result<(), ErrorGuaranteed> {
297    if !!tcx.is_typeck_child(owner.to_def_id()) {
    ::core::panicking::panic("assertion failed: !tcx.is_typeck_child(owner.to_def_id())")
};assert!(!tcx.is_typeck_child(owner.to_def_id()));
298    let typeck_results = tcx.typeck(owner);
299    if let Some(e) = typeck_results.tainted_by_errors {
300        return Err(e);
301    };
302
303    let typing_env = ty::TypingEnv::codegen(tcx, owner);
304    let mut result = Ok(());
305    for &(kernel_ty, args_ty, ret_ty, hir_id) in &typeck_results.offloads_to_check {
306        result = result.and(check_offload(tcx, typing_env, kernel_ty, args_ty, ret_ty, hir_id));
307    }
308    result
309}