Merge from rustc
This commit is contained in:
commit
070e8ceab3
252 changed files with 3536 additions and 899 deletions
9
.github/workflows/dependencies.yml
vendored
9
.github/workflows/dependencies.yml
vendored
|
|
@ -42,7 +42,7 @@ jobs:
|
|||
|
||||
# Exit with error if open and S-waiting-on-bors
|
||||
if [[ "$STATE" == "OPEN" && "$WAITING_ON_BORS" == "true" ]]; then
|
||||
exit 1
|
||||
gh run cancel ${{ github.run_id }}
|
||||
fi
|
||||
|
||||
update:
|
||||
|
|
@ -65,7 +65,10 @@ jobs:
|
|||
|
||||
- name: cargo update
|
||||
# Remove first line that always just says "Updating crates.io index"
|
||||
run: cargo update 2>&1 | sed '/crates.io index/d' | tee -a cargo_update.log
|
||||
# If there are no changes, cancel the job here
|
||||
run: |
|
||||
cargo update 2>&1 | sed '/crates.io index/d' | tee -a cargo_update.log
|
||||
git status --porcelain | grep -q Cargo.lock || gh run cancel ${{ github.run_id }}
|
||||
- name: upload Cargo.lock artifact for use in PR
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
|
@ -131,7 +134,7 @@ jobs:
|
|||
# Exit with error if PR is closed
|
||||
STATE=$(gh pr view cargo_update --repo $GITHUB_REPOSITORY --json state --jq '.state')
|
||||
if [[ "$STATE" != "OPEN" ]]; then
|
||||
exit 1
|
||||
gh run cancel ${{ github.run_id }}
|
||||
fi
|
||||
|
||||
gh pr edit cargo_update --title "${PR_TITLE}" --body-file body.md --repo $GITHUB_REPOSITORY
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
|||
(self.arena.alloc_from_iter(stmts), expr)
|
||||
}
|
||||
|
||||
fn lower_local(&mut self, l: &Local) -> &'hir hir::Local<'hir> {
|
||||
fn lower_local(&mut self, l: &Local) -> &'hir hir::LetStmt<'hir> {
|
||||
let ty = l
|
||||
.ty
|
||||
.as_ref()
|
||||
|
|
@ -97,7 +97,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
|||
let span = self.lower_span(l.span);
|
||||
let source = hir::LocalSource::Normal;
|
||||
self.lower_attrs(hir_id, &l.attrs);
|
||||
self.arena.alloc(hir::Local { hir_id, ty, pat, init, els, span, source })
|
||||
self.arena.alloc(hir::LetStmt { hir_id, ty, pat, init, els, span, source })
|
||||
}
|
||||
|
||||
fn lower_block_check_mode(&mut self, b: &BlockCheckMode) -> hir::BlockCheckMode {
|
||||
|
|
|
|||
|
|
@ -302,8 +302,8 @@ impl<'a, 'hir> Visitor<'hir> for NodeCollector<'a, 'hir> {
|
|||
});
|
||||
}
|
||||
|
||||
fn visit_local(&mut self, l: &'hir Local<'hir>) {
|
||||
self.insert(l.span, l.hir_id, Node::Local(l));
|
||||
fn visit_local(&mut self, l: &'hir LetStmt<'hir>) {
|
||||
self.insert(l.span, l.hir_id, Node::LetStmt(l));
|
||||
self.with_parent(l.hir_id, |this| {
|
||||
intravisit::walk_local(this, l);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2341,7 +2341,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
|||
debug_assert!(!a.is_empty());
|
||||
self.attrs.insert(hir_id.local_id, a);
|
||||
}
|
||||
let local = hir::Local {
|
||||
let local = hir::LetStmt {
|
||||
hir_id,
|
||||
init,
|
||||
pat,
|
||||
|
|
|
|||
|
|
@ -622,7 +622,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
|
|||
|
||||
// FIXME: We make sure that this is a normal top-level binding,
|
||||
// but we could suggest `todo!()` for all uninitalized bindings in the pattern pattern
|
||||
if let hir::StmtKind::Let(hir::Local { span, ty, init: None, pat, .. }) =
|
||||
if let hir::StmtKind::Let(hir::LetStmt { span, ty, init: None, pat, .. }) =
|
||||
&ex.kind
|
||||
&& let hir::PatKind::Binding(..) = pat.kind
|
||||
&& span.contains(self.decl_span)
|
||||
|
|
@ -800,7 +800,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
|
|||
for (_, node) in tcx.hir().parent_iter(expr.hir_id) {
|
||||
let e = match node {
|
||||
hir::Node::Expr(e) => e,
|
||||
hir::Node::Local(hir::Local { els: Some(els), .. }) => {
|
||||
hir::Node::LetStmt(hir::LetStmt { els: Some(els), .. }) => {
|
||||
let mut finder = BreakFinder { found_breaks: vec![], found_continues: vec![] };
|
||||
finder.visit_block(els);
|
||||
if !finder.found_breaks.is_empty() {
|
||||
|
|
@ -2124,7 +2124,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
|
|||
hir::intravisit::walk_expr(self, e);
|
||||
}
|
||||
|
||||
fn visit_local(&mut self, local: &'hir hir::Local<'hir>) {
|
||||
fn visit_local(&mut self, local: &'hir hir::LetStmt<'hir>) {
|
||||
if let hir::Pat { kind: hir::PatKind::Binding(_, hir_id, _ident, _), .. } =
|
||||
local.pat
|
||||
&& let Some(init) = local.init
|
||||
|
|
|
|||
|
|
@ -558,7 +558,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
|
|||
hir::intravisit::walk_stmt(self, stmt);
|
||||
let expr = match stmt.kind {
|
||||
hir::StmtKind::Semi(expr) | hir::StmtKind::Expr(expr) => expr,
|
||||
hir::StmtKind::Let(hir::Local { init: Some(expr), .. }) => expr,
|
||||
hir::StmtKind::Let(hir::LetStmt { init: Some(expr), .. }) => expr,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
|
|
@ -737,7 +737,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
|
|||
&& let body = self.infcx.tcx.hir().body(body_id)
|
||||
&& let Some(hir_id) = (BindingFinder { span: pat_span }).visit_body(body).break_value()
|
||||
&& let node = self.infcx.tcx.hir_node(hir_id)
|
||||
&& let hir::Node::Local(hir::Local {
|
||||
&& let hir::Node::LetStmt(hir::LetStmt {
|
||||
pat: hir::Pat { kind: hir::PatKind::Ref(_, _), .. },
|
||||
..
|
||||
})
|
||||
|
|
@ -1170,7 +1170,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
|
|||
};
|
||||
|
||||
if let Some(hir_id) = hir_id
|
||||
&& let hir::Node::Local(local) = self.infcx.tcx.hir_node(hir_id)
|
||||
&& let hir::Node::LetStmt(local) = self.infcx.tcx.hir_node(hir_id)
|
||||
{
|
||||
let tables = self.infcx.tcx.typeck(def_id.as_local().unwrap());
|
||||
if let Some(clone_trait) = self.infcx.tcx.lang_items().clone_trait()
|
||||
|
|
|
|||
|
|
@ -2000,7 +2000,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
|
|||
ConstraintCategory::SizedBound,
|
||||
);
|
||||
}
|
||||
&Rvalue::NullaryOp(NullOp::UbCheck(_), _) => {}
|
||||
&Rvalue::NullaryOp(NullOp::UbChecks, _) => {}
|
||||
|
||||
Rvalue::ShallowInitBox(operand, ty) => {
|
||||
self.check_operand(operand, location);
|
||||
|
|
|
|||
|
|
@ -780,7 +780,7 @@ fn codegen_stmt<'tcx>(
|
|||
NullOp::OffsetOf(fields) => {
|
||||
layout.offset_of_subfield(fx, fields.iter()).bytes()
|
||||
}
|
||||
NullOp::UbCheck(_) => {
|
||||
NullOp::UbChecks => {
|
||||
let val = fx.tcx.sess.opts.debug_assertions;
|
||||
let val = CValue::by_val(
|
||||
fx.bcx.ins().iconst(types::I8, i64::try_from(val).unwrap()),
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use crate::traits::*;
|
|||
use crate::MemFlags;
|
||||
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||
use rustc_session::config::OptLevel;
|
||||
use rustc_span::{sym, Span};
|
||||
use rustc_target::abi::{
|
||||
call::{FnAbi, PassMode},
|
||||
|
|
@ -75,6 +76,29 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
let name = bx.tcx().item_name(def_id);
|
||||
let name_str = name.as_str();
|
||||
|
||||
// If we're swapping something that's *not* an `OperandValue::Ref`,
|
||||
// then we can do it directly and avoid the alloca.
|
||||
// Otherwise, we'll let the fallback MIR body take care of it.
|
||||
if let sym::typed_swap = name {
|
||||
let pointee_ty = fn_args.type_at(0);
|
||||
let pointee_layout = bx.layout_of(pointee_ty);
|
||||
if !bx.is_backend_ref(pointee_layout)
|
||||
// But if we're not going to optimize, trying to use the fallback
|
||||
// body just makes things worse, so don't bother.
|
||||
|| bx.sess().opts.optimize == OptLevel::No
|
||||
// NOTE(eddyb) SPIR-V's Logical addressing model doesn't allow for arbitrary
|
||||
// reinterpretation of values as (chunkable) byte arrays, and the loop in the
|
||||
// block optimization in `ptr::swap_nonoverlapping` is hard to rewrite back
|
||||
// into the (unoptimized) direct swapping implementation, so we disable it.
|
||||
|| bx.sess().target.arch == "spirv"
|
||||
{
|
||||
let x_place = PlaceRef::new_sized(args[0].immediate(), pointee_layout);
|
||||
let y_place = PlaceRef::new_sized(args[1].immediate(), pointee_layout);
|
||||
bx.typed_place_swap(x_place, y_place);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let llret_ty = bx.backend_type(bx.layout_of(ret_ty));
|
||||
let result = PlaceRef::new_sized(llresult, fn_abi.ret.layout);
|
||||
|
||||
|
|
|
|||
|
|
@ -680,8 +680,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
let val = layout.offset_of_subfield(bx.cx(), fields.iter()).bytes();
|
||||
bx.cx().const_usize(val)
|
||||
}
|
||||
mir::NullOp::UbCheck(_) => {
|
||||
// In codegen, we want to check for language UB and library UB
|
||||
mir::NullOp::UbChecks => {
|
||||
let val = bx.tcx().sess.opts.debug_assertions;
|
||||
bx.cx().const_bool(val)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,24 @@
|
|||
use super::abi::AbiBuilderMethods;
|
||||
use super::asm::AsmBuilderMethods;
|
||||
use super::consts::ConstMethods;
|
||||
use super::coverageinfo::CoverageInfoBuilderMethods;
|
||||
use super::debuginfo::DebugInfoBuilderMethods;
|
||||
use super::intrinsic::IntrinsicCallMethods;
|
||||
use super::misc::MiscMethods;
|
||||
use super::type_::{ArgAbiMethods, BaseTypeMethods};
|
||||
use super::type_::{ArgAbiMethods, BaseTypeMethods, LayoutTypeMethods};
|
||||
use super::{HasCodegen, StaticBuilderMethods};
|
||||
|
||||
use crate::common::{
|
||||
AtomicOrdering, AtomicRmwBinOp, IntPredicate, RealPredicate, SynchronizationScope, TypeKind,
|
||||
};
|
||||
use crate::mir::operand::OperandRef;
|
||||
use crate::mir::operand::{OperandRef, OperandValue};
|
||||
use crate::mir::place::PlaceRef;
|
||||
use crate::MemFlags;
|
||||
|
||||
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
|
||||
use rustc_middle::ty::layout::{HasParamEnv, TyAndLayout};
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_session::config::OptLevel;
|
||||
use rustc_span::Span;
|
||||
use rustc_target::abi::call::FnAbi;
|
||||
use rustc_target::abi::{Abi, Align, Scalar, Size, WrappingRange};
|
||||
|
|
@ -267,6 +269,54 @@ pub trait BuilderMethods<'a, 'tcx>:
|
|||
flags: MemFlags,
|
||||
);
|
||||
|
||||
/// *Typed* copy for non-overlapping places.
|
||||
///
|
||||
/// Has a default implementation in terms of `memcpy`, but specific backends
|
||||
/// can override to do something smarter if possible.
|
||||
///
|
||||
/// (For example, typed load-stores with alias metadata.)
|
||||
fn typed_place_copy(
|
||||
&mut self,
|
||||
dst: PlaceRef<'tcx, Self::Value>,
|
||||
src: PlaceRef<'tcx, Self::Value>,
|
||||
) {
|
||||
debug_assert!(src.llextra.is_none());
|
||||
debug_assert!(dst.llextra.is_none());
|
||||
debug_assert_eq!(dst.layout.size, src.layout.size);
|
||||
if self.sess().opts.optimize == OptLevel::No && self.is_backend_immediate(dst.layout) {
|
||||
// If we're not optimizing, the aliasing information from `memcpy`
|
||||
// isn't useful, so just load-store the value for smaller code.
|
||||
let temp = self.load_operand(src);
|
||||
temp.val.store(self, dst);
|
||||
} else if !dst.layout.is_zst() {
|
||||
let bytes = self.const_usize(dst.layout.size.bytes());
|
||||
self.memcpy(dst.llval, dst.align, src.llval, src.align, bytes, MemFlags::empty());
|
||||
}
|
||||
}
|
||||
|
||||
/// *Typed* swap for non-overlapping places.
|
||||
///
|
||||
/// Avoids `alloca`s for Immediates and ScalarPairs.
|
||||
///
|
||||
/// FIXME: Maybe do something smarter for Ref types too?
|
||||
/// For now, the `typed_swap` intrinsic just doesn't call this for those
|
||||
/// cases (in non-debug), preferring the fallback body instead.
|
||||
fn typed_place_swap(
|
||||
&mut self,
|
||||
left: PlaceRef<'tcx, Self::Value>,
|
||||
right: PlaceRef<'tcx, Self::Value>,
|
||||
) {
|
||||
let mut temp = self.load_operand(left);
|
||||
if let OperandValue::Ref(..) = temp.val {
|
||||
// The SSA value isn't stand-alone, so we need to copy it elsewhere
|
||||
let alloca = PlaceRef::alloca(self, left.layout);
|
||||
self.typed_place_copy(alloca, left);
|
||||
temp = self.load_operand(alloca);
|
||||
}
|
||||
self.typed_place_copy(left, right);
|
||||
temp.val.store(self, right);
|
||||
}
|
||||
|
||||
fn select(
|
||||
&mut self,
|
||||
cond: Self::Value,
|
||||
|
|
|
|||
|
|
@ -120,6 +120,20 @@ pub trait LayoutTypeMethods<'tcx>: Backend<'tcx> {
|
|||
immediate: bool,
|
||||
) -> Self::Type;
|
||||
|
||||
/// A type that produces an [`OperandValue::Ref`] when loaded.
|
||||
///
|
||||
/// AKA one that's not a ZST, not `is_backend_immediate`, and
|
||||
/// not `is_backend_scalar_pair`. For such a type, a
|
||||
/// [`load_operand`] doesn't actually `load` anything.
|
||||
///
|
||||
/// [`OperandValue::Ref`]: crate::mir::operand::OperandValue::Ref
|
||||
/// [`load_operand`]: super::BuilderMethods::load_operand
|
||||
fn is_backend_ref(&self, layout: TyAndLayout<'tcx>) -> bool {
|
||||
!(layout.is_zst()
|
||||
|| self.is_backend_immediate(layout)
|
||||
|| self.is_backend_scalar_pair(layout))
|
||||
}
|
||||
|
||||
/// A type that can be used in a [`super::BuilderMethods::load`] +
|
||||
/// [`super::BuilderMethods::store`] pair to implement a *typed* copy,
|
||||
/// such as a MIR `*_0 = *_1`.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use either::{Left, Right};
|
|||
use rustc_hir::def::DefKind;
|
||||
use rustc_middle::mir::interpret::{AllocId, ErrorHandled, InterpErrorInfo};
|
||||
use rustc_middle::mir::{self, ConstAlloc, ConstValue};
|
||||
use rustc_middle::query::{Key, TyCtxtAt};
|
||||
use rustc_middle::query::TyCtxtAt;
|
||||
use rustc_middle::traits::Reveal;
|
||||
use rustc_middle::ty::layout::LayoutOf;
|
||||
use rustc_middle::ty::print::with_no_trimmed_paths;
|
||||
|
|
@ -243,24 +243,6 @@ pub(crate) fn turn_into_const_value<'tcx>(
|
|||
op_to_const(&ecx, &mplace.into(), /* for diagnostics */ false)
|
||||
}
|
||||
|
||||
/// Computes the tag (if any) for a given type and variant.
|
||||
#[instrument(skip(tcx), level = "debug")]
|
||||
pub fn tag_for_variant_provider<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
(ty, variant_index): (Ty<'tcx>, abi::VariantIdx),
|
||||
) -> Option<ty::ScalarInt> {
|
||||
assert!(ty.is_enum());
|
||||
|
||||
let ecx = InterpCx::new(
|
||||
tcx,
|
||||
ty.default_span(tcx),
|
||||
ty::ParamEnv::reveal_all(),
|
||||
crate::const_eval::DummyMachine,
|
||||
);
|
||||
|
||||
ecx.tag_for_variant(ty, variant_index).unwrap().map(|(tag, _tag_field)| tag)
|
||||
}
|
||||
|
||||
#[instrument(skip(tcx), level = "debug")]
|
||||
pub fn eval_to_const_value_raw_provider<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
use rustc_middle::mir;
|
||||
use rustc_middle::mir::interpret::InterpErrorInfo;
|
||||
use rustc_middle::query::TyCtxtAt;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_middle::query::{Key, TyCtxtAt};
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||
use rustc_target::abi::VariantIdx;
|
||||
|
||||
use crate::interpret::format_interp_error;
|
||||
use crate::interpret::{format_interp_error, InterpCx};
|
||||
|
||||
mod dummy_machine;
|
||||
mod error;
|
||||
|
|
@ -77,3 +78,21 @@ pub(crate) fn try_destructure_mir_constant_for_user_output<'tcx>(
|
|||
|
||||
Some(mir::DestructuredConstant { variant, fields })
|
||||
}
|
||||
|
||||
/// Computes the tag (if any) for a given type and variant.
|
||||
#[instrument(skip(tcx), level = "debug")]
|
||||
pub fn tag_for_variant_provider<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
(ty, variant_index): (Ty<'tcx>, VariantIdx),
|
||||
) -> Option<ty::ScalarInt> {
|
||||
assert!(ty.is_enum());
|
||||
|
||||
let ecx = InterpCx::new(
|
||||
tcx,
|
||||
ty.default_span(tcx),
|
||||
ty::ParamEnv::reveal_all(),
|
||||
crate::const_eval::DummyMachine,
|
||||
);
|
||||
|
||||
ecx.tag_for_variant(ty, variant_index).unwrap().map(|(tag, _tag_field)| tag)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
}
|
||||
None => {
|
||||
// No need to write the tag here, because an untagged variant is
|
||||
// implicitly encoded. For `Niche`-optimized enums, it's by
|
||||
// implicitly encoded. For `Niche`-optimized enums, this works by
|
||||
// simply by having a value that is outside the niche variants.
|
||||
// But what if the data stored here does not actually encode
|
||||
// this variant? That would be bad! So let's double-check...
|
||||
|
|
@ -227,8 +227,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
Ok(ImmTy::from_scalar(discr_value, discr_layout))
|
||||
}
|
||||
|
||||
/// Computes the tag value and its field number (if any) of a given variant
|
||||
/// of type `ty`.
|
||||
/// Computes how to write the tag of a given variant of enum `ty`:
|
||||
/// - `None` means that nothing needs to be done as the variant is encoded implicitly
|
||||
/// - `Some((val, field_idx))` means that the given integer value needs to be stored at the
|
||||
/// given field index.
|
||||
pub(crate) fn tag_for_variant(
|
||||
&self,
|
||||
ty: Ty<'tcx>,
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ use rustc_span::symbol::{sym, Symbol};
|
|||
use rustc_target::abi::Size;
|
||||
|
||||
use super::{
|
||||
util::ensure_monomorphic_enough, CheckInAllocMsg, ImmTy, InterpCx, MPlaceTy, Machine, OpTy,
|
||||
Pointer,
|
||||
memory::MemoryKind, util::ensure_monomorphic_enough, CheckInAllocMsg, ImmTy, InterpCx,
|
||||
MPlaceTy, Machine, OpTy, Pointer,
|
||||
};
|
||||
|
||||
use crate::fluent_generated as fluent;
|
||||
|
|
@ -414,6 +414,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
let result = self.raw_eq_intrinsic(&args[0], &args[1])?;
|
||||
self.write_scalar(result, dest)?;
|
||||
}
|
||||
sym::typed_swap => {
|
||||
self.typed_swap_intrinsic(&args[0], &args[1])?;
|
||||
}
|
||||
|
||||
sym::vtable_size => {
|
||||
let ptr = self.read_pointer(&args[0])?;
|
||||
|
|
@ -607,6 +610,24 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
self.mem_copy(src, dst, size, nonoverlapping)
|
||||
}
|
||||
|
||||
/// Does a *typed* swap of `*left` and `*right`.
|
||||
fn typed_swap_intrinsic(
|
||||
&mut self,
|
||||
left: &OpTy<'tcx, <M as Machine<'mir, 'tcx>>::Provenance>,
|
||||
right: &OpTy<'tcx, <M as Machine<'mir, 'tcx>>::Provenance>,
|
||||
) -> InterpResult<'tcx> {
|
||||
let left = self.deref_pointer(left)?;
|
||||
let right = self.deref_pointer(right)?;
|
||||
debug_assert_eq!(left.layout, right.layout);
|
||||
let kind = MemoryKind::Stack;
|
||||
let temp = self.allocate(left.layout, kind)?;
|
||||
self.copy_op(&left, &temp)?;
|
||||
self.copy_op(&right, &left)?;
|
||||
self.copy_op(&temp, &right)?;
|
||||
self.deallocate_ptr(temp.ptr(), None, kind)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn write_bytes_intrinsic(
|
||||
&mut self,
|
||||
dst: &OpTy<'tcx, <M as Machine<'mir, 'tcx>>::Provenance>,
|
||||
|
|
|
|||
|
|
@ -258,17 +258,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
let val = layout.offset_of_subfield(self, fields.iter()).bytes();
|
||||
Scalar::from_target_usize(val, self)
|
||||
}
|
||||
mir::NullOp::UbCheck(kind) => {
|
||||
// We want to enable checks for library UB, because the interpreter doesn't
|
||||
// know about those on its own.
|
||||
// But we want to disable checks for language UB, because the interpreter
|
||||
// has its own better checks for that.
|
||||
let should_check = match kind {
|
||||
mir::UbKind::LibraryUb => self.tcx.sess.opts.debug_assertions,
|
||||
mir::UbKind::LanguageUb => false,
|
||||
};
|
||||
Scalar::from_bool(should_check)
|
||||
}
|
||||
mir::NullOp::UbChecks => Scalar::from_bool(self.tcx.sess.opts.debug_assertions),
|
||||
};
|
||||
self.write_scalar(val, &dest)?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -558,7 +558,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
|
|||
Rvalue::Cast(_, _, _) => {}
|
||||
|
||||
Rvalue::NullaryOp(
|
||||
NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbCheck(_),
|
||||
NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks,
|
||||
_,
|
||||
) => {}
|
||||
Rvalue::ShallowInitBox(_, _) => {}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ impl<'tcx> MirPass<'tcx> for Validator {
|
|||
cfg_checker.check_cleanup_control_flow();
|
||||
|
||||
// Also run the TypeChecker.
|
||||
for (location, msg) in validate_types(tcx, self.mir_phase, param_env, body) {
|
||||
for (location, msg) in validate_types(tcx, self.mir_phase, param_env, body, body) {
|
||||
cfg_checker.fail(location, msg);
|
||||
}
|
||||
|
||||
|
|
@ -541,19 +541,25 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> {
|
|||
|
||||
/// A faster version of the validation pass that only checks those things which may break when
|
||||
/// instantiating any generic parameters.
|
||||
///
|
||||
/// `caller_body` is used to detect cycles in MIR inlining and MIR validation before
|
||||
/// `optimized_mir` is available.
|
||||
pub fn validate_types<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
mir_phase: MirPhase,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
body: &Body<'tcx>,
|
||||
caller_body: &Body<'tcx>,
|
||||
) -> Vec<(Location, String)> {
|
||||
let mut type_checker = TypeChecker { body, tcx, param_env, mir_phase, failures: Vec::new() };
|
||||
let mut type_checker =
|
||||
TypeChecker { body, caller_body, tcx, param_env, mir_phase, failures: Vec::new() };
|
||||
type_checker.visit_body(body);
|
||||
type_checker.failures
|
||||
}
|
||||
|
||||
struct TypeChecker<'a, 'tcx> {
|
||||
body: &'a Body<'tcx>,
|
||||
caller_body: &'a Body<'tcx>,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
param_env: ParamEnv<'tcx>,
|
||||
mir_phase: MirPhase,
|
||||
|
|
@ -705,8 +711,13 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
|
|||
}
|
||||
&ty::Coroutine(def_id, args) => {
|
||||
let f_ty = if let Some(var) = parent_ty.variant_index {
|
||||
let gen_body = if def_id == self.body.source.def_id() {
|
||||
self.body
|
||||
// If we're currently validating an inlined copy of this body,
|
||||
// then it will no longer be parameterized over the original
|
||||
// args of the coroutine. Otherwise, we prefer to use this body
|
||||
// since we may be in the process of computing this MIR in the
|
||||
// first place.
|
||||
let gen_body = if def_id == self.caller_body.source.def_id() {
|
||||
self.caller_body
|
||||
} else {
|
||||
self.tcx.optimized_mir(def_id)
|
||||
};
|
||||
|
|
@ -1168,7 +1179,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
|
|||
Rvalue::Repeat(_, _)
|
||||
| Rvalue::ThreadLocalRef(_)
|
||||
| Rvalue::AddressOf(_, _)
|
||||
| Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::UbCheck(_), _)
|
||||
| Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::UbChecks, _)
|
||||
| Rvalue::Discriminant(_) => {}
|
||||
}
|
||||
self.super_rvalue(rvalue, location);
|
||||
|
|
|
|||
|
|
@ -1220,7 +1220,7 @@ pub struct Stmt<'hir> {
|
|||
#[derive(Debug, Clone, Copy, HashStable_Generic)]
|
||||
pub enum StmtKind<'hir> {
|
||||
/// A local (`let`) binding.
|
||||
Let(&'hir Local<'hir>),
|
||||
Let(&'hir LetStmt<'hir>),
|
||||
|
||||
/// An item binding.
|
||||
Item(ItemId),
|
||||
|
|
@ -1234,7 +1234,7 @@ pub enum StmtKind<'hir> {
|
|||
|
||||
/// Represents a `let` statement (i.e., `let <pat>:<ty> = <init>;`).
|
||||
#[derive(Debug, Clone, Copy, HashStable_Generic)]
|
||||
pub struct Local<'hir> {
|
||||
pub struct LetStmt<'hir> {
|
||||
pub pat: &'hir Pat<'hir>,
|
||||
/// Type annotation, if any (otherwise the type will be inferred).
|
||||
pub ty: Option<&'hir Ty<'hir>>,
|
||||
|
|
@ -1264,7 +1264,7 @@ pub struct Arm<'hir> {
|
|||
pub body: &'hir Expr<'hir>,
|
||||
}
|
||||
|
||||
/// Represents a `let <pat>[: <ty>] = <expr>` expression (not a [`Local`]), occurring in an `if-let`
|
||||
/// Represents a `let <pat>[: <ty>] = <expr>` expression (not a [`LetStmt`]), occurring in an `if-let`
|
||||
/// or `let-else`, evaluating to a boolean. Typically the pattern is refutable.
|
||||
///
|
||||
/// In an `if let`, imagine it as `if (let <pat> = <expr>) { ... }`; in a let-else, it is part of
|
||||
|
|
@ -1861,7 +1861,7 @@ pub enum ExprKind<'hir> {
|
|||
DropTemps(&'hir Expr<'hir>),
|
||||
/// A `let $pat = $expr` expression.
|
||||
///
|
||||
/// These are not `Local` and only occur as expressions.
|
||||
/// These are not [`LetStmt`] and only occur as expressions.
|
||||
/// The `let Some(x) = foo()` in `if let Some(x) = foo()` is an example of `Let(..)`.
|
||||
Let(&'hir LetExpr<'hir>),
|
||||
/// An `if` block, with an optional else block.
|
||||
|
|
@ -3529,7 +3529,7 @@ pub enum Node<'hir> {
|
|||
PatField(&'hir PatField<'hir>),
|
||||
Arm(&'hir Arm<'hir>),
|
||||
Block(&'hir Block<'hir>),
|
||||
Local(&'hir Local<'hir>),
|
||||
LetStmt(&'hir LetStmt<'hir>),
|
||||
/// `Ctor` refers to the constructor of an enum variant or struct. Only tuple or unit variants
|
||||
/// with synthesized constructors.
|
||||
Ctor(&'hir VariantData<'hir>),
|
||||
|
|
@ -3585,7 +3585,7 @@ impl<'hir> Node<'hir> {
|
|||
| Node::Ctor(..)
|
||||
| Node::Pat(..)
|
||||
| Node::Arm(..)
|
||||
| Node::Local(..)
|
||||
| Node::LetStmt(..)
|
||||
| Node::Crate(..)
|
||||
| Node::Ty(..)
|
||||
| Node::TraitRef(..)
|
||||
|
|
@ -3757,7 +3757,7 @@ impl<'hir> Node<'hir> {
|
|||
expect_pat_field, &'hir PatField<'hir>, Node::PatField(n), n;
|
||||
expect_arm, &'hir Arm<'hir>, Node::Arm(n), n;
|
||||
expect_block, &'hir Block<'hir>, Node::Block(n), n;
|
||||
expect_local, &'hir Local<'hir>, Node::Local(n), n;
|
||||
expect_let_stmt, &'hir LetStmt<'hir>, Node::LetStmt(n), n;
|
||||
expect_ctor, &'hir VariantData<'hir>, Node::Ctor(n), n;
|
||||
expect_lifetime, &'hir Lifetime, Node::Lifetime(n), n;
|
||||
expect_generic_param, &'hir GenericParam<'hir>, Node::GenericParam(n), n;
|
||||
|
|
@ -3787,7 +3787,7 @@ mod size_asserts {
|
|||
static_assert_size!(ImplItemKind<'_>, 40);
|
||||
static_assert_size!(Item<'_>, 88);
|
||||
static_assert_size!(ItemKind<'_>, 56);
|
||||
static_assert_size!(Local<'_>, 64);
|
||||
static_assert_size!(LetStmt<'_>, 64);
|
||||
static_assert_size!(Param<'_>, 32);
|
||||
static_assert_size!(Pat<'_>, 72);
|
||||
static_assert_size!(Path<'_>, 40);
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@ pub trait Visitor<'v>: Sized {
|
|||
fn visit_foreign_item(&mut self, i: &'v ForeignItem<'v>) -> Self::Result {
|
||||
walk_foreign_item(self, i)
|
||||
}
|
||||
fn visit_local(&mut self, l: &'v Local<'v>) -> Self::Result {
|
||||
fn visit_local(&mut self, l: &'v LetStmt<'v>) -> Self::Result {
|
||||
walk_local(self, l)
|
||||
}
|
||||
fn visit_block(&mut self, b: &'v Block<'v>) -> Self::Result {
|
||||
|
|
@ -606,7 +606,7 @@ pub fn walk_foreign_item<'v, V: Visitor<'v>>(
|
|||
V::Result::output()
|
||||
}
|
||||
|
||||
pub fn walk_local<'v, V: Visitor<'v>>(visitor: &mut V, local: &'v Local<'v>) -> V::Result {
|
||||
pub fn walk_local<'v, V: Visitor<'v>>(visitor: &mut V, local: &'v LetStmt<'v>) -> V::Result {
|
||||
// Intentionally visiting the expr first - the initialization expr
|
||||
// dominates the local's definition.
|
||||
visit_opt!(visitor, visit_expr, local.init);
|
||||
|
|
|
|||
|
|
@ -639,10 +639,9 @@ pub(super) fn collect_return_position_impl_trait_in_trait_tys<'tcx>(
|
|||
}
|
||||
}
|
||||
|
||||
if !unnormalized_trait_sig.output().references_error() {
|
||||
debug_assert!(
|
||||
!collector.types.is_empty(),
|
||||
"expect >0 RPITITs in call to `collect_return_position_impl_trait_in_trait_tys`"
|
||||
if !unnormalized_trait_sig.output().references_error() && collector.types.is_empty() {
|
||||
tcx.dcx().delayed_bug(
|
||||
"expect >0 RPITITs in call to `collect_return_position_impl_trait_in_trait_tys`",
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -127,8 +127,7 @@ pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -
|
|||
| sym::variant_count
|
||||
| sym::is_val_statically_known
|
||||
| sym::ptr_mask
|
||||
| sym::check_language_ub
|
||||
| sym::check_library_ub
|
||||
| sym::ub_checks
|
||||
| sym::fadd_algebraic
|
||||
| sym::fsub_algebraic
|
||||
| sym::fmul_algebraic
|
||||
|
|
@ -484,6 +483,8 @@ pub fn check_intrinsic_type(
|
|||
(1, 0, vec![Ty::new_mut_ptr(tcx, param(0)), param(0)], Ty::new_unit(tcx))
|
||||
}
|
||||
|
||||
sym::typed_swap => (1, 1, vec![Ty::new_mut_ptr(tcx, param(0)); 2], Ty::new_unit(tcx)),
|
||||
|
||||
sym::discriminant_value => {
|
||||
let assoc_items = tcx.associated_item_def_ids(
|
||||
tcx.require_lang_item(hir::LangItem::DiscriminantKind, None),
|
||||
|
|
@ -569,7 +570,7 @@ pub fn check_intrinsic_type(
|
|||
(0, 0, vec![Ty::new_imm_ptr(tcx, Ty::new_unit(tcx))], tcx.types.usize)
|
||||
}
|
||||
|
||||
sym::check_language_ub | sym::check_library_ub => (0, 1, Vec::new(), tcx.types.bool),
|
||||
sym::ub_checks => (0, 1, Vec::new(), tcx.types.bool),
|
||||
|
||||
sym::simd_eq
|
||||
| sym::simd_ne
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use rustc_data_structures::fx::FxHashSet;
|
|||
use rustc_hir as hir;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::intravisit::{self, Visitor};
|
||||
use rustc_hir::{Arm, Block, Expr, Local, Pat, PatKind, Stmt};
|
||||
use rustc_hir::{Arm, Block, Expr, LetStmt, Pat, PatKind, Stmt};
|
||||
use rustc_index::Idx;
|
||||
use rustc_middle::middle::region::*;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
|
|
@ -123,7 +123,7 @@ fn resolve_block<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, blk: &'tcx h
|
|||
|
||||
for (i, statement) in blk.stmts.iter().enumerate() {
|
||||
match statement.kind {
|
||||
hir::StmtKind::Let(hir::Local { els: Some(els), .. }) => {
|
||||
hir::StmtKind::Let(LetStmt { els: Some(els), .. }) => {
|
||||
// Let-else has a special lexical structure for variables.
|
||||
// First we take a checkpoint of the current scope context here.
|
||||
let mut prev_cx = visitor.cx;
|
||||
|
|
@ -855,7 +855,7 @@ impl<'tcx> Visitor<'tcx> for RegionResolutionVisitor<'tcx> {
|
|||
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
|
||||
resolve_expr(self, ex);
|
||||
}
|
||||
fn visit_local(&mut self, l: &'tcx Local<'tcx>) {
|
||||
fn visit_local(&mut self, l: &'tcx LetStmt<'tcx>) {
|
||||
resolve_local(self, Some(l.pat), l.init)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ impl<'a> State<'a> {
|
|||
// `hir_map` to reconstruct their full structure for pretty
|
||||
// printing.
|
||||
Node::Ctor(..) => panic!("cannot print isolated Ctor"),
|
||||
Node::Local(a) => self.print_local_decl(a),
|
||||
Node::LetStmt(a) => self.print_local_decl(a),
|
||||
Node::Crate(..) => panic!("cannot print Crate"),
|
||||
Node::WhereBoundPredicate(pred) => {
|
||||
self.print_formal_generic_params(pred.bound_generic_params);
|
||||
|
|
@ -1544,7 +1544,7 @@ impl<'a> State<'a> {
|
|||
self.end()
|
||||
}
|
||||
|
||||
fn print_local_decl(&mut self, loc: &hir::Local<'_>) {
|
||||
fn print_local_decl(&mut self, loc: &hir::LetStmt<'_>) {
|
||||
self.print_pat(loc.pat);
|
||||
if let Some(ty) = loc.ty {
|
||||
self.word_space(":");
|
||||
|
|
|
|||
|
|
@ -408,7 +408,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
}
|
||||
}
|
||||
}
|
||||
if let hir::Node::Local(hir::Local { ty: Some(_), pat, .. }) = node {
|
||||
if let hir::Node::LetStmt(hir::LetStmt { ty: Some(_), pat, .. }) = node {
|
||||
return Some((pat.span, "expected because of this assignment".to_string()));
|
||||
}
|
||||
None
|
||||
|
|
|
|||
|
|
@ -299,8 +299,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
return false;
|
||||
};
|
||||
let (init_ty_hir_id, init) = match self.tcx.parent_hir_node(pat.hir_id) {
|
||||
hir::Node::Local(hir::Local { ty: Some(ty), init, .. }) => (ty.hir_id, *init),
|
||||
hir::Node::Local(hir::Local { init: Some(init), .. }) => (init.hir_id, Some(*init)),
|
||||
hir::Node::LetStmt(hir::LetStmt { ty: Some(ty), init, .. }) => (ty.hir_id, *init),
|
||||
hir::Node::LetStmt(hir::LetStmt { init: Some(init), .. }) => (init.hir_id, Some(*init)),
|
||||
_ => return false,
|
||||
};
|
||||
let Some(init_ty) = self.node_ty_opt(init_ty_hir_id) else {
|
||||
|
|
@ -678,7 +678,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
error: Option<TypeError<'tcx>>,
|
||||
) {
|
||||
match (self.tcx.parent_hir_node(expr.hir_id), error) {
|
||||
(hir::Node::Local(hir::Local { ty: Some(ty), init: Some(init), .. }), _)
|
||||
(hir::Node::LetStmt(hir::LetStmt { ty: Some(ty), init: Some(init), .. }), _)
|
||||
if init.hir_id == expr.hir_id =>
|
||||
{
|
||||
// Point at `let` assignment type.
|
||||
|
|
@ -724,11 +724,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
primary_span = pat.span;
|
||||
secondary_span = pat.span;
|
||||
match self.tcx.parent_hir_node(pat.hir_id) {
|
||||
hir::Node::Local(hir::Local { ty: Some(ty), .. }) => {
|
||||
hir::Node::LetStmt(hir::LetStmt { ty: Some(ty), .. }) => {
|
||||
primary_span = ty.span;
|
||||
post_message = " type";
|
||||
}
|
||||
hir::Node::Local(hir::Local { init: Some(init), .. }) => {
|
||||
hir::Node::LetStmt(hir::LetStmt { init: Some(init), .. }) => {
|
||||
primary_span = init.span;
|
||||
post_message = " value";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1451,7 +1451,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
});
|
||||
let Some((
|
||||
_,
|
||||
hir::Node::Local(hir::Local { ty: Some(ty), .. })
|
||||
hir::Node::LetStmt(hir::LetStmt { ty: Some(ty), .. })
|
||||
| hir::Node::Item(hir::Item { kind: hir::ItemKind::Const(ty, _, _), .. }),
|
||||
)) = parent_node
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -371,7 +371,7 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
|
|||
|
||||
fn walk_stmt(&mut self, stmt: &hir::Stmt<'_>) {
|
||||
match stmt.kind {
|
||||
hir::StmtKind::Let(hir::Local { pat, init: Some(expr), els, .. }) => {
|
||||
hir::StmtKind::Let(hir::LetStmt { pat, init: Some(expr), els, .. }) => {
|
||||
self.walk_local(expr, pat, *els, |_| {})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1601,7 +1601,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
}
|
||||
|
||||
/// Type check a `let` statement.
|
||||
pub fn check_decl_local(&self, local: &'tcx hir::Local<'tcx>) {
|
||||
pub fn check_decl_local(&self, local: &'tcx hir::LetStmt<'tcx>) {
|
||||
self.check_decl(local.into());
|
||||
if local.pat.is_never_pattern() {
|
||||
self.diverges.set(Diverges::Always {
|
||||
|
|
@ -1789,7 +1789,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
[
|
||||
hir::Stmt {
|
||||
kind:
|
||||
hir::StmtKind::Let(hir::Local {
|
||||
hir::StmtKind::Let(hir::LetStmt {
|
||||
source:
|
||||
hir::LocalSource::AssignDesugar(_),
|
||||
..
|
||||
|
|
|
|||
|
|
@ -317,7 +317,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
);
|
||||
expr_id = parent_id;
|
||||
}
|
||||
Node::Local(local) => {
|
||||
Node::LetStmt(local) => {
|
||||
if let Some(mut ty) = local.ty {
|
||||
while let Some(index) = tuple_indexes.pop() {
|
||||
match ty.kind {
|
||||
|
|
@ -1348,7 +1348,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
// ++++++++++
|
||||
// since the user probably just misunderstood how `let else`
|
||||
// and `&&` work together.
|
||||
if let Some((_, hir::Node::Local(local))) = cond_parent
|
||||
if let Some((_, hir::Node::LetStmt(local))) = cond_parent
|
||||
&& let hir::PatKind::Path(qpath) | hir::PatKind::TupleStruct(qpath, _, _) =
|
||||
&local.pat.kind
|
||||
&& let hir::QPath::Resolved(None, path) = qpath
|
||||
|
|
@ -1748,7 +1748,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
|
||||
match self.tcx.parent_hir_node(*hir_id) {
|
||||
// foo.clone()
|
||||
hir::Node::Local(hir::Local { init: Some(init), .. }) => {
|
||||
hir::Node::LetStmt(hir::LetStmt { init: Some(init), .. }) => {
|
||||
self.note_type_is_not_clone_inner_expr(init)
|
||||
}
|
||||
// When `expr` is more complex like a tuple
|
||||
|
|
@ -1757,7 +1757,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
kind: hir::PatKind::Tuple(pats, ..),
|
||||
..
|
||||
}) => {
|
||||
let hir::Node::Local(hir::Local { init: Some(init), .. }) =
|
||||
let hir::Node::LetStmt(hir::LetStmt { init: Some(init), .. }) =
|
||||
self.tcx.parent_hir_node(*pat_hir_id)
|
||||
else {
|
||||
return expr;
|
||||
|
|
@ -1791,7 +1791,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
&& let hir::Path { segments: [_], res: crate::Res::Local(binding), .. } =
|
||||
call_expr_path
|
||||
&& let hir::Node::Pat(hir::Pat { hir_id, .. }) = self.tcx.hir_node(*binding)
|
||||
&& let hir::Node::Local(hir::Local { init: Some(init), .. }) =
|
||||
&& let hir::Node::LetStmt(hir::LetStmt { init: Some(init), .. }) =
|
||||
self.tcx.parent_hir_node(*hir_id)
|
||||
&& let Expr {
|
||||
kind: hir::ExprKind::Closure(hir::Closure { body: body_id, .. }),
|
||||
|
|
@ -3147,7 +3147,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
let hir::Node::Pat(pat) = self.tcx.hir_node(hir_id) else {
|
||||
return;
|
||||
};
|
||||
let hir::Node::Local(hir::Local { ty: None, init: Some(init), .. }) =
|
||||
let hir::Node::LetStmt(hir::LetStmt { ty: None, init: Some(init), .. }) =
|
||||
self.tcx.parent_hir_node(pat.hir_id)
|
||||
else {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ impl<'a> DeclOrigin<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// A declaration is an abstraction of [hir::Local] and [hir::LetExpr].
|
||||
/// A declaration is an abstraction of [hir::LetStmt] and [hir::LetExpr].
|
||||
///
|
||||
/// It must have a hir_id, as this is how we connect gather_locals to the check functions.
|
||||
pub(super) struct Declaration<'a> {
|
||||
|
|
@ -41,9 +41,9 @@ pub(super) struct Declaration<'a> {
|
|||
pub origin: DeclOrigin<'a>,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a hir::Local<'a>> for Declaration<'a> {
|
||||
fn from(local: &'a hir::Local<'a>) -> Self {
|
||||
let hir::Local { hir_id, pat, ty, span, init, els, source: _ } = *local;
|
||||
impl<'a> From<&'a hir::LetStmt<'a>> for Declaration<'a> {
|
||||
fn from(local: &'a hir::LetStmt<'a>) -> Self {
|
||||
let hir::LetStmt { hir_id, pat, ty, span, init, els, source: _ } = *local;
|
||||
Declaration { hir_id, pat, ty, span, init, origin: DeclOrigin::LocalDecl { els } }
|
||||
}
|
||||
}
|
||||
|
|
@ -120,7 +120,7 @@ impl<'a, 'tcx> GatherLocalsVisitor<'a, 'tcx> {
|
|||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for GatherLocalsVisitor<'a, 'tcx> {
|
||||
// Add explicitly-declared locals.
|
||||
fn visit_local(&mut self, local: &'tcx hir::Local<'tcx>) {
|
||||
fn visit_local(&mut self, local: &'tcx hir::LetStmt<'tcx>) {
|
||||
self.declare(local.into());
|
||||
intravisit::walk_local(self, local)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2163,7 +2163,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
match (filename, parent_node) {
|
||||
(
|
||||
FileName::Real(_),
|
||||
Node::Local(hir::Local {
|
||||
Node::LetStmt(hir::LetStmt {
|
||||
source: hir::LocalSource::Normal,
|
||||
ty,
|
||||
..
|
||||
|
|
@ -2218,7 +2218,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
impl<'v> Visitor<'v> for LetVisitor {
|
||||
type Result = ControlFlow<Option<&'v hir::Expr<'v>>>;
|
||||
fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) -> Self::Result {
|
||||
if let hir::StmtKind::Let(&hir::Local { pat, init, .. }) = ex.kind
|
||||
if let hir::StmtKind::Let(&hir::LetStmt { pat, init, .. }) = ex.kind
|
||||
&& let Binding(_, _, ident, ..) = pat.kind
|
||||
&& ident.name == self.ident_name
|
||||
{
|
||||
|
|
|
|||
|
|
@ -744,7 +744,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
|
||||
let ident_kind = match binding_parent {
|
||||
hir::Node::Param(_) => "parameter",
|
||||
hir::Node::Local(_) => "variable",
|
||||
hir::Node::LetStmt(_) => "variable",
|
||||
hir::Node::Arm(_) => "binding",
|
||||
|
||||
// Provide diagnostics only if the parent pattern is struct-like,
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
bug!();
|
||||
};
|
||||
for stmt in block.stmts {
|
||||
let hir::StmtKind::Let(hir::Local {
|
||||
let hir::StmtKind::Let(hir::LetStmt {
|
||||
init: Some(init),
|
||||
source: hir::LocalSource::AsyncFn,
|
||||
pat,
|
||||
|
|
|
|||
|
|
@ -351,7 +351,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for WritebackCx<'cx, 'tcx> {
|
|||
intravisit::walk_pat(self, p);
|
||||
}
|
||||
|
||||
fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) {
|
||||
fn visit_local(&mut self, l: &'tcx hir::LetStmt<'tcx>) {
|
||||
intravisit::walk_local(self, l);
|
||||
let var_ty = self.fcx.local_ty(l.span, l.hir_id);
|
||||
let var_ty = self.resolve(var_ty, &l.span);
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ infer_lifetime_param_suggestion_elided = each elided lifetime in input position
|
|||
|
||||
infer_meant_byte_literal = if you meant to write a byte literal, prefix with `b`
|
||||
infer_meant_char_literal = if you meant to write a `char` literal, use single quotes
|
||||
infer_meant_str_literal = if you meant to write a `str` literal, use double quotes
|
||||
infer_meant_str_literal = if you meant to write a string literal, use double quotes
|
||||
infer_mismatched_static_lifetime = incompatible lifetime on type
|
||||
infer_more_targeted = {$has_param_name ->
|
||||
[true] `{$param_name}`
|
||||
|
|
|
|||
|
|
@ -1339,15 +1339,12 @@ pub enum TypeErrorAdditionalDiags {
|
|||
span: Span,
|
||||
code: String,
|
||||
},
|
||||
#[suggestion(
|
||||
infer_meant_str_literal,
|
||||
code = "\"{code}\"",
|
||||
applicability = "machine-applicable"
|
||||
)]
|
||||
#[multipart_suggestion(infer_meant_str_literal, applicability = "machine-applicable")]
|
||||
MeantStrLiteral {
|
||||
#[primary_span]
|
||||
span: Span,
|
||||
code: String,
|
||||
#[suggestion_part(code = "\"")]
|
||||
start: Span,
|
||||
#[suggestion_part(code = "\"")]
|
||||
end: Span,
|
||||
},
|
||||
#[suggestion(
|
||||
infer_consider_specifying_length,
|
||||
|
|
|
|||
|
|
@ -2079,16 +2079,10 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
|||
// If a string was expected and the found expression is a character literal,
|
||||
// perhaps the user meant to write `"s"` to specify a string literal.
|
||||
(ty::Ref(_, r, _), ty::Char) if r.is_str() => {
|
||||
if let Ok(code) = self.tcx.sess().source_map().span_to_snippet(span) {
|
||||
if let Some(code) =
|
||||
code.strip_prefix('\'').and_then(|s| s.strip_suffix('\''))
|
||||
{
|
||||
suggestions.push(TypeErrorAdditionalDiags::MeantStrLiteral {
|
||||
span,
|
||||
code: escape_literal(code),
|
||||
})
|
||||
}
|
||||
}
|
||||
suggestions.push(TypeErrorAdditionalDiags::MeantStrLiteral {
|
||||
start: span.with_hi(span.lo() + BytePos(1)),
|
||||
end: span.with_lo(span.hi() - BytePos(1)),
|
||||
})
|
||||
}
|
||||
// For code `if Some(..) = expr `, the type mismatch may be expected `bool` but found `()`,
|
||||
// we try to suggest to add the missing `let` for `if let Some(..) = expr`
|
||||
|
|
@ -2140,7 +2134,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
|||
// the same span as the error and the type is specified.
|
||||
if let hir::Stmt {
|
||||
kind:
|
||||
hir::StmtKind::Let(hir::Local {
|
||||
hir::StmtKind::Let(hir::LetStmt {
|
||||
init: Some(hir::Expr { span: init_span, .. }),
|
||||
ty: Some(array_ty),
|
||||
..
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use rustc_hir::def::Res;
|
|||
use rustc_hir::def::{CtorOf, DefKind, Namespace};
|
||||
use rustc_hir::def_id::{DefId, LocalDefId};
|
||||
use rustc_hir::intravisit::{self, Visitor};
|
||||
use rustc_hir::{Body, Closure, Expr, ExprKind, FnRetTy, HirId, Local, LocalSource};
|
||||
use rustc_hir::{Body, Closure, Expr, ExprKind, FnRetTy, HirId, LetStmt, LocalSource};
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::infer::unify_key::{
|
||||
ConstVariableOrigin, ConstVariableOriginKind, ConstVariableValue,
|
||||
|
|
@ -1122,7 +1122,7 @@ impl<'a, 'tcx> Visitor<'tcx> for FindInferSourceVisitor<'a, 'tcx> {
|
|||
self.tecx.tcx.hir()
|
||||
}
|
||||
|
||||
fn visit_local(&mut self, local: &'tcx Local<'tcx>) {
|
||||
fn visit_local(&mut self, local: &'tcx LetStmt<'tcx>) {
|
||||
intravisit::walk_local(self, local);
|
||||
|
||||
if let Some(ty) = self.opt_node_type(local.hir_id) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use crate::infer::error_reporting::hir::Path;
|
|||
use core::ops::ControlFlow;
|
||||
use hir::def::CtorKind;
|
||||
use hir::intravisit::{walk_expr, walk_stmt, Visitor};
|
||||
use hir::{Local, QPath};
|
||||
use hir::{LetStmt, QPath};
|
||||
use rustc_data_structures::fx::FxIndexSet;
|
||||
use rustc_errors::{Applicability, Diag};
|
||||
use rustc_hir as hir;
|
||||
|
|
@ -321,7 +321,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
|||
&& let Some(expr) = block.expr
|
||||
&& let hir::ExprKind::Path(QPath::Resolved(_, Path { res, .. })) = expr.kind
|
||||
&& let Res::Local(local) = res
|
||||
&& let Node::Local(Local { init: Some(init), .. }) = self.tcx.parent_hir_node(*local)
|
||||
&& let Node::LetStmt(LetStmt { init: Some(init), .. }) =
|
||||
self.tcx.parent_hir_node(*local)
|
||||
{
|
||||
fn collect_blocks<'hir>(expr: &hir::Expr<'hir>, blocks: &mut Vec<&hir::Block<'hir>>) {
|
||||
match expr.kind {
|
||||
|
|
@ -585,7 +586,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
|||
}
|
||||
|
||||
fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) -> Self::Result {
|
||||
if let hir::StmtKind::Let(hir::Local {
|
||||
if let hir::StmtKind::Let(LetStmt {
|
||||
span,
|
||||
pat: hir::Pat { .. },
|
||||
ty: None,
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ impl<'a> Cursor<'a> {
|
|||
/// If requested position doesn't exist, `EOF_CHAR` is returned.
|
||||
/// However, getting `EOF_CHAR` doesn't always mean actual end of file,
|
||||
/// it should be checked with `is_eof` method.
|
||||
pub(crate) fn first(&self) -> char {
|
||||
pub fn first(&self) -> char {
|
||||
// `.next()` optimizes better than `.nth(0)`
|
||||
self.chars.clone().next().unwrap_or(EOF_CHAR)
|
||||
}
|
||||
|
|
@ -59,6 +59,15 @@ impl<'a> Cursor<'a> {
|
|||
iter.next().unwrap_or(EOF_CHAR)
|
||||
}
|
||||
|
||||
/// Peeks the third symbol from the input stream without consuming it.
|
||||
pub fn third(&self) -> char {
|
||||
// `.next()` optimizes better than `.nth(1)`
|
||||
let mut iter = self.chars.clone();
|
||||
iter.next();
|
||||
iter.next();
|
||||
iter.next().unwrap_or(EOF_CHAR)
|
||||
}
|
||||
|
||||
/// Checks if there is nothing more to consume.
|
||||
pub(crate) fn is_eof(&self) -> bool {
|
||||
self.chars.as_str().is_empty()
|
||||
|
|
|
|||
|
|
@ -937,7 +937,7 @@ impl<'tcx> LateContext<'tcx> {
|
|||
}
|
||||
&& let Some(init) = match parent_node {
|
||||
hir::Node::Expr(expr) => Some(expr),
|
||||
hir::Node::Local(hir::Local { init, .. }) => *init,
|
||||
hir::Node::LetStmt(hir::LetStmt { init, .. }) => *init,
|
||||
_ => None,
|
||||
}
|
||||
{
|
||||
|
|
@ -982,7 +982,7 @@ impl<'tcx> LateContext<'tcx> {
|
|||
}
|
||||
&& let Some(init) = match parent_node {
|
||||
hir::Node::Expr(expr) => Some(expr),
|
||||
hir::Node::Local(hir::Local { init, .. }) => *init,
|
||||
hir::Node::LetStmt(hir::LetStmt { init, .. }) => *init,
|
||||
hir::Node::Item(item) => match item.kind {
|
||||
hir::ItemKind::Const(.., body_id) | hir::ItemKind::Static(.., body_id) => {
|
||||
Some(self.tcx.hir().body(body_id).value)
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ impl<'tcx, T: LateLintPass<'tcx>> hir_visit::Visitor<'tcx> for LateContextAndPas
|
|||
}
|
||||
}
|
||||
|
||||
fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) {
|
||||
fn visit_local(&mut self, l: &'tcx hir::LetStmt<'tcx>) {
|
||||
self.with_lint_attrs(l.hir_id, |cx| {
|
||||
lint_callback!(cx, check_local, l);
|
||||
hir_visit::walk_local(cx, l);
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ const SYNC_GUARD_SYMBOLS: [Symbol; 3] = [
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
|
||||
#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
|
||||
fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) {
|
||||
fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::LetStmt<'_>) {
|
||||
if matches!(local.source, rustc_hir::LocalSource::AsyncFn) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -354,7 +354,7 @@ impl<'tcx> Visitor<'tcx> for LintLevelsBuilder<'_, LintLevelQueryMap<'tcx>> {
|
|||
intravisit::walk_variant(self, v);
|
||||
}
|
||||
|
||||
fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) {
|
||||
fn visit_local(&mut self, l: &'tcx hir::LetStmt<'tcx>) {
|
||||
self.add_id(l.hir_id);
|
||||
intravisit::walk_local(self, l);
|
||||
}
|
||||
|
|
@ -428,7 +428,7 @@ impl<'tcx> Visitor<'tcx> for LintLevelsBuilder<'_, QueryMapExpectationsWrapper<'
|
|||
intravisit::walk_variant(self, v);
|
||||
}
|
||||
|
||||
fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) {
|
||||
fn visit_local(&mut self, l: &'tcx hir::LetStmt<'tcx>) {
|
||||
self.add_id(l.hir_id);
|
||||
intravisit::walk_local(self, l);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ macro_rules! late_lint_methods {
|
|||
fn check_foreign_item(a: &'tcx rustc_hir::ForeignItem<'tcx>);
|
||||
fn check_item(a: &'tcx rustc_hir::Item<'tcx>);
|
||||
fn check_item_post(a: &'tcx rustc_hir::Item<'tcx>);
|
||||
fn check_local(a: &'tcx rustc_hir::Local<'tcx>);
|
||||
fn check_local(a: &'tcx rustc_hir::LetStmt<'tcx>);
|
||||
fn check_block(a: &'tcx rustc_hir::Block<'tcx>);
|
||||
fn check_block_post(a: &'tcx rustc_hir::Block<'tcx>);
|
||||
fn check_stmt(a: &'tcx rustc_hir::Stmt<'tcx>);
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ declare_lint! {
|
|||
declare_lint_pass!(UnitBindings => [UNIT_BINDINGS]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for UnitBindings {
|
||||
fn check_local(&mut self, cx: &crate::LateContext<'tcx>, local: &'tcx hir::Local<'tcx>) {
|
||||
fn check_local(&mut self, cx: &crate::LateContext<'tcx>, local: &'tcx hir::LetStmt<'tcx>) {
|
||||
// Suppress warning if user:
|
||||
// - explicitly ascribes a type to the pattern
|
||||
// - explicitly wrote `let pat = ();`
|
||||
|
|
|
|||
|
|
@ -567,7 +567,7 @@ impl<'hir> Map<'hir> {
|
|||
}
|
||||
// Ignore `return`s on the first iteration
|
||||
Node::Expr(Expr { kind: ExprKind::Loop(..) | ExprKind::Ret(..), .. })
|
||||
| Node::Local(_) => {
|
||||
| Node::LetStmt(_) => {
|
||||
return None;
|
||||
}
|
||||
_ => {}
|
||||
|
|
@ -906,7 +906,7 @@ impl<'hir> Map<'hir> {
|
|||
Node::Lifetime(lifetime) => lifetime.ident.span,
|
||||
Node::GenericParam(param) => param.span,
|
||||
Node::Infer(i) => i.span,
|
||||
Node::Local(local) => local.span,
|
||||
Node::LetStmt(local) => local.span,
|
||||
Node::Crate(item) => item.spans.inner_span,
|
||||
Node::WhereBoundPredicate(pred) => pred.span,
|
||||
Node::ArrayLenInfer(inf) => inf.span,
|
||||
|
|
@ -1163,7 +1163,7 @@ fn hir_id_to_string(map: Map<'_>, id: HirId) -> String {
|
|||
Node::Arm(_) => node_str("arm"),
|
||||
Node::Block(_) => node_str("block"),
|
||||
Node::Infer(_) => node_str("infer"),
|
||||
Node::Local(_) => node_str("local"),
|
||||
Node::LetStmt(_) => node_str("local"),
|
||||
Node::Ctor(ctor) => format!(
|
||||
"{id} (ctor {})",
|
||||
ctor.ctor_def_id().map_or("<missing path>".into(), |def_id| path_str(def_id)),
|
||||
|
|
|
|||
|
|
@ -796,7 +796,7 @@ impl<'tcx> Body<'tcx> {
|
|||
}
|
||||
|
||||
match rvalue {
|
||||
Rvalue::NullaryOp(NullOp::UbCheck(_), _) => {
|
||||
Rvalue::NullaryOp(NullOp::UbChecks, _) => {
|
||||
Some((tcx.sess.opts.debug_assertions as u128, targets))
|
||||
}
|
||||
Rvalue::Use(Operand::Constant(constant)) => {
|
||||
|
|
|
|||
|
|
@ -944,7 +944,7 @@ impl<'tcx> Debug for Rvalue<'tcx> {
|
|||
NullOp::SizeOf => write!(fmt, "SizeOf({t})"),
|
||||
NullOp::AlignOf => write!(fmt, "AlignOf({t})"),
|
||||
NullOp::OffsetOf(fields) => write!(fmt, "OffsetOf({t}, {fields:?})"),
|
||||
NullOp::UbCheck(kind) => write!(fmt, "UbCheck({kind:?})"),
|
||||
NullOp::UbChecks => write!(fmt, "UbChecks()"),
|
||||
}
|
||||
}
|
||||
ThreadLocalRef(did) => ty::tls::with(|tcx| {
|
||||
|
|
|
|||
|
|
@ -1367,16 +1367,9 @@ pub enum NullOp<'tcx> {
|
|||
AlignOf,
|
||||
/// Returns the offset of a field
|
||||
OffsetOf(&'tcx List<(VariantIdx, FieldIdx)>),
|
||||
/// Returns whether we want to check for library UB or language UB at monomorphization time.
|
||||
/// Both kinds of UB evaluate to `true` in codegen, and only library UB evalutes to `true` in
|
||||
/// const-eval/Miri, because the interpreter has its own better checks for language UB.
|
||||
UbCheck(UbKind),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)]
|
||||
pub enum UbKind {
|
||||
LanguageUb,
|
||||
LibraryUb,
|
||||
/// Returns whether we want to check for UB.
|
||||
/// This returns the value of `cfg!(debug_assertions)` at monomorphization time.
|
||||
UbChecks,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ impl<'tcx> Rvalue<'tcx> {
|
|||
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => {
|
||||
tcx.types.usize
|
||||
}
|
||||
Rvalue::NullaryOp(NullOp::UbCheck(_), _) => tcx.types.bool,
|
||||
Rvalue::NullaryOp(NullOp::UbChecks, _) => tcx.types.bool,
|
||||
Rvalue::Aggregate(ref ak, ref ops) => match **ak {
|
||||
AggregateKind::Array(ty) => Ty::new_array(tcx, ty, ops.len() as u64),
|
||||
AggregateKind::Tuple => {
|
||||
|
|
|
|||
|
|
@ -433,7 +433,7 @@ impl<'b, 'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> Gatherer<'b, 'a, 'tcx, F> {
|
|||
| Rvalue::Discriminant(..)
|
||||
| Rvalue::Len(..)
|
||||
| Rvalue::NullaryOp(
|
||||
NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..) | NullOp::UbCheck(_),
|
||||
NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..) | NullOp::UbChecks,
|
||||
_,
|
||||
) => {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,12 +64,9 @@ impl<'tcx> MirPass<'tcx> for ByMoveBody {
|
|||
let mut by_move_body = body.clone();
|
||||
MakeByMoveBody { tcx, by_ref_fields, by_move_coroutine_ty }.visit_body(&mut by_move_body);
|
||||
dump_mir(tcx, false, "coroutine_by_move", &0, &by_move_body, |_, _| Ok(()));
|
||||
by_move_body.source = mir::MirSource {
|
||||
instance: InstanceDef::CoroutineKindShim {
|
||||
coroutine_def_id: coroutine_def_id.to_def_id(),
|
||||
},
|
||||
promoted: None,
|
||||
};
|
||||
by_move_body.source = mir::MirSource::from_instance(InstanceDef::CoroutineKindShim {
|
||||
coroutine_def_id: coroutine_def_id.to_def_id(),
|
||||
});
|
||||
body.coroutine.as_mut().unwrap().by_move_body = Some(by_move_body);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -487,7 +487,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
|
|||
NullOp::OffsetOf(fields) => {
|
||||
layout.offset_of_subfield(&self.ecx, fields.iter()).bytes()
|
||||
}
|
||||
NullOp::UbCheck(_) => return None,
|
||||
NullOp::UbChecks => return None,
|
||||
};
|
||||
let usize_layout = self.ecx.layout_of(self.tcx.types.usize).unwrap();
|
||||
let imm = ImmTy::try_from_uint(val, usize_layout)?;
|
||||
|
|
|
|||
|
|
@ -213,6 +213,7 @@ impl<'tcx> Inliner<'tcx> {
|
|||
MirPhase::Runtime(RuntimePhase::Optimized),
|
||||
self.param_env,
|
||||
&callee_body,
|
||||
&caller_body,
|
||||
)
|
||||
.is_empty()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -639,7 +639,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
|
|||
NullOp::OffsetOf(fields) => {
|
||||
op_layout.offset_of_subfield(self, fields.iter()).bytes()
|
||||
}
|
||||
NullOp::UbCheck(_) => return None,
|
||||
NullOp::UbChecks => return None,
|
||||
};
|
||||
ImmTy::from_scalar(Scalar::from_target_usize(val, self), layout).into()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,30 +20,13 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics {
|
|||
sym::unreachable => {
|
||||
terminator.kind = TerminatorKind::Unreachable;
|
||||
}
|
||||
sym::check_language_ub => {
|
||||
sym::ub_checks => {
|
||||
let target = target.unwrap();
|
||||
block.statements.push(Statement {
|
||||
source_info: terminator.source_info,
|
||||
kind: StatementKind::Assign(Box::new((
|
||||
*destination,
|
||||
Rvalue::NullaryOp(
|
||||
NullOp::UbCheck(UbKind::LanguageUb),
|
||||
tcx.types.bool,
|
||||
),
|
||||
))),
|
||||
});
|
||||
terminator.kind = TerminatorKind::Goto { target };
|
||||
}
|
||||
sym::check_library_ub => {
|
||||
let target = target.unwrap();
|
||||
block.statements.push(Statement {
|
||||
source_info: terminator.source_info,
|
||||
kind: StatementKind::Assign(Box::new((
|
||||
*destination,
|
||||
Rvalue::NullaryOp(
|
||||
NullOp::UbCheck(UbKind::LibraryUb),
|
||||
tcx.types.bool,
|
||||
),
|
||||
Rvalue::NullaryOp(NullOp::UbChecks, tcx.types.bool),
|
||||
))),
|
||||
});
|
||||
terminator.kind = TerminatorKind::Goto { target };
|
||||
|
|
|
|||
|
|
@ -446,7 +446,7 @@ impl<'tcx> Validator<'_, 'tcx> {
|
|||
NullOp::SizeOf => {}
|
||||
NullOp::AlignOf => {}
|
||||
NullOp::OffsetOf(_) => {}
|
||||
NullOp::UbCheck(_) => {}
|
||||
NullOp::UbChecks => {}
|
||||
},
|
||||
|
||||
Rvalue::ShallowInitBox(_, _) => return Err(Unpromotable),
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ impl<Infcx: InferCtxtLike<Interner = I>, I: Interner> TypeFolder<I>
|
|||
// FIXME: We should investigate the perf implications of not uniquifying
|
||||
// `ReErased`. We may be able to short-circuit registering region
|
||||
// obligations if we encounter a `ReErased` on one side, for example.
|
||||
ty::ReStatic | ty::ReErased => match self.canonicalize_mode {
|
||||
ty::ReStatic | ty::ReErased | ty::ReError(_) => match self.canonicalize_mode {
|
||||
CanonicalizeMode::Input => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
|
||||
CanonicalizeMode::Response { .. } => return r,
|
||||
},
|
||||
|
|
@ -277,7 +277,6 @@ impl<Infcx: InferCtxtLike<Interner = I>, I: Interner> TypeFolder<I>
|
|||
}
|
||||
}
|
||||
}
|
||||
ty::ReError(_) => return r,
|
||||
};
|
||||
|
||||
let existing_bound_var = match self.canonicalize_mode {
|
||||
|
|
|
|||
|
|
@ -568,7 +568,7 @@ parse_more_than_one_char = character literal may only contain one codepoint
|
|||
.remove_non = consider removing the non-printing characters
|
||||
.use_double_quotes = if you meant to write a {$is_byte ->
|
||||
[true] byte string
|
||||
*[false] `str`
|
||||
*[false] string
|
||||
} literal, use double quotes
|
||||
|
||||
parse_multiple_skipped_lines = multiple lines skipped by escaped newline
|
||||
|
|
@ -833,6 +833,7 @@ parse_unknown_prefix = prefix `{$prefix}` is unknown
|
|||
.label = unknown prefix
|
||||
.note = prefixed identifiers and literals are reserved since Rust 2021
|
||||
.suggestion_br = use `br` for a raw byte string
|
||||
.suggestion_str = if you meant to write a string literal, use double quotes
|
||||
.suggestion_whitespace = consider inserting whitespace here
|
||||
|
||||
parse_unknown_start_of_token = unknown start of token: {$escaped}
|
||||
|
|
|
|||
|
|
@ -1987,6 +1987,17 @@ pub enum UnknownPrefixSugg {
|
|||
style = "verbose"
|
||||
)]
|
||||
Whitespace(#[primary_span] Span),
|
||||
#[multipart_suggestion(
|
||||
parse_suggestion_str,
|
||||
applicability = "maybe-incorrect",
|
||||
style = "verbose"
|
||||
)]
|
||||
MeantStr {
|
||||
#[suggestion_part(code = "\"")]
|
||||
start: Span,
|
||||
#[suggestion_part(code = "\"")]
|
||||
end: Span,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
|
|
@ -2198,12 +2209,21 @@ pub enum MoreThanOneCharSugg {
|
|||
ch: String,
|
||||
},
|
||||
#[suggestion(parse_use_double_quotes, code = "{sugg}", applicability = "machine-applicable")]
|
||||
Quotes {
|
||||
QuotesFull {
|
||||
#[primary_span]
|
||||
span: Span,
|
||||
is_byte: bool,
|
||||
sugg: String,
|
||||
},
|
||||
#[multipart_suggestion(parse_use_double_quotes, applicability = "machine-applicable")]
|
||||
Quotes {
|
||||
#[suggestion_part(code = "{prefix}\"")]
|
||||
start: Span,
|
||||
#[suggestion_part(code = "\"")]
|
||||
end: Span,
|
||||
is_byte: bool,
|
||||
prefix: &'static str,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subdiagnostic)]
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ pub(crate) fn parse_token_trees<'psess, 'src>(
|
|||
cursor,
|
||||
override_span,
|
||||
nbsp_is_whitespace: false,
|
||||
last_lifetime: None,
|
||||
};
|
||||
let (stream, res, unmatched_delims) =
|
||||
tokentrees::TokenTreesReader::parse_all_token_trees(string_reader);
|
||||
|
|
@ -105,6 +106,10 @@ struct StringReader<'psess, 'src> {
|
|||
/// in this file, it's safe to treat further occurrences of the non-breaking
|
||||
/// space character as whitespace.
|
||||
nbsp_is_whitespace: bool,
|
||||
|
||||
/// Track the `Span` for the leading `'` of the last lifetime. Used for
|
||||
/// diagnostics to detect possible typo where `"` was meant.
|
||||
last_lifetime: Option<Span>,
|
||||
}
|
||||
|
||||
impl<'psess, 'src> StringReader<'psess, 'src> {
|
||||
|
|
@ -130,6 +135,18 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
|
|||
|
||||
debug!("next_token: {:?}({:?})", token.kind, self.str_from(start));
|
||||
|
||||
if let rustc_lexer::TokenKind::Semi
|
||||
| rustc_lexer::TokenKind::LineComment { .. }
|
||||
| rustc_lexer::TokenKind::BlockComment { .. }
|
||||
| rustc_lexer::TokenKind::CloseParen
|
||||
| rustc_lexer::TokenKind::CloseBrace
|
||||
| rustc_lexer::TokenKind::CloseBracket = token.kind
|
||||
{
|
||||
// Heuristic: we assume that it is unlikely we're dealing with an unterminated
|
||||
// string surrounded by single quotes.
|
||||
self.last_lifetime = None;
|
||||
}
|
||||
|
||||
// Now "cook" the token, converting the simple `rustc_lexer::TokenKind` enum into a
|
||||
// rich `rustc_ast::TokenKind`. This turns strings into interned symbols and runs
|
||||
// additional validation.
|
||||
|
|
@ -247,6 +264,7 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
|
|||
// expansion purposes. See #12512 for the gory details of why
|
||||
// this is necessary.
|
||||
let lifetime_name = self.str_from(start);
|
||||
self.last_lifetime = Some(self.mk_sp(start, start + BytePos(1)));
|
||||
if starts_with_number {
|
||||
let span = self.mk_sp(start, self.pos);
|
||||
self.dcx().struct_err("lifetimes cannot start with a number")
|
||||
|
|
@ -395,10 +413,21 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
|
|||
match kind {
|
||||
rustc_lexer::LiteralKind::Char { terminated } => {
|
||||
if !terminated {
|
||||
self.dcx()
|
||||
let mut err = self
|
||||
.dcx()
|
||||
.struct_span_fatal(self.mk_sp(start, end), "unterminated character literal")
|
||||
.with_code(E0762)
|
||||
.emit()
|
||||
.with_code(E0762);
|
||||
if let Some(lt_sp) = self.last_lifetime {
|
||||
err.multipart_suggestion(
|
||||
"if you meant to write a string literal, use double quotes",
|
||||
vec![
|
||||
(lt_sp, "\"".to_string()),
|
||||
(self.mk_sp(start, start + BytePos(1)), "\"".to_string()),
|
||||
],
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
err.emit()
|
||||
}
|
||||
self.cook_unicode(token::Char, Mode::Char, start, end, 1, 1) // ' '
|
||||
}
|
||||
|
|
@ -669,15 +698,33 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
|
|||
let expn_data = prefix_span.ctxt().outer_expn_data();
|
||||
|
||||
if expn_data.edition >= Edition::Edition2021 {
|
||||
let mut silence = false;
|
||||
// In Rust 2021, this is a hard error.
|
||||
let sugg = if prefix == "rb" {
|
||||
Some(errors::UnknownPrefixSugg::UseBr(prefix_span))
|
||||
} else if expn_data.is_root() {
|
||||
Some(errors::UnknownPrefixSugg::Whitespace(prefix_span.shrink_to_hi()))
|
||||
if self.cursor.first() == '\''
|
||||
&& let Some(start) = self.last_lifetime
|
||||
&& self.cursor.third() != '\''
|
||||
{
|
||||
// An "unclosed `char`" error will be emitted already, silence redundant error.
|
||||
silence = true;
|
||||
Some(errors::UnknownPrefixSugg::MeantStr {
|
||||
start,
|
||||
end: self.mk_sp(self.pos, self.pos + BytePos(1)),
|
||||
})
|
||||
} else {
|
||||
Some(errors::UnknownPrefixSugg::Whitespace(prefix_span.shrink_to_hi()))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.dcx().emit_err(errors::UnknownPrefix { span: prefix_span, prefix, sugg });
|
||||
let err = errors::UnknownPrefix { span: prefix_span, prefix, sugg };
|
||||
if silence {
|
||||
self.dcx().create_err(err).delay_as_bug();
|
||||
} else {
|
||||
self.dcx().emit_err(err);
|
||||
}
|
||||
} else {
|
||||
// Before Rust 2021, only emit a lint for migration.
|
||||
self.psess.buffer_lint_with_diagnostic(
|
||||
|
|
|
|||
|
|
@ -95,11 +95,21 @@ pub(crate) fn emit_unescape_error(
|
|||
}
|
||||
escaped.push(c);
|
||||
}
|
||||
let sugg = format!("{prefix}\"{escaped}\"");
|
||||
MoreThanOneCharSugg::Quotes {
|
||||
span: full_lit_span,
|
||||
is_byte: mode == Mode::Byte,
|
||||
sugg,
|
||||
if escaped.len() != lit.len() || full_lit_span.is_empty() {
|
||||
let sugg = format!("{prefix}\"{escaped}\"");
|
||||
MoreThanOneCharSugg::QuotesFull {
|
||||
span: full_lit_span,
|
||||
is_byte: mode == Mode::Byte,
|
||||
sugg,
|
||||
}
|
||||
} else {
|
||||
MoreThanOneCharSugg::Quotes {
|
||||
start: full_lit_span
|
||||
.with_hi(full_lit_span.lo() + BytePos((prefix.len() + 1) as u32)),
|
||||
end: full_lit_span.with_lo(full_lit_span.hi() - BytePos(1)),
|
||||
is_byte: mode == Mode::Byte,
|
||||
prefix,
|
||||
}
|
||||
}
|
||||
});
|
||||
dcx.emit_err(UnescapeError::MoreThanOneChar {
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> {
|
|||
hir_visit::walk_foreign_item(self, i)
|
||||
}
|
||||
|
||||
fn visit_local(&mut self, l: &'v hir::Local<'v>) {
|
||||
fn visit_local(&mut self, l: &'v hir::LetStmt<'v>) {
|
||||
self.record("Local", Id::Node(l.hir_id), l);
|
||||
hir_visit::walk_local(self, l)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -342,7 +342,7 @@ impl<'tcx> IrMaps<'tcx> {
|
|||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> {
|
||||
fn visit_local(&mut self, local: &'tcx hir::Local<'tcx>) {
|
||||
fn visit_local(&mut self, local: &'tcx hir::LetStmt<'tcx>) {
|
||||
self.add_from_pat(local.pat);
|
||||
if local.els.is_some() {
|
||||
self.add_live_node_for_node(local.hir_id, ExprNode(local.span, local.hir_id));
|
||||
|
|
@ -1350,7 +1350,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
|
|||
// Checking for error conditions
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for Liveness<'a, 'tcx> {
|
||||
fn visit_local(&mut self, local: &'tcx hir::Local<'tcx>) {
|
||||
fn visit_local(&mut self, local: &'tcx hir::LetStmt<'tcx>) {
|
||||
self.check_unused_vars_in_pat(local.pat, None, None, |spans, hir_id, ln, var| {
|
||||
if local.init.is_some() {
|
||||
self.warn_about_dead_assign(spans, hir_id, ln, var);
|
||||
|
|
|
|||
|
|
@ -1209,7 +1209,7 @@ impl<'tcx> Visitor<'tcx> for TypePrivacyVisitor<'tcx> {
|
|||
intravisit::walk_pat(self, pattern);
|
||||
}
|
||||
|
||||
fn visit_local(&mut self, local: &'tcx hir::Local<'tcx>) {
|
||||
fn visit_local(&mut self, local: &'tcx hir::LetStmt<'tcx>) {
|
||||
if let Some(init) = local.init {
|
||||
if self.check_expr_pat_type(init.hir_id, init.span) {
|
||||
// Do not report duplicate errors for `let x = y`.
|
||||
|
|
|
|||
|
|
@ -44,6 +44,10 @@ pub struct FieldInfo {
|
|||
pub offset: u64,
|
||||
pub size: u64,
|
||||
pub align: u64,
|
||||
/// Name of the type of this field.
|
||||
/// Present only if the creator thought that this would be important for identifying the field,
|
||||
/// typically because the field name is uninformative.
|
||||
pub type_name: Option<Symbol>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
|
|
@ -192,7 +196,7 @@ impl CodeStats {
|
|||
fields.sort_by_key(|f| (f.offset, f.size));
|
||||
|
||||
for field in fields {
|
||||
let FieldInfo { kind, ref name, offset, size, align } = field;
|
||||
let FieldInfo { kind, ref name, offset, size, align, type_name } = field;
|
||||
|
||||
if offset > min_offset {
|
||||
let pad = offset - min_offset;
|
||||
|
|
@ -201,21 +205,27 @@ impl CodeStats {
|
|||
|
||||
if offset < min_offset {
|
||||
// If this happens it's probably a union.
|
||||
println!(
|
||||
print!(
|
||||
"print-type-size {indent}{kind} `.{name}`: {size} bytes, \
|
||||
offset: {offset} bytes, \
|
||||
alignment: {align} bytes"
|
||||
);
|
||||
} else if info.packed || offset == min_offset {
|
||||
println!("print-type-size {indent}{kind} `.{name}`: {size} bytes");
|
||||
print!("print-type-size {indent}{kind} `.{name}`: {size} bytes");
|
||||
} else {
|
||||
// Include field alignment in output only if it caused padding injection
|
||||
println!(
|
||||
print!(
|
||||
"print-type-size {indent}{kind} `.{name}`: {size} bytes, \
|
||||
alignment: {align} bytes"
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(type_name) = type_name {
|
||||
println!(", type: {type_name}");
|
||||
} else {
|
||||
println!();
|
||||
}
|
||||
|
||||
min_offset = offset + size;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -251,19 +251,13 @@ impl<'tcx> Stable<'tcx> for mir::NullOp<'tcx> {
|
|||
type T = stable_mir::mir::NullOp;
|
||||
fn stable(&self, tables: &mut Tables<'_>) -> Self::T {
|
||||
use rustc_middle::mir::NullOp::*;
|
||||
use rustc_middle::mir::UbKind;
|
||||
match self {
|
||||
SizeOf => stable_mir::mir::NullOp::SizeOf,
|
||||
AlignOf => stable_mir::mir::NullOp::AlignOf,
|
||||
OffsetOf(indices) => stable_mir::mir::NullOp::OffsetOf(
|
||||
indices.iter().map(|idx| idx.stable(tables)).collect(),
|
||||
),
|
||||
UbCheck(UbKind::LanguageUb) => {
|
||||
stable_mir::mir::NullOp::UbCheck(stable_mir::mir::UbKind::LanguageUb)
|
||||
}
|
||||
UbCheck(UbKind::LibraryUb) => {
|
||||
stable_mir::mir::NullOp::UbCheck(stable_mir::mir::UbKind::LibraryUb)
|
||||
}
|
||||
UbChecks => stable_mir::mir::NullOp::UbChecks,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -518,8 +518,6 @@ symbols! {
|
|||
cfi,
|
||||
cfi_encoding,
|
||||
char,
|
||||
check_language_ub,
|
||||
check_library_ub,
|
||||
client,
|
||||
clippy,
|
||||
clobber_abi,
|
||||
|
|
@ -1836,6 +1834,7 @@ symbols! {
|
|||
type_macros,
|
||||
type_name,
|
||||
type_privacy_lints,
|
||||
typed_swap,
|
||||
u128,
|
||||
u128_legacy_const_max,
|
||||
u128_legacy_const_min,
|
||||
|
|
@ -1866,6 +1865,7 @@ symbols! {
|
|||
u8_legacy_fn_max_value,
|
||||
u8_legacy_fn_min_value,
|
||||
u8_legacy_mod,
|
||||
ub_checks,
|
||||
unaligned_volatile_load,
|
||||
unaligned_volatile_store,
|
||||
unboxed_closures,
|
||||
|
|
|
|||
|
|
@ -768,7 +768,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
|||
}
|
||||
// Different to previous arm because one is `&hir::Local` and the other
|
||||
// is `P<hir::Local>`.
|
||||
hir::Node::Local(local) => get_name(err, &local.pat.kind),
|
||||
hir::Node::LetStmt(local) => get_name(err, &local.pat.kind),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -930,7 +930,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
|||
let hir::Node::Pat(pat) = self.tcx.hir_node(hir_id) else {
|
||||
return;
|
||||
};
|
||||
let hir::Node::Local(hir::Local { ty: None, init: Some(init), .. }) =
|
||||
let hir::Node::LetStmt(hir::LetStmt { ty: None, init: Some(init), .. }) =
|
||||
self.tcx.parent_hir_node(pat.hir_id)
|
||||
else {
|
||||
return;
|
||||
|
|
@ -1562,7 +1562,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
|||
if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind
|
||||
&& let Res::Local(hir_id) = path.res
|
||||
&& let hir::Node::Pat(binding) = self.tcx.hir_node(hir_id)
|
||||
&& let hir::Node::Local(local) = self.tcx.parent_hir_node(binding.hir_id)
|
||||
&& let hir::Node::LetStmt(local) = self.tcx.parent_hir_node(binding.hir_id)
|
||||
&& let None = local.ty
|
||||
&& let Some(binding_expr) = local.init
|
||||
{
|
||||
|
|
@ -2966,7 +2966,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
|||
err.downgrade_to_delayed_bug();
|
||||
}
|
||||
match tcx.parent_hir_node(hir_id) {
|
||||
Node::Local(hir::Local { ty: Some(ty), .. }) => {
|
||||
Node::LetStmt(hir::LetStmt { ty: Some(ty), .. }) => {
|
||||
err.span_suggestion_verbose(
|
||||
ty.span.shrink_to_lo(),
|
||||
"consider borrowing here",
|
||||
|
|
@ -2975,7 +2975,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
|||
);
|
||||
err.note("all local variables must have a statically known size");
|
||||
}
|
||||
Node::Local(hir::Local {
|
||||
Node::LetStmt(hir::LetStmt {
|
||||
init: Some(hir::Expr { kind: hir::ExprKind::Index(..), span, .. }),
|
||||
..
|
||||
}) => {
|
||||
|
|
@ -3867,7 +3867,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
|||
if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind
|
||||
&& let hir::Path { res: Res::Local(hir_id), .. } = path
|
||||
&& let hir::Node::Pat(binding) = self.tcx.hir_node(*hir_id)
|
||||
&& let hir::Node::Local(local) = self.tcx.parent_hir_node(binding.hir_id)
|
||||
&& let hir::Node::LetStmt(local) = self.tcx.parent_hir_node(binding.hir_id)
|
||||
&& let Some(binding_expr) = local.init
|
||||
{
|
||||
// If the expression we're calling on is a binding, we want to point at the
|
||||
|
|
@ -4128,7 +4128,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
|||
{
|
||||
let parent = self.tcx.parent_hir_node(binding.hir_id);
|
||||
// We've reached the root of the method call chain...
|
||||
if let hir::Node::Local(local) = parent
|
||||
if let hir::Node::LetStmt(local) = parent
|
||||
&& let Some(binding_expr) = local.init
|
||||
{
|
||||
// ...and it is a binding. Get the binding creation and continue the chain.
|
||||
|
|
|
|||
|
|
@ -1272,7 +1272,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
|||
{
|
||||
let parent = self.tcx.parent_hir_node(binding.hir_id);
|
||||
// We've reached the root of the method call chain...
|
||||
if let hir::Node::Local(local) = parent
|
||||
if let hir::Node::LetStmt(local) = parent
|
||||
&& let Some(binding_expr) = local.init
|
||||
{
|
||||
// ...and it is a binding. Get the binding creation and continue the chain.
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use rustc_middle::ty::layout::{
|
|||
use rustc_middle::ty::print::with_no_trimmed_paths;
|
||||
use rustc_middle::ty::{self, AdtDef, EarlyBinder, GenericArgsRef, Ty, TyCtxt, TypeVisitableExt};
|
||||
use rustc_session::{DataTypeKind, FieldInfo, FieldKind, SizeKind, VariantInfo};
|
||||
use rustc_span::sym;
|
||||
use rustc_span::symbol::Symbol;
|
||||
use rustc_target::abi::*;
|
||||
|
||||
|
|
@ -1007,6 +1008,7 @@ fn variant_info_for_adt<'tcx>(
|
|||
offset: offset.bytes(),
|
||||
size: field_layout.size.bytes(),
|
||||
align: field_layout.align.abi.bytes(),
|
||||
type_name: None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
|
@ -1090,6 +1092,7 @@ fn variant_info_for_coroutine<'tcx>(
|
|||
offset: offset.bytes(),
|
||||
size: field_layout.size.bytes(),
|
||||
align: field_layout.align.abi.bytes(),
|
||||
type_name: None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
|
@ -1104,19 +1107,24 @@ fn variant_info_for_coroutine<'tcx>(
|
|||
.iter()
|
||||
.enumerate()
|
||||
.map(|(field_idx, local)| {
|
||||
let field_name = coroutine.field_names[*local];
|
||||
let field_layout = variant_layout.field(cx, field_idx);
|
||||
let offset = variant_layout.fields.offset(field_idx);
|
||||
// The struct is as large as the last field's end
|
||||
variant_size = variant_size.max(offset + field_layout.size);
|
||||
FieldInfo {
|
||||
kind: FieldKind::CoroutineLocal,
|
||||
name: coroutine.field_names[*local].unwrap_or(Symbol::intern(&format!(
|
||||
name: field_name.unwrap_or(Symbol::intern(&format!(
|
||||
".coroutine_field{}",
|
||||
local.as_usize()
|
||||
))),
|
||||
offset: offset.bytes(),
|
||||
size: field_layout.size.bytes(),
|
||||
align: field_layout.align.abi.bytes(),
|
||||
// Include the type name if there is no field name, or if the name is the
|
||||
// __awaitee placeholder symbol which means a child future being `.await`ed.
|
||||
type_name: (field_name.is_none() || field_name == Some(sym::__awaitee))
|
||||
.then(|| Symbol::intern(&field_layout.ty.to_string())),
|
||||
}
|
||||
})
|
||||
.chain(upvar_fields.iter().copied())
|
||||
|
|
|
|||
|
|
@ -621,7 +621,7 @@ impl Rvalue {
|
|||
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => {
|
||||
Ok(Ty::usize_ty())
|
||||
}
|
||||
Rvalue::NullaryOp(NullOp::UbCheck(_), _) => Ok(Ty::bool_ty()),
|
||||
Rvalue::NullaryOp(NullOp::UbChecks, _) => Ok(Ty::bool_ty()),
|
||||
Rvalue::Aggregate(ak, ops) => match *ak {
|
||||
AggregateKind::Array(ty) => Ty::try_new_array(ty, ops.len() as u64),
|
||||
AggregateKind::Tuple => Ok(Ty::new_tuple(
|
||||
|
|
@ -989,13 +989,7 @@ pub enum NullOp {
|
|||
/// Returns the offset of a field.
|
||||
OffsetOf(Vec<(VariantIdx, FieldIdx)>),
|
||||
/// cfg!(debug_assertions), but at codegen time
|
||||
UbCheck(UbKind),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum UbKind {
|
||||
LanguageUb,
|
||||
LibraryUb,
|
||||
UbChecks,
|
||||
}
|
||||
|
||||
impl Operand {
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ use crate::char::TryFromCharError;
|
|||
use crate::convert::TryFrom;
|
||||
use crate::error::Error;
|
||||
use crate::fmt;
|
||||
use crate::intrinsics::assert_unsafe_precondition;
|
||||
use crate::mem::transmute;
|
||||
use crate::str::FromStr;
|
||||
use crate::ub_checks::assert_unsafe_precondition;
|
||||
|
||||
/// Converts a `u32` to a `char`. See [`char::from_u32`].
|
||||
#[must_use]
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
//! Hints may be compile time or runtime.
|
||||
|
||||
use crate::intrinsics;
|
||||
use crate::ub_checks;
|
||||
|
||||
/// Informs the compiler that the site which is calling this function is not
|
||||
/// reachable, possibly enabling further optimizations.
|
||||
|
|
@ -98,7 +99,7 @@ use crate::intrinsics;
|
|||
#[rustc_const_stable(feature = "const_unreachable_unchecked", since = "1.57.0")]
|
||||
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
|
||||
pub const unsafe fn unreachable_unchecked() -> ! {
|
||||
intrinsics::assert_unsafe_precondition!(
|
||||
ub_checks::assert_unsafe_precondition!(
|
||||
check_language_ub,
|
||||
"hint::unreachable_unchecked must never be reached",
|
||||
() => false
|
||||
|
|
@ -148,7 +149,7 @@ pub const unsafe fn unreachable_unchecked() -> ! {
|
|||
pub const unsafe fn assert_unchecked(cond: bool) {
|
||||
// SAFETY: The caller promised `cond` is true.
|
||||
unsafe {
|
||||
intrinsics::assert_unsafe_precondition!(
|
||||
ub_checks::assert_unsafe_precondition!(
|
||||
check_language_ub,
|
||||
"hint::assert_unchecked must never be called when the condition is false",
|
||||
(cond: bool = cond) => cond,
|
||||
|
|
|
|||
|
|
@ -66,6 +66,8 @@
|
|||
use crate::marker::DiscriminantKind;
|
||||
use crate::marker::Tuple;
|
||||
use crate::mem::align_of;
|
||||
use crate::ptr;
|
||||
use crate::ub_checks;
|
||||
|
||||
pub mod mir;
|
||||
pub mod simd;
|
||||
|
|
@ -1163,14 +1165,6 @@ extern "rust-intrinsic" {
|
|||
/// may lead to unexpected and unstable compilation results. This makes `transmute` **incredibly
|
||||
/// unsafe**. `transmute` should be the absolute last resort.
|
||||
///
|
||||
/// Transmuting pointers *to* integers in a `const` context is [undefined behavior][ub],
|
||||
/// unless the pointer was originally created *from* an integer.
|
||||
/// (That includes this function specifically, integer-to-pointer casts, and helpers like [`invalid`][crate::ptr::dangling],
|
||||
/// but also semantically-equivalent conversions such as punning through `repr(C)` union fields.)
|
||||
/// Any attempt to use the resulting value for integer operations will abort const-evaluation.
|
||||
/// (And even outside `const`, such transmutation is touching on many unspecified aspects of the
|
||||
/// Rust memory model and should be avoided. See below for alternatives.)
|
||||
///
|
||||
/// Because `transmute` is a by-value operation, alignment of the *transmuted values
|
||||
/// themselves* is not a concern. As with any other function, the compiler already ensures
|
||||
/// both `Src` and `Dst` are properly aligned. However, when transmuting values that *point
|
||||
|
|
@ -1181,6 +1175,39 @@ extern "rust-intrinsic" {
|
|||
///
|
||||
/// [ub]: ../../reference/behavior-considered-undefined.html
|
||||
///
|
||||
/// # Transmutation between pointers and integers
|
||||
///
|
||||
/// Special care has to be taken when transmuting between pointers and integers, e.g.
|
||||
/// transmuting between `*const ()` and `usize`.
|
||||
///
|
||||
/// Transmuting *pointers to integers* in a `const` context is [undefined behavior][ub], unless
|
||||
/// the pointer was originally created *from* an integer. (That includes this function
|
||||
/// specifically, integer-to-pointer casts, and helpers like [`dangling`][crate::ptr::dangling],
|
||||
/// but also semantically-equivalent conversions such as punning through `repr(C)` union
|
||||
/// fields.) Any attempt to use the resulting value for integer operations will abort
|
||||
/// const-evaluation. (And even outside `const`, such transmutation is touching on many
|
||||
/// unspecified aspects of the Rust memory model and should be avoided. See below for
|
||||
/// alternatives.)
|
||||
///
|
||||
/// Transmuting *integers to pointers* is a largely unspecified operation. It is likely *not*
|
||||
/// equivalent to an `as` cast. Doing non-zero-sized memory accesses with a pointer constructed
|
||||
/// this way is currently considered undefined behavior.
|
||||
///
|
||||
/// All this also applies when the integer is nested inside an array, tuple, struct, or enum.
|
||||
/// However, `MaybeUninit<usize>` is not considered an integer type for the purpose of this
|
||||
/// section. Transmuting `*const ()` to `MaybeUninit<usize>` is fine---but then calling
|
||||
/// `assume_init()` on that result is considered as completing the pointer-to-integer transmute
|
||||
/// and thus runs into the issues discussed above.
|
||||
///
|
||||
/// In particular, doing a pointer-to-integer-to-pointer roundtrip via `transmute` is *not* a
|
||||
/// lossless process. If you want to round-trip a pointer through an integer in a way that you
|
||||
/// can get back the original pointer, you need to use `as` casts, or replace the integer type
|
||||
/// by `MaybeUninit<$int>` (and never call `assume_init()`). If you are looking for a way to
|
||||
/// store data of arbitrary type, also use `MaybeUninit<T>` (that will also handle uninitialized
|
||||
/// memory due to padding). If you specifically need to store something that is "either an
|
||||
/// integer or a pointer", use `*mut ()`: integers can be converted to pointers and back without
|
||||
/// any loss (via `as` casts or via `transmute`).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// There are a few things that `transmute` is really useful for.
|
||||
|
|
@ -2638,38 +2665,43 @@ pub const fn is_val_statically_known<T: Copy>(_arg: T) -> bool {
|
|||
false
|
||||
}
|
||||
|
||||
/// Returns whether we should check for library UB. This evaluate to the value of `cfg!(debug_assertions)`
|
||||
/// during monomorphization.
|
||||
/// Non-overlapping *typed* swap of a single value.
|
||||
///
|
||||
/// This intrinsic is evaluated after monomorphization, and therefore branching on this value can
|
||||
/// be used to implement debug assertions that are included in the precompiled standard library,
|
||||
/// but can be optimized out by builds that monomorphize the standard library code with debug
|
||||
/// assertions disabled. This intrinsic is primarily used by [`assert_unsafe_precondition`].
|
||||
/// The codegen backends will replace this with a better implementation when
|
||||
/// `T` is a simple type that can be loaded and stored as an immediate.
|
||||
///
|
||||
/// We have separate intrinsics for library UB and language UB because checkers like the const-eval
|
||||
/// interpreter and Miri already implement checks for language UB. Since such checkers do not know
|
||||
/// about library preconditions, checks guarded by this intrinsic let them find more UB.
|
||||
#[rustc_const_unstable(feature = "ub_checks", issue = "none")]
|
||||
#[unstable(feature = "core_intrinsics", issue = "none")]
|
||||
#[inline(always)]
|
||||
#[rustc_intrinsic]
|
||||
pub(crate) const fn check_library_ub() -> bool {
|
||||
cfg!(debug_assertions)
|
||||
/// The stabilized form of this intrinsic is [`crate::mem::swap`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `x` and `y` are readable and writable as `T`, and non-overlapping.
|
||||
#[rustc_nounwind]
|
||||
#[inline]
|
||||
#[cfg_attr(not(bootstrap), rustc_intrinsic)]
|
||||
// This has fallback `const fn` MIR, so shouldn't need stability, see #122652
|
||||
#[rustc_const_unstable(feature = "const_typed_swap", issue = "none")]
|
||||
pub const unsafe fn typed_swap<T>(x: *mut T, y: *mut T) {
|
||||
// SAFETY: The caller provided single non-overlapping items behind
|
||||
// pointers, so swapping them with `count: 1` is fine.
|
||||
unsafe { ptr::swap_nonoverlapping(x, y, 1) };
|
||||
}
|
||||
|
||||
/// Returns whether we should check for language UB. This evaluate to the value of `cfg!(debug_assertions)`
|
||||
/// during monomorphization.
|
||||
/// Returns whether we should perform some UB-checking at runtime. This evaluate to the value of
|
||||
/// `cfg!(debug_assertions)` during monomorphization.
|
||||
///
|
||||
/// Since checks implemented at the source level must come strictly before the operation that
|
||||
/// executes UB, if we enabled language UB checks in const-eval/Miri we would miss out on the
|
||||
/// interpreter's improved diagnostics for the cases that our source-level checks catch.
|
||||
///
|
||||
/// See `check_library_ub` for more information.
|
||||
#[rustc_const_unstable(feature = "ub_checks", issue = "none")]
|
||||
/// This intrinsic is evaluated after monomorphization, which is relevant when mixing crates
|
||||
/// compiled with and without debug_assertions. The common case here is a user program built with
|
||||
/// debug_assertions linked against the distributed sysroot which is built without debug_assertions.
|
||||
/// For code that gets monomorphized in the user crate (i.e., generic functions and functions with
|
||||
/// `#[inline]`), gating assertions on `ub_checks()` rather than `cfg!(debug_assertions)` means that
|
||||
/// assertions are enabled whenever the *user crate* has debug assertions enabled. However if the
|
||||
/// user has debug assertions disabled, the checks will still get optimized out. This intrinsic is
|
||||
/// primarily used by [`ub_checks::assert_unsafe_precondition`].
|
||||
#[rustc_const_unstable(feature = "const_ub_checks", issue = "none")]
|
||||
#[unstable(feature = "core_intrinsics", issue = "none")]
|
||||
#[inline(always)]
|
||||
#[rustc_intrinsic]
|
||||
pub(crate) const fn check_language_ub() -> bool {
|
||||
#[cfg_attr(not(bootstrap), rustc_intrinsic)] // just make it a regular fn in bootstrap
|
||||
pub(crate) const fn ub_checks() -> bool {
|
||||
cfg!(debug_assertions)
|
||||
}
|
||||
|
||||
|
|
@ -2733,132 +2765,6 @@ pub unsafe fn vtable_align(_ptr: *const ()) -> usize {
|
|||
// (`transmute` also falls into this category, but it cannot be wrapped due to the
|
||||
// check that `T` and `U` have the same size.)
|
||||
|
||||
/// Check that the preconditions of an unsafe function are followed. The check is enabled at
|
||||
/// runtime if debug assertions are enabled when the caller is monomorphized. In const-eval/Miri
|
||||
/// checks implemented with this macro for language UB are always ignored.
|
||||
///
|
||||
/// This macro should be called as
|
||||
/// `assert_unsafe_precondition!(check_{library,lang}_ub, "message", (ident: type = expr, ident: type = expr) => check_expr)`
|
||||
/// where each `expr` will be evaluated and passed in as function argument `ident: type`. Then all
|
||||
/// those arguments are passed to a function with the body `check_expr`.
|
||||
/// Pick `check_language_ub` when this is guarding a violation of language UB, i.e., immediate UB
|
||||
/// according to the Rust Abstract Machine. Pick `check_library_ub` when this is guarding a violation
|
||||
/// of a documented library precondition that does not *immediately* lead to language UB.
|
||||
///
|
||||
/// If `check_library_ub` is used but the check is actually guarding language UB, the check will
|
||||
/// slow down const-eval/Miri and we'll get the panic message instead of the interpreter's nice
|
||||
/// diagnostic, but our ability to detect UB is unchanged.
|
||||
/// But if `check_language_ub` is used when the check is actually for library UB, the check is
|
||||
/// omitted in const-eval/Miri and thus if we eventually execute language UB which relies on the
|
||||
/// library UB, the backtrace Miri reports may be far removed from original cause.
|
||||
///
|
||||
/// These checks are behind a condition which is evaluated at codegen time, not expansion time like
|
||||
/// [`debug_assert`]. This means that a standard library built with optimizations and debug
|
||||
/// assertions disabled will have these checks optimized out of its monomorphizations, but if a
|
||||
/// caller of the standard library has debug assertions enabled and monomorphizes an expansion of
|
||||
/// this macro, that monomorphization will contain the check.
|
||||
///
|
||||
/// Since these checks cannot be optimized out in MIR, some care must be taken in both call and
|
||||
/// implementation to mitigate their compile-time overhead. Calls to this macro always expand to
|
||||
/// this structure:
|
||||
/// ```ignore (pseudocode)
|
||||
/// if ::core::intrinsics::check_language_ub() {
|
||||
/// precondition_check(args)
|
||||
/// }
|
||||
/// ```
|
||||
/// where `precondition_check` is monomorphic with the attributes `#[rustc_nounwind]`, `#[inline]` and
|
||||
/// `#[rustc_no_mir_inline]`. This combination of attributes ensures that the actual check logic is
|
||||
/// compiled only once and generates a minimal amount of IR because the check cannot be inlined in
|
||||
/// MIR, but *can* be inlined and fully optimized by a codegen backend.
|
||||
///
|
||||
/// Callers should avoid introducing any other `let` bindings or any code outside this macro in
|
||||
/// order to call it. Since the precompiled standard library is built with full debuginfo and these
|
||||
/// variables cannot be optimized out in MIR, an innocent-looking `let` can produce enough
|
||||
/// debuginfo to have a measurable compile-time impact on debug builds.
|
||||
#[allow_internal_unstable(ub_checks)] // permit this to be called in stably-const fn
|
||||
macro_rules! assert_unsafe_precondition {
|
||||
($kind:ident, $message:expr, ($($name:ident:$ty:ty = $arg:expr),*$(,)?) => $e:expr $(,)?) => {
|
||||
{
|
||||
// This check is inlineable, but not by the MIR inliner.
|
||||
// The reason for this is that the MIR inliner is in an exceptionally bad position
|
||||
// to think about whether or not to inline this. In MIR, this call is gated behind `debug_assertions`,
|
||||
// which will codegen to `false` in release builds. Inlining the check would be wasted work in that case and
|
||||
// would be bad for compile times.
|
||||
//
|
||||
// LLVM on the other hand sees the constant branch, so if it's `false`, it can immediately delete it without
|
||||
// inlining the check. If it's `true`, it can inline it and get significantly better performance.
|
||||
#[rustc_no_mir_inline]
|
||||
#[inline]
|
||||
#[rustc_nounwind]
|
||||
#[rustc_const_unstable(feature = "ub_checks", issue = "none")]
|
||||
const fn precondition_check($($name:$ty),*) {
|
||||
if !$e {
|
||||
::core::panicking::panic_nounwind(
|
||||
concat!("unsafe precondition(s) violated: ", $message)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ::core::intrinsics::$kind() {
|
||||
precondition_check($($arg,)*);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use assert_unsafe_precondition;
|
||||
|
||||
/// Checks whether `ptr` is properly aligned with respect to
|
||||
/// `align_of::<T>()`.
|
||||
///
|
||||
/// In `const` this is approximate and can fail spuriously. It is primarily intended
|
||||
/// for `assert_unsafe_precondition!` with `check_language_ub`, in which case the
|
||||
/// check is anyway not executed in `const`.
|
||||
#[inline]
|
||||
pub(crate) const fn is_aligned_and_not_null(ptr: *const (), align: usize) -> bool {
|
||||
!ptr.is_null() && ptr.is_aligned_to(align)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) const fn is_valid_allocation_size(size: usize, len: usize) -> bool {
|
||||
let max_len = if size == 0 { usize::MAX } else { isize::MAX as usize / size };
|
||||
len <= max_len
|
||||
}
|
||||
|
||||
/// Checks whether the regions of memory starting at `src` and `dst` of size
|
||||
/// `count * size` do *not* overlap.
|
||||
///
|
||||
/// Note that in const-eval this function just returns `true` and therefore must
|
||||
/// only be used with `assert_unsafe_precondition!`, similar to `is_aligned_and_not_null`.
|
||||
#[inline]
|
||||
pub(crate) const fn is_nonoverlapping(
|
||||
src: *const (),
|
||||
dst: *const (),
|
||||
size: usize,
|
||||
count: usize,
|
||||
) -> bool {
|
||||
#[inline]
|
||||
fn runtime(src: *const (), dst: *const (), size: usize, count: usize) -> bool {
|
||||
let src_usize = src.addr();
|
||||
let dst_usize = dst.addr();
|
||||
let Some(size) = size.checked_mul(count) else {
|
||||
crate::panicking::panic_nounwind(
|
||||
"is_nonoverlapping: `size_of::<T>() * count` overflows a usize",
|
||||
)
|
||||
};
|
||||
let diff = src_usize.abs_diff(dst_usize);
|
||||
// If the absolute distance between the ptrs is at least as big as the size of the buffer,
|
||||
// they do not overlap.
|
||||
diff >= size
|
||||
}
|
||||
|
||||
#[inline]
|
||||
const fn comptime(_: *const (), _: *const (), _: usize, _: usize) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
const_eval_select((src, dst, size, count), comptime, runtime)
|
||||
}
|
||||
|
||||
/// Copies `count * size_of::<T>()` bytes from `src` to `dst`. The source
|
||||
/// and destination must *not* overlap.
|
||||
///
|
||||
|
|
@ -2957,7 +2863,7 @@ pub const unsafe fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: us
|
|||
pub fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: usize);
|
||||
}
|
||||
|
||||
assert_unsafe_precondition!(
|
||||
ub_checks::assert_unsafe_precondition!(
|
||||
check_language_ub,
|
||||
"ptr::copy_nonoverlapping requires that both pointer arguments are aligned and non-null \
|
||||
and the specified memory ranges do not overlap",
|
||||
|
|
@ -2968,9 +2874,9 @@ pub const unsafe fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: us
|
|||
align: usize = align_of::<T>(),
|
||||
count: usize = count,
|
||||
) =>
|
||||
is_aligned_and_not_null(src, align)
|
||||
&& is_aligned_and_not_null(dst, align)
|
||||
&& is_nonoverlapping(src, dst, size, count)
|
||||
ub_checks::is_aligned_and_not_null(src, align)
|
||||
&& ub_checks::is_aligned_and_not_null(dst, align)
|
||||
&& ub_checks::is_nonoverlapping(src, dst, size, count)
|
||||
);
|
||||
|
||||
// SAFETY: the safety contract for `copy_nonoverlapping` must be
|
||||
|
|
@ -3061,7 +2967,7 @@ pub const unsafe fn copy<T>(src: *const T, dst: *mut T, count: usize) {
|
|||
|
||||
// SAFETY: the safety contract for `copy` must be upheld by the caller.
|
||||
unsafe {
|
||||
assert_unsafe_precondition!(
|
||||
ub_checks::assert_unsafe_precondition!(
|
||||
check_language_ub,
|
||||
"ptr::copy_nonoverlapping requires that both pointer arguments are aligned and non-null \
|
||||
and the specified memory ranges do not overlap",
|
||||
|
|
@ -3070,8 +2976,8 @@ pub const unsafe fn copy<T>(src: *const T, dst: *mut T, count: usize) {
|
|||
dst: *mut () = dst as *mut (),
|
||||
align: usize = align_of::<T>(),
|
||||
) =>
|
||||
is_aligned_and_not_null(src, align)
|
||||
&& is_aligned_and_not_null(dst, align)
|
||||
ub_checks::is_aligned_and_not_null(src, align)
|
||||
&& ub_checks::is_aligned_and_not_null(dst, align)
|
||||
);
|
||||
copy(src, dst, count)
|
||||
}
|
||||
|
|
@ -3142,13 +3048,13 @@ pub const unsafe fn write_bytes<T>(dst: *mut T, val: u8, count: usize) {
|
|||
|
||||
// SAFETY: the safety contract for `write_bytes` must be upheld by the caller.
|
||||
unsafe {
|
||||
assert_unsafe_precondition!(
|
||||
ub_checks::assert_unsafe_precondition!(
|
||||
check_language_ub,
|
||||
"ptr::write_bytes requires that the destination pointer is aligned and non-null",
|
||||
(
|
||||
addr: *const () = dst as *const (),
|
||||
align: usize = align_of::<T>(),
|
||||
) => is_aligned_and_not_null(addr, align)
|
||||
) => ub_checks::is_aligned_and_not_null(addr, align)
|
||||
);
|
||||
write_bytes(dst, val, count)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,6 +170,8 @@
|
|||
#![feature(const_try)]
|
||||
#![feature(const_type_id)]
|
||||
#![feature(const_type_name)]
|
||||
#![feature(const_typed_swap)]
|
||||
#![feature(const_ub_checks)]
|
||||
#![feature(const_unicode_case_lookup)]
|
||||
#![feature(const_unsafecell_get_mut)]
|
||||
#![feature(const_waker)]
|
||||
|
|
@ -189,7 +191,6 @@
|
|||
#![feature(ptr_metadata)]
|
||||
#![feature(set_ptr_value)]
|
||||
#![feature(slice_ptr_get)]
|
||||
#![feature(slice_split_at_unchecked)]
|
||||
#![feature(split_at_checked)]
|
||||
#![feature(str_internals)]
|
||||
#![feature(str_split_inclusive_remainder)]
|
||||
|
|
@ -366,6 +367,7 @@ pub mod hint;
|
|||
pub mod intrinsics;
|
||||
pub mod mem;
|
||||
pub mod ptr;
|
||||
mod ub_checks;
|
||||
|
||||
/* Core language traits */
|
||||
|
||||
|
|
|
|||
|
|
@ -726,63 +726,9 @@ pub unsafe fn uninitialized<T>() -> T {
|
|||
#[rustc_const_unstable(feature = "const_swap", issue = "83163")]
|
||||
#[rustc_diagnostic_item = "mem_swap"]
|
||||
pub const fn swap<T>(x: &mut T, y: &mut T) {
|
||||
// NOTE(eddyb) SPIR-V's Logical addressing model doesn't allow for arbitrary
|
||||
// reinterpretation of values as (chunkable) byte arrays, and the loop in the
|
||||
// block optimization in `swap_slice` is hard to rewrite back
|
||||
// into the (unoptimized) direct swapping implementation, so we disable it.
|
||||
#[cfg(not(any(target_arch = "spirv")))]
|
||||
{
|
||||
// For types that are larger multiples of their alignment, the simple way
|
||||
// tends to copy the whole thing to stack rather than doing it one part
|
||||
// at a time, so instead treat them as one-element slices and piggy-back
|
||||
// the slice optimizations that will split up the swaps.
|
||||
if const { size_of::<T>() / align_of::<T>() > 2 } {
|
||||
// SAFETY: exclusive references always point to one non-overlapping
|
||||
// element and are non-null and properly aligned.
|
||||
return unsafe { ptr::swap_nonoverlapping(x, y, 1) };
|
||||
}
|
||||
}
|
||||
|
||||
// If a scalar consists of just a small number of alignment units, let
|
||||
// the codegen just swap those pieces directly, as it's likely just a
|
||||
// few instructions and anything else is probably overcomplicated.
|
||||
//
|
||||
// Most importantly, this covers primitives and simd types that tend to
|
||||
// have size=align where doing anything else can be a pessimization.
|
||||
// (This will also be used for ZSTs, though any solution works for them.)
|
||||
swap_simple(x, y);
|
||||
}
|
||||
|
||||
/// Same as [`swap`] semantically, but always uses the simple implementation.
|
||||
///
|
||||
/// Used elsewhere in `mem` and `ptr` at the bottom layer of calls.
|
||||
#[rustc_const_unstable(feature = "const_swap", issue = "83163")]
|
||||
#[inline]
|
||||
pub(crate) const fn swap_simple<T>(x: &mut T, y: &mut T) {
|
||||
// We arrange for this to typically be called with small types,
|
||||
// so this reads-and-writes approach is actually better than using
|
||||
// copy_nonoverlapping as it easily puts things in LLVM registers
|
||||
// directly and doesn't end up inlining allocas.
|
||||
// And LLVM actually optimizes it to 3×memcpy if called with
|
||||
// a type larger than it's willing to keep in a register.
|
||||
// Having typed reads and writes in MIR here is also good as
|
||||
// it lets Miri and CTFE understand them better, including things
|
||||
// like enforcing type validity for them.
|
||||
// Importantly, read+copy_nonoverlapping+write introduces confusing
|
||||
// asymmetry to the behaviour where one value went through read+write
|
||||
// whereas the other was copied over by the intrinsic (see #94371).
|
||||
// Furthermore, using only read+write here benefits limited backends
|
||||
// such as SPIR-V that work on an underlying *typed* view of memory,
|
||||
// and thus have trouble with Rust's untyped memory operations.
|
||||
|
||||
// SAFETY: exclusive references are always valid to read/write,
|
||||
// including being aligned, and nothing here panics so it's drop-safe.
|
||||
unsafe {
|
||||
let a = ptr::read(x);
|
||||
let b = ptr::read(y);
|
||||
ptr::write(x, b);
|
||||
ptr::write(y, a);
|
||||
}
|
||||
// SAFETY: `&mut` guarantees these are typed readable and writable
|
||||
// as well as non-overlapping.
|
||||
unsafe { intrinsics::typed_swap(x, y) }
|
||||
}
|
||||
|
||||
/// Replaces `dest` with the default value of `T`, returning the previous `dest` value.
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use crate::ops::{BitOr, BitOrAssign, Div, DivAssign, Neg, Rem, RemAssign};
|
|||
use crate::panic::{RefUnwindSafe, UnwindSafe};
|
||||
use crate::ptr;
|
||||
use crate::str::FromStr;
|
||||
use crate::ub_checks;
|
||||
|
||||
use super::from_str_radix;
|
||||
use super::{IntErrorKind, ParseIntError};
|
||||
|
|
@ -369,7 +370,7 @@ where
|
|||
None => {
|
||||
// SAFETY: The caller guarantees that `n` is non-zero, so this is unreachable.
|
||||
unsafe {
|
||||
intrinsics::assert_unsafe_precondition!(
|
||||
ub_checks::assert_unsafe_precondition!(
|
||||
check_language_ub,
|
||||
"NonZero::new_unchecked requires the argument to be non-zero",
|
||||
() => false,
|
||||
|
|
@ -409,7 +410,7 @@ where
|
|||
None => {
|
||||
// SAFETY: The caller guarantees that `n` references a value that is non-zero, so this is unreachable.
|
||||
unsafe {
|
||||
intrinsics::assert_unsafe_precondition!(
|
||||
ub_checks::assert_unsafe_precondition!(
|
||||
check_library_ub,
|
||||
"NonZero::from_mut_unchecked requires the argument to dereference as non-zero",
|
||||
() => false,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use crate::intrinsics::{assert_unsafe_precondition, unchecked_add, unchecked_sub};
|
||||
use crate::intrinsics::{unchecked_add, unchecked_sub};
|
||||
use crate::iter::{FusedIterator, TrustedLen};
|
||||
use crate::num::NonZero;
|
||||
use crate::ub_checks;
|
||||
|
||||
/// Like a `Range<usize>`, but with a safety invariant that `start <= end`.
|
||||
///
|
||||
|
|
@ -19,7 +20,7 @@ impl IndexRange {
|
|||
/// - `start <= end`
|
||||
#[inline]
|
||||
pub const unsafe fn new_unchecked(start: usize, end: usize) -> Self {
|
||||
assert_unsafe_precondition!(
|
||||
ub_checks::assert_unsafe_precondition!(
|
||||
check_library_ub,
|
||||
"IndexRange::new_unchecked requires `start <= end`",
|
||||
(start: usize = start, end: usize = end) => start <= end,
|
||||
|
|
|
|||
|
|
@ -161,11 +161,12 @@ impl fmt::Display for PanicInfo<'_> {
|
|||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("panicked at ")?;
|
||||
self.location.fmt(formatter)?;
|
||||
formatter.write_str(":")?;
|
||||
if let Some(message) = self.message {
|
||||
formatter.write_str(":\n")?;
|
||||
formatter.write_str("\n")?;
|
||||
formatter.write_fmt(*message)?;
|
||||
} else if let Some(payload) = self.payload.downcast_ref::<&'static str>() {
|
||||
formatter.write_str(":\n")?;
|
||||
formatter.write_str("\n")?;
|
||||
formatter.write_str(payload)?;
|
||||
}
|
||||
// NOTE: we cannot use downcast_ref::<String>() here
|
||||
|
|
|
|||
|
|
@ -132,11 +132,11 @@ pub const fn panic_nounwind_fmt(fmt: fmt::Arguments<'_>, force_no_backtrace: boo
|
|||
#[rustc_const_unstable(feature = "panic_internals", issue = "none")]
|
||||
#[lang = "panic"] // needed by codegen for panic on overflow and other `Assert` MIR terminators
|
||||
pub const fn panic(expr: &'static str) -> ! {
|
||||
// Use Arguments::new_v1 instead of format_args!("{expr}") to potentially
|
||||
// Use Arguments::new_const instead of format_args!("{expr}") to potentially
|
||||
// reduce size overhead. The format_args! macro uses str's Display trait to
|
||||
// write expr, which calls Formatter::pad, which must accommodate string
|
||||
// truncation and padding (even though none is used here). Using
|
||||
// Arguments::new_v1 may allow the compiler to omit Formatter::pad from the
|
||||
// Arguments::new_const may allow the compiler to omit Formatter::pad from the
|
||||
// output binary, saving up to a few kilobytes.
|
||||
panic_fmt(fmt::Arguments::new_const(&[expr]));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@
|
|||
//! * e.g. [`drop`]ping the [`Future`] [^pin-drop-future]
|
||||
//!
|
||||
//! There are two possible ways to ensure the invariants required for 2. and 3. above (which
|
||||
//! apply to any address-sensitive type, not just self-referrential types) do not get broken.
|
||||
//! apply to any address-sensitive type, not just self-referential types) do not get broken.
|
||||
//!
|
||||
//! 1. Have the value detect when it is moved and update all the pointers that point to itself.
|
||||
//! 2. Guarantee that the address of the value does not change (and that memory is not re-used
|
||||
|
|
@ -170,7 +170,7 @@
|
|||
//! become viral throughout all code that interacts with the object.
|
||||
//!
|
||||
//! The second option is a viable solution to the problem for some use cases, in particular
|
||||
//! for self-referrential types. Under this model, any type that has an address sensitive state
|
||||
//! for self-referential types. Under this model, any type that has an address sensitive state
|
||||
//! would ultimately store its data in something like a [`Box<T>`], carefully manage internal
|
||||
//! access to that data to ensure no *moves* or other invalidation occurs, and finally
|
||||
//! provide a safe interface on top.
|
||||
|
|
@ -186,8 +186,8 @@
|
|||
//!
|
||||
//! Although there were other reason as well, this issue of expensive composition is the key thing
|
||||
//! that drove Rust towards adopting a different model. It is particularly a problem
|
||||
//! when one considers, for exapmle, the implications of composing together the [`Future`]s which
|
||||
//! will eventaully make up an asynchronous task (including address-sensitive `async fn` state
|
||||
//! when one considers, for example, the implications of composing together the [`Future`]s which
|
||||
//! will eventually make up an asynchronous task (including address-sensitive `async fn` state
|
||||
//! machines). It is plausible that there could be many layers of [`Future`]s composed together,
|
||||
//! including multiple layers of `async fn`s handling different parts of a task. It was deemed
|
||||
//! unacceptable to force indirection and allocation for each layer of composition in this case.
|
||||
|
|
@ -359,7 +359,7 @@
|
|||
//! Builtin types that are [`Unpin`] include all of the primitive types, like [`bool`], [`i32`],
|
||||
//! and [`f32`], references (<code>[&]T</code> and <code>[&mut] T</code>), etc., as well as many
|
||||
//! core and standard library types like [`Box<T>`], [`String`], and more.
|
||||
//! These types are marked [`Unpin`] because they do not have an ddress-sensitive state like the
|
||||
//! These types are marked [`Unpin`] because they do not have an address-sensitive state like the
|
||||
//! ones we discussed above. If they did have such a state, those parts of their interface would be
|
||||
//! unsound without being expressed through pinning, and they would then need to not
|
||||
//! implement [`Unpin`].
|
||||
|
|
@ -953,7 +953,7 @@ use crate::{
|
|||
/// discussed below.
|
||||
///
|
||||
/// We call such a [`Pin`]-wrapped pointer a **pinning pointer** (or pinning ref, or pinning
|
||||
/// [`Box`], etc.) because its existince is the thing that is pinning the underlying pointee in
|
||||
/// [`Box`], etc.) because its existence is the thing that is pinning the underlying pointee in
|
||||
/// place: it is the metaphorical "pin" securing the data in place on the pinboard (in memory).
|
||||
///
|
||||
/// It is important to stress that the thing in the [`Pin`] is not the value which we want to pin
|
||||
|
|
@ -962,7 +962,7 @@ use crate::{
|
|||
///
|
||||
/// The most common set of types which require pinning related guarantees for soundness are the
|
||||
/// compiler-generated state machines that implement [`Future`] for the return value of
|
||||
/// `async fn`s. These compiler-generated [`Future`]s may contain self-referrential pointers, one
|
||||
/// `async fn`s. These compiler-generated [`Future`]s may contain self-referential pointers, one
|
||||
/// of the most common use cases for [`Pin`]. More details on this point are provided in the
|
||||
/// [`pin` module] docs, but suffice it to say they require the guarantees provided by pinning to
|
||||
/// be implemented soundly.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::convert::{TryFrom, TryInto};
|
||||
#[cfg(debug_assertions)]
|
||||
use crate::intrinsics::assert_unsafe_precondition;
|
||||
use crate::num::NonZero;
|
||||
#[cfg(debug_assertions)]
|
||||
use crate::ub_checks::assert_unsafe_precondition;
|
||||
use crate::{cmp, fmt, hash, mem, num};
|
||||
|
||||
/// A type storing a `usize` which is a power of two, and thus
|
||||
|
|
|
|||
|
|
@ -818,7 +818,7 @@ impl<T: ?Sized> *const T {
|
|||
intrinsics::const_eval_select((this, origin), comptime, runtime)
|
||||
}
|
||||
|
||||
assert_unsafe_precondition!(
|
||||
ub_checks::assert_unsafe_precondition!(
|
||||
check_language_ub,
|
||||
"ptr::sub_ptr requires `self >= origin`",
|
||||
(
|
||||
|
|
|
|||
|
|
@ -388,10 +388,9 @@
|
|||
use crate::cmp::Ordering;
|
||||
use crate::fmt;
|
||||
use crate::hash;
|
||||
use crate::intrinsics::{
|
||||
self, assert_unsafe_precondition, is_aligned_and_not_null, is_nonoverlapping,
|
||||
};
|
||||
use crate::intrinsics;
|
||||
use crate::marker::FnPtr;
|
||||
use crate::ub_checks;
|
||||
|
||||
use crate::mem::{self, align_of, size_of, MaybeUninit};
|
||||
|
||||
|
|
@ -1019,7 +1018,7 @@ pub const unsafe fn swap_nonoverlapping<T>(x: *mut T, y: *mut T, count: usize) {
|
|||
};
|
||||
}
|
||||
|
||||
assert_unsafe_precondition!(
|
||||
ub_checks::assert_unsafe_precondition!(
|
||||
check_language_ub,
|
||||
"ptr::swap_nonoverlapping requires that both pointer arguments are aligned and non-null \
|
||||
and the specified memory ranges do not overlap",
|
||||
|
|
@ -1030,9 +1029,9 @@ pub const unsafe fn swap_nonoverlapping<T>(x: *mut T, y: *mut T, count: usize) {
|
|||
align: usize = align_of::<T>(),
|
||||
count: usize = count,
|
||||
) =>
|
||||
is_aligned_and_not_null(x, align)
|
||||
&& is_aligned_and_not_null(y, align)
|
||||
&& is_nonoverlapping(x, y, size, count)
|
||||
ub_checks::is_aligned_and_not_null(x, align)
|
||||
&& ub_checks::is_aligned_and_not_null(y, align)
|
||||
&& ub_checks::is_nonoverlapping(x, y, size, count)
|
||||
);
|
||||
|
||||
// Split up the slice into small power-of-two-sized chunks that LLVM is able
|
||||
|
|
@ -1062,11 +1061,26 @@ const unsafe fn swap_nonoverlapping_simple_untyped<T>(x: *mut T, y: *mut T, coun
|
|||
let mut i = 0;
|
||||
while i < count {
|
||||
// SAFETY: By precondition, `i` is in-bounds because it's below `n`
|
||||
let x = unsafe { &mut *x.add(i) };
|
||||
let x = unsafe { x.add(i) };
|
||||
// SAFETY: By precondition, `i` is in-bounds because it's below `n`
|
||||
// and it's distinct from `x` since the ranges are non-overlapping
|
||||
let y = unsafe { &mut *y.add(i) };
|
||||
mem::swap_simple::<MaybeUninit<T>>(x, y);
|
||||
let y = unsafe { y.add(i) };
|
||||
|
||||
// If we end up here, it's because we're using a simple type -- like
|
||||
// a small power-of-two-sized thing -- or a special type with particularly
|
||||
// large alignment, particularly SIMD types.
|
||||
// Thus we're fine just reading-and-writing it, as either it's small
|
||||
// and that works well anyway or it's special and the type's author
|
||||
// presumably wanted things to be done in the larger chunk.
|
||||
|
||||
// SAFETY: we're only ever given pointers that are valid to read/write,
|
||||
// including being aligned, and nothing here panics so it's drop-safe.
|
||||
unsafe {
|
||||
let a: MaybeUninit<T> = read(x);
|
||||
let b: MaybeUninit<T> = read(y);
|
||||
write(x, b);
|
||||
write(y, a);
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
|
@ -1120,13 +1134,13 @@ pub const unsafe fn replace<T>(dst: *mut T, src: T) -> T {
|
|||
// and cannot overlap `src` since `dst` must point to a distinct
|
||||
// allocated object.
|
||||
unsafe {
|
||||
assert_unsafe_precondition!(
|
||||
ub_checks::assert_unsafe_precondition!(
|
||||
check_language_ub,
|
||||
"ptr::replace requires that the pointer argument is aligned and non-null",
|
||||
(
|
||||
addr: *const () = dst as *const (),
|
||||
align: usize = align_of::<T>(),
|
||||
) => is_aligned_and_not_null(addr, align)
|
||||
) => ub_checks::is_aligned_and_not_null(addr, align)
|
||||
);
|
||||
mem::replace(&mut *dst, src)
|
||||
}
|
||||
|
|
@ -1272,13 +1286,13 @@ pub const unsafe fn read<T>(src: *const T) -> T {
|
|||
// SAFETY: the caller must guarantee that `src` is valid for reads.
|
||||
unsafe {
|
||||
#[cfg(debug_assertions)] // Too expensive to always enable (for now?)
|
||||
assert_unsafe_precondition!(
|
||||
ub_checks::assert_unsafe_precondition!(
|
||||
check_language_ub,
|
||||
"ptr::read requires that the pointer argument is aligned and non-null",
|
||||
(
|
||||
addr: *const () = src as *const (),
|
||||
align: usize = align_of::<T>(),
|
||||
) => is_aligned_and_not_null(addr, align)
|
||||
) => ub_checks::is_aligned_and_not_null(addr, align)
|
||||
);
|
||||
crate::intrinsics::read_via_copy(src)
|
||||
}
|
||||
|
|
@ -1481,13 +1495,13 @@ pub const unsafe fn write<T>(dst: *mut T, src: T) {
|
|||
// to `dst` while `src` is owned by this function.
|
||||
unsafe {
|
||||
#[cfg(debug_assertions)] // Too expensive to always enable (for now?)
|
||||
assert_unsafe_precondition!(
|
||||
ub_checks::assert_unsafe_precondition!(
|
||||
check_language_ub,
|
||||
"ptr::write requires that the pointer argument is aligned and non-null",
|
||||
(
|
||||
addr: *mut () = dst as *mut (),
|
||||
align: usize = align_of::<T>(),
|
||||
) => is_aligned_and_not_null(addr, align)
|
||||
) => ub_checks::is_aligned_and_not_null(addr, align)
|
||||
);
|
||||
intrinsics::write_via_move(dst, src)
|
||||
}
|
||||
|
|
@ -1653,13 +1667,13 @@ pub const unsafe fn write_unaligned<T>(dst: *mut T, src: T) {
|
|||
pub unsafe fn read_volatile<T>(src: *const T) -> T {
|
||||
// SAFETY: the caller must uphold the safety contract for `volatile_load`.
|
||||
unsafe {
|
||||
assert_unsafe_precondition!(
|
||||
ub_checks::assert_unsafe_precondition!(
|
||||
check_language_ub,
|
||||
"ptr::read_volatile requires that the pointer argument is aligned and non-null",
|
||||
(
|
||||
addr: *const () = src as *const (),
|
||||
align: usize = align_of::<T>(),
|
||||
) => is_aligned_and_not_null(addr, align)
|
||||
) => ub_checks::is_aligned_and_not_null(addr, align)
|
||||
);
|
||||
intrinsics::volatile_load(src)
|
||||
}
|
||||
|
|
@ -1732,13 +1746,13 @@ pub unsafe fn read_volatile<T>(src: *const T) -> T {
|
|||
pub unsafe fn write_volatile<T>(dst: *mut T, src: T) {
|
||||
// SAFETY: the caller must uphold the safety contract for `volatile_store`.
|
||||
unsafe {
|
||||
assert_unsafe_precondition!(
|
||||
ub_checks::assert_unsafe_precondition!(
|
||||
check_language_ub,
|
||||
"ptr::write_volatile requires that the pointer argument is aligned and non-null",
|
||||
(
|
||||
addr: *mut () = dst as *mut (),
|
||||
align: usize = align_of::<T>(),
|
||||
) => is_aligned_and_not_null(addr, align)
|
||||
) => ub_checks::is_aligned_and_not_null(addr, align)
|
||||
);
|
||||
intrinsics::volatile_store(dst, src);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ use crate::cmp::Ordering;
|
|||
use crate::fmt;
|
||||
use crate::hash;
|
||||
use crate::intrinsics;
|
||||
use crate::intrinsics::assert_unsafe_precondition;
|
||||
use crate::marker::Unsize;
|
||||
use crate::mem::{MaybeUninit, SizedTypeProperties};
|
||||
use crate::num::NonZero;
|
||||
|
|
@ -10,6 +9,7 @@ use crate::ops::{CoerceUnsized, DispatchFromDyn};
|
|||
use crate::ptr;
|
||||
use crate::ptr::Unique;
|
||||
use crate::slice::{self, SliceIndex};
|
||||
use crate::ub_checks::assert_unsafe_precondition;
|
||||
|
||||
/// `*mut T` but non-zero and [covariant].
|
||||
///
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
//! Indexing implementations for `[T]`.
|
||||
|
||||
use crate::intrinsics::assert_unsafe_precondition;
|
||||
use crate::intrinsics::const_eval_select;
|
||||
use crate::intrinsics::unchecked_sub;
|
||||
use crate::ops;
|
||||
use crate::ptr;
|
||||
use crate::ub_checks::assert_unsafe_precondition;
|
||||
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
impl<T, I> ops::Index<I> for [T]
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
use crate::cmp::Ordering::{self, Equal, Greater, Less};
|
||||
use crate::fmt;
|
||||
use crate::hint;
|
||||
use crate::intrinsics::assert_unsafe_precondition;
|
||||
use crate::intrinsics::exact_div;
|
||||
use crate::mem::{self, SizedTypeProperties};
|
||||
use crate::num::NonZero;
|
||||
|
|
@ -17,6 +16,7 @@ use crate::ops::{Bound, OneSidedRange, Range, RangeBounds};
|
|||
use crate::ptr;
|
||||
use crate::simd::{self, Simd};
|
||||
use crate::slice;
|
||||
use crate::ub_checks::assert_unsafe_precondition;
|
||||
|
||||
#[unstable(
|
||||
feature = "slice_internals",
|
||||
|
|
@ -1944,8 +1944,6 @@ impl<T> [T] {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(slice_split_at_unchecked)]
|
||||
///
|
||||
/// let v = [1, 2, 3, 4, 5, 6];
|
||||
///
|
||||
/// unsafe {
|
||||
|
|
@ -1966,7 +1964,7 @@ impl<T> [T] {
|
|||
/// assert_eq!(right, []);
|
||||
/// }
|
||||
/// ```
|
||||
#[unstable(feature = "slice_split_at_unchecked", reason = "new API", issue = "76014")]
|
||||
#[stable(feature = "slice_split_at_unchecked", since = "CURRENT_RUSTC_VERSION")]
|
||||
#[rustc_const_stable(feature = "const_slice_split_at_unchecked", since = "1.77.0")]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
|
|
@ -2008,8 +2006,6 @@ impl<T> [T] {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(slice_split_at_unchecked)]
|
||||
///
|
||||
/// let mut v = [1, 0, 3, 0, 5, 6];
|
||||
/// // scoped to restrict the lifetime of the borrows
|
||||
/// unsafe {
|
||||
|
|
@ -2021,7 +2017,7 @@ impl<T> [T] {
|
|||
/// }
|
||||
/// assert_eq!(v, [1, 2, 3, 4, 5, 6]);
|
||||
/// ```
|
||||
#[unstable(feature = "slice_split_at_unchecked", reason = "new API", issue = "76014")]
|
||||
#[stable(feature = "slice_split_at_unchecked", since = "CURRENT_RUSTC_VERSION")]
|
||||
#[rustc_const_unstable(feature = "const_slice_split_at_mut", issue = "101804")]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
//! Free functions to create `&[T]` and `&mut [T]`.
|
||||
|
||||
use crate::array;
|
||||
use crate::intrinsics::{
|
||||
assert_unsafe_precondition, is_aligned_and_not_null, is_valid_allocation_size,
|
||||
};
|
||||
use crate::mem::{align_of, size_of};
|
||||
use crate::ops::Range;
|
||||
use crate::ptr;
|
||||
use crate::ub_checks;
|
||||
|
||||
/// Forms a slice from a pointer and a length.
|
||||
///
|
||||
|
|
@ -95,7 +93,7 @@ use crate::ptr;
|
|||
pub const unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T] {
|
||||
// SAFETY: the caller must uphold the safety contract for `from_raw_parts`.
|
||||
unsafe {
|
||||
assert_unsafe_precondition!(
|
||||
ub_checks::assert_unsafe_precondition!(
|
||||
check_language_ub,
|
||||
"slice::from_raw_parts requires the pointer to be aligned and non-null, and the total size of the slice not to exceed `isize::MAX`",
|
||||
(
|
||||
|
|
@ -104,8 +102,8 @@ pub const unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T]
|
|||
align: usize = align_of::<T>(),
|
||||
len: usize = len,
|
||||
) =>
|
||||
is_aligned_and_not_null(data, align)
|
||||
&& is_valid_allocation_size(size, len)
|
||||
ub_checks::is_aligned_and_not_null(data, align)
|
||||
&& ub_checks::is_valid_allocation_size(size, len)
|
||||
);
|
||||
&*ptr::slice_from_raw_parts(data, len)
|
||||
}
|
||||
|
|
@ -149,7 +147,7 @@ pub const unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T]
|
|||
pub const unsafe fn from_raw_parts_mut<'a, T>(data: *mut T, len: usize) -> &'a mut [T] {
|
||||
// SAFETY: the caller must uphold the safety contract for `from_raw_parts_mut`.
|
||||
unsafe {
|
||||
assert_unsafe_precondition!(
|
||||
ub_checks::assert_unsafe_precondition!(
|
||||
check_language_ub,
|
||||
"slice::from_raw_parts_mut requires the pointer to be aligned and non-null, and the total size of the slice not to exceed `isize::MAX`",
|
||||
(
|
||||
|
|
@ -158,8 +156,8 @@ pub const unsafe fn from_raw_parts_mut<'a, T>(data: *mut T, len: usize) -> &'a m
|
|||
align: usize = align_of::<T>(),
|
||||
len: usize = len,
|
||||
) =>
|
||||
is_aligned_and_not_null(data, align)
|
||||
&& is_valid_allocation_size(size, len)
|
||||
ub_checks::is_aligned_and_not_null(data, align)
|
||||
&& ub_checks::is_valid_allocation_size(size, len)
|
||||
);
|
||||
&mut *ptr::slice_from_raw_parts_mut(data, len)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
//! Trait implementations for `str`.
|
||||
|
||||
use crate::cmp::Ordering;
|
||||
use crate::intrinsics::assert_unsafe_precondition;
|
||||
use crate::ops;
|
||||
use crate::ptr;
|
||||
use crate::slice::SliceIndex;
|
||||
use crate::ub_checks::assert_unsafe_precondition;
|
||||
|
||||
use super::ParseBoolError;
|
||||
|
||||
|
|
|
|||
158
library/core/src/ub_checks.rs
Normal file
158
library/core/src/ub_checks.rs
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
//! Provides the [`assert_unsafe_precondition`] macro as well as some utility functions that cover
|
||||
//! common preconditions.
|
||||
|
||||
use crate::intrinsics::{self, const_eval_select};
|
||||
|
||||
/// Check that the preconditions of an unsafe function are followed. The check is enabled at
|
||||
/// runtime if debug assertions are enabled when the caller is monomorphized. In const-eval/Miri
|
||||
/// checks implemented with this macro for language UB are always ignored.
|
||||
///
|
||||
/// This macro should be called as
|
||||
/// `assert_unsafe_precondition!(check_{library,lang}_ub, "message", (ident: type = expr, ident: type = expr) => check_expr)`
|
||||
/// where each `expr` will be evaluated and passed in as function argument `ident: type`. Then all
|
||||
/// those arguments are passed to a function with the body `check_expr`.
|
||||
/// Pick `check_language_ub` when this is guarding a violation of language UB, i.e., immediate UB
|
||||
/// according to the Rust Abstract Machine. Pick `check_library_ub` when this is guarding a violation
|
||||
/// of a documented library precondition that does not *immediately* lead to language UB.
|
||||
///
|
||||
/// If `check_library_ub` is used but the check is actually guarding language UB, the check will
|
||||
/// slow down const-eval/Miri and we'll get the panic message instead of the interpreter's nice
|
||||
/// diagnostic, but our ability to detect UB is unchanged.
|
||||
/// But if `check_language_ub` is used when the check is actually for library UB, the check is
|
||||
/// omitted in const-eval/Miri and thus if we eventually execute language UB which relies on the
|
||||
/// library UB, the backtrace Miri reports may be far removed from original cause.
|
||||
///
|
||||
/// These checks are behind a condition which is evaluated at codegen time, not expansion time like
|
||||
/// [`debug_assert`]. This means that a standard library built with optimizations and debug
|
||||
/// assertions disabled will have these checks optimized out of its monomorphizations, but if a
|
||||
/// caller of the standard library has debug assertions enabled and monomorphizes an expansion of
|
||||
/// this macro, that monomorphization will contain the check.
|
||||
///
|
||||
/// Since these checks cannot be optimized out in MIR, some care must be taken in both call and
|
||||
/// implementation to mitigate their compile-time overhead. Calls to this macro always expand to
|
||||
/// this structure:
|
||||
/// ```ignore (pseudocode)
|
||||
/// if ::core::intrinsics::check_language_ub() {
|
||||
/// precondition_check(args)
|
||||
/// }
|
||||
/// ```
|
||||
/// where `precondition_check` is monomorphic with the attributes `#[rustc_nounwind]`, `#[inline]` and
|
||||
/// `#[rustc_no_mir_inline]`. This combination of attributes ensures that the actual check logic is
|
||||
/// compiled only once and generates a minimal amount of IR because the check cannot be inlined in
|
||||
/// MIR, but *can* be inlined and fully optimized by a codegen backend.
|
||||
///
|
||||
/// Callers should avoid introducing any other `let` bindings or any code outside this macro in
|
||||
/// order to call it. Since the precompiled standard library is built with full debuginfo and these
|
||||
/// variables cannot be optimized out in MIR, an innocent-looking `let` can produce enough
|
||||
/// debuginfo to have a measurable compile-time impact on debug builds.
|
||||
#[allow_internal_unstable(const_ub_checks)] // permit this to be called in stably-const fn
|
||||
macro_rules! assert_unsafe_precondition {
|
||||
($kind:ident, $message:expr, ($($name:ident:$ty:ty = $arg:expr),*$(,)?) => $e:expr $(,)?) => {
|
||||
{
|
||||
// This check is inlineable, but not by the MIR inliner.
|
||||
// The reason for this is that the MIR inliner is in an exceptionally bad position
|
||||
// to think about whether or not to inline this. In MIR, this call is gated behind `debug_assertions`,
|
||||
// which will codegen to `false` in release builds. Inlining the check would be wasted work in that case and
|
||||
// would be bad for compile times.
|
||||
//
|
||||
// LLVM on the other hand sees the constant branch, so if it's `false`, it can immediately delete it without
|
||||
// inlining the check. If it's `true`, it can inline it and get significantly better performance.
|
||||
#[rustc_no_mir_inline]
|
||||
#[inline]
|
||||
#[rustc_nounwind]
|
||||
#[rustc_const_unstable(feature = "const_ub_checks", issue = "none")]
|
||||
const fn precondition_check($($name:$ty),*) {
|
||||
if !$e {
|
||||
::core::panicking::panic_nounwind(
|
||||
concat!("unsafe precondition(s) violated: ", $message)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ::core::ub_checks::$kind() {
|
||||
precondition_check($($arg,)*);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use assert_unsafe_precondition;
|
||||
|
||||
/// Checking library UB is always enabled when UB-checking is done
|
||||
/// (and we use a reexport so that there is no unnecessary wrapper function).
|
||||
pub(crate) use intrinsics::ub_checks as check_library_ub;
|
||||
|
||||
/// Determines whether we should check for language UB.
|
||||
///
|
||||
/// The intention is to not do that when running in the interpreter, as that one has its own
|
||||
/// language UB checks which generally produce better errors.
|
||||
#[rustc_const_unstable(feature = "const_ub_checks", issue = "none")]
|
||||
#[inline]
|
||||
pub(crate) const fn check_language_ub() -> bool {
|
||||
#[inline]
|
||||
fn runtime() -> bool {
|
||||
// Disable UB checks in Miri.
|
||||
!cfg!(miri)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
const fn comptime() -> bool {
|
||||
// Always disable UB checks.
|
||||
false
|
||||
}
|
||||
|
||||
// Only used for UB checks so we may const_eval_select.
|
||||
intrinsics::ub_checks() && const_eval_select((), comptime, runtime)
|
||||
}
|
||||
|
||||
/// Checks whether `ptr` is properly aligned with respect to
|
||||
/// `align_of::<T>()`.
|
||||
///
|
||||
/// In `const` this is approximate and can fail spuriously. It is primarily intended
|
||||
/// for `assert_unsafe_precondition!` with `check_language_ub`, in which case the
|
||||
/// check is anyway not executed in `const`.
|
||||
#[inline]
|
||||
pub(crate) const fn is_aligned_and_not_null(ptr: *const (), align: usize) -> bool {
|
||||
!ptr.is_null() && ptr.is_aligned_to(align)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) const fn is_valid_allocation_size(size: usize, len: usize) -> bool {
|
||||
let max_len = if size == 0 { usize::MAX } else { isize::MAX as usize / size };
|
||||
len <= max_len
|
||||
}
|
||||
|
||||
/// Checks whether the regions of memory starting at `src` and `dst` of size
|
||||
/// `count * size` do *not* overlap.
|
||||
///
|
||||
/// Note that in const-eval this function just returns `true` and therefore must
|
||||
/// only be used with `assert_unsafe_precondition!`, similar to `is_aligned_and_not_null`.
|
||||
#[inline]
|
||||
pub(crate) const fn is_nonoverlapping(
|
||||
src: *const (),
|
||||
dst: *const (),
|
||||
size: usize,
|
||||
count: usize,
|
||||
) -> bool {
|
||||
#[inline]
|
||||
fn runtime(src: *const (), dst: *const (), size: usize, count: usize) -> bool {
|
||||
let src_usize = src.addr();
|
||||
let dst_usize = dst.addr();
|
||||
let Some(size) = size.checked_mul(count) else {
|
||||
crate::panicking::panic_nounwind(
|
||||
"is_nonoverlapping: `size_of::<T>() * count` overflows a usize",
|
||||
)
|
||||
};
|
||||
let diff = src_usize.abs_diff(dst_usize);
|
||||
// If the absolute distance between the ptrs is at least as big as the size of the buffer,
|
||||
// they do not overlap.
|
||||
diff >= size
|
||||
}
|
||||
|
||||
#[inline]
|
||||
const fn comptime(_: *const (), _: *const (), _: usize, _: usize) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
// This is just for safety checks so we can const_eval_select.
|
||||
const_eval_select((src, dst, size, count), comptime, runtime)
|
||||
}
|
||||
|
|
@ -141,6 +141,11 @@ jobs:
|
|||
- name: Test (release)
|
||||
run: cargo test --verbose --target=${{ matrix.target }} --release
|
||||
|
||||
- name: Generate docs
|
||||
run: cargo doc --verbose --target=${{ matrix.target }}
|
||||
env:
|
||||
RUSTDOCFLAGS: -Dwarnings
|
||||
|
||||
wasm-tests:
|
||||
name: "wasm (firefox, ${{ matrix.name }})"
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
|||
|
|
@ -177,6 +177,9 @@ name = "std_float"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"core_simd",
|
||||
"test_helpers",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -13,11 +13,12 @@
|
|||
simd_ffi,
|
||||
staged_api,
|
||||
strict_provenance,
|
||||
prelude_import,
|
||||
ptr_metadata
|
||||
)]
|
||||
#![cfg_attr(
|
||||
all(
|
||||
any(target_arch = "aarch64", target_arch = "arm",),
|
||||
any(target_arch = "aarch64", target_arch = "arm64ec", target_arch = "arm",),
|
||||
any(
|
||||
all(target_feature = "v6", not(target_feature = "mclass")),
|
||||
all(target_feature = "mclass", target_feature = "dsp"),
|
||||
|
|
@ -33,12 +34,21 @@
|
|||
any(target_arch = "powerpc", target_arch = "powerpc64"),
|
||||
feature(stdarch_powerpc)
|
||||
)]
|
||||
#![cfg_attr(
|
||||
all(target_arch = "x86_64", target_feature = "avx512f"),
|
||||
feature(stdarch_x86_avx512)
|
||||
)]
|
||||
#![warn(missing_docs, clippy::missing_inline_in_public_items)] // basically all items, really
|
||||
#![deny(unsafe_op_in_unsafe_fn, clippy::undocumented_unsafe_blocks)]
|
||||
#![doc(test(attr(deny(warnings))))]
|
||||
#![allow(internal_features)]
|
||||
#![unstable(feature = "portable_simd", issue = "86656")]
|
||||
//! Portable SIMD module.
|
||||
|
||||
#[prelude_import]
|
||||
#[allow(unused_imports)]
|
||||
use core::prelude::v1::*;
|
||||
|
||||
#[path = "mod.rs"]
|
||||
mod core_simd;
|
||||
pub use self::core_simd::simd;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ mod sealed {
|
|||
fn eq(self, other: Self) -> bool;
|
||||
|
||||
fn to_usize(self) -> usize;
|
||||
fn max_unsigned() -> u64;
|
||||
|
||||
type Unsigned: SimdElement;
|
||||
|
||||
|
|
@ -78,6 +79,11 @@ macro_rules! impl_element {
|
|||
self as usize
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn max_unsigned() -> u64 {
|
||||
<$unsigned>::MAX as u64
|
||||
}
|
||||
|
||||
type Unsigned = $unsigned;
|
||||
|
||||
const TRUE: Self = -1;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ where
|
|||
#[inline]
|
||||
pub fn swizzle_dyn(self, idxs: Simd<u8, N>) -> Self {
|
||||
#![allow(unused_imports, unused_unsafe)]
|
||||
#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
|
||||
#[cfg(all(
|
||||
any(target_arch = "aarch64", target_arch = "arm64ec"),
|
||||
target_endian = "little"
|
||||
))]
|
||||
use core::arch::aarch64::{uint8x8_t, vqtbl1q_u8, vtbl1_u8};
|
||||
#[cfg(all(
|
||||
target_arch = "arm",
|
||||
|
|
@ -37,6 +40,7 @@ where
|
|||
#[cfg(all(
|
||||
any(
|
||||
target_arch = "aarch64",
|
||||
target_arch = "arm64ec",
|
||||
all(target_arch = "arm", target_feature = "v7")
|
||||
),
|
||||
target_feature = "neon",
|
||||
|
|
@ -48,7 +52,7 @@ where
|
|||
#[cfg(target_feature = "simd128")]
|
||||
16 => transize(wasm::i8x16_swizzle, self, idxs),
|
||||
#[cfg(all(
|
||||
target_arch = "aarch64",
|
||||
any(target_arch = "aarch64", target_arch = "arm64ec"),
|
||||
target_feature = "neon",
|
||||
target_endian = "little"
|
||||
))]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::simd::{
|
||||
cmp::SimdPartialOrd,
|
||||
num::SimdUint,
|
||||
ptr::{SimdConstPtr, SimdMutPtr},
|
||||
LaneCount, Mask, MaskElement, SupportedLaneCount, Swizzle,
|
||||
};
|
||||
|
|
@ -262,6 +263,7 @@ where
|
|||
/// # Panics
|
||||
///
|
||||
/// Panics if the slice's length is less than the vector's `Simd::N`.
|
||||
/// Use `load_or_default` for an alternative that does not panic.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
|
|
@ -315,6 +317,143 @@ where
|
|||
unsafe { self.store(slice.as_mut_ptr().cast()) }
|
||||
}
|
||||
|
||||
/// Reads contiguous elements from `slice`. Elements are read so long as they're in-bounds for
|
||||
/// the `slice`. Otherwise, the default value for the element type is returned.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # #![feature(portable_simd)]
|
||||
/// # #[cfg(feature = "as_crate")] use core_simd::simd;
|
||||
/// # #[cfg(not(feature = "as_crate"))] use core::simd;
|
||||
/// # use simd::Simd;
|
||||
/// let vec: Vec<i32> = vec![10, 11];
|
||||
///
|
||||
/// let result = Simd::<i32, 4>::load_or_default(&vec);
|
||||
/// assert_eq!(result, Simd::from_array([10, 11, 0, 0]));
|
||||
/// ```
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn load_or_default(slice: &[T]) -> Self
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
Self::load_or(slice, Default::default())
|
||||
}
|
||||
|
||||
/// Reads contiguous elements from `slice`. Elements are read so long as they're in-bounds for
|
||||
/// the `slice`. Otherwise, the corresponding value from `or` is passed through.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # #![feature(portable_simd)]
|
||||
/// # #[cfg(feature = "as_crate")] use core_simd::simd;
|
||||
/// # #[cfg(not(feature = "as_crate"))] use core::simd;
|
||||
/// # use simd::Simd;
|
||||
/// let vec: Vec<i32> = vec![10, 11];
|
||||
/// let or = Simd::from_array([-5, -4, -3, -2]);
|
||||
///
|
||||
/// let result = Simd::load_or(&vec, or);
|
||||
/// assert_eq!(result, Simd::from_array([10, 11, -3, -2]));
|
||||
/// ```
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn load_or(slice: &[T], or: Self) -> Self {
|
||||
Self::load_select(slice, Mask::splat(true), or)
|
||||
}
|
||||
|
||||
/// Reads contiguous elements from `slice`. Each element is read from memory if its
|
||||
/// corresponding element in `enable` is `true`.
|
||||
///
|
||||
/// When the element is disabled or out of bounds for the slice, that memory location
|
||||
/// is not accessed and the corresponding value from `or` is passed through.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # #![feature(portable_simd)]
|
||||
/// # #[cfg(feature = "as_crate")] use core_simd::simd;
|
||||
/// # #[cfg(not(feature = "as_crate"))] use core::simd;
|
||||
/// # use simd::{Simd, Mask};
|
||||
/// let vec: Vec<i32> = vec![10, 11, 12, 13, 14, 15, 16, 17, 18];
|
||||
/// let enable = Mask::from_array([true, true, false, true]);
|
||||
/// let or = Simd::from_array([-5, -4, -3, -2]);
|
||||
///
|
||||
/// let result = Simd::load_select(&vec, enable, or);
|
||||
/// assert_eq!(result, Simd::from_array([10, 11, -3, 13]));
|
||||
/// ```
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn load_select_or_default(slice: &[T], enable: Mask<<T as SimdElement>::Mask, N>) -> Self
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
Self::load_select(slice, enable, Default::default())
|
||||
}
|
||||
|
||||
/// Reads contiguous elements from `slice`. Each element is read from memory if its
|
||||
/// corresponding element in `enable` is `true`.
|
||||
///
|
||||
/// When the element is disabled or out of bounds for the slice, that memory location
|
||||
/// is not accessed and the corresponding value from `or` is passed through.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # #![feature(portable_simd)]
|
||||
/// # #[cfg(feature = "as_crate")] use core_simd::simd;
|
||||
/// # #[cfg(not(feature = "as_crate"))] use core::simd;
|
||||
/// # use simd::{Simd, Mask};
|
||||
/// let vec: Vec<i32> = vec![10, 11, 12, 13, 14, 15, 16, 17, 18];
|
||||
/// let enable = Mask::from_array([true, true, false, true]);
|
||||
/// let or = Simd::from_array([-5, -4, -3, -2]);
|
||||
///
|
||||
/// let result = Simd::load_select(&vec, enable, or);
|
||||
/// assert_eq!(result, Simd::from_array([10, 11, -3, 13]));
|
||||
/// ```
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn load_select(
|
||||
slice: &[T],
|
||||
mut enable: Mask<<T as SimdElement>::Mask, N>,
|
||||
or: Self,
|
||||
) -> Self {
|
||||
enable &= mask_up_to(slice.len());
|
||||
// SAFETY: We performed the bounds check by updating the mask. &[T] is properly aligned to
|
||||
// the element.
|
||||
unsafe { Self::load_select_ptr(slice.as_ptr(), enable, or) }
|
||||
}
|
||||
|
||||
/// Reads contiguous elements from `slice`. Each element is read from memory if its
|
||||
/// corresponding element in `enable` is `true`.
|
||||
///
|
||||
/// When the element is disabled, that memory location is not accessed and the corresponding
|
||||
/// value from `or` is passed through.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub unsafe fn load_select_unchecked(
|
||||
slice: &[T],
|
||||
enable: Mask<<T as SimdElement>::Mask, N>,
|
||||
or: Self,
|
||||
) -> Self {
|
||||
let ptr = slice.as_ptr();
|
||||
// SAFETY: The safety of reading elements from `slice` is ensured by the caller.
|
||||
unsafe { Self::load_select_ptr(ptr, enable, or) }
|
||||
}
|
||||
|
||||
/// Reads contiguous elements starting at `ptr`. Each element is read from memory if its
|
||||
/// corresponding element in `enable` is `true`.
|
||||
///
|
||||
/// When the element is disabled, that memory location is not accessed and the corresponding
|
||||
/// value from `or` is passed through.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub unsafe fn load_select_ptr(
|
||||
ptr: *const T,
|
||||
enable: Mask<<T as SimdElement>::Mask, N>,
|
||||
or: Self,
|
||||
) -> Self {
|
||||
// SAFETY: The safety of reading elements through `ptr` is ensured by the caller.
|
||||
unsafe { core::intrinsics::simd::simd_masked_load(enable.to_int(), ptr, or) }
|
||||
}
|
||||
|
||||
/// Reads from potentially discontiguous indices in `slice` to construct a SIMD vector.
|
||||
/// If an index is out-of-bounds, the element is instead selected from the `or` vector.
|
||||
///
|
||||
|
|
@ -493,6 +632,77 @@ where
|
|||
unsafe { core::intrinsics::simd::simd_gather(or, source, enable.to_int()) }
|
||||
}
|
||||
|
||||
/// Conditionally write contiguous elements to `slice`. The `enable` mask controls
|
||||
/// which elements are written, as long as they're in-bounds of the `slice`.
|
||||
/// If the element is disabled or out of bounds, no memory access to that location
|
||||
/// is made.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # #![feature(portable_simd)]
|
||||
/// # #[cfg(feature = "as_crate")] use core_simd::simd;
|
||||
/// # #[cfg(not(feature = "as_crate"))] use core::simd;
|
||||
/// # use simd::{Simd, Mask};
|
||||
/// let mut arr = [0i32; 4];
|
||||
/// let write = Simd::from_array([-5, -4, -3, -2]);
|
||||
/// let enable = Mask::from_array([false, true, true, true]);
|
||||
///
|
||||
/// write.store_select(&mut arr[..3], enable);
|
||||
/// assert_eq!(arr, [0, -4, -3, 0]);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn store_select(self, slice: &mut [T], mut enable: Mask<<T as SimdElement>::Mask, N>) {
|
||||
enable &= mask_up_to(slice.len());
|
||||
// SAFETY: We performed the bounds check by updating the mask. &[T] is properly aligned to
|
||||
// the element.
|
||||
unsafe { self.store_select_ptr(slice.as_mut_ptr(), enable) }
|
||||
}
|
||||
|
||||
/// Conditionally write contiguous elements to `slice`. The `enable` mask controls
|
||||
/// which elements are written.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Every enabled element must be in bounds for the `slice`.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # #![feature(portable_simd)]
|
||||
/// # #[cfg(feature = "as_crate")] use core_simd::simd;
|
||||
/// # #[cfg(not(feature = "as_crate"))] use core::simd;
|
||||
/// # use simd::{Simd, Mask};
|
||||
/// let mut arr = [0i32; 4];
|
||||
/// let write = Simd::from_array([-5, -4, -3, -2]);
|
||||
/// let enable = Mask::from_array([false, true, true, true]);
|
||||
///
|
||||
/// unsafe { write.store_select_unchecked(&mut arr, enable) };
|
||||
/// assert_eq!(arr, [0, -4, -3, -2]);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub unsafe fn store_select_unchecked(
|
||||
self,
|
||||
slice: &mut [T],
|
||||
enable: Mask<<T as SimdElement>::Mask, N>,
|
||||
) {
|
||||
let ptr = slice.as_mut_ptr();
|
||||
// SAFETY: The safety of writing elements in `slice` is ensured by the caller.
|
||||
unsafe { self.store_select_ptr(ptr, enable) }
|
||||
}
|
||||
|
||||
/// Conditionally write contiguous elements starting from `ptr`.
|
||||
/// The `enable` mask controls which elements are written.
|
||||
/// When disabled, the memory location corresponding to that element is not accessed.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Memory addresses for element are calculated [`pointer::wrapping_offset`] and
|
||||
/// each enabled element must satisfy the same conditions as [`core::ptr::write`].
|
||||
#[inline]
|
||||
pub unsafe fn store_select_ptr(self, ptr: *mut T, enable: Mask<<T as SimdElement>::Mask, N>) {
|
||||
// SAFETY: The safety of writing elements through `ptr` is ensured by the caller.
|
||||
unsafe { core::intrinsics::simd::simd_masked_store(enable.to_int(), ptr, self) }
|
||||
}
|
||||
|
||||
/// Writes the values in a SIMD vector to potentially discontiguous indices in `slice`.
|
||||
/// If an index is out-of-bounds, the write is suppressed without panicking.
|
||||
/// If two elements in the scattered vector would write to the same index
|
||||
|
|
@ -980,3 +1190,37 @@ where
|
|||
{
|
||||
type Mask = isize;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn lane_indices<const N: usize>() -> Simd<usize, N>
|
||||
where
|
||||
LaneCount<N>: SupportedLaneCount,
|
||||
{
|
||||
let mut index = [0; N];
|
||||
for i in 0..N {
|
||||
index[i] = i;
|
||||
}
|
||||
Simd::from_array(index)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn mask_up_to<M, const N: usize>(len: usize) -> Mask<M, N>
|
||||
where
|
||||
LaneCount<N>: SupportedLaneCount,
|
||||
M: MaskElement,
|
||||
{
|
||||
let index = lane_indices::<N>();
|
||||
let max_value: u64 = M::max_unsigned();
|
||||
macro_rules! case {
|
||||
($ty:ty) => {
|
||||
if N < <$ty>::MAX as usize && max_value as $ty as u64 == max_value {
|
||||
return index.cast().simd_lt(Simd::splat(len.min(N) as $ty)).cast();
|
||||
}
|
||||
};
|
||||
}
|
||||
case!(u8);
|
||||
case!(u16);
|
||||
case!(u32);
|
||||
case!(u64);
|
||||
index.simd_lt(Simd::splat(len)).cast()
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue