Skip to content

Commit 250e2c1

Browse files
authored
Merge pull request #15 from LLeavesG/fix/recovered-phi-inputs
Preserve recovered phi inputs
2 parents dea9008 + f967d2c commit 250e2c1

2 files changed

Lines changed: 141 additions & 5 deletions

File tree

dexdec/src/ir/analysis/source_variables.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ impl SourceVariableAllocation {
9090
let liveness = SsaLiveness::analyze(cfg, &retained)?;
9191
let edge_arguments =
9292
edge_arguments::ContractedEdgeArguments::new(cfg, values, constants, &contractions);
93-
let mut interference = InterferenceGraph::build(cfg, root, &liveness)?;
93+
let mut interference = InterferenceGraph::build(cfg, root, &liveness, &required_phis)?;
9494
interference.add_exceptional_edge_interference(cfg, &liveness, &edge_arguments)?;
9595
let classes = SsaClasses::new(
9696
values

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

Lines changed: 140 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ impl InterferenceGraph {
202202
cfg: &CFG,
203203
semantic: &SemanticNode,
204204
liveness: &SsaLiveness,
205+
required_phis: &BTreeSet<SsaVar>,
205206
) -> Result<Self, SourceVariableError> {
206207
let mut graph = Self::default();
207208
if let Some(inputs) = liveness.live_in.get(&cfg.entry) {
@@ -249,7 +250,9 @@ impl InterferenceGraph {
249250
return Err(SourceVariableError::LivenessMismatch(block));
250251
}
251252
}
252-
SemanticLiveness::analyze(semantic, &liveness.retained)?.add_interference(&mut graph);
253+
let semantic_liveness = SemanticLiveness::analyze(semantic, &liveness.retained)?;
254+
semantic_liveness.add_recovered_phi_interference(cfg, required_phis, &mut graph);
255+
semantic_liveness.add_interference(&mut graph);
253256
Ok(graph)
254257
}
255258

@@ -324,6 +327,7 @@ impl InterferenceGraph {
324327
struct SemanticSiteFacts {
325328
uses: BTreeSet<SsaVar>,
326329
definitions: BTreeSet<SsaVar>,
330+
blocks: BTreeSet<BlockId>,
327331
}
328332

329333
struct SemanticLiveness {
@@ -400,6 +404,60 @@ impl SemanticLiveness {
400404
}
401405
}
402406
}
407+
408+
fn add_recovered_phi_interference(
409+
&self,
410+
cfg: &CFG,
411+
required_phis: &BTreeSet<SsaVar>,
412+
graph: &mut InterferenceGraph,
413+
) {
414+
let mut uses_by_block = BTreeMap::<BlockId, BTreeSet<SsaVar>>::new();
415+
for facts in self.sites.values() {
416+
for block in &facts.blocks {
417+
uses_by_block
418+
.entry(*block)
419+
.or_default()
420+
.extend(facts.uses.iter().copied());
421+
}
422+
}
423+
424+
for (block, uses) in uses_by_block {
425+
let Some(body) = cfg.block(block) else {
426+
continue;
427+
};
428+
for phi in body
429+
.insns
430+
.iter()
431+
.filter(|instruction| instruction.insn_type == InsnType::Phi)
432+
{
433+
let Some(result) = phi.result.as_ref().and_then(SsaVar::from_reg) else {
434+
continue;
435+
};
436+
if !required_phis.contains(&result) {
437+
continue;
438+
}
439+
let inputs = phi
440+
.args
441+
.iter()
442+
.filter_map(InsnArg::as_register)
443+
.filter_map(SsaVar::from_reg)
444+
.collect::<BTreeSet<_>>();
445+
// Value recovery can synthesize a use of a pre-Phi SSA value
446+
// in a statement originating in the Phi block. The CFG never
447+
// contained that use, so physical liveness alone would allow
448+
// the Phi result to share its source variable and its edge
449+
// copy would overwrite the old value before the recovered
450+
// expression reads it.
451+
for usage in uses
452+
.intersection(&inputs)
453+
.copied()
454+
.filter(|usage| *usage != result)
455+
{
456+
graph.add(result, usage);
457+
}
458+
}
459+
}
460+
}
403461
}
404462

405463
struct SemanticSiteCollector<'a> {
@@ -517,6 +575,9 @@ impl<'a> SemanticSiteCollector<'a> {
517575
.site
518576
.ok_or(SourceVariableError::MissingSemanticSite("statement"))?;
519577
self.site(site);
578+
if let Some(origin) = &statement.origin {
579+
self.site(site).blocks.insert(origin.block);
580+
}
520581
match &statement.kind {
521582
SemanticStatementKind::Instruction(operation) => {
522583
self.add_uses(
@@ -679,7 +740,7 @@ impl InstructionVisitor for InstructionUses {
679740
#[cfg(test)]
680741
mod tests {
681742
use super::{InterferenceGraph, SsaLiveness};
682-
use crate::ir::{analysis::SsaVar, ArgType, Block, IfOp, InsnArg, InsnNode, CFG};
743+
use crate::ir::{analysis::SsaVar, ArgType, Block, IfOp, InsnArg, InsnNode, RegisterArg, CFG};
683744
use std::collections::BTreeSet;
684745

685746
#[test]
@@ -698,12 +759,87 @@ mod tests {
698759

699760
let retained = BTreeSet::from([left, right]);
700761
let liveness = SsaLiveness::analyze(&cfg, &retained).unwrap();
701-
let interference =
702-
InterferenceGraph::build(&cfg, &crate::ir::SemanticNode::Empty, &liveness).unwrap();
762+
let interference = InterferenceGraph::build(
763+
&cfg,
764+
&crate::ir::SemanticNode::Empty,
765+
&liveness,
766+
&BTreeSet::new(),
767+
)
768+
.unwrap();
703769

704770
assert!(interference
705771
.hard_edges
706772
.get(&left)
707773
.is_some_and(|neighbors| neighbors.contains(&right)));
708774
}
775+
776+
#[test]
777+
fn recovered_same_block_use_interferes_with_required_phi_result() {
778+
use crate::ir::{
779+
BlockId, InstructionId, SemanticBlock, SemanticNode, SemanticSiteNumbering,
780+
SemanticStatement, StatementOrigin,
781+
};
782+
783+
let predecessor = BlockId::new(0);
784+
let join = BlockId::new(1);
785+
let old = RegisterArg::new_ssa(0, 0, ArgType::INT);
786+
let unrelated = RegisterArg::new_ssa(2, 0, ArgType::INT);
787+
let phi_result = RegisterArg::new_ssa(0, 1, ArgType::INT);
788+
let old_value = SsaVar::from_reg(&old).unwrap();
789+
let unrelated_value = SsaVar::from_reg(&unrelated).unwrap();
790+
let phi_value = SsaVar::from_reg(&phi_result).unwrap();
791+
792+
let mut predecessor_block = Block::new(predecessor);
793+
predecessor_block.push(InsnNode::const_value(old.clone(), 0));
794+
predecessor_block.push(InsnNode::const_value(unrelated.clone(), 1));
795+
let mut join_block = Block::new(join);
796+
join_block.push(InsnNode::phi(
797+
phi_result,
798+
vec![(predecessor.raw(), InsnArg::Reg(old.clone()))],
799+
));
800+
let mut cfg = CFG::new("recovered-phi-use");
801+
cfg.entry = predecessor;
802+
cfg.add_block(predecessor_block);
803+
cfg.add_block(join_block);
804+
cfg.add_edge(predecessor, join, crate::ir::EdgeKind::Normal);
805+
806+
let mut statement = SemanticStatement::instruction(InsnNode::mov(
807+
RegisterArg::new_ssa(1, 0, ArgType::INT),
808+
InsnArg::Reg(old),
809+
))
810+
.unwrap();
811+
statement.origin = Some(StatementOrigin {
812+
block: join,
813+
instruction: InstructionId::new(0),
814+
});
815+
let mut unrelated_statement = SemanticStatement::instruction(InsnNode::mov(
816+
RegisterArg::new_ssa(3, 0, ArgType::INT),
817+
InsnArg::Reg(unrelated),
818+
))
819+
.unwrap();
820+
unrelated_statement.origin = Some(StatementOrigin {
821+
block: join,
822+
instruction: InstructionId::new(0),
823+
});
824+
let mut semantic = SemanticNode::BasicBlock(SemanticBlock {
825+
id: join,
826+
statements: vec![statement, unrelated_statement],
827+
});
828+
SemanticSiteNumbering::assign(&mut semantic).unwrap();
829+
830+
let retained = BTreeSet::from([old_value, unrelated_value, phi_value]);
831+
let liveness = SsaLiveness::analyze(&cfg, &retained).unwrap();
832+
let interference =
833+
InterferenceGraph::build(&cfg, &semantic, &liveness, &BTreeSet::from([phi_value]))
834+
.unwrap();
835+
836+
assert!(interference
837+
.hard_edges
838+
.get(&phi_value)
839+
.is_some_and(|neighbors| neighbors.contains(&old_value)));
840+
assert!(!interference
841+
.hard_edges
842+
.get(&phi_value)
843+
.is_some_and(|neighbors| neighbors.contains(&unrelated_value)));
844+
}
709845
}

0 commit comments

Comments
 (0)