Skip to main content

rustc_codegen_ssa/mir/
intrinsic.rs

1use rustc_abi::{Align, WrappingRange};
2use rustc_middle::mir::SourceInfo;
3use rustc_middle::ty::{self, Ty, TyCtxt};
4use rustc_middle::{bug, span_bug};
5use rustc_session::config::OptLevel;
6use rustc_span::sym;
7use rustc_target::spec::Arch;
8
9use super::FunctionCx;
10use super::operand::OperandRef;
11use super::place::PlaceRef;
12use crate::common::{AtomicRmwBinOp, SynchronizationScope};
13use crate::errors::InvalidMonomorphization;
14use crate::traits::*;
15use crate::{MemFlags, meth, size_of_val};
16
17fn copy_intrinsic<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
18    bx: &mut Bx,
19    allow_overlap: bool,
20    volatile: bool,
21    ty: Ty<'tcx>,
22    dst: Bx::Value,
23    src: Bx::Value,
24    count: Bx::Value,
25) {
26    let layout = bx.layout_of(ty);
27    let size = layout.size;
28    let align = layout.align.abi;
29    let size = bx.mul(bx.const_usize(size.bytes()), count);
30    let flags = if volatile { MemFlags::VOLATILE } else { MemFlags::empty() };
31    if allow_overlap {
32        bx.memmove(dst, align, src, align, size, flags);
33    } else {
34        bx.memcpy(dst, align, src, align, size, flags, None);
35    }
36}
37
38fn memset_intrinsic<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
39    bx: &mut Bx,
40    volatile: bool,
41    ty: Ty<'tcx>,
42    dst: Bx::Value,
43    val: Bx::Value,
44    count: Bx::Value,
45) {
46    let layout = bx.layout_of(ty);
47    let size = layout.size;
48    let align = layout.align.abi;
49    let size = bx.mul(bx.const_usize(size.bytes()), count);
50    let flags = if volatile { MemFlags::VOLATILE } else { MemFlags::empty() };
51    bx.memset(dst, val, size, align, flags);
52}
53
54impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
55    /// In the `Err` case, returns the instance that should be called instead.
56    pub fn codegen_intrinsic_call(
57        &mut self,
58        bx: &mut Bx,
59        instance: ty::Instance<'tcx>,
60        args: &[OperandRef<'tcx, Bx::Value>],
61        result: PlaceRef<'tcx, Bx::Value>,
62        source_info: SourceInfo,
63    ) -> Result<(), ty::Instance<'tcx>> {
64        let span = source_info.span;
65
66        let name = bx.tcx().item_name(instance.def_id());
67        let fn_args = instance.args;
68
69        // If we're swapping something that's *not* an `OperandValue::Ref`,
70        // then we can do it directly and avoid the alloca.
71        // Otherwise, we'll let the fallback MIR body take care of it.
72        if let sym::typed_swap_nonoverlapping = name {
73            let pointee_ty = fn_args.type_at(0);
74            let pointee_layout = bx.layout_of(pointee_ty);
75            if !bx.is_backend_ref(pointee_layout)
76                // But if we're not going to optimize, trying to use the fallback
77                // body just makes things worse, so don't bother.
78                || bx.sess().opts.optimize == OptLevel::No
79                // NOTE(eddyb) SPIR-V's Logical addressing model doesn't allow for arbitrary
80                // reinterpretation of values as (chunkable) byte arrays, and the loop in the
81                // block optimization in `ptr::swap_nonoverlapping` is hard to rewrite back
82                // into the (unoptimized) direct swapping implementation, so we disable it.
83                || bx.sess().target.arch == Arch::SpirV
84            {
85                let align = pointee_layout.align.abi;
86                let x_place = args[0].val.deref(align);
87                let y_place = args[1].val.deref(align);
88                bx.typed_place_swap(x_place, y_place, pointee_layout);
89                return Ok(());
90            }
91        }
92
93        let invalid_monomorphization_int_type = |ty| {
94            bx.tcx().dcx().emit_err(InvalidMonomorphization::BasicIntegerType { span, name, ty });
95        };
96        let invalid_monomorphization_int_or_ptr_type = |ty| {
97            bx.tcx().dcx().emit_err(InvalidMonomorphization::BasicIntegerOrPtrType {
98                span,
99                name,
100                ty,
101            });
102        };
103
104        let parse_atomic_ordering = |ord: ty::Value<'tcx>| {
105            let discr = ord.to_branch()[0].to_leaf();
106            discr.to_atomic_ordering()
107        };
108
109        if args.is_empty() {
110            match name {
111                sym::abort
112                | sym::unreachable
113                | sym::cold_path
114                | sym::gpu_launch_sized_workgroup_mem
115                | sym::breakpoint
116                | sym::amdgpu_dispatch_ptr
117                | sym::assert_zero_valid
118                | sym::assert_mem_uninitialized_valid
119                | sym::assert_inhabited
120                | sym::ub_checks
121                | sym::contract_checks
122                | sym::atomic_fence
123                | sym::atomic_singlethreadfence
124                | sym::caller_location
125                | sym::return_address => {}
126                _ => {
127                    ::rustc_middle::util::bug::span_bug_fmt(span,
    format_args!("Nullary intrinsic {0} must be called in a const block. If you are seeing this message from code outside the standard library, the unstable implementation details of the relevant intrinsic may have changed. Consider using stable APIs instead. If you are adding a new nullary intrinsic that is inherently a runtime intrinsic, update this check.",
        name));span_bug!(
128                        span,
129                        "Nullary intrinsic {name} must be called in a const block. \
130                        If you are seeing this message from code outside the standard library, the \
131                        unstable implementation details of the relevant intrinsic may have changed. \
132                        Consider using stable APIs instead. \
133                        If you are adding a new nullary intrinsic that is inherently a runtime \
134                        intrinsic, update this check."
135                    );
136                }
137            }
138        }
139
140        let llval = match name {
141            sym::abort => {
142                bx.abort();
143                return Ok(());
144            }
145
146            sym::caller_location => {
147                let location = self.get_caller_location(bx, source_info);
148                location.val.store(bx, result);
149                return Ok(());
150            }
151
152            // va_end uses the fallback body (a no-op).
153            sym::va_start => bx.va_start(args[0].immediate()),
154
155            sym::size_of_val => {
156                let tp_ty = fn_args.type_at(0);
157                let (_, meta) = args[0].val.pointer_parts();
158                let (llsize, _) = size_of_val::size_and_align_of_dst(bx, tp_ty, meta);
159                llsize
160            }
161            sym::align_of_val => {
162                let tp_ty = fn_args.type_at(0);
163                let (_, meta) = args[0].val.pointer_parts();
164                let (_, llalign) = size_of_val::size_and_align_of_dst(bx, tp_ty, meta);
165                llalign
166            }
167            sym::vtable_size | sym::vtable_align => {
168                let vtable = args[0].immediate();
169                let idx = match name {
170                    sym::vtable_size => ty::COMMON_VTABLE_ENTRIES_SIZE,
171                    sym::vtable_align => ty::COMMON_VTABLE_ENTRIES_ALIGN,
172                    _ => ::rustc_middle::util::bug::bug_fmt(format_args!("impossible case reached"))bug!(),
173                };
174                let value = meth::VirtualIndex::from_index(idx).get_usize(
175                    bx,
176                    vtable,
177                    instance.ty(bx.tcx(), bx.typing_env()),
178                );
179                match name {
180                    // Size is always <= isize::MAX.
181                    sym::vtable_size => {
182                        let size_bound = bx.data_layout().ptr_sized_integer().signed_max() as u128;
183                        bx.range_metadata(value, WrappingRange { start: 0, end: size_bound });
184                    }
185                    // Alignment is always a power of two, thus 1..=0x800…000,
186                    // but also bounded by the maximum we support in type layout.
187                    sym::vtable_align => {
188                        let align_bound = Align::max_for_target(bx.data_layout()).bytes().into();
189                        bx.range_metadata(value, WrappingRange { start: 1, end: align_bound })
190                    }
191                    _ => {}
192                }
193                value
194            }
195            sym::arith_offset => {
196                let ty = fn_args.type_at(0);
197                let layout = bx.layout_of(ty);
198                let ptr = args[0].immediate();
199                let offset = args[1].immediate();
200                bx.gep(bx.backend_type(layout), ptr, &[offset])
201            }
202            sym::copy => {
203                copy_intrinsic(
204                    bx,
205                    true,
206                    false,
207                    fn_args.type_at(0),
208                    args[1].immediate(),
209                    args[0].immediate(),
210                    args[2].immediate(),
211                );
212                return Ok(());
213            }
214            sym::write_bytes => {
215                memset_intrinsic(
216                    bx,
217                    false,
218                    fn_args.type_at(0),
219                    args[0].immediate(),
220                    args[1].immediate(),
221                    args[2].immediate(),
222                );
223                return Ok(());
224            }
225
226            sym::volatile_copy_nonoverlapping_memory => {
227                copy_intrinsic(
228                    bx,
229                    false,
230                    true,
231                    fn_args.type_at(0),
232                    args[0].immediate(),
233                    args[1].immediate(),
234                    args[2].immediate(),
235                );
236                return Ok(());
237            }
238            sym::volatile_copy_memory => {
239                copy_intrinsic(
240                    bx,
241                    true,
242                    true,
243                    fn_args.type_at(0),
244                    args[0].immediate(),
245                    args[1].immediate(),
246                    args[2].immediate(),
247                );
248                return Ok(());
249            }
250            sym::volatile_set_memory => {
251                memset_intrinsic(
252                    bx,
253                    true,
254                    fn_args.type_at(0),
255                    args[0].immediate(),
256                    args[1].immediate(),
257                    args[2].immediate(),
258                );
259                return Ok(());
260            }
261            sym::volatile_store => {
262                let dst = args[0].deref(bx.cx());
263                args[1].val.volatile_store(bx, dst);
264                return Ok(());
265            }
266            sym::unaligned_volatile_store => {
267                let dst = args[0].deref(bx.cx());
268                args[1].val.unaligned_volatile_store(bx, dst);
269                return Ok(());
270            }
271            sym::disjoint_bitor => {
272                let a = args[0].immediate();
273                let b = args[1].immediate();
274                bx.or_disjoint(a, b)
275            }
276            sym::exact_div => {
277                let ty = args[0].layout.ty;
278                match int_type_width_signed(ty, bx.tcx()) {
279                    Some((_width, signed)) => {
280                        if signed {
281                            bx.exactsdiv(args[0].immediate(), args[1].immediate())
282                        } else {
283                            bx.exactudiv(args[0].immediate(), args[1].immediate())
284                        }
285                    }
286                    None => {
287                        bx.tcx().dcx().emit_err(InvalidMonomorphization::BasicIntegerType {
288                            span,
289                            name,
290                            ty,
291                        });
292                        return Ok(());
293                    }
294                }
295            }
296            sym::fadd_fast | sym::fsub_fast | sym::fmul_fast | sym::fdiv_fast | sym::frem_fast => {
297                match float_type_width(args[0].layout.ty) {
298                    Some(_width) => match name {
299                        sym::fadd_fast => bx.fadd_fast(args[0].immediate(), args[1].immediate()),
300                        sym::fsub_fast => bx.fsub_fast(args[0].immediate(), args[1].immediate()),
301                        sym::fmul_fast => bx.fmul_fast(args[0].immediate(), args[1].immediate()),
302                        sym::fdiv_fast => bx.fdiv_fast(args[0].immediate(), args[1].immediate()),
303                        sym::frem_fast => bx.frem_fast(args[0].immediate(), args[1].immediate()),
304                        _ => ::rustc_middle::util::bug::bug_fmt(format_args!("impossible case reached"))bug!(),
305                    },
306                    None => {
307                        bx.tcx().dcx().emit_err(InvalidMonomorphization::BasicFloatType {
308                            span,
309                            name,
310                            ty: args[0].layout.ty,
311                        });
312                        return Ok(());
313                    }
314                }
315            }
316            sym::fadd_algebraic
317            | sym::fsub_algebraic
318            | sym::fmul_algebraic
319            | sym::fdiv_algebraic
320            | sym::frem_algebraic => match float_type_width(args[0].layout.ty) {
321                Some(_width) => match name {
322                    sym::fadd_algebraic => {
323                        bx.fadd_algebraic(args[0].immediate(), args[1].immediate())
324                    }
325                    sym::fsub_algebraic => {
326                        bx.fsub_algebraic(args[0].immediate(), args[1].immediate())
327                    }
328                    sym::fmul_algebraic => {
329                        bx.fmul_algebraic(args[0].immediate(), args[1].immediate())
330                    }
331                    sym::fdiv_algebraic => {
332                        bx.fdiv_algebraic(args[0].immediate(), args[1].immediate())
333                    }
334                    sym::frem_algebraic => {
335                        bx.frem_algebraic(args[0].immediate(), args[1].immediate())
336                    }
337                    _ => ::rustc_middle::util::bug::bug_fmt(format_args!("impossible case reached"))bug!(),
338                },
339                None => {
340                    bx.tcx().dcx().emit_err(InvalidMonomorphization::BasicFloatType {
341                        span,
342                        name,
343                        ty: args[0].layout.ty,
344                    });
345                    return Ok(());
346                }
347            },
348
349            sym::float_to_int_unchecked => {
350                if float_type_width(args[0].layout.ty).is_none() {
351                    bx.tcx().dcx().emit_err(InvalidMonomorphization::FloatToIntUnchecked {
352                        span,
353                        ty: args[0].layout.ty,
354                    });
355                    return Ok(());
356                }
357                let Some((_width, signed)) = int_type_width_signed(result.layout.ty, bx.tcx())
358                else {
359                    bx.tcx().dcx().emit_err(InvalidMonomorphization::FloatToIntUnchecked {
360                        span,
361                        ty: result.layout.ty,
362                    });
363                    return Ok(());
364                };
365                if signed {
366                    bx.fptosi(args[0].immediate(), bx.backend_type(result.layout))
367                } else {
368                    bx.fptoui(args[0].immediate(), bx.backend_type(result.layout))
369                }
370            }
371
372            sym::atomic_load => {
373                let ty = fn_args.type_at(0);
374                if !(int_type_width_signed(ty, bx.tcx()).is_some() || ty.is_raw_ptr()) {
375                    invalid_monomorphization_int_or_ptr_type(ty);
376                    return Ok(());
377                }
378                let ordering = fn_args.const_at(1).to_value();
379                let layout = bx.layout_of(ty);
380                let source = args[0].immediate();
381                bx.atomic_load(
382                    bx.backend_type(layout),
383                    source,
384                    parse_atomic_ordering(ordering),
385                    layout.size,
386                )
387            }
388            sym::atomic_store => {
389                let ty = fn_args.type_at(0);
390                if !(int_type_width_signed(ty, bx.tcx()).is_some() || ty.is_raw_ptr()) {
391                    invalid_monomorphization_int_or_ptr_type(ty);
392                    return Ok(());
393                }
394                let ordering = fn_args.const_at(1).to_value();
395                let size = bx.layout_of(ty).size;
396                let val = args[1].immediate();
397                let ptr = args[0].immediate();
398                bx.atomic_store(val, ptr, parse_atomic_ordering(ordering), size);
399                return Ok(());
400            }
401            // These are all AtomicRMW ops
402            sym::atomic_cxchg | sym::atomic_cxchgweak => {
403                let ty = fn_args.type_at(0);
404                if !(int_type_width_signed(ty, bx.tcx()).is_some() || ty.is_raw_ptr()) {
405                    invalid_monomorphization_int_or_ptr_type(ty);
406                    return Ok(());
407                }
408                let succ_ordering = fn_args.const_at(1).to_value();
409                let fail_ordering = fn_args.const_at(2).to_value();
410                let weak = name == sym::atomic_cxchgweak;
411                let dst = args[0].immediate();
412                let cmp = args[1].immediate();
413                let src = args[2].immediate();
414                let (val, success) = bx.atomic_cmpxchg(
415                    dst,
416                    cmp,
417                    src,
418                    parse_atomic_ordering(succ_ordering),
419                    parse_atomic_ordering(fail_ordering),
420                    weak,
421                );
422                let val = bx.from_immediate(val);
423                let success = bx.from_immediate(success);
424
425                let dest = result.project_field(bx, 0);
426                bx.store_to_place(val, dest.val);
427                let dest = result.project_field(bx, 1);
428                bx.store_to_place(success, dest.val);
429
430                return Ok(());
431            }
432            sym::atomic_max | sym::atomic_min => {
433                let atom_op = if name == sym::atomic_max {
434                    AtomicRmwBinOp::AtomicMax
435                } else {
436                    AtomicRmwBinOp::AtomicMin
437                };
438
439                let ty = fn_args.type_at(0);
440                if #[allow(non_exhaustive_omitted_patterns)] match ty.kind() {
    ty::Int(_) => true,
    _ => false,
}matches!(ty.kind(), ty::Int(_)) {
441                    let ordering = fn_args.const_at(1).to_value();
442                    let ptr = args[0].immediate();
443                    let val = args[1].immediate();
444                    bx.atomic_rmw(
445                        atom_op,
446                        ptr,
447                        val,
448                        parse_atomic_ordering(ordering),
449                        /* ret_ptr */ false,
450                    )
451                } else {
452                    invalid_monomorphization_int_type(ty);
453                    return Ok(());
454                }
455            }
456            sym::atomic_umax | sym::atomic_umin => {
457                let atom_op = if name == sym::atomic_umax {
458                    AtomicRmwBinOp::AtomicUMax
459                } else {
460                    AtomicRmwBinOp::AtomicUMin
461                };
462
463                let ty = fn_args.type_at(0);
464                if #[allow(non_exhaustive_omitted_patterns)] match ty.kind() {
    ty::Uint(_) => true,
    _ => false,
}matches!(ty.kind(), ty::Uint(_)) {
465                    let ordering = fn_args.const_at(1).to_value();
466                    let ptr = args[0].immediate();
467                    let val = args[1].immediate();
468                    bx.atomic_rmw(
469                        atom_op,
470                        ptr,
471                        val,
472                        parse_atomic_ordering(ordering),
473                        /* ret_ptr */ false,
474                    )
475                } else {
476                    invalid_monomorphization_int_type(ty);
477                    return Ok(());
478                }
479            }
480            sym::atomic_xchg => {
481                let ty = fn_args.type_at(0);
482                let ordering = fn_args.const_at(1).to_value();
483                if int_type_width_signed(ty, bx.tcx()).is_some() || ty.is_raw_ptr() {
484                    let ptr = args[0].immediate();
485                    let val = args[1].immediate();
486                    let atomic_op = AtomicRmwBinOp::AtomicXchg;
487                    bx.atomic_rmw(
488                        atomic_op,
489                        ptr,
490                        val,
491                        parse_atomic_ordering(ordering),
492                        /* ret_ptr */ ty.is_raw_ptr(),
493                    )
494                } else {
495                    invalid_monomorphization_int_or_ptr_type(ty);
496                    return Ok(());
497                }
498            }
499            sym::atomic_xadd
500            | sym::atomic_xsub
501            | sym::atomic_and
502            | sym::atomic_nand
503            | sym::atomic_or
504            | sym::atomic_xor => {
505                let atom_op = match name {
506                    sym::atomic_xadd => AtomicRmwBinOp::AtomicAdd,
507                    sym::atomic_xsub => AtomicRmwBinOp::AtomicSub,
508                    sym::atomic_and => AtomicRmwBinOp::AtomicAnd,
509                    sym::atomic_nand => AtomicRmwBinOp::AtomicNand,
510                    sym::atomic_or => AtomicRmwBinOp::AtomicOr,
511                    sym::atomic_xor => AtomicRmwBinOp::AtomicXor,
512                    _ => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
513                };
514
515                // The type of the in-memory data.
516                let ty_mem = fn_args.type_at(0);
517                // The type of the 2nd operand, given by-value.
518                let ty_op = fn_args.type_at(1);
519
520                let ordering = fn_args.const_at(2).to_value();
521                // We require either both arguments to have the same integer type, or the first to
522                // be a pointer and the second to be `usize`.
523                if (int_type_width_signed(ty_mem, bx.tcx()).is_some() && ty_op == ty_mem)
524                    || (ty_mem.is_raw_ptr() && ty_op == bx.tcx().types.usize)
525                {
526                    let ptr = args[0].immediate(); // of type "pointer to `ty_mem`"
527                    let val = args[1].immediate(); // of type `ty_op`
528                    bx.atomic_rmw(
529                        atom_op,
530                        ptr,
531                        val,
532                        parse_atomic_ordering(ordering),
533                        /* ret_ptr */ ty_mem.is_raw_ptr(),
534                    )
535                } else {
536                    invalid_monomorphization_int_or_ptr_type(ty_mem);
537                    return Ok(());
538                }
539            }
540            sym::atomic_fence => {
541                let ordering = fn_args.const_at(0).to_value();
542                bx.atomic_fence(parse_atomic_ordering(ordering), SynchronizationScope::CrossThread);
543                return Ok(());
544            }
545
546            sym::atomic_singlethreadfence => {
547                let ordering = fn_args.const_at(0).to_value();
548                bx.atomic_fence(
549                    parse_atomic_ordering(ordering),
550                    SynchronizationScope::SingleThread,
551                );
552                return Ok(());
553            }
554
555            sym::nontemporal_store => {
556                let dst = args[0].deref(bx.cx());
557                args[1].val.nontemporal_store(bx, dst);
558                return Ok(());
559            }
560
561            sym::ptr_offset_from | sym::ptr_offset_from_unsigned => {
562                let ty = fn_args.type_at(0);
563                let pointee_size = bx.layout_of(ty).size;
564
565                let a = args[0].immediate();
566                let b = args[1].immediate();
567                let a = bx.ptrtoint(a, bx.type_isize());
568                let b = bx.ptrtoint(b, bx.type_isize());
569                let pointee_size = bx.const_usize(pointee_size.bytes());
570                if name == sym::ptr_offset_from {
571                    // This is the same sequence that Clang emits for pointer subtraction.
572                    // It can be neither `nsw` nor `nuw` because the input is treated as
573                    // unsigned but then the output is treated as signed, so neither works.
574                    let d = bx.sub(a, b);
575                    // this is where the signed magic happens (notice the `s` in `exactsdiv`)
576                    bx.exactsdiv(d, pointee_size)
577                } else {
578                    // The `_unsigned` version knows the relative ordering of the pointers,
579                    // so can use `sub nuw` and `udiv exact` instead of dealing in signed.
580                    let d = bx.unchecked_usub(a, b);
581                    bx.exactudiv(d, pointee_size)
582                }
583            }
584
585            sym::cold_path => {
586                // This is a no-op. The intrinsic is just a hint to the optimizer.
587                return Ok(());
588            }
589
590            _ => {
591                // Need to use backend-specific things in the implementation.
592                return bx.codegen_intrinsic_call(instance, args, result, span);
593            }
594        };
595
596        if result.layout.ty.is_bool() {
597            let val = bx.from_immediate(llval);
598            bx.store_to_place(val, result.val);
599        } else if !result.layout.ty.is_unit() {
600            bx.store_to_place(llval, result.val);
601        }
602        Ok(())
603    }
604}
605
606// Returns the width of an int Ty, and if it's signed or not
607// Returns None if the type is not an integer
608// FIXME: there’s multiple of this functions, investigate using some of the already existing
609// stuffs.
610fn int_type_width_signed(ty: Ty<'_>, tcx: TyCtxt<'_>) -> Option<(u64, bool)> {
611    match ty.kind() {
612        ty::Int(t) => {
613            Some((t.bit_width().unwrap_or(u64::from(tcx.sess.target.pointer_width)), true))
614        }
615        ty::Uint(t) => {
616            Some((t.bit_width().unwrap_or(u64::from(tcx.sess.target.pointer_width)), false))
617        }
618        _ => None,
619    }
620}
621
622// Returns the width of a float Ty
623// Returns None if the type is not a float
624fn float_type_width(ty: Ty<'_>) -> Option<u64> {
625    match ty.kind() {
626        ty::Float(t) => Some(t.bit_width()),
627        _ => None,
628    }
629}