Merge branch 'feature/fix-exit-ssa'

This commit is contained in:
Pietro Fezzardi
2026-06-19 15:27:57 +02:00
3 changed files with 484 additions and 20 deletions
+126 -20
View File
@@ -22,6 +22,7 @@
#include "llvm/IR/Function.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/ModuleSlotTracker.h"
#include "llvm/Pass.h"
#include "llvm/Support/Casting.h"
@@ -53,38 +54,119 @@ struct IncomingInfo {
};
static bool haveIncompatibleIncomings(const std::set<IncomingInfo> &LHS,
const std::set<IncomingInfo> &RHS) {
const std::set<IncomingInfo> &RHS,
llvm::ModuleSlotTracker &MST) {
if (Log.isEnabled()) {
{
revng_log(Log, "LHS: {");
for (const auto &[PHIBlock, IncomingBlock, IncomingValue] : LHS) {
LoggerIndent LHSIndent{ Log };
revng_log(Log, "{");
{
LoggerIndent MoreIndent{ Log };
revng_log(Log, "PHIBlock: " << PHIBlock->getName());
revng_log(Log, "IncomingBlock: " << IncomingBlock->getName());
revng_log(Log, "IncomingValue: " << dumpToString(IncomingValue, MST));
}
revng_log(Log, "}");
}
revng_log(Log, "}");
}
{
revng_log(Log, "RHS: {");
for (const auto &[PHIBlock, IncomingBlock, IncomingValue] : RHS) {
LoggerIndent LHSIndent{ Log };
revng_log(Log, "{");
{
LoggerIndent MoreIndent{ Log };
revng_log(Log, "PHIBlock: " << PHIBlock->getName());
revng_log(Log, "IncomingBlock: " << IncomingBlock->getName());
revng_log(Log, "IncomingValue: " << dumpToString(IncomingValue, MST));
}
revng_log(Log, "}");
}
revng_log(Log, "}");
}
}
revng_log(Log, "Evaluating incompatibility");
for (const auto &[PHIBlock, IncomingBlock, IncomingValue] : LHS) {
LoggerIndent IndentIncompatibilityCheck{ Log };
{
revng_log(Log, "LHS: {");
{
LoggerIndent MoreIndent{ Log };
revng_log(Log, "PHIBlock: " << PHIBlock->getName());
revng_log(Log, "IncomingBlock: " << IncomingBlock->getName());
revng_log(Log, "IncomingValue: " << dumpToString(IncomingValue, MST));
}
revng_log(Log, "}");
}
auto It = RHS.lower_bound(IncomingInfo{ PHIBlock, IncomingBlock, nullptr });
auto End = RHS.upper_bound(IncomingInfo{
PHIBlock, IncomingBlock, std::numeric_limits<Value *>::max() });
PHIBlock,
IncomingBlock,
(Value *) std::numeric_limits<intptr_t>::max() });
// If RHS contains a PHI that is in the same block as PHIBlock, and has a
// different incoming value on the same incoming block, the two are
// incompatible, because they would assign two different values to the same
// local variable along the same edge.
if (It != End and IncomingValue != It->IncomingValue)
return false;
while (It != End) {
revng_log(Log, "RHS: {");
{
LoggerIndent MoreIndent{ Log };
revng_log(Log, "It->PHIBlock: " << It->PHIBlock->getName());
revng_log(Log, "It->IncomingBlock: " << It->IncomingBlock->getName());
revng_log(Log,
"It->IncomingValue: " << dumpToString(It->IncomingValue,
MST));
}
revng_log(Log, "}");
if (IncomingValue != It->IncomingValue) {
revng_log(Log, "conflicting!");
return true;
}
++It;
}
}
return true;
return false;
}
static std::vector<SetVector<PHINode *>> getPHIEquivalenceClasses(Function &F) {
llvm::ModuleSlotTracker MST(F.getParent(),
/* ShouldInitializeAllMetadata = */ false);
if (Log.isEnabled()) {
MST.incorporateFunction(F);
F.viewCFG();
}
revng_log(Log, "getPHIEquivalenceClasses for Function: " << F.getName());
LoggerIndent Indent{ Log };
// PHINodes in the same class are mapped onto the same local variable.
llvm::EquivalenceClasses<PHINode *> PHISameVariableClasses;
std::unordered_map<PHINode *, std::set<IncomingInfo>> PerClassIncomings;
const auto InitVariableClass = [&PHISameVariableClasses,
&PerClassIncomings](PHINode *PHI) {
if (PHISameVariableClasses.findValue(PHI) != PHISameVariableClasses.end())
&PerClassIncomings,
&MST](PHINode *PHI) {
revng_log(Log, "InitVariableClass for PHI: " << dumpToString(PHI, MST));
LoggerIndent InitIndent{ Log };
if (PHISameVariableClasses.findValue(PHI) != PHISameVariableClasses.end()) {
revng_log(Log, "found");
return;
}
PHISameVariableClasses.insert(PHI);
revng_log(Log, "new");
auto &CurrentIncomingInfo = PerClassIncomings[PHI];
unsigned NumIncomings = PHI->getNumIncomingValues();
BasicBlock *PHIBlock = PHI->getParent();
revng_log(Log, "NumIncomings: " << NumIncomings);
LoggerIndent IncomingIndent{ Log };
for (unsigned I = 0U; I < NumIncomings; ++I) {
Value *IncomingValue = PHI->getIncomingValue(I);
BasicBlock *IncomingBlock = PHI->getIncomingBlock(I);
@@ -92,22 +174,30 @@ static std::vector<SetVector<PHINode *>> getPHIEquivalenceClasses(Function &F) {
IncomingBlock,
IncomingValue };
CurrentIncomingInfo.insert(std::move(NewIncomingInfo));
revng_log(Log, "I = " << I << ": " << dumpToString(IncomingValue, MST));
}
return;
};
for (BasicBlock *BB : llvm::ReversePostOrderTraversal(&F)) {
LoggerIndent PHIIndent{ Log };
for (auto &PHI : BB->phis()) {
revng_log(Log, "From PHI: " << dumpToString(PHI, MST));
// Set up an equivalence class for PHI, if necessary
InitVariableClass(&PHI);
// Then, for each user, if it's a PHINode, try to see if we can insert it
// in the same equivalence class as PHI.
LoggerIndent UserIndent{ Log };
for (User *U : PHI.users()) {
revng_log(Log, "PHIUser: " << dumpToString(U, MST));
LoggerIndent MoreUserIndent{ Log };
auto *PHIUser = dyn_cast<PHINode>(U);
if (not PHIUser or PHIUser == &PHI)
if (not PHIUser or PHIUser == &PHI) {
revng_log(Log, "not a PHINode");
continue;
}
// Set up an equivalence class for PHIUser, if necessary.
// Sometimes this might not be necessary, because we might have already
@@ -119,14 +209,11 @@ static std::vector<SetVector<PHINode *>> getPHIEquivalenceClasses(Function &F) {
// If PHI and PHIUser are already in the same equivalence class, there's
// nothing to do.
if (PHISameVariableClasses.isEquivalent(&PHI, PHIUser))
continue;
// If the PHI has a user that is not another PHI, it cannot be put in
// the same equivalence class as the PHIUser, so we bail out.
if (llvm::any_of(PHI.users(),
[](const User *U) { return not isa<PHINode>(U); }))
if (PHISameVariableClasses.isEquivalent(&PHI, PHIUser)) {
revng_log(Log, "PHI and PHIUser are equivalent");
continue;
}
revng_log(Log, "PHI and PHIUser are NOT equivalent");
PHINode *PHILeader = PHISameVariableClasses.getLeaderValue(&PHI);
PHINode *UserLeader = PHISameVariableClasses.getLeaderValue(PHIUser);
@@ -141,8 +228,12 @@ static std::vector<SetVector<PHINode *>> getPHIEquivalenceClasses(Function &F) {
// hold different values that must be kept alive at the same time,
// otherwise we'll lose one of them. In this case we have to bail out.
if (haveIncompatibleIncomings(PHIIncomingInfo->second,
UserIncomingInfo->second))
UserIncomingInfo->second,
MST)) {
revng_log(Log, "PHI and PHIUser haveIncompatibleIncomings");
continue;
}
revng_log(Log, "PHI and PHIUser DON'T haveIncompatibleIncomings");
// Here the two are compatible so we join the equivalence classes.
PHISameVariableClasses.unionSets(&PHI, PHIUser);
@@ -186,6 +277,21 @@ static std::vector<SetVector<PHINode *>> getPHIEquivalenceClasses(Function &F) {
}
}
if (Log.isEnabled()) {
revng_log(Log, "Result (size " << Result.size() << ") = {");
size_t I = 0;
for (const auto &Class : Result) {
LoggerIndent IndentResult{ Log };
revng_log(Log, "Class (size " << Class.size() << "): {" << I++);
for (const PHINode *PHI : Class) {
LoggerIndent IndentClass{ Log };
revng_log(Log, dumpToString(PHI, MST));
}
revng_log(Log, "};");
}
revng_log(Log, "};");
}
return Result;
}
@@ -433,11 +539,11 @@ static void replacePHIEquivalenceClass(const SetVector<PHINode *> &PHIs,
for (Use &U : llvm::make_early_inc_range(PHI->uses())) {
revng_log(Log, "in User: " << dumpToString(U.getUser()));
Value *NewOperand = nullptr;
if (isa<PHINode>(U.getUser()))
Value *NewOperand = NewLoad;
if (auto *PHIUser = dyn_cast<PHINode>(U.getUser());
PHIUser and PHIs.contains(PHIUser)) {
NewOperand = UndefValue::get(PHI->getType());
else
NewOperand = NewLoad;
}
revng_log(Log, "replaced with: " << dumpToString(NewOperand));
U.set(NewOperand);
}
+1
View File
@@ -401,6 +401,7 @@ set(FILECHECK_TESTS
llvm-passes/detect-uninlinable-helpers.ll
llvm-passes/enforce-single-exit.ll
llvm-passes/example.ll
llvm-passes/exit-ssa.ll
llvm-passes/generic-region-info.ll
llvm-passes/inline-divergent-scopes-and-materialize-trivial-goto.ll
llvm-passes/inline-divergent-scopes.ll
+357
View File
@@ -0,0 +1,357 @@
;
; This file is distributed under the MIT License. See LICENSE.md for details.
;
; RUN: %root/bin/revng opt %s -exit-ssa -S -o - | FileCheck %s
;
; CHECK-LABEL: define i32 @double_swap(i32 %a, i32 %b)
;
define i32 @double_swap(i32 %a, i32 %b) {
; CHECK: entry:
entry:
; CHECK: [[ALLOCA0:%[a-zA-Z0-9_]+]] = alloca i32
; CHECK: [[ALLOCA1:%[a-zA-Z0-9_]+]] = alloca i32
; CHECK: store i32 %b, ptr [[ALLOCA0]]
; CHECK: store i32 %a, ptr [[ALLOCA1]]
; CHECK: br
br label %while.cond
; CHECK: while.cond:
while.cond:
; CHECK: [[LOAD1:%[a-zA-Z0-9_]+]] = load i32, ptr [[ALLOCA1]]
; CHECK: [[LOAD0:%[a-zA-Z0-9_]+]] = load i32, ptr [[ALLOCA0]]
; CHECK: store i32 [[LOAD0]], ptr [[ALLOCA1]]
; CHECK: store i32 [[LOAD1]], ptr [[ALLOCA0]]
%b.addr.0 = phi i32 [ %b, %entry ], [ %a.addr.0, %while.cond ]
%a.addr.0 = phi i32 [ %a, %entry ], [ %b.addr.0, %while.cond ]
br i1 undef, label %while.end, label %while.cond
; CHECK: br
; CHECK: while.end:
while.end:
; CHECK: sub i32 [[LOAD1]], [[LOAD0]]
%sub = sub i32 %a.addr.0, %b.addr.0
ret i32 %sub
}
;
; CHECK-LABEL: define i32 @lost_copy(i32 %a)
;
define i32 @lost_copy(i32 %a) {
; CHECK: entry:
entry:
; CHECK: [[ALLOCA:%[a-zA-Z0-9_]+]] = alloca i32
; CHECK-NOT: alloca
; CHECK: store i32 %a, ptr [[ALLOCA]]
br label %while.cond
; CHECK: br
; CHECK: while.cond:
while.cond:
; CHECK: [[LOAD:%[a-zA-Z0-9_]+]] = load i32, ptr [[ALLOCA]]
; CHECK: [[ADD:%[a-zA-Z0-9_]+]] = add i32 [[LOAD]], 1
; CHECK: store i32 [[ADD]], ptr [[ALLOCA]]
%b = phi i32 [ %a, %entry ], [ %c, %while.cond ]
%c = add i32 %b, 1
br i1 undef, label %while.end, label %while.cond
; CHECK: br
; CHECK: while.end:
while.end:
; CHECK: ret i32 [[LOAD]]
ret i32 %b
}
;
; CHECK-LABEL: define i32 @chain(i32 %a, i32 %b, i32 %c)
;
; Two distinct PHINodes where the first (%p1) is an incoming of the second
; (%p2), and both also have non-PHI users (%u1 and %u2). They are collapsed
; onto the same alloca.
define i32 @chain(i32 %a, i32 %b, i32 %c) {
; CHECK: entry:
entry:
; CHECK: [[ALLOCA:%[a-zA-Z0-9_]+]] = alloca i32
; CHECK-NOT: alloca
br i1 undef, label %A, label %B
; CHECK: A:
A:
; CHECK: store i32 %a, ptr [[ALLOCA]]
br label %M1
; CHECK: B:
B:
; CHECK: store i32 %b, ptr [[ALLOCA]]
br label %M1
; CHECK: M1:
M1:
; CHECK: [[LOAD1:%[a-zA-Z0-9_]+]] = load i32, ptr [[ALLOCA]]
; CHECK: add i32 [[LOAD1]], 1
%p1 = phi i32 [ %a, %A ], [ %b, %B ]
%u1 = add i32 %p1, 1
br i1 undef, label %C, label %M2
; CHECK: C:
C:
; CHECK: store i32 %c, ptr [[ALLOCA]]
br label %M2
; CHECK: M2:
M2:
; CHECK: [[LOAD2:%[a-zA-Z0-9_]+]] = load i32, ptr [[ALLOCA]]
; CHECK: add i32 [[LOAD2]], 2
%p2 = phi i32 [ %p1, %M1 ], [ %c, %C ]
%u2 = add i32 %p2, 2
%r = add i32 %u1, %u2
ret i32 %r
}
;
; CHECK-LABEL: define i32 @cone(i32 %a, i32 %b, i32 %c, i32 %d)
;
; A cone (DAG) of PHINodes coming from a nested if-else: %p1 and %p2 are both
; incomings of %pt. The whole cone is collapsed onto a single alloca, with no
; edge splitting (each incoming block contributes a single value).
define i32 @cone(i32 %a, i32 %b, i32 %c, i32 %d) {
; CHECK: entry:
entry:
; CHECK: [[ALLOCA:%[a-zA-Z0-9_]+]] = alloca i32
; CHECK-NOT: alloca
br i1 undef, label %if1, label %else1
; CHECK: if1:
if1:
; CHECK: store i32 %a, ptr [[ALLOCA]]
br i1 undef, label %m1, label %f1
; CHECK: f1:
f1:
; CHECK: store i32 %b, ptr [[ALLOCA]]
br label %m1
m1:
%p1 = phi i32 [ %a, %if1 ], [ %b, %f1 ]
br label %exit
; CHECK: else1:
else1:
; CHECK: store i32 %c, ptr [[ALLOCA]]
br i1 undef, label %m2, label %f2
; CHECK: f2:
f2:
; CHECK: store i32 %d, ptr [[ALLOCA]]
br label %m2
m2:
%p2 = phi i32 [ %c, %else1 ], [ %d, %f2 ]
br label %exit
; CHECK: exit:
exit:
; CHECK: load i32, ptr [[ALLOCA]]
%pt = phi i32 [ %p1, %m1 ], [ %p2, %m2 ]
%u = add i32 %pt, 0
ret i32 %u
}
;
; CHECK-LABEL: define i32 @edgesplit(i32 %a, i32 %b, i32 %c, i1 %t)
;
; %p1 and %p2 belong to the same equivalence class, but from the same incoming
; block (%B1) they receive different values (%a for %p1, %b for %p2). Only one
; store can live in %B1, so the edges towards the other PHIs are split into new
; blocks where the remaining stores are emitted.
define i32 @edgesplit(i32 %a, i32 %b, i32 %c, i1 %t) {
; CHECK: entry:
entry:
; CHECK: [[ALLOCA:%[a-zA-Z0-9_]+]] = alloca i32
br i1 undef, label %B1, label %B2
; CHECK: B1:
B1:
; CHECK: store i32 %a, ptr [[ALLOCA]]
; CHECK: br i1 %t, label %J1, label %B1-to-J2
br i1 %t, label %J1, label %J2
; CHECK: B2:
B2:
; CHECK: store i32 %b, ptr [[ALLOCA]]
; CHECK: br i1 %t, label %J1, label %B2-to-J2
br i1 %t, label %J1, label %J2
J1:
%p1 = phi i32 [ %a, %B1 ], [ %b, %B2 ]
br label %exit
J2:
%p2 = phi i32 [ %b, %B1 ], [ %c, %B2 ]
br label %exit
; CHECK: exit:
exit:
; CHECK: load i32, ptr [[ALLOCA]]
%pt = phi i32 [ %p1, %J1 ], [ %p2, %J2 ]
%u = add i32 %pt, 0
ret i32 %u
; The remaining stores live in the freshly split edge blocks.
; CHECK: B1-to-J2:
; CHECK: store i32 %b, ptr [[ALLOCA]]
; CHECK: br label %J2
; CHECK: B2-to-J2:
; CHECK: store i32 %c, ptr [[ALLOCA]]
; CHECK: br label %J2
}
;
; CHECK-LABEL: define i32 @conflicting(i32 %a1, i32 %a2, i32 %x, i32 %y)
;
; %a is an incoming of both %b and %c, but from different predecessors (%P1 for
; %b, %P2 for %c). %b and %c hold conflicting values, so %a, %b and %c cannot be
; merged into a single class: two allocas are needed.
define i32 @conflicting(i32 %a1, i32 %a2, i32 %x, i32 %y) {
; CHECK: entry:
entry:
; CHECK: [[ALLOCA0:%[a-zA-Z0-9_]+]] = alloca i32
; CHECK: [[ALLOCA1:%[a-zA-Z0-9_]+]] = alloca i32
; CHECK-NOT: alloca
br i1 undef, label %e1, label %e2
; CHECK: e1:
e1:
; CHECK: store i32 %a1, ptr [[ALLOCA1]]
br label %B1
; CHECK: e2:
e2:
; CHECK: store i32 %a2, ptr [[ALLOCA1]]
br label %B1
; CHECK: B1:
B1:
; CHECK: [[PHIA:%[a-zA-Z0-9_]+]] = load i32, ptr [[ALLOCA1]]
%a = phi i32 [ %a1, %e1 ], [ %a2, %e2 ]
br i1 undef, label %P1, label %P2
; CHECK: P1:
P1:
; CHECK: store i32 [[PHIA]], ptr [[ALLOCA0]]
; CHECK: store i32 %y, ptr [[ALLOCA1]]
br label %B2
; CHECK: P2:
P2:
; CHECK: store i32 %x, ptr [[ALLOCA0]]
br label %B2
; CHECK: B2:
B2:
; The two PHINodes live in two distinct allocas.
; CHECK-DAG: load i32, ptr [[ALLOCA0]]
; CHECK-DAG: load i32, ptr [[ALLOCA1]]
%b = phi i32 [ %a, %P1 ], [ %x, %P2 ]
%c = phi i32 [ %y, %P1 ], [ %a, %P2 ]
%r = add i32 %b, %c
ret i32 %r
}
;
; CHECK-LABEL: define i32 @conflicting_2(i32 %a1, i32 %a2, i32 %x, i32 %x2)
;
; Like conflicting, but %a is an incoming of both %b and %c from the same predecessor
; (%P1). We still need two allocas, because %b and %c receive conflicting values
; (%x and %x2) from the other predecessor (%P2).
define i32 @conflicting_2(i32 %a1, i32 %a2, i32 %x, i32 %x2) {
; CHECK: entry:
entry:
; CHECK: [[ALLOCA0:%[a-zA-Z0-9_]+]] = alloca i32
; CHECK: [[ALLOCA1:%[a-zA-Z0-9_]+]] = alloca i32
; CHECK-NOT: alloca
br i1 undef, label %e1, label %e2
; CHECK: e1:
e1:
; CHECK: store i32 %a1, ptr [[ALLOCA1]]
br label %B1
; CHECK: e2:
e2:
; CHECK: store i32 %a2, ptr [[ALLOCA1]]
br label %B1
B1:
; CHECK: [[PHIA:%[a-zA-Z0-9_]+]] = load i32, ptr [[ALLOCA1]]
%a = phi i32 [ %a1, %e1 ], [ %a2, %e2 ]
br i1 undef, label %P1, label %P2
; CHECK: P1:
P1:
; CHECK: store i32 [[PHIA]], ptr [[ALLOCA0]]
br label %B2
; CHECK: P2:
P2:
; CHECK: store i32 %x, ptr [[ALLOCA0]]
; CHECK: store i32 %x2, ptr [[ALLOCA1]]
br label %B2
; CHECK: B2:
B2:
; The two PHINodes live in two distinct allocas.
; CHECK-DAG: load i32, ptr [[ALLOCA0]]
; CHECK-DAG: load i32, ptr [[ALLOCA1]]
%b = phi i32 [ %a, %P1 ], [ %x, %P2 ]
%c = phi i32 [ %a, %P1 ], [ %x2, %P2 ]
%r = add i32 %b, %c
ret i32 %r
}
;
; CHECK-LABEL: define i32 @non_conflicting(i32 %a1, i32 %a2, i32 %x)
;
; Like conflicting_2, but %b and %c receive the same values from every predecessor, so
; %a, %b and %c can all be grouped together and a single alloca is enough.
define i32 @non_conflicting(i32 %a1, i32 %a2, i32 %x) {
; CHECK: entry:
entry:
; CHECK: [[ALLOCA:%[a-zA-Z0-9_]+]] = alloca i32
; CHECK-NOT: alloca
br i1 undef, label %e1, label %e2
; CHECK: e1:
e1:
; CHECK: store i32 %a1, ptr [[ALLOCA]]
br label %B1
; CHECK: e2:
e2:
; CHECK: store i32 %a2, ptr [[ALLOCA]]
br label %B1
B1:
%a = phi i32 [ %a1, %e1 ], [ %a2, %e2 ]
br i1 undef, label %P1, label %P2
; CHECK: P1:
P1:
; CHECK-NOT: store
br label %B2
; CHECK: P2:
P2:
; CHECK: store i32 %x, ptr [[ALLOCA]]
br label %B2
; CHECK: B2:
B2:
; Both PHINodes share the same, single alloca.
; CHECK: load i32, ptr [[ALLOCA]]
; CHECK: load i32, ptr [[ALLOCA]]
%b = phi i32 [ %a, %P1 ], [ %x, %P2 ]
%c = phi i32 [ %a, %P1 ], [ %x, %P2 ]
%r = add i32 %b, %c
ret i32 %r
}