1
Fork 0

Add initial AST and MIR support for unwinding from inline assembly

This commit is contained in:
Amanieu d'Antras 2021-08-30 01:23:33 +01:00 committed by cynecx
parent 532d2b14c0
commit 940b2eabad
39 changed files with 355 additions and 212 deletions

View file

@ -4,7 +4,9 @@ use rustc_middle::ty::TyCtxt;
use std::ops::RangeInclusive;
use super::visitor::{ResultsVisitable, ResultsVisitor};
use super::{Analysis, Effect, EffectIndex, GenKillAnalysis, GenKillSet, SwitchIntTarget};
use super::{
Analysis, CallReturnPlaces, Effect, EffectIndex, GenKillAnalysis, GenKillSet, SwitchIntTarget,
};
pub trait Direction {
fn is_forward() -> bool;
@ -235,14 +237,26 @@ impl Direction for Backward {
// Apply terminator-specific edge effects.
//
// FIXME(ecstaticmorse): Avoid cloning the exit state unconditionally.
mir::TerminatorKind::Call {
destination: Some((return_place, dest)),
ref func,
ref args,
..
mir::TerminatorKind::Call { destination: Some((return_place, dest)), .. }
if dest == bb =>
{
let mut tmp = exit_state.clone();
analysis.apply_call_return_effect(
&mut tmp,
pred,
CallReturnPlaces::Call(return_place),
);
propagate(pred, &tmp);
}
mir::TerminatorKind::InlineAsm {
destination: Some(dest), ref operands, ..
} if dest == bb => {
let mut tmp = exit_state.clone();
analysis.apply_call_return_effect(&mut tmp, pred, func, args, return_place);
analysis.apply_call_return_effect(
&mut tmp,
pred,
CallReturnPlaces::InlineAsm(operands),
);
propagate(pred, &tmp);
}
@ -258,6 +272,7 @@ impl Direction for Backward {
| mir::TerminatorKind::Drop { unwind: Some(unwind), .. }
| mir::TerminatorKind::DropAndReplace { unwind: Some(unwind), .. }
| mir::TerminatorKind::FalseUnwind { unwind: Some(unwind), .. }
| mir::TerminatorKind::InlineAsm { cleanup: Some(unwind), .. }
if unwind == bb =>
{
if dead_unwinds.map_or(true, |dead| !dead.contains(bb)) {
@ -467,7 +482,7 @@ impl Direction for Forward {
propagate(target, exit_state);
}
Call { cleanup, destination, ref func, ref args, from_hir_call: _, fn_span: _ } => {
Call { cleanup, destination, func: _, args: _, from_hir_call: _, fn_span: _ } => {
if let Some(unwind) = cleanup {
if dead_unwinds.map_or(true, |dead| !dead.contains(bb)) {
propagate(unwind, exit_state);
@ -477,13 +492,37 @@ impl Direction for Forward {
if let Some((dest_place, target)) = destination {
// N.B.: This must be done *last*, otherwise the unwind path will see the call
// return effect.
analysis.apply_call_return_effect(exit_state, bb, func, args, dest_place);
analysis.apply_call_return_effect(
exit_state,
bb,
CallReturnPlaces::Call(dest_place),
);
propagate(target, exit_state);
}
}
InlineAsm { template: _, operands: _, options: _, line_spans: _, destination } => {
InlineAsm {
template: _,
ref operands,
options: _,
line_spans: _,
destination,
cleanup,
} => {
if let Some(unwind) = cleanup {
if dead_unwinds.map_or(true, |dead| !dead.contains(bb)) {
propagate(unwind, exit_state);
}
}
if let Some(target) = destination {
// N.B.: This must be done *last*, otherwise the unwind path will see the call
// return effect.
analysis.apply_call_return_effect(
exit_state,
bb,
CallReturnPlaces::InlineAsm(operands),
);
propagate(target, exit_state);
}
}

View file

@ -10,7 +10,7 @@ use rustc_middle::mir::graphviz_safe_def_name;
use rustc_middle::mir::{self, BasicBlock, Body, Location};
use super::fmt::{DebugDiffWithAdapter, DebugWithAdapter, DebugWithContext};
use super::{Analysis, Direction, Results, ResultsRefCursor, ResultsVisitor};
use super::{Analysis, CallReturnPlaces, Direction, Results, ResultsRefCursor, ResultsVisitor};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OutputStyle {
@ -231,16 +231,15 @@ where
// for the basic block itself. That way, we could display terminator-specific effects for
// backward dataflow analyses as well as effects for `SwitchInt` terminators.
match terminator.kind {
mir::TerminatorKind::Call {
destination: Some((return_place, _)),
ref func,
ref args,
..
} => {
mir::TerminatorKind::Call { destination: Some((return_place, _)), .. } => {
self.write_row(w, "", "(on successful return)", |this, w, fmt| {
let state_on_unwind = this.results.get().clone();
this.results.apply_custom_effect(|analysis, state| {
analysis.apply_call_return_effect(state, block, func, args, return_place);
analysis.apply_call_return_effect(
state,
block,
CallReturnPlaces::Call(return_place),
);
});
write!(
@ -278,6 +277,31 @@ where
})?;
}
mir::TerminatorKind::InlineAsm { destination: Some(_), ref operands, .. } => {
self.write_row(w, "", "(on successful return)", |this, w, fmt| {
let state_on_unwind = this.results.get().clone();
this.results.apply_custom_effect(|analysis, state| {
analysis.apply_call_return_effect(
state,
block,
CallReturnPlaces::InlineAsm(operands),
);
});
write!(
w,
r#"<td balign="left" colspan="{colspan}" {fmt} align="left">{diff}</td>"#,
colspan = this.style.num_state_columns(),
fmt = fmt,
diff = diff_pretty(
this.results.get(),
&state_on_unwind,
this.results.analysis()
),
)
})?;
}
_ => {}
};

View file

@ -160,9 +160,7 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
&self,
state: &mut Self::Domain,
block: BasicBlock,
func: &mir::Operand<'tcx>,
args: &[mir::Operand<'tcx>],
return_place: mir::Place<'tcx>,
return_places: CallReturnPlaces<'_, 'tcx>,
);
/// Updates the current dataflow state with the effect of resuming from a `Yield` terminator.
@ -276,9 +274,7 @@ pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
&self,
trans: &mut impl GenKill<Self::Idx>,
block: BasicBlock,
func: &mir::Operand<'tcx>,
args: &[mir::Operand<'tcx>],
return_place: mir::Place<'tcx>,
return_places: CallReturnPlaces<'_, 'tcx>,
);
/// See `Analysis::apply_yield_resume_effect`.
@ -347,11 +343,9 @@ where
&self,
state: &mut A::Domain,
block: BasicBlock,
func: &mir::Operand<'tcx>,
args: &[mir::Operand<'tcx>],
return_place: mir::Place<'tcx>,
return_places: CallReturnPlaces<'_, 'tcx>,
) {
self.call_return_effect(state, block, func, args, return_place);
self.call_return_effect(state, block, return_places);
}
fn apply_yield_resume_effect(
@ -542,5 +536,29 @@ pub trait SwitchIntEdgeEffects<D> {
fn apply(&mut self, apply_edge_effect: impl FnMut(&mut D, SwitchIntTarget));
}
/// List of places that are written to after a successful (non-unwind) return
/// from a `Call` or `InlineAsm`.
pub enum CallReturnPlaces<'a, 'tcx> {
Call(mir::Place<'tcx>),
InlineAsm(&'a [mir::InlineAsmOperand<'tcx>]),
}
impl<'tcx> CallReturnPlaces<'_, 'tcx> {
pub fn for_each(&self, mut f: impl FnMut(mir::Place<'tcx>)) {
match *self {
Self::Call(place) => f(place),
Self::InlineAsm(operands) => {
for op in operands {
match *op {
mir::InlineAsmOperand::Out { place: Some(place), .. }
| mir::InlineAsmOperand::InOut { out_place: Some(place), .. } => f(place),
_ => {}
}
}
}
}
}
}
#[cfg(test)]
mod tests;

View file

@ -220,9 +220,7 @@ impl<D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
&self,
_state: &mut Self::Domain,
_block: BasicBlock,
_func: &mir::Operand<'tcx>,
_args: &[mir::Operand<'tcx>],
_return_place: mir::Place<'tcx>,
_return_places: CallReturnPlaces<'_, 'tcx>,
) {
}
}

View file

@ -1,6 +1,6 @@
use super::*;
use crate::{AnalysisDomain, GenKill, GenKillAnalysis};
use crate::{AnalysisDomain, CallReturnPlaces, GenKill, GenKillAnalysis};
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::*;
@ -84,9 +84,7 @@ impl GenKillAnalysis<'tcx> for MaybeBorrowedLocals {
&self,
_trans: &mut impl GenKill<Self::Idx>,
_block: mir::BasicBlock,
_func: &mir::Operand<'tcx>,
_args: &[mir::Operand<'tcx>],
_dest_place: mir::Place<'tcx>,
_return_places: CallReturnPlaces<'_, 'tcx>,
) {
}
}

View file

@ -2,7 +2,7 @@
//!
//! A local will be maybe initialized if *any* projections of that local might be initialized.
use crate::GenKill;
use crate::{CallReturnPlaces, GenKill};
use rustc_index::bit_set::BitSet;
use rustc_middle::mir::visit::{PlaceContext, Visitor};
@ -53,11 +53,9 @@ impl crate::GenKillAnalysis<'tcx> for MaybeInitializedLocals {
&self,
trans: &mut impl GenKill<Self::Idx>,
_block: BasicBlock,
_func: &mir::Operand<'tcx>,
_args: &[mir::Operand<'tcx>],
return_place: mir::Place<'tcx>,
return_places: CallReturnPlaces<'_, 'tcx>,
) {
trans.gen(return_place.local)
return_places.for_each(|place| trans.gen(place.local));
}
/// See `Analysis::apply_yield_resume_effect`.
@ -83,7 +81,11 @@ where
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, NonUseContext};
match context {
// These are handled specially in `call_return_effect` and `yield_resume_effect`.
PlaceContext::MutatingUse(MutatingUseContext::Call | MutatingUseContext::Yield) => {}
PlaceContext::MutatingUse(
MutatingUseContext::Call
| MutatingUseContext::AsmOutput
| MutatingUseContext::Yield,
) => {}
// Otherwise, when a place is mutated, we must consider it possibly initialized.
PlaceContext::MutatingUse(_) => self.trans.gen(local),

View file

@ -2,7 +2,7 @@ use rustc_index::bit_set::BitSet;
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
use rustc_middle::mir::{self, Local, Location};
use crate::{AnalysisDomain, Backward, GenKill, GenKillAnalysis};
use crate::{AnalysisDomain, Backward, CallReturnPlaces, GenKill, GenKillAnalysis};
/// A [live-variable dataflow analysis][liveness].
///
@ -94,13 +94,13 @@ impl GenKillAnalysis<'tcx> for MaybeLiveLocals {
&self,
trans: &mut impl GenKill<Self::Idx>,
_block: mir::BasicBlock,
_func: &mir::Operand<'tcx>,
_args: &[mir::Operand<'tcx>],
dest_place: mir::Place<'tcx>,
return_places: CallReturnPlaces<'_, 'tcx>,
) {
if let Some(local) = dest_place.as_local() {
trans.kill(local);
}
return_places.for_each(|place| {
if let Some(local) = place.as_local() {
trans.kill(local);
}
});
}
fn yield_resume_effect(
@ -167,12 +167,16 @@ impl DefUse {
// destination place for a `Call` return or `Yield` resume respectively. Since this is
// only a `Def` when the function returns successfully, we handle this case separately
// in `call_return_effect` above.
PlaceContext::MutatingUse(MutatingUseContext::Call | MutatingUseContext::Yield) => None,
PlaceContext::MutatingUse(
MutatingUseContext::Call
| MutatingUseContext::AsmOutput
| MutatingUseContext::Yield,
) => None,
// All other contexts are uses...
PlaceContext::MutatingUse(
MutatingUseContext::AddressOf
| MutatingUseContext::AsmOutput
| MutatingUseContext::LlvmAsmOutput
| MutatingUseContext::Borrow
| MutatingUseContext::Drop
| MutatingUseContext::Retag,

View file

@ -11,7 +11,7 @@ use rustc_middle::ty::{self, TyCtxt};
use crate::drop_flag_effects_for_function_entry;
use crate::drop_flag_effects_for_location;
use crate::elaborate_drops::DropFlagState;
use crate::framework::SwitchIntEdgeEffects;
use crate::framework::{CallReturnPlaces, SwitchIntEdgeEffects};
use crate::move_paths::{HasMoveData, InitIndex, InitKind, LookupResult, MoveData, MovePathIndex};
use crate::on_lookup_result_bits;
use crate::MoveDataParamEnv;
@ -354,21 +354,21 @@ impl<'tcx> GenKillAnalysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
&self,
trans: &mut impl GenKill<Self::Idx>,
_block: mir::BasicBlock,
_func: &mir::Operand<'tcx>,
_args: &[mir::Operand<'tcx>],
dest_place: mir::Place<'tcx>,
return_places: CallReturnPlaces<'_, 'tcx>,
) {
// when a call returns successfully, that means we need to set
// the bits for that dest_place to 1 (initialized).
on_lookup_result_bits(
self.tcx,
self.body,
self.move_data(),
self.move_data().rev_lookup.find(dest_place.as_ref()),
|mpi| {
trans.gen(mpi);
},
);
return_places.for_each(|place| {
// when a call returns successfully, that means we need to set
// the bits for that dest_place to 1 (initialized).
on_lookup_result_bits(
self.tcx,
self.body,
self.move_data(),
self.move_data().rev_lookup.find(place.as_ref()),
|mpi| {
trans.gen(mpi);
},
);
});
}
fn switch_int_edge_effects<G: GenKill<Self::Idx>>(
@ -472,21 +472,21 @@ impl<'tcx> GenKillAnalysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
&self,
trans: &mut impl GenKill<Self::Idx>,
_block: mir::BasicBlock,
_func: &mir::Operand<'tcx>,
_args: &[mir::Operand<'tcx>],
dest_place: mir::Place<'tcx>,
return_places: CallReturnPlaces<'_, 'tcx>,
) {
// when a call returns successfully, that means we need to set
// the bits for that dest_place to 0 (initialized).
on_lookup_result_bits(
self.tcx,
self.body,
self.move_data(),
self.move_data().rev_lookup.find(dest_place.as_ref()),
|mpi| {
trans.kill(mpi);
},
);
return_places.for_each(|place| {
// when a call returns successfully, that means we need to set
// the bits for that dest_place to 0 (initialized).
on_lookup_result_bits(
self.tcx,
self.body,
self.move_data(),
self.move_data().rev_lookup.find(place.as_ref()),
|mpi| {
trans.kill(mpi);
},
);
});
}
fn switch_int_edge_effects<G: GenKill<Self::Idx>>(
@ -591,21 +591,21 @@ impl<'tcx> GenKillAnalysis<'tcx> for DefinitelyInitializedPlaces<'_, 'tcx> {
&self,
trans: &mut impl GenKill<Self::Idx>,
_block: mir::BasicBlock,
_func: &mir::Operand<'tcx>,
_args: &[mir::Operand<'tcx>],
dest_place: mir::Place<'tcx>,
return_places: CallReturnPlaces<'_, 'tcx>,
) {
// when a call returns successfully, that means we need to set
// the bits for that dest_place to 1 (initialized).
on_lookup_result_bits(
self.tcx,
self.body,
self.move_data(),
self.move_data().rev_lookup.find(dest_place.as_ref()),
|mpi| {
trans.gen(mpi);
},
);
return_places.for_each(|place| {
// when a call returns successfully, that means we need to set
// the bits for that dest_place to 1 (initialized).
on_lookup_result_bits(
self.tcx,
self.body,
self.move_data(),
self.move_data().rev_lookup.find(place.as_ref()),
|mpi| {
trans.gen(mpi);
},
);
});
}
}
@ -679,9 +679,7 @@ impl<'tcx> GenKillAnalysis<'tcx> for EverInitializedPlaces<'_, 'tcx> {
&self,
trans: &mut impl GenKill<Self::Idx>,
block: mir::BasicBlock,
_func: &mir::Operand<'tcx>,
_args: &[mir::Operand<'tcx>],
_dest_place: mir::Place<'tcx>,
_return_places: CallReturnPlaces<'_, 'tcx>,
) {
let move_data = self.move_data();
let init_loc_map = &move_data.init_loc_map;

View file

@ -1,7 +1,7 @@
pub use super::*;
use crate::storage::AlwaysLiveLocals;
use crate::{GenKill, Results, ResultsRefCursor};
use crate::{CallReturnPlaces, GenKill, Results, ResultsRefCursor};
use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor};
use rustc_middle::mir::*;
use std::cell::RefCell;
@ -68,9 +68,7 @@ impl crate::GenKillAnalysis<'tcx> for MaybeStorageLive {
&self,
_trans: &mut impl GenKill<Self::Idx>,
_block: BasicBlock,
_func: &mir::Operand<'tcx>,
_args: &[mir::Operand<'tcx>],
_return_place: mir::Place<'tcx>,
_return_places: CallReturnPlaces<'_, 'tcx>,
) {
// Nothing to do when a call returns successfully
}
@ -226,7 +224,7 @@ impl<'mir, 'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir, 'tc
terminator: &mir::Terminator<'tcx>,
loc: Location,
) {
match &terminator.kind {
match terminator.kind {
// For call terminators the destination requires storage for the call
// and after the call returns successfully, but not after a panic.
// Since `propagate_call_unwind` doesn't exist, we have to kill the
@ -235,6 +233,11 @@ impl<'mir, 'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir, 'tc
trans.kill(place.local);
}
// The same applies to InlineAsm outputs.
TerminatorKind::InlineAsm { ref operands, .. } => {
CallReturnPlaces::InlineAsm(operands).for_each(|place| trans.kill(place.local));
}
// Nothing to do for these. Match exhaustively so this fails to compile when new
// variants are added.
TerminatorKind::Call { destination: None, .. }
@ -247,7 +250,6 @@ impl<'mir, 'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir, 'tc
| TerminatorKind::FalseUnwind { .. }
| TerminatorKind::GeneratorDrop
| TerminatorKind::Goto { .. }
| TerminatorKind::InlineAsm { .. }
| TerminatorKind::Resume
| TerminatorKind::Return
| TerminatorKind::SwitchInt { .. }
@ -261,11 +263,9 @@ impl<'mir, 'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir, 'tc
&self,
trans: &mut impl GenKill<Self::Idx>,
_block: BasicBlock,
_func: &mir::Operand<'tcx>,
_args: &[mir::Operand<'tcx>],
return_place: mir::Place<'tcx>,
return_places: CallReturnPlaces<'_, 'tcx>,
) {
trans.gen(return_place.local);
return_places.for_each(|place| trans.gen(place.local));
}
fn yield_resume_effect(

View file

@ -28,9 +28,9 @@ pub use self::drop_flag_effects::{
on_lookup_result_bits,
};
pub use self::framework::{
fmt, graphviz, lattice, visit_results, Analysis, AnalysisDomain, Backward, Direction, Engine,
Forward, GenKill, GenKillAnalysis, JoinSemiLattice, Results, ResultsCursor, ResultsRefCursor,
ResultsVisitable, ResultsVisitor,
fmt, graphviz, lattice, visit_results, Analysis, AnalysisDomain, Backward, CallReturnPlaces,
Direction, Engine, Forward, GenKill, GenKillAnalysis, JoinSemiLattice, Results, ResultsCursor,
ResultsRefCursor, ResultsVisitable, ResultsVisitor,
};
use self::move_paths::MoveData;

View file

@ -419,6 +419,7 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
options: _,
line_spans: _,
destination: _,
cleanup: _,
} => {
for op in operands {
match *op {