Rollup merge of #100239 - RalfJung:const-prop-uninit, r=oli-obk
remove an ineffective check in const_prop Based on https://github.com/rust-lang/rust/pull/100043, only the last two commits are new. ConstProp has a special check when reading from a local that prevents reading uninit locals. However, if that local flows into `force_allocation`, then no check fires and evaluation proceeds. So this check is not really effective at preventing accesses to uninit locals. With https://github.com/rust-lang/rust/pull/100043, `read_immediate` and friends always fail when reading uninit locals, so I don't see why ConstProp would need a separate check. Thus I propose we remove it. This is needed to be able to do https://github.com/rust-lang/rust/pull/100085.
This commit is contained in:
commit
3ea5456366
@ -187,9 +187,6 @@ pub enum LocalValue<Prov: Provenance = AllocId> {
|
|||||||
|
|
||||||
impl<'tcx, Prov: Provenance + 'static> LocalState<'tcx, Prov> {
|
impl<'tcx, Prov: Provenance + 'static> LocalState<'tcx, Prov> {
|
||||||
/// Read the local's value or error if the local is not yet live or not live anymore.
|
/// Read the local's value or error if the local is not yet live or not live anymore.
|
||||||
///
|
|
||||||
/// Note: This may only be invoked from the `Machine::access_local` hook and not from
|
|
||||||
/// anywhere else. You may be invalidating machine invariants if you do!
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn access(&self) -> InterpResult<'tcx, &Operand<Prov>> {
|
pub fn access(&self) -> InterpResult<'tcx, &Operand<Prov>> {
|
||||||
match &self.value {
|
match &self.value {
|
||||||
|
@ -215,23 +215,12 @@ fn binary_ptr_op(
|
|||||||
right: &ImmTy<'tcx, Self::Provenance>,
|
right: &ImmTy<'tcx, Self::Provenance>,
|
||||||
) -> InterpResult<'tcx, (Scalar<Self::Provenance>, bool, Ty<'tcx>)>;
|
) -> InterpResult<'tcx, (Scalar<Self::Provenance>, bool, Ty<'tcx>)>;
|
||||||
|
|
||||||
/// Called to read the specified `local` from the `frame`.
|
|
||||||
/// Since reading a ZST is not actually accessing memory or locals, this is never invoked
|
|
||||||
/// for ZST reads.
|
|
||||||
#[inline]
|
|
||||||
fn access_local<'a>(
|
|
||||||
frame: &'a Frame<'mir, 'tcx, Self::Provenance, Self::FrameExtra>,
|
|
||||||
local: mir::Local,
|
|
||||||
) -> InterpResult<'tcx, &'a Operand<Self::Provenance>>
|
|
||||||
where
|
|
||||||
'tcx: 'mir,
|
|
||||||
{
|
|
||||||
frame.locals[local].access()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called to write the specified `local` from the `frame`.
|
/// Called to write the specified `local` from the `frame`.
|
||||||
/// Since writing a ZST is not actually accessing memory or locals, this is never invoked
|
/// Since writing a ZST is not actually accessing memory or locals, this is never invoked
|
||||||
/// for ZST reads.
|
/// for ZST reads.
|
||||||
|
///
|
||||||
|
/// Due to borrow checker trouble, we indicate the `frame` as an index rather than an `&mut
|
||||||
|
/// Frame`.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn access_local_mut<'a>(
|
fn access_local_mut<'a>(
|
||||||
ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
|
ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
|
||||||
|
@ -444,7 +444,7 @@ pub fn operand_to_simd(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read from a local. Will not actually access the local if reading from a ZST.
|
/// Read from a local.
|
||||||
/// Will not access memory, instead an indirect `Operand` is returned.
|
/// Will not access memory, instead an indirect `Operand` is returned.
|
||||||
///
|
///
|
||||||
/// This is public because it is used by [priroda](https://github.com/oli-obk/priroda) to get an
|
/// This is public because it is used by [priroda](https://github.com/oli-obk/priroda) to get an
|
||||||
@ -456,12 +456,7 @@ pub fn local_to_op(
|
|||||||
layout: Option<TyAndLayout<'tcx>>,
|
layout: Option<TyAndLayout<'tcx>>,
|
||||||
) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> {
|
) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> {
|
||||||
let layout = self.layout_of_local(frame, local, layout)?;
|
let layout = self.layout_of_local(frame, local, layout)?;
|
||||||
let op = if layout.is_zst() {
|
let op = *frame.locals[local].access()?;
|
||||||
// Bypass `access_local` (helps in ConstProp)
|
|
||||||
Operand::Immediate(Immediate::Uninit)
|
|
||||||
} else {
|
|
||||||
*M::access_local(frame, local)?
|
|
||||||
};
|
|
||||||
Ok(OpTy { op, layout, align: Some(layout.align.abi) })
|
Ok(OpTy { op, layout, align: Some(layout.align.abi) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -642,7 +642,7 @@ fn copy_op_no_validate(
|
|||||||
// avoid force_allocation.
|
// avoid force_allocation.
|
||||||
let src = match self.read_immediate_raw(src)? {
|
let src = match self.read_immediate_raw(src)? {
|
||||||
Ok(src_val) => {
|
Ok(src_val) => {
|
||||||
assert!(!src.layout.is_unsized(), "cannot have unsized immediates");
|
assert!(!src.layout.is_unsized(), "cannot copy unsized immediates");
|
||||||
assert!(
|
assert!(
|
||||||
!dest.layout.is_unsized(),
|
!dest.layout.is_unsized(),
|
||||||
"the src is sized, so the dest must also be sized"
|
"the src is sized, so the dest must also be sized"
|
||||||
|
@ -100,6 +100,8 @@ pub fn operand_field(
|
|||||||
// This makes several assumptions about what layouts we will encounter; we match what
|
// This makes several assumptions about what layouts we will encounter; we match what
|
||||||
// codegen does as good as we can (see `extract_field` in `rustc_codegen_ssa/src/mir/operand.rs`).
|
// codegen does as good as we can (see `extract_field` in `rustc_codegen_ssa/src/mir/operand.rs`).
|
||||||
let field_val: Immediate<_> = match (*base, base.layout.abi) {
|
let field_val: Immediate<_> = match (*base, base.layout.abi) {
|
||||||
|
// if the entire value is uninit, then so is the field (can happen in ConstProp)
|
||||||
|
(Immediate::Uninit, _) => Immediate::Uninit,
|
||||||
// the field contains no information, can be left uninit
|
// the field contains no information, can be left uninit
|
||||||
_ if field_layout.is_zst() => Immediate::Uninit,
|
_ if field_layout.is_zst() => Immediate::Uninit,
|
||||||
// the field covers the entire type
|
// the field covers the entire type
|
||||||
@ -124,6 +126,7 @@ pub fn operand_field(
|
|||||||
b_val
|
b_val
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// everything else is a bug
|
||||||
_ => span_bug!(
|
_ => span_bug!(
|
||||||
self.cur_span(),
|
self.cur_span(),
|
||||||
"invalid field access on immediate {}, layout {:#?}",
|
"invalid field access on immediate {}, layout {:#?}",
|
||||||
|
@ -243,24 +243,6 @@ fn binary_ptr_op(
|
|||||||
throw_machine_stop_str!("pointer arithmetic or comparisons aren't supported in ConstProp")
|
throw_machine_stop_str!("pointer arithmetic or comparisons aren't supported in ConstProp")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn access_local<'a>(
|
|
||||||
frame: &'a Frame<'mir, 'tcx, Self::Provenance, Self::FrameExtra>,
|
|
||||||
local: Local,
|
|
||||||
) -> InterpResult<'tcx, &'a interpret::Operand<Self::Provenance>> {
|
|
||||||
let l = &frame.locals[local];
|
|
||||||
|
|
||||||
if matches!(
|
|
||||||
l.value,
|
|
||||||
LocalValue::Live(interpret::Operand::Immediate(interpret::Immediate::Uninit))
|
|
||||||
) {
|
|
||||||
// For us "uninit" means "we don't know its value, might be initiailized or not".
|
|
||||||
// So stop here.
|
|
||||||
throw_machine_stop_str!("tried to access alocal with unknown value ")
|
|
||||||
}
|
|
||||||
|
|
||||||
l.access()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn access_local_mut<'a>(
|
fn access_local_mut<'a>(
|
||||||
ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
|
ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
|
||||||
frame: usize,
|
frame: usize,
|
||||||
@ -431,7 +413,13 @@ fn new(
|
|||||||
|
|
||||||
fn get_const(&self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
|
fn get_const(&self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
|
||||||
let op = match self.ecx.eval_place_to_op(place, None) {
|
let op = match self.ecx.eval_place_to_op(place, None) {
|
||||||
Ok(op) => op,
|
Ok(op) => {
|
||||||
|
if matches!(*op, interpret::Operand::Immediate(Immediate::Uninit)) {
|
||||||
|
// Make sure nobody accidentally uses this value.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
op
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
trace!("get_const failed: {}", e);
|
trace!("get_const failed: {}", e);
|
||||||
return None;
|
return None;
|
||||||
@ -643,6 +631,14 @@ fn const_prop(&mut self, rvalue: &Rvalue<'tcx>, place: Place<'tcx>) -> Option<()
|
|||||||
if rvalue.needs_subst() {
|
if rvalue.needs_subst() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
if !rvalue
|
||||||
|
.ty(&self.ecx.frame().body.local_decls, *self.ecx.tcx)
|
||||||
|
.is_sized(self.ecx.tcx, self.param_env)
|
||||||
|
{
|
||||||
|
// the interpreter doesn't support unsized locals (only unsized arguments),
|
||||||
|
// but rustc does (in a kinda broken way), so we have to skip them here
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
if self.tcx.sess.mir_opt_level() >= 4 {
|
if self.tcx.sess.mir_opt_level() >= 4 {
|
||||||
self.eval_rvalue_with_identities(rvalue, place)
|
self.eval_rvalue_with_identities(rvalue, place)
|
||||||
@ -660,18 +656,20 @@ fn eval_rvalue_with_identities(
|
|||||||
self.use_ecx(|this| match rvalue {
|
self.use_ecx(|this| match rvalue {
|
||||||
Rvalue::BinaryOp(op, box (left, right))
|
Rvalue::BinaryOp(op, box (left, right))
|
||||||
| Rvalue::CheckedBinaryOp(op, box (left, right)) => {
|
| Rvalue::CheckedBinaryOp(op, box (left, right)) => {
|
||||||
let l = this.ecx.eval_operand(left, None);
|
let l = this.ecx.eval_operand(left, None).and_then(|x| this.ecx.read_immediate(&x));
|
||||||
let r = this.ecx.eval_operand(right, None);
|
let r =
|
||||||
|
this.ecx.eval_operand(right, None).and_then(|x| this.ecx.read_immediate(&x));
|
||||||
|
|
||||||
let const_arg = match (l, r) {
|
let const_arg = match (l, r) {
|
||||||
(Ok(ref x), Err(_)) | (Err(_), Ok(ref x)) => this.ecx.read_immediate(x)?,
|
(Ok(x), Err(_)) | (Err(_), Ok(x)) => x, // exactly one side is known
|
||||||
(Err(e), Err(_)) => return Err(e),
|
(Err(e), Err(_)) => return Err(e), // neither side is known
|
||||||
(Ok(_), Ok(_)) => return this.ecx.eval_rvalue_into_place(rvalue, place),
|
(Ok(_), Ok(_)) => return this.ecx.eval_rvalue_into_place(rvalue, place), // both sides are known
|
||||||
};
|
};
|
||||||
|
|
||||||
if !matches!(const_arg.layout.abi, abi::Abi::Scalar(..)) {
|
if !matches!(const_arg.layout.abi, abi::Abi::Scalar(..)) {
|
||||||
// We cannot handle Scalar Pair stuff.
|
// We cannot handle Scalar Pair stuff.
|
||||||
return this.ecx.eval_rvalue_into_place(rvalue, place);
|
// No point in calling `eval_rvalue_into_place`, since only one side is known
|
||||||
|
throw_machine_stop_str!("cannot optimize this")
|
||||||
}
|
}
|
||||||
|
|
||||||
let arg_value = const_arg.to_scalar().to_bits(const_arg.layout.size)?;
|
let arg_value = const_arg.to_scalar().to_bits(const_arg.layout.size)?;
|
||||||
@ -696,7 +694,7 @@ fn eval_rvalue_with_identities(
|
|||||||
this.ecx.write_immediate(*const_arg, &dest)
|
this.ecx.write_immediate(*const_arg, &dest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => this.ecx.eval_rvalue_into_place(rvalue, place),
|
_ => throw_machine_stop_str!("cannot optimize this"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => this.ecx.eval_rvalue_into_place(rvalue, place),
|
_ => this.ecx.eval_rvalue_into_place(rvalue, place),
|
||||||
@ -1073,7 +1071,11 @@ fn visit_terminator(&mut self, terminator: &mut Terminator<'tcx>, location: Loca
|
|||||||
if let Some(ref value) = self.eval_operand(&cond) {
|
if let Some(ref value) = self.eval_operand(&cond) {
|
||||||
trace!("assertion on {:?} should be {:?}", value, expected);
|
trace!("assertion on {:?} should be {:?}", value, expected);
|
||||||
let expected = Scalar::from_bool(*expected);
|
let expected = Scalar::from_bool(*expected);
|
||||||
let value_const = self.ecx.read_scalar(&value).unwrap();
|
let Ok(value_const) = self.ecx.read_scalar(&value) else {
|
||||||
|
// FIXME should be used use_ecx rather than a local match... but we have
|
||||||
|
// quite a few of these read_scalar/read_immediate that need fixing.
|
||||||
|
return
|
||||||
|
};
|
||||||
if expected != value_const {
|
if expected != value_const {
|
||||||
// Poison all places this operand references so that further code
|
// Poison all places this operand references so that further code
|
||||||
// doesn't use the invalid value
|
// doesn't use the invalid value
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
use crate::const_prop::ConstPropMode;
|
use crate::const_prop::ConstPropMode;
|
||||||
use crate::MirLint;
|
use crate::MirLint;
|
||||||
use rustc_const_eval::const_eval::ConstEvalErr;
|
use rustc_const_eval::const_eval::ConstEvalErr;
|
||||||
|
use rustc_const_eval::interpret::Immediate;
|
||||||
use rustc_const_eval::interpret::{
|
use rustc_const_eval::interpret::{
|
||||||
self, InterpCx, InterpResult, LocalState, LocalValue, MemoryKind, OpTy, Scalar, StackPopCleanup,
|
self, InterpCx, InterpResult, LocalState, LocalValue, MemoryKind, OpTy, Scalar, StackPopCleanup,
|
||||||
};
|
};
|
||||||
@ -229,7 +230,13 @@ fn new(
|
|||||||
|
|
||||||
fn get_const(&self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
|
fn get_const(&self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
|
||||||
let op = match self.ecx.eval_place_to_op(place, None) {
|
let op = match self.ecx.eval_place_to_op(place, None) {
|
||||||
Ok(op) => op,
|
Ok(op) => {
|
||||||
|
if matches!(*op, interpret::Operand::Immediate(Immediate::Uninit)) {
|
||||||
|
// Make sure nobody accidentally uses this value.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
op
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
trace!("get_const failed: {}", e);
|
trace!("get_const failed: {}", e);
|
||||||
return None;
|
return None;
|
||||||
@ -515,6 +522,14 @@ fn const_prop(
|
|||||||
if rvalue.needs_subst() {
|
if rvalue.needs_subst() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
if !rvalue
|
||||||
|
.ty(&self.ecx.frame().body.local_decls, *self.ecx.tcx)
|
||||||
|
.is_sized(self.ecx.tcx, self.param_env)
|
||||||
|
{
|
||||||
|
// the interpreter doesn't support unsized locals (only unsized arguments),
|
||||||
|
// but rustc does (in a kinda broken way), so we have to skip them here
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
self.use_ecx(source_info, |this| this.ecx.eval_rvalue_into_place(rvalue, place))
|
self.use_ecx(source_info, |this| this.ecx.eval_rvalue_into_place(rvalue, place))
|
||||||
}
|
}
|
||||||
@ -624,7 +639,11 @@ fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location
|
|||||||
if let Some(ref value) = self.eval_operand(&cond, source_info) {
|
if let Some(ref value) = self.eval_operand(&cond, source_info) {
|
||||||
trace!("assertion on {:?} should be {:?}", value, expected);
|
trace!("assertion on {:?} should be {:?}", value, expected);
|
||||||
let expected = Scalar::from_bool(*expected);
|
let expected = Scalar::from_bool(*expected);
|
||||||
let value_const = self.ecx.read_scalar(&value).unwrap();
|
let Ok(value_const) = self.ecx.read_scalar(&value) else {
|
||||||
|
// FIXME should be used use_ecx rather than a local match... but we have
|
||||||
|
// quite a few of these read_scalar/read_immediate that need fixing.
|
||||||
|
return
|
||||||
|
};
|
||||||
if expected != value_const {
|
if expected != value_const {
|
||||||
enum DbgVal<T> {
|
enum DbgVal<T> {
|
||||||
Val(T),
|
Val(T),
|
||||||
@ -641,9 +660,9 @@ fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|||||||
let mut eval_to_int = |op| {
|
let mut eval_to_int = |op| {
|
||||||
// This can be `None` if the lhs wasn't const propagated and we just
|
// This can be `None` if the lhs wasn't const propagated and we just
|
||||||
// triggered the assert on the value of the rhs.
|
// triggered the assert on the value of the rhs.
|
||||||
self.eval_operand(op, source_info).map_or(DbgVal::Underscore, |op| {
|
self.eval_operand(op, source_info)
|
||||||
DbgVal::Val(self.ecx.read_immediate(&op).unwrap().to_const_int())
|
.and_then(|op| self.ecx.read_immediate(&op).ok())
|
||||||
})
|
.map_or(DbgVal::Underscore, |op| DbgVal::Val(op.to_const_int()))
|
||||||
};
|
};
|
||||||
let msg = match msg {
|
let msg = match msg {
|
||||||
AssertKind::DivisionByZero(op) => {
|
AssertKind::DivisionByZero(op) => {
|
||||||
|
@ -41,7 +41,8 @@
|
|||||||
StorageLive(_4); // scope 2 at $DIR/mutable_variable_unprop_assign.rs:+4:9: +4:10
|
StorageLive(_4); // scope 2 at $DIR/mutable_variable_unprop_assign.rs:+4:9: +4:10
|
||||||
_4 = (_2.1: i32); // scope 2 at $DIR/mutable_variable_unprop_assign.rs:+4:13: +4:16
|
_4 = (_2.1: i32); // scope 2 at $DIR/mutable_variable_unprop_assign.rs:+4:13: +4:16
|
||||||
StorageLive(_5); // scope 3 at $DIR/mutable_variable_unprop_assign.rs:+5:9: +5:10
|
StorageLive(_5); // scope 3 at $DIR/mutable_variable_unprop_assign.rs:+5:9: +5:10
|
||||||
_5 = (_2.0: i32); // scope 3 at $DIR/mutable_variable_unprop_assign.rs:+5:13: +5:16
|
- _5 = (_2.0: i32); // scope 3 at $DIR/mutable_variable_unprop_assign.rs:+5:13: +5:16
|
||||||
|
+ _5 = const 1_i32; // scope 3 at $DIR/mutable_variable_unprop_assign.rs:+5:13: +5:16
|
||||||
nop; // scope 0 at $DIR/mutable_variable_unprop_assign.rs:+0:11: +6:2
|
nop; // scope 0 at $DIR/mutable_variable_unprop_assign.rs:+0:11: +6:2
|
||||||
StorageDead(_5); // scope 3 at $DIR/mutable_variable_unprop_assign.rs:+6:1: +6:2
|
StorageDead(_5); // scope 3 at $DIR/mutable_variable_unprop_assign.rs:+6:1: +6:2
|
||||||
StorageDead(_4); // scope 2 at $DIR/mutable_variable_unprop_assign.rs:+6:1: +6:2
|
StorageDead(_4); // scope 2 at $DIR/mutable_variable_unprop_assign.rs:+6:1: +6:2
|
||||||
|
Loading…
Reference in New Issue
Block a user