// option. This file may not be copied, modified, or distributed
// except according to those terms.
+use array_vec::ArrayVec;
use std::borrow::{Borrow, BorrowMut, ToOwned};
use std::fmt;
use std::iter;
///
/// In other words, `T` is the type used to index into the bitvector
/// this type uses to represent the set of object it holds.
+///
+/// The representation is dense, using one bit per possible element.
#[derive(Eq, PartialEq)]
pub struct IdxSetBuf<T: Idx> {
_pd: PhantomData<fn(&T)>,
}
}
+const BITS_PER_WORD: usize = mem::size_of::<Word>() * 8;
+
impl<T: Idx> fmt::Debug for IdxSetBuf<T> {
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
w.debug_list()
impl<T: Idx> IdxSetBuf<T> {
fn new(init: Word, universe_size: usize) -> Self {
- let bits_per_word = mem::size_of::<Word>() * 8;
- let num_words = (universe_size + (bits_per_word - 1)) / bits_per_word;
+ let num_words = (universe_size + (BITS_PER_WORD - 1)) / BITS_PER_WORD;
IdxSetBuf {
_pd: Default::default(),
bits: vec![init; num_words],
}
}
+ /// Duplicates as a hybrid set.
+ pub fn to_hybrid(&self) -> HybridIdxSetBuf<T> {
+ // This universe_size may be slightly larger than the one specified
+ // upon creation, due to rounding up to a whole word. That's ok.
+ let universe_size = self.bits.len() * BITS_PER_WORD;
+
+ // Note: we currently don't bother trying to make a Sparse set.
+ HybridIdxSetBuf::Dense(self.to_owned(), universe_size)
+ }
+
/// Removes all elements
pub fn clear(&mut self) {
for b in &mut self.bits {
/// Clear all elements above `universe_size`.
fn trim_to(&mut self, universe_size: usize) {
- let word_bits = mem::size_of::<Word>() * 8;
-
// `trim_block` is the first block where some bits have
// to be cleared.
- let trim_block = universe_size / word_bits;
+ let trim_block = universe_size / BITS_PER_WORD;
// all the blocks above it have to be completely cleared.
if trim_block < self.bits.len() {
*b = 0;
}
- // at that block, the `universe_size % word_bits` lsbs
+ // at that block, the `universe_size % BITS_PER_WORD` lsbs
// should remain.
- let remaining_bits = universe_size % word_bits;
+ let remaining_bits = universe_size % BITS_PER_WORD;
let mask = (1<<remaining_bits)-1;
self.bits[trim_block] &= mask;
}
bitwise(self.words_mut(), other.words(), &Union)
}
+ /// Like `union()`, but takes a `SparseIdxSetBuf` argument.
+ fn union_sparse(&mut self, other: &SparseIdxSetBuf<T>) -> bool {
+ let mut changed = false;
+ for elem in other.iter() {
+ changed |= self.add(&elem);
+ }
+ changed
+ }
+
+ /// Like `union()`, but takes a `HybridIdxSetBuf` argument.
+ pub fn union_hybrid(&mut self, other: &HybridIdxSetBuf<T>) -> bool {
+ match other {
+ HybridIdxSetBuf::Sparse(sparse, _) => self.union_sparse(sparse),
+ HybridIdxSetBuf::Dense(dense, _) => self.union(dense),
+ }
+ }
+
/// Set `self = self - other` and return true if `self` changed.
/// (i.e., if any bits were removed).
pub fn subtract(&mut self, other: &IdxSet<T>) -> bool {
bitwise(self.words_mut(), other.words(), &Subtract)
}
+ /// Like `subtract()`, but takes a `SparseIdxSetBuf` argument.
+ fn subtract_sparse(&mut self, other: &SparseIdxSetBuf<T>) -> bool {
+ let mut changed = false;
+ for elem in other.iter() {
+ changed |= self.remove(&elem);
+ }
+ changed
+ }
+
+ /// Like `subtract()`, but takes a `HybridIdxSetBuf` argument.
+ pub fn subtract_hybrid(&mut self, other: &HybridIdxSetBuf<T>) -> bool {
+ match other {
+ HybridIdxSetBuf::Sparse(sparse, _) => self.subtract_sparse(sparse),
+ HybridIdxSetBuf::Dense(dense, _) => self.subtract(dense),
+ }
+ }
+
/// Set `self = self & other` and return true if `self` changed.
/// (i.e., if any bits were removed).
pub fn intersect(&mut self, other: &IdxSet<T>) -> bool {
type Item = T;
fn next(&mut self) -> Option<T> {
- let word_bits = mem::size_of::<Word>() * 8;
loop {
if let Some((ref mut word, offset)) = self.cur {
let bit_pos = word.trailing_zeros() as usize;
- if bit_pos != word_bits {
+ if bit_pos != BITS_PER_WORD {
let bit = 1 << bit_pos;
*word ^= bit;
return Some(T::new(bit_pos + offset))
}
let (i, word) = self.iter.next()?;
- self.cur = Some((*word, word_bits * i));
+ self.cur = Some((*word, BITS_PER_WORD * i));
+ }
+ }
+}
+
+const SPARSE_MAX: usize = 8;
+
+/// A sparse index set with a maximum of SPARSE_MAX elements. Used by
+/// HybridIdxSetBuf; do not use directly.
+///
+/// The elements are stored as an unsorted vector with no duplicates.
+#[derive(Clone, Debug)]
+pub struct SparseIdxSetBuf<T: Idx>(ArrayVec<[T; SPARSE_MAX]>);
+
+impl<T: Idx> SparseIdxSetBuf<T> {
+ fn new() -> Self {
+ SparseIdxSetBuf(ArrayVec::new())
+ }
+
+ fn len(&self) -> usize {
+ self.0.len()
+ }
+
+ fn contains(&self, elem: &T) -> bool {
+ self.0.contains(elem)
+ }
+
+ fn add(&mut self, elem: &T) -> bool {
+ // Ensure there are no duplicates.
+ if self.0.contains(elem) {
+ false
+ } else {
+ self.0.push(*elem);
+ true
+ }
+ }
+
+ fn remove(&mut self, elem: &T) -> bool {
+ if let Some(i) = self.0.iter().position(|e| e == elem) {
+ // Swap the found element to the end, then pop it.
+ let len = self.0.len();
+ self.0.swap(i, len - 1);
+ self.0.pop();
+ true
+ } else {
+ false
+ }
+ }
+
+ fn to_dense(&self, universe_size: usize) -> IdxSetBuf<T> {
+ let mut dense = IdxSetBuf::new_empty(universe_size);
+ for elem in self.0.iter() {
+ dense.add(elem);
+ }
+ dense
+ }
+
+ fn iter(&self) -> SparseIter<T> {
+ SparseIter {
+ iter: self.0.iter(),
+ }
+ }
+}
+
+pub struct SparseIter<'a, T: Idx> {
+ iter: slice::Iter<'a, T>,
+}
+
+impl<'a, T: Idx> Iterator for SparseIter<'a, T> {
+ type Item = T;
+
+ fn next(&mut self) -> Option<T> {
+ self.iter.next().map(|e| *e)
+ }
+}
+
+/// Like IdxSetBuf, but with a hybrid representation: sparse when there are few
+/// elements in the set, but dense when there are many. It's especially
+/// efficient for sets that typically have a small number of elements, but a
+/// large `universe_size`, and are cleared frequently.
+#[derive(Clone, Debug)]
+pub enum HybridIdxSetBuf<T: Idx> {
+ Sparse(SparseIdxSetBuf<T>, usize),
+ Dense(IdxSetBuf<T>, usize),
+}
+
+impl<T: Idx> HybridIdxSetBuf<T> {
+ pub fn new_empty(universe_size: usize) -> Self {
+ HybridIdxSetBuf::Sparse(SparseIdxSetBuf::new(), universe_size)
+ }
+
+ fn universe_size(&mut self) -> usize {
+ match *self {
+ HybridIdxSetBuf::Sparse(_, size) => size,
+ HybridIdxSetBuf::Dense(_, size) => size,
+ }
+ }
+
+ pub fn clear(&mut self) {
+ let universe_size = self.universe_size();
+ *self = HybridIdxSetBuf::new_empty(universe_size);
+ }
+
+ /// Returns true iff set `self` contains `elem`.
+ pub fn contains(&self, elem: &T) -> bool {
+ match self {
+ HybridIdxSetBuf::Sparse(sparse, _) => sparse.contains(elem),
+ HybridIdxSetBuf::Dense(dense, _) => dense.contains(elem),
+ }
+ }
+
+ /// Adds `elem` to the set `self`.
+ pub fn add(&mut self, elem: &T) -> bool {
+ match self {
+ HybridIdxSetBuf::Sparse(sparse, _) if sparse.len() < SPARSE_MAX => {
+ // The set is sparse and has space for `elem`.
+ sparse.add(elem)
+ }
+ HybridIdxSetBuf::Sparse(sparse, _) if sparse.contains(elem) => {
+ // The set is sparse and does not have space for `elem`, but
+ // that doesn't matter because `elem` is already present.
+ false
+ }
+ HybridIdxSetBuf::Sparse(_, _) => {
+ // The set is sparse and full. Convert to a dense set.
+ //
+ // FIXME: This code is awful, but I can't work out how else to
+ // appease the borrow checker.
+ let dummy = HybridIdxSetBuf::Sparse(SparseIdxSetBuf::new(), 0);
+ match mem::replace(self, dummy) {
+ HybridIdxSetBuf::Sparse(sparse, universe_size) => {
+ let mut dense = sparse.to_dense(universe_size);
+ let changed = dense.add(elem);
+ assert!(changed);
+ mem::replace(self, HybridIdxSetBuf::Dense(dense, universe_size));
+ changed
+ }
+ _ => panic!("impossible"),
+ }
+ }
+
+ HybridIdxSetBuf::Dense(dense, _) => dense.add(elem),
+ }
+ }
+
+ /// Removes `elem` from the set `self`.
+ pub fn remove(&mut self, elem: &T) -> bool {
+ // Note: we currently don't bother going from Dense back to Sparse.
+ match self {
+ HybridIdxSetBuf::Sparse(sparse, _) => sparse.remove(elem),
+ HybridIdxSetBuf::Dense(dense, _) => dense.remove(elem),
+ }
+ }
+
+ /// Converts to a dense set, consuming itself in the process.
+ pub fn to_dense(self) -> IdxSetBuf<T> {
+ match self {
+ HybridIdxSetBuf::Sparse(sparse, universe_size) => sparse.to_dense(universe_size),
+ HybridIdxSetBuf::Dense(dense, _) => dense,
+ }
+ }
+
+ /// Iteration order is unspecified.
+ pub fn iter(&self) -> HybridIter<T> {
+ match self {
+ HybridIdxSetBuf::Sparse(sparse, _) => HybridIter::Sparse(sparse.iter()),
+ HybridIdxSetBuf::Dense(dense, _) => HybridIter::Dense(dense.iter()),
+ }
+ }
+}
+
+pub enum HybridIter<'a, T: Idx> {
+ Sparse(SparseIter<'a, T>),
+ Dense(Iter<'a, T>),
+}
+
+impl<'a, T: Idx> Iterator for HybridIter<'a, T> {
+ type Item = T;
+
+ fn next(&mut self) -> Option<T> {
+ match self {
+ HybridIter::Sparse(sparse) => sparse.next(),
+ HybridIter::Dense(dense) => dense.next(),
}
}
}
//! locations.
use rustc::mir::{BasicBlock, Location};
-use rustc_data_structures::indexed_set::{IdxSetBuf, Iter};
+use rustc_data_structures::indexed_set::{HybridIdxSetBuf, IdxSetBuf, Iter};
use rustc_data_structures::indexed_vec::Idx;
use dataflow::{BitDenotation, BlockSets, DataflowResults};
{
base_results: DataflowResults<BD>,
curr_state: IdxSetBuf<BD::Idx>,
- stmt_gen: IdxSetBuf<BD::Idx>,
- stmt_kill: IdxSetBuf<BD::Idx>,
+ stmt_gen: HybridIdxSetBuf<BD::Idx>,
+ stmt_kill: HybridIdxSetBuf<BD::Idx>,
}
impl<BD> FlowAtLocation<BD>
pub fn new(results: DataflowResults<BD>) -> Self {
let bits_per_block = results.sets().bits_per_block();
let curr_state = IdxSetBuf::new_empty(bits_per_block);
- let stmt_gen = IdxSetBuf::new_empty(bits_per_block);
- let stmt_kill = IdxSetBuf::new_empty(bits_per_block);
+ let stmt_gen = HybridIdxSetBuf::new_empty(bits_per_block);
+ let stmt_kill = HybridIdxSetBuf::new_empty(bits_per_block);
FlowAtLocation {
base_results: results,
curr_state: curr_state,
F: FnOnce(Iter<BD::Idx>),
{
let mut curr_state = self.curr_state.clone();
- curr_state.union(&self.stmt_gen);
- curr_state.subtract(&self.stmt_kill);
+ curr_state.union_hybrid(&self.stmt_gen);
+ curr_state.subtract_hybrid(&self.stmt_kill);
f(curr_state.iter());
}
}
}
fn apply_local_effect(&mut self, _loc: Location) {
- self.curr_state.union(&self.stmt_gen);
- self.curr_state.subtract(&self.stmt_kill);
+ self.curr_state.union_hybrid(&self.stmt_gen);
+ self.curr_state.subtract_hybrid(&self.stmt_kill);
}
}
let i = n.index();
macro_rules! dump_set_for {
- ($set:ident) => {
+ ($set:ident, $interpret:ident) => {
write!(w, "<td>")?;
let flow = self.mbcx.flow_state();
- let entry_interp = flow.interpret_set(&flow.operator,
- flow.sets.$set(i),
- &self.render_idx);
+ let entry_interp = flow.$interpret(&flow.operator,
+ flow.sets.$set(i),
+ &self.render_idx);
for e in &entry_interp {
write!(w, "{:?}<br/>", e)?;
}
write!(w, "<tr>")?;
// Entry
- dump_set_for!(on_entry_set_for);
+ dump_set_for!(on_entry_set_for, interpret_set);
// MIR statements
write!(w, "<td>")?;
write!(w, "</td>")?;
// Gen
- dump_set_for!(gen_set_for);
+ dump_set_for!(gen_set_for, interpret_hybrid_set);
// Kill
- dump_set_for!(kill_set_for);
+ dump_set_for!(kill_set_for, interpret_hybrid_set);
write!(w, "</tr>")?;
-> io::Result<()> {
let i = n.index();
- macro_rules! dump_set_for {
- ($set:ident) => {
- let flow = self.mbcx.flow_state();
- let bits_per_block = flow.sets.bits_per_block();
- let set = flow.sets.$set(i);
- write!(w, "<td>{:?}</td>",
- dot::escape_html(&bits_to_string(set.words(), bits_per_block)))?;
- }
- }
+ let flow = self.mbcx.flow_state();
+ let bits_per_block = flow.sets.bits_per_block();
write!(w, "<tr>")?;
+
// Entry
- dump_set_for!(on_entry_set_for);
+ let set = flow.sets.on_entry_set_for(i);
+ write!(w, "<td>{:?}</td>", dot::escape_html(&bits_to_string(set.words(), bits_per_block)))?;
// Terminator
write!(w, "<td>")?;
write!(w, "</td>")?;
// Gen
- dump_set_for!(gen_set_for);
+ let set = flow.sets.gen_set_for(i);
+ write!(w, "<td>{:?}</td>", dot::escape_html(&format!("{:?}", set)))?;
// Kill
- dump_set_for!(kill_set_for);
+ let set = flow.sets.kill_set_for(i);
+ write!(w, "<td>{:?}</td>", dot::escape_html(&format!("{:?}", set)))?;
write!(w, "</tr>")?;
use syntax::ast::{self, MetaItem};
-use rustc_data_structures::indexed_set::{IdxSet, IdxSetBuf};
-use rustc_data_structures::indexed_vec::Idx;
use rustc_data_structures::bitslice::{bitwise, BitwiseOperator, Word};
+use rustc_data_structures::indexed_set::{HybridIdxSetBuf, IdxSet, IdxSetBuf};
+use rustc_data_structures::indexed_vec::Idx;
use rustc_data_structures::work_queue::WorkQueue;
use rustc::ty::{self, TyCtxt};
builder: self,
};
propcx.walk_cfg(&mut temp);
-
}
fn build_sets(&mut self) {
let sets = self.builder.flow_state.sets.for_block(bb.index());
debug_assert!(in_out.words().len() == sets.on_entry.words().len());
in_out.overwrite(sets.on_entry);
- in_out.union(sets.gen_set);
- in_out.subtract(sets.kill_set);
+ in_out.union_hybrid(sets.gen_set);
+ in_out.subtract_hybrid(sets.kill_set);
}
self.builder.propagate_bits_into_graph_successors_of(
in_out, (bb, bb_data), &mut dirty_queue);
}
/// Maps each block to a set of bits
-#[derive(Debug)]
+#[derive(Clone, Debug)]
pub(crate) struct Bits<E:Idx> {
bits: IdxSetBuf<E>,
}
-impl<E:Idx> Clone for Bits<E> {
- fn clone(&self) -> Self { Bits { bits: self.bits.clone() } }
-}
-
impl<E:Idx> Bits<E> {
fn new(bits: IdxSetBuf<E>) -> Self {
Bits { bits: bits }
result: &DataflowResults<T>,
mir: &Mir<'tcx>)
-> IdxSetBuf<T::Idx> {
- let mut entry = result.sets().on_entry_set_for(loc.block.index()).to_owned();
+ let mut on_entry = result.sets().on_entry_set_for(loc.block.index()).to_owned();
+ let mut kill_set = on_entry.to_hybrid();
+ let mut gen_set = kill_set.clone();
{
let mut sets = BlockSets {
- on_entry: &mut entry.clone(),
- kill_set: &mut entry.clone(),
- gen_set: &mut entry,
+ on_entry: &mut on_entry,
+ kill_set: &mut kill_set,
+ gen_set: &mut gen_set,
};
for stmt in 0..loc.statement_index {
}
}
- entry
+ gen_set.to_dense()
}
pub struct DataflowAnalysis<'a, 'tcx: 'a, O> where O: BitDenotation
impl<O: BitDenotation> DataflowState<O> {
pub(crate) fn interpret_set<'c, P>(&self,
o: &'c O,
- words: &IdxSet<O::Idx>,
+ set: &IdxSet<O::Idx>,
render_idx: &P)
-> Vec<DebugFormatted>
where P: Fn(&O, O::Idx) -> DebugFormatted
{
- words.iter().map(|i| render_idx(o, i)).collect()
+ set.iter().map(|i| render_idx(o, i)).collect()
+ }
+
+ pub(crate) fn interpret_hybrid_set<'c, P>(&self,
+ o: &'c O,
+ set: &HybridIdxSetBuf<O::Idx>,
+ render_idx: &P)
+ -> Vec<DebugFormatted>
+ where P: Fn(&O, O::Idx) -> DebugFormatted
+ {
+ set.iter().map(|i| render_idx(o, i)).collect()
}
}
/// equal to bits_per_block / (mem::size_of::<Word> * 8), rounded up.
words_per_block: usize,
+ /// For each block, bits valid on entry to the block.
+ on_entry_sets: Bits<E>,
+
/// For each block, bits generated by executing the statements in
/// the block. (For comparison, the Terminator for each block is
/// handled in a flow-specific manner during propagation.)
- gen_sets: Bits<E>,
+ gen_sets: Vec<HybridIdxSetBuf<E>>,
/// For each block, bits killed by executing the statements in the
/// block. (For comparison, the Terminator for each block is
/// handled in a flow-specific manner during propagation.)
- kill_sets: Bits<E>,
-
- /// For each block, bits valid on entry to the block.
- on_entry_sets: Bits<E>,
+ kill_sets: Vec<HybridIdxSetBuf<E>>,
}
/// Triple of sets associated with a given block.
/// Dataflow state immediately before control flow enters the given block.
pub(crate) on_entry: &'a mut IdxSet<E>,
- /// Bits that are set to 1 by the time we exit the given block.
- pub(crate) gen_set: &'a mut IdxSet<E>,
+ /// Bits that are set to 1 by the time we exit the given block. Hybrid
+ /// because it usually contains only 0 or 1 elements.
+ pub(crate) gen_set: &'a mut HybridIdxSetBuf<E>,
- /// Bits that are set to 0 by the time we exit the given block.
- pub(crate) kill_set: &'a mut IdxSet<E>,
+ /// Bits that are set to 0 by the time we exit the given block. Hybrid
+ /// because it usually contains only 0 or 1 elements.
+ pub(crate) kill_set: &'a mut HybridIdxSetBuf<E>,
}
impl<'a, E:Idx> BlockSets<'a, E> {
}
fn apply_local_effect(&mut self) {
- self.on_entry.union(&self.gen_set);
- self.on_entry.subtract(&self.kill_set);
+ self.on_entry.union_hybrid(&self.gen_set);
+ self.on_entry.subtract_hybrid(&self.kill_set);
}
}
let range = E::new(offset)..E::new(offset + self.words_per_block);
BlockSets {
on_entry: self.on_entry_sets.bits.range_mut(&range),
- gen_set: self.gen_sets.bits.range_mut(&range),
- kill_set: self.kill_sets.bits.range_mut(&range),
+ gen_set: &mut self.gen_sets[block_idx],
+ kill_set: &mut self.kill_sets[block_idx],
}
}
- fn lookup_set_for<'a>(&self, sets: &'a Bits<E>, block_idx: usize) -> &'a IdxSet<E> {
+ pub fn on_entry_set_for(&self, block_idx: usize) -> &IdxSet<E> {
let offset = self.words_per_block * block_idx;
let range = E::new(offset)..E::new(offset + self.words_per_block);
- sets.bits.range(&range)
+ self.on_entry_sets.bits.range(&range)
}
- pub fn gen_set_for(&self, block_idx: usize) -> &IdxSet<E> {
- self.lookup_set_for(&self.gen_sets, block_idx)
+ pub fn gen_set_for(&self, block_idx: usize) -> &HybridIdxSetBuf<E> {
+ &self.gen_sets[block_idx]
}
- pub fn kill_set_for(&self, block_idx: usize) -> &IdxSet<E> {
- self.lookup_set_for(&self.kill_sets, block_idx)
- }
- pub fn on_entry_set_for(&self, block_idx: usize) -> &IdxSet<E> {
- self.lookup_set_for(&self.on_entry_sets, block_idx)
+ pub fn kill_set_for(&self, block_idx: usize) -> &HybridIdxSetBuf<E> {
+ &self.kill_sets[block_idx]
}
}
let num_blocks = mir.basic_blocks().len();
let num_overall = num_blocks * bits_per_block_rounded_up;
- let zeroes = Bits::new(IdxSetBuf::new_empty(num_overall));
let on_entry = Bits::new(if D::bottom_value() {
IdxSetBuf::new_filled(num_overall)
} else {
IdxSetBuf::new_empty(num_overall)
});
+ let empties = vec![HybridIdxSetBuf::new_empty(bits_per_block); num_blocks];
DataflowAnalysis {
mir,
sets: AllSets {
bits_per_block,
words_per_block,
- gen_sets: zeroes.clone(),
- kill_sets: zeroes,
on_entry_sets: on_entry,
+ gen_sets: empties.clone(),
+ kill_sets: empties,
},
operator: denotation,
}
}
};
- let mut entry = results.0.sets.on_entry_set_for(bb.index()).to_owned();
- let mut gen = results.0.sets.gen_set_for(bb.index()).to_owned();
- let mut kill = results.0.sets.kill_set_for(bb.index()).to_owned();
+ let mut on_entry = results.0.sets.on_entry_set_for(bb.index()).to_owned();
+ let mut gen_set = results.0.sets.gen_set_for(bb.index()).clone();
+ let mut kill_set = results.0.sets.kill_set_for(bb.index()).clone();
// Emulate effect of all statements in the block up to (but not
// including) the borrow within `peek_arg_place`. Do *not* include
// of the argument at time immediate preceding Call to
// `rustc_peek`).
- let mut sets = dataflow::BlockSets { on_entry: &mut entry,
- gen_set: &mut gen,
- kill_set: &mut kill };
+ let mut sets = dataflow::BlockSets { on_entry: &mut on_entry,
+ gen_set: &mut gen_set,
+ kill_set: &mut kill_set };
for (j, stmt) in statements.iter().enumerate() {
debug!("rustc_peek: ({:?},{}) {:?}", bb, j, stmt);
debug!("rustc_peek: computing effect on place: {:?} ({:?}) in stmt: {:?}",
place, lhs_mpi, stmt);
// reset GEN and KILL sets before emulating their effect.
- for e in sets.gen_set.words_mut() { *e = 0; }
- for e in sets.kill_set.words_mut() { *e = 0; }
+ sets.gen_set.clear();
+ sets.kill_set.clear();
results.0.operator.before_statement_effect(
&mut sets, Location { block: bb, statement_index: j });
results.0.operator.statement_effect(
&mut sets, Location { block: bb, statement_index: j });
- sets.on_entry.union(sets.gen_set);
- sets.on_entry.subtract(sets.kill_set);
+ sets.on_entry.union_hybrid(sets.gen_set);
+ sets.on_entry.subtract_hybrid(sets.kill_set);
}
results.0.operator.before_terminator_effect(