use std::collections::{BTreeSet, VecDeque};
use std::fmt;
use std::ops::Range;
use crate::transformer::{InstrRef, Reg};
use super::cfg::{BlockRef, Cfg};
use super::storage::{CompactSet, CompactSetIter, RegValueMap, RegValueMapIter};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ValueFactsStorage {
Materialized {
reaching_values: Vec<InstrReachingValues>,
use_values: Vec<InstrUseValues>,
},
NoPhi,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DataflowFacts {
pub instr_effects: Vec<InstrEffect>,
pub effect_summaries: Vec<SideEffectSummary>,
pub defs: Vec<Def>,
pub open_defs: Vec<OpenDef>,
pub instr_defs: Vec<Vec<DefId>>,
pub reaching_defs: Vec<InstrReachingDefs>,
pub use_defs: Vec<InstrUseDefs>,
pub def_uses: Vec<Vec<UseSite>>,
pub open_reaching_defs: Vec<BTreeSet<OpenDefId>>,
pub open_use_defs: Vec<BTreeSet<OpenDefId>>,
pub open_def_uses: Vec<Vec<OpenUseSite>>,
pub live_in: Vec<BTreeSet<Reg>>,
pub live_out: Vec<BTreeSet<Reg>>,
pub open_live_in: Vec<bool>,
pub open_live_out: Vec<bool>,
pub phi_candidates: Vec<PhiCandidate>,
pub(crate) phi_block_ranges: Vec<Range<usize>>,
pub(crate) value_facts: ValueFactsStorage,
pub children: Vec<DataflowFacts>,
}
#[derive(Debug, Clone, Copy)]
pub enum ValueMapRef<'a> {
Materialized(&'a RegValueMap<SsaValue>),
DefBacked(&'a RegValueMap<DefId>),
}
impl<'a> ValueMapRef<'a> {
pub fn get(self, reg: Reg) -> Option<ValueSetRef<'a>> {
match self {
Self::Materialized(map) => map.get(reg).map(ValueSetRef::Materialized),
Self::DefBacked(map) => map.get(reg).map(ValueSetRef::DefBacked),
}
}
pub fn iter(self) -> ValueMapIter<'a> {
match self {
Self::Materialized(map) => ValueMapIter::Materialized(map.iter()),
Self::DefBacked(map) => ValueMapIter::DefBacked(map.iter()),
}
}
pub fn values(self) -> ValueMapValuesIter<'a> {
ValueMapValuesIter { inner: self.iter() }
}
}
pub struct ValueMapValuesIter<'a> {
inner: ValueMapIter<'a>,
}
impl<'a> Iterator for ValueMapValuesIter<'a> {
type Item = ValueSetRef<'a>;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(_, values)| values)
}
}
pub enum ValueMapIter<'a> {
Materialized(RegValueMapIter<'a, SsaValue>),
DefBacked(RegValueMapIter<'a, DefId>),
}
impl<'a> Iterator for ValueMapIter<'a> {
type Item = (Reg, ValueSetRef<'a>);
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Materialized(iter) => iter
.next()
.map(|(reg, values)| (reg, ValueSetRef::Materialized(values))),
Self::DefBacked(iter) => iter
.next()
.map(|(reg, values)| (reg, ValueSetRef::DefBacked(values))),
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum ValueSetRef<'a> {
Materialized(&'a CompactSet<SsaValue>),
DefBacked(&'a CompactSet<DefId>),
}
impl<'a> ValueSetRef<'a> {
pub fn is_empty(self) -> bool {
match self {
Self::Materialized(values) => values.is_empty(),
Self::DefBacked(values) => values.is_empty(),
}
}
pub fn len(self) -> usize {
match self {
Self::Materialized(values) => values.len(),
Self::DefBacked(values) => values.len(),
}
}
pub fn contains(self, needle: &SsaValue) -> bool {
match (self, needle) {
(Self::Materialized(values), _) => values.contains(needle),
(Self::DefBacked(values), SsaValue::Def(def_id)) => values.contains(def_id),
(Self::DefBacked(_), SsaValue::Phi(_)) => false,
}
}
pub fn iter(self) -> ValueSetIter<'a> {
match self {
Self::Materialized(values) => ValueSetIter::Materialized(values.iter()),
Self::DefBacked(values) => ValueSetIter::DefBacked(values.iter()),
}
}
pub fn to_compact_set(self) -> CompactSet<SsaValue> {
match self {
Self::Materialized(values) => values.clone(),
Self::DefBacked(values) => match values {
CompactSet::Empty => CompactSet::Empty,
CompactSet::One(def_id) => CompactSet::singleton(SsaValue::Def(*def_id)),
CompactSet::Many(def_ids) => {
CompactSet::Many(def_ids.iter().copied().map(SsaValue::Def).collect())
}
},
}
}
}
pub enum ValueSetIter<'a> {
Materialized(CompactSetIter<'a, SsaValue>),
DefBacked(CompactSetIter<'a, DefId>),
}
impl<'a> Iterator for ValueSetIter<'a> {
type Item = SsaValue;
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Materialized(iter) => iter.next().copied(),
Self::DefBacked(iter) => iter.next().copied().map(SsaValue::Def),
}
}
}
impl DataflowFacts {
pub fn reaching_defs_at(&self, instr: InstrRef) -> &InstrReachingDefs {
self.reaching_defs
.get(instr.index())
.expect("dataflow should have a reaching-def snapshot for every instruction")
}
pub fn reaching_values_at(&self, instr: InstrRef) -> ValueMapRef<'_> {
match &self.value_facts {
ValueFactsStorage::Materialized {
reaching_values, ..
} => {
let values = reaching_values
.get(instr.index())
.expect("dataflow should have a reaching-value snapshot for every instruction");
ValueMapRef::Materialized(&values.fixed)
}
ValueFactsStorage::NoPhi => {
let defs = self
.reaching_defs
.get(instr.index())
.expect("dataflow should have a reaching-def snapshot for every instruction");
ValueMapRef::DefBacked(&defs.fixed)
}
}
}
pub fn use_defs_at(&self, instr: InstrRef) -> &InstrUseDefs {
self.use_defs
.get(instr.index())
.expect("dataflow should have a use-def summary for every instruction")
}
pub fn use_values_at(&self, instr: InstrRef) -> ValueMapRef<'_> {
match &self.value_facts {
ValueFactsStorage::Materialized { use_values, .. } => {
let values = use_values
.get(instr.index())
.expect("dataflow should have a use-value summary for every instruction");
ValueMapRef::Materialized(&values.fixed)
}
ValueFactsStorage::NoPhi => {
let defs = self
.use_defs
.get(instr.index())
.expect("dataflow should have a use-def summary for every instruction");
ValueMapRef::DefBacked(&defs.fixed)
}
}
}
pub fn open_reaching_defs_at(&self, instr: InstrRef) -> &BTreeSet<OpenDefId> {
self.open_reaching_defs
.get(instr.index())
.expect("dataflow should have an open-def snapshot for every instruction")
}
pub fn open_use_defs_at(&self, instr: InstrRef) -> &BTreeSet<OpenDefId> {
self.open_use_defs
.get(instr.index())
.expect("dataflow should have an open-def use summary for every instruction")
}
pub fn live_in_regs(&self, block: BlockRef) -> &BTreeSet<Reg> {
self.live_in
.get(block.index())
.expect("dataflow should have a live-in set for every block")
}
pub fn live_out_regs(&self, block: BlockRef) -> &BTreeSet<Reg> {
self.live_out
.get(block.index())
.expect("dataflow should have a live-out set for every block")
}
pub fn block_open_live_in(&self, block: BlockRef) -> bool {
self.open_live_in
.get(block.index())
.copied()
.expect("dataflow should have an open-live-in flag for every block")
}
pub fn block_open_live_out(&self, block: BlockRef) -> bool {
self.open_live_out
.get(block.index())
.copied()
.expect("dataflow should have an open-live-out flag for every block")
}
pub fn phi_candidate(&self, phi_id: PhiId) -> Option<&PhiCandidate> {
self.phi_candidates.get(phi_id.index())
}
pub fn phi_candidates_in_block(&self, block: BlockRef) -> &[PhiCandidate] {
let Some(range) = self.phi_block_ranges.get(block.index()) else {
return &[];
};
&self.phi_candidates[range.clone()]
}
pub fn phi_candidate_for_reg(&self, block: BlockRef, reg: Reg) -> Option<&PhiCandidate> {
self.phi_candidates_in_block(block)
.iter()
.find(|phi| phi.reg == reg)
}
pub fn phi_use_count(&self, phi_id: PhiId) -> usize {
if self.phi_candidates.is_empty() {
return 0;
}
(0..self.use_defs.len())
.map(|instr_index| self.use_values_at(InstrRef(instr_index)))
.flat_map(ValueMapRef::values)
.filter(|values| values.contains(&SsaValue::Phi(phi_id)))
.count()
}
pub fn def_reg(&self, def_id: DefId) -> Reg {
self.defs
.get(def_id.index())
.map(|def| def.reg)
.expect("dataflow should have a def record for every def id")
}
pub fn def_block(&self, def_id: DefId) -> BlockRef {
self.defs
.get(def_id.index())
.map(|def| def.block)
.expect("dataflow should have a def record for every def id")
}
pub fn def_instr(&self, def_id: DefId) -> InstrRef {
self.defs
.get(def_id.index())
.map(|def| def.instr)
.expect("dataflow should have a def record for every def id")
}
pub fn instr_def_for_reg(&self, instr: InstrRef, reg: Reg) -> Option<DefId> {
self.instr_defs
.get(instr.index())?
.iter()
.copied()
.find(|def_id| self.def_reg(*def_id) == reg)
}
pub fn latest_local_def_in_block(
&self,
block: BlockRef,
defs: impl IntoIterator<Item = DefId>,
) -> Option<DefId> {
defs.into_iter()
.filter(|def_id| self.def_block(*def_id) == block)
.max_by_key(|def_id| self.def_instr(*def_id).index())
}
pub fn phi_used_only_in_block(&self, cfg: &Cfg, phi_id: PhiId, block: BlockRef) -> bool {
if self.phi_candidates.is_empty() {
return false;
}
let mut saw_use = false;
for instr_index in 0..self.use_defs.len() {
let used_here = self
.use_values_at(InstrRef(instr_index))
.values()
.any(|values| values.contains(&SsaValue::Phi(phi_id)));
if !used_here {
continue;
}
saw_use = true;
if cfg.instr_to_block[instr_index] != block {
return false;
}
}
saw_use
}
pub fn compute_truly_dead_phis(&self, cfg: &Cfg) -> BTreeSet<PhiId> {
if self.phi_candidates.is_empty() {
return BTreeSet::new();
}
let mut alive = BTreeSet::new();
for instr_index in 0..self.use_defs.len() {
for values in self.use_values_at(InstrRef(instr_index)).values() {
for value in values.iter() {
if let SsaValue::Phi(phi_id) = value {
alive.insert(phi_id);
}
}
}
}
let mut queue: VecDeque<PhiId> = alive.iter().copied().collect();
while let Some(phi_id) = queue.pop_front() {
let phi = &self.phi_candidates[phi_id.index()];
for incoming in &phi.incoming {
self.propagate_phi_liveness_from_block(
cfg,
incoming.pred,
phi.reg,
&mut alive,
&mut queue,
);
}
}
self.phi_candidates
.iter()
.map(|phi| phi.id)
.filter(|id| !alive.contains(id))
.collect()
}
fn propagate_phi_liveness_from_block(
&self,
cfg: &Cfg,
block: BlockRef,
reg: Reg,
alive: &mut BTreeSet<PhiId>,
queue: &mut VecDeque<PhiId>,
) {
let block_range = &cfg.blocks[block.index()].instrs;
if let Some(last_instr) = block_range.last() {
let effect = &self.instr_effects[last_instr.index()];
if effect.fixed_must_defs.contains(®) {
return;
}
if let Some(reg_values) = self.reaching_values_at(last_instr).get(reg) {
for value in reg_values.iter() {
if let SsaValue::Phi(upstream) = value
&& alive.insert(upstream)
{
queue.push_back(upstream);
}
}
}
} else {
let phi_range = &self.phi_block_ranges[block.index()];
if let Some(phi) = self.phi_candidates[phi_range.clone()]
.iter()
.find(|p| p.reg == reg)
&& alive.insert(phi.id)
{
queue.push_back(phi.id);
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct InstrEffect {
pub fixed_uses: BTreeSet<Reg>,
pub fixed_must_defs: BTreeSet<Reg>,
pub fixed_may_defs: BTreeSet<Reg>,
pub open_use: Option<Reg>,
pub open_must_def: Option<Reg>,
pub open_may_def: Option<Reg>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct SideEffectSummary {
pub tags: BTreeSet<EffectTag>,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum EffectTag {
Alloc,
ReadTable,
WriteTable,
ReadEnv,
WriteEnv,
ReadUpvalue,
WriteUpvalue,
Call,
Close,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct DefId(pub usize);
impl DefId {
pub const fn index(self) -> usize {
self.0
}
}
impl fmt::Display for DefId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "def{}", self.0)
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct OpenDefId(pub usize);
impl OpenDefId {
pub const fn index(self) -> usize {
self.0
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct Def {
pub id: DefId,
pub reg: Reg,
pub instr: InstrRef,
pub block: BlockRef,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct OpenDef {
pub id: OpenDefId,
pub start_reg: Reg,
pub instr: InstrRef,
pub block: BlockRef,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct InstrReachingDefs {
pub fixed: RegValueMap<DefId>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct InstrUseDefs {
pub fixed: RegValueMap<DefId>,
pub open: BTreeSet<OpenDefId>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct InstrReachingValues {
pub fixed: RegValueMap<SsaValue>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct InstrUseValues {
pub fixed: RegValueMap<SsaValue>,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct UseSite {
pub instr: InstrRef,
pub reg: Reg,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct OpenUseSite {
pub instr: InstrRef,
pub start_reg: Reg,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PhiCandidate {
pub id: PhiId,
pub block: BlockRef,
pub reg: Reg,
pub incoming: Vec<PhiIncoming>,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct PhiId(pub usize);
impl PhiId {
pub const fn index(self) -> usize {
self.0
}
}
impl fmt::Display for PhiId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "phi{}", self.0)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct PhiIncoming {
pub pred: BlockRef,
pub defs: BTreeSet<DefId>,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum SsaValue {
Def(DefId),
Phi(PhiId),
}