Skip to content

Commit ad97f57

Browse files
authored
Merge pull request #19 from LLeavesG/fix/exception-sync-cleanup
Recover split finally families and synchronized try nesting
2 parents e8ccad2 + a0ce8a6 commit ad97f57

21 files changed

Lines changed: 3125 additions & 142 deletions

dexdec/src/ir/analysis/control_contractions.rs

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ pub struct ControlContractions {
2323
}
2424

2525
impl ControlContractions {
26+
#[cfg(test)]
27+
pub(crate) fn identity() -> Self {
28+
Self {
29+
domains: Vec::new(),
30+
}
31+
}
32+
2633
pub fn from_regions(regions: &RegionGraph) -> Self {
2734
ContractionGraph::new(Self::region_relations(regions)).solve(CyclePolicy::Canonical)
2835
}
@@ -116,8 +123,8 @@ impl ControlContractions {
116123
///
117124
/// Cleanup contraction can turn a non-critical physical edge into a
118125
/// critical quotient edge. When the contracted entry has one external
119-
/// ingress and immediately continues inside the same component, its tail
120-
/// is the edge-specific copy site.
126+
/// ingress and every normal branch remains inside the same component, its
127+
/// incoming edge is the edge-specific copy site.
121128
pub fn normal_copy_site(
122129
&self,
123130
cfg: &CFG,
@@ -144,7 +151,7 @@ impl ControlContractions {
144151
return None;
145152
}
146153
let entry_targets = cfg.normal_successors(entry).collect::<BTreeSet<_>>();
147-
if entry_targets.len() != 1 || !entry_targets.is_subset(&component) {
154+
if entry_targets.is_empty() || !entry_targets.is_subset(&component) {
148155
return None;
149156
}
150157
let edge = cfg
@@ -521,7 +528,10 @@ impl ComponentTerminals {
521528
mod tests {
522529
use std::collections::BTreeSet;
523530

524-
use super::{BlockId, ContractionGraph, CyclePolicy};
531+
use super::{
532+
BlockId, ContractionGraph, CyclePolicy, EdgeKind, NormalCopySite, RegionEdge, CFG,
533+
};
534+
use crate::ir::Block;
525535

526536
#[test]
527537
fn contracts_acyclic_chain_to_its_sink() {
@@ -608,4 +618,48 @@ mod tests {
608618
assert!(!contractions.contracts_to(BlockId(1), BlockId(2)));
609619
assert!(!contractions.contracts_to(BlockId(2), BlockId(1)));
610620
}
621+
622+
#[test]
623+
fn places_copy_on_branching_contracted_entry_edge() {
624+
let predecessor = BlockId::new(0);
625+
let entry = BlockId::new(1);
626+
let left = BlockId::new(2);
627+
let right = BlockId::new(3);
628+
let successor = BlockId::new(4);
629+
let alternative = BlockId::new(5);
630+
let outside = BlockId::new(6);
631+
let contractions = ContractionGraph::new([
632+
(entry, left),
633+
(entry, right),
634+
(left, successor),
635+
(right, successor),
636+
])
637+
.solve(CyclePolicy::Canonical);
638+
let mut cfg = CFG::new("branching_contracted_entry");
639+
for block in 0..=6 {
640+
cfg.add_block(Block::new(block));
641+
}
642+
cfg.add_edge(predecessor, entry, EdgeKind::True);
643+
cfg.add_edge(predecessor, alternative, EdgeKind::False);
644+
cfg.add_edge(entry, left, EdgeKind::True);
645+
cfg.add_edge(entry, right, EdgeKind::False);
646+
cfg.add_edge(left, successor, EdgeKind::Normal);
647+
cfg.add_edge(right, successor, EdgeKind::Normal);
648+
649+
assert_eq!(
650+
contractions.normal_copy_site(&cfg, predecessor, entry, successor),
651+
Some(NormalCopySite::Edge(RegionEdge {
652+
source: predecessor,
653+
target: entry,
654+
kind: EdgeKind::True,
655+
}))
656+
);
657+
658+
cfg.add_edge(entry, outside, EdgeKind::SwitchDefault);
659+
assert_eq!(
660+
contractions.normal_copy_site(&cfg, predecessor, entry, successor),
661+
None,
662+
"a branch leaving the contracted component must reject edge placement"
663+
);
664+
}
611665
}

dexdec/src/ir/analysis/register_liveness.rs

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,26 @@ pub struct RegisterLiveness {
1313
impl RegisterLiveness {
1414
pub fn analyze(cfg: &CFG) -> Self {
1515
let mut definitions = BTreeMap::new();
16+
let mut definitions_before_exception = BTreeMap::new();
1617
let mut upward_uses = BTreeMap::new();
1718
for block in cfg.blocks.values() {
1819
let mut block_definitions = BTreeSet::new();
20+
let mut exceptional_definitions = BTreeSet::new();
1921
let mut block_uses = BTreeSet::new();
20-
for instruction in &block.insns {
22+
// Exception successors observe the register state immediately before the
23+
// terminal throwing instruction. Its result exists only on normal exit.
24+
let exceptional_boundary = cfg
25+
.successors_with_kind(block.id)
26+
.iter()
27+
.any(|(_, kind)| kind.is_exception())
28+
.then(|| {
29+
block
30+
.insns
31+
.iter()
32+
.rposition(|instruction| instruction.can_throw())
33+
})
34+
.flatten();
35+
for (index, instruction) in block.insns.iter().enumerate() {
2136
let uses = instruction
2237
.args
2338
.iter()
@@ -36,9 +51,13 @@ impl RegisterLiveness {
3651
}
3752
if let Some(result) = &instruction.result {
3853
block_definitions.insert(result.reg_num);
54+
if exceptional_boundary.is_none_or(|boundary| index < boundary) {
55+
exceptional_definitions.insert(result.reg_num);
56+
}
3957
}
4058
}
4159
definitions.insert(block.id, block_definitions);
60+
definitions_before_exception.insert(block.id, exceptional_definitions);
4261
upward_uses.insert(block.id, block_uses);
4362
}
4463

@@ -51,13 +70,32 @@ impl RegisterLiveness {
5170
loop {
5271
let mut changed = false;
5372
for block in cfg.block_ids().into_iter().rev() {
54-
let output = cfg
55-
.successors(block)
56-
.flat_map(|successor| live_in.get(&successor).into_iter().flatten().copied())
73+
let mut normal_output = BTreeSet::new();
74+
let mut exceptional_output = BTreeSet::new();
75+
for (successor, kind) in cfg.successors_with_kind(block) {
76+
let output = if kind.is_exception() {
77+
&mut exceptional_output
78+
} else {
79+
&mut normal_output
80+
};
81+
output.extend(live_in.get(successor).into_iter().flatten().copied());
82+
}
83+
let output = normal_output
84+
.union(&exceptional_output)
85+
.copied()
5786
.collect::<BTreeSet<_>>();
5887
let mut input = upward_uses.get(&block).cloned().unwrap_or_default();
5988
let defined = definitions.get(&block).cloned().unwrap_or_default();
60-
input.extend(output.difference(&defined).copied());
89+
let defined_before_exception = definitions_before_exception
90+
.get(&block)
91+
.cloned()
92+
.unwrap_or_default();
93+
input.extend(normal_output.difference(&defined).copied());
94+
input.extend(
95+
exceptional_output
96+
.difference(&defined_before_exception)
97+
.copied(),
98+
);
6199
if live_out.get(&block) != Some(&output) {
62100
live_out.insert(block, output);
63101
changed = true;

dexdec/src/ir/analysis/source_variables.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,8 @@ impl SourceVariableAllocation {
5454
regions: &RegionGraph,
5555
) -> Result<Self, SourceVariableError> {
5656
let contractions = ControlContractions::for_edge_arguments(cfg, regions);
57-
let required_phis = RequiredPhiValues::collect(
58-
cfg,
59-
values,
60-
root,
61-
constants,
62-
recovered_phis,
63-
&contractions,
64-
)?;
57+
let required_phis =
58+
RequiredPhiValues::collect(cfg, values, root, constants, &contractions)?;
6559
let statement_definitions = StatementDefinitions::collect(root);
6660
let mut cleanup_values = regions
6761
.cleanup_value_bindings()

dexdec/src/ir/analysis/source_variables/phi.rs

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,6 @@ impl RequiredPhiValues {
505505
graph: &SsaValueGraph,
506506
root: &SemanticNode,
507507
constants: &BTreeMap<SsaVar, InsnArg>,
508-
recovered: &BTreeSet<SsaVar>,
509508
contractions: &ControlContractions,
510509
) -> Result<BTreeSet<SsaVar>, SourceVariableError> {
511510
let mut collector = Self {
@@ -520,7 +519,13 @@ impl RequiredPhiValues {
520519
let edge_arguments = ContractedEdgeArguments::new(cfg, graph, constants, contractions);
521520
while let Some(value) = pending.pop() {
522521
if !visited.insert(value)
523-
|| (recovered.contains(&value) && materialized.contains(&value))
522+
// A semantic statement owns its rewritten dependencies. In
523+
// particular, gated value recovery can replace a CFG move
524+
// from a Phi with a Select expression while retaining the
525+
// move's result identity. Following the stale CFG operand
526+
// would lower that already-recovered Phi a second time and
527+
// place copies before the Select's branch definitions.
528+
|| materialized.contains(&value)
524529
|| PhiCopies::canonical_constant(constants, value).is_some()
525530
{
526531
continue;
@@ -2837,6 +2842,67 @@ mod tests {
28372842
assert_eq!(resolver.physical_type(moved_value), Some(ArgType::BOOLEAN));
28382843
}
28392844

2845+
#[test]
2846+
fn semantic_select_definition_does_not_rematerialize_its_cfg_phi() {
2847+
let entry = BlockId::new(0);
2848+
let left_block = BlockId::new(1);
2849+
let right_block = BlockId::new(2);
2850+
let join = BlockId::new(3);
2851+
let left = RegisterArg::new_ssa(1, 0, ArgType::INT);
2852+
let right = RegisterArg::new_ssa(1, 1, ArgType::INT);
2853+
let phi_result = RegisterArg::new_ssa(1, 2, ArgType::INT);
2854+
let moved = RegisterArg::new_ssa(3, 0, ArgType::INT);
2855+
let phi_value = SsaVar::from_reg(&phi_result).expect("phi SSA value");
2856+
2857+
let mut cfg = CFG::new("recovered_select_move");
2858+
cfg.entry = entry;
2859+
cfg.add_block(Block::new(entry));
2860+
let mut left_body = Block::new(left_block);
2861+
left_body.push(InsnNode::const_value(left.clone(), 1));
2862+
cfg.add_block(left_body);
2863+
let mut right_body = Block::new(right_block);
2864+
right_body.push(InsnNode::const_value(right.clone(), 2));
2865+
cfg.add_block(right_body);
2866+
let mut join_body = Block::new(join);
2867+
join_body.push(InsnNode::phi(
2868+
phi_result.clone(),
2869+
vec![
2870+
(left_block.raw(), InsnArg::Reg(left.clone())),
2871+
(right_block.raw(), InsnArg::Reg(right.clone())),
2872+
],
2873+
));
2874+
join_body.push(InsnNode::move_insn(moved.clone(), InsnArg::Reg(phi_result)));
2875+
cfg.add_block(join_body);
2876+
cfg.add_edge(entry, left_block, EdgeKind::True);
2877+
cfg.add_edge(entry, right_block, EdgeKind::False);
2878+
cfg.add_edge(left_block, join, EdgeKind::Normal);
2879+
cfg.add_edge(right_block, join, EdgeKind::Normal);
2880+
2881+
let values = SsaValueGraph::build(&cfg).expect("SSA graph");
2882+
let root = SemanticNode::BasicBlock(SemanticBlock {
2883+
id: join,
2884+
statements: vec![SemanticStatement::definition(
2885+
InstructionId::new(7),
2886+
moved,
2887+
SemanticExpression::select(
2888+
crate::ir::SemanticPredicate::True,
2889+
SemanticExpression::Register(left),
2890+
SemanticExpression::Register(right),
2891+
),
2892+
)],
2893+
});
2894+
let required = RequiredPhiValues::collect(
2895+
&cfg,
2896+
&values,
2897+
&root,
2898+
&BTreeMap::new(),
2899+
&ControlContractions::identity(),
2900+
)
2901+
.expect("required Phi analysis");
2902+
2903+
assert!(!required.contains(&phi_value));
2904+
}
2905+
28402906
#[test]
28412907
fn copy_resolver_selects_nested_phi_value_for_exception_edge() {
28422908
let entry = BlockId::new(3);

dexdec/src/ir/cfg.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,14 @@ impl CFG {
561561
self.preds_dirty = true;
562562
}
563563

564+
/// Remove one typed edge while preserving parallel edges with a different kind.
565+
pub fn remove_edge_with_kind(&mut self, from: BlockId, to: BlockId, kind: EdgeKind) {
566+
if let Some(successors) = self.successors.get_mut(&from) {
567+
successors.retain(|edge| *edge != (to, kind));
568+
}
569+
self.preds_dirty = true;
570+
}
571+
564572
/// Get the kind of edge between two blocks, if it exists.
565573
pub fn get_edge_kind(&self, from: BlockId, to: BlockId) -> Option<EdgeKind> {
566574
self.successors

0 commit comments

Comments
 (0)