diff --git a/include/revng-c/Backend/VariableScopeAnalysis.h b/include/revng-c/Backend/VariableScopeAnalysis.h deleted file mode 100644 index fb53e28d8..000000000 --- a/include/revng-c/Backend/VariableScopeAnalysis.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -// -// Copyright rev.ng Labs Srl. See LICENSE.md for details. -// - -#include "llvm/ADT/SetVector.h" -#include "llvm/IR/BasicBlock.h" -#include "llvm/IR/Function.h" - -namespace llvm { - -class Function; -class Value; -class Instruction; - -} // end namespace llvm - -class ASTTree; - -/// Check if the GHAST has loop dispatchers, which indicates the need for -/// a loop state variable to be declared. -bool hasLoopDispatchers(const ASTTree &GHAST); - -/// Decide whether a single instruction needs a top-scope variable or not. -inline bool needsTopScopeDeclaration(const llvm::Instruction &I) { - const llvm::BasicBlock *CurBB = I.getParent(); - const llvm::BasicBlock &EntryBB = CurBB->getParent()->getEntryBlock(); - - if (CurBB == &EntryBB) { - // An instruction located in the first basic block of a function is already - // in the top scope, so there is no need to add a separate variable for it. - return false; - } - - // If the instruction has uses outside its own basic block, we need a top - // scope variable for it. - // TODO: we can further refine this logic introducing the concept of - // scopes and associating variable declarations to a scope. - // For now, we decided to declare all variables that have at least one - // use outside of their basic block right at the start of the - // function, which is correct but overly conservative. - auto HasDifferentParent = [Parent = I.getParent()](const llvm::User *U) { - return cast(U)->getParent() != Parent; - }; - return any_of(I.users(), HasDifferentParent); -} - -/// Returns a set of all the llvm::Values for which we need a top-level -/// variable declaration. -llvm::SmallSetVector -collectTopScopeVariables(const llvm::Function &F); diff --git a/include/revng-c/Support/DecompilationHelpers.h b/include/revng-c/Support/DecompilationHelpers.h index 4e3893e54..aadfa2e70 100644 --- a/include/revng-c/Support/DecompilationHelpers.h +++ b/include/revng-c/Support/DecompilationHelpers.h @@ -64,3 +64,27 @@ inline bool areMemOpCompatible(const model::QualifiedType &ModelType, return ModelSize * 8 == LLVMSize; } + +/// Decide whether a single instruction needs a top-scope variable or not. +inline bool needsTopScopeDeclaration(const llvm::Instruction &I) { + const llvm::BasicBlock *CurBB = I.getParent(); + const llvm::BasicBlock &EntryBB = CurBB->getParent()->getEntryBlock(); + + if (CurBB == &EntryBB) { + // An instruction located in the first basic block of a function is already + // in the top scope, so there is no need to add a separate variable for it. + return false; + } + + // If the instruction has uses outside its own basic block, we need a top + // scope variable for it. + // TODO: we can further refine this logic introducing the concept of + // scopes and associating variable declarations to a scope. + // For now, we decided to declare all variables that have at least one + // use outside of their basic block right at the start of the + // function, which is correct but overly conservative. + auto HasDifferentParent = [Parent = I.getParent()](const llvm::User *U) { + return cast(U)->getParent() != Parent; + }; + return any_of(I.users(), HasDifferentParent); +} diff --git a/include/revng-c/Support/FunctionTags.h b/include/revng-c/Support/FunctionTags.h index 34194ea9a..6242c1380 100644 --- a/include/revng-c/Support/FunctionTags.h +++ b/include/revng-c/Support/FunctionTags.h @@ -23,7 +23,6 @@ extern Tag AddressOf; extern Tag ModelGEP; extern Tag ModelCast; extern Tag ModelGEPRef; -extern Tag AssignmentMarker; extern Tag OpaqueExtractValue; extern Tag Parentheses; extern Tag HexInteger; @@ -140,8 +139,6 @@ using SegmentRefPoolKey = std::pair, void initSegmentRefPool(OpaqueFunctionsPool &Pool, llvm::Module *M); -llvm::Function *getAssignmentMarker(llvm::Module &M, llvm::Type *T); - /// Derive the function type of the corresponding OpaqueExtractValue() function /// from an ExtractValue instruction. OpaqueExtractValues wrap an /// ExtractValue to prevent it from being optimized out, so the return type and diff --git a/include/revng-c/Support/ModelHelpers.h b/include/revng-c/Support/ModelHelpers.h index 5e6ac80a7..c8ece8d4f 100644 --- a/include/revng-c/Support/ModelHelpers.h +++ b/include/revng-c/Support/ModelHelpers.h @@ -37,7 +37,7 @@ deserializeFromLLVMString(llvm::Value *V, const model::Binary &Model); /// Create a global string in the given LLVM module that contains a /// serialization of \a QT. llvm::Constant * -serializeToLLVMString(model::QualifiedType &QT, llvm::Module &M); +serializeToLLVMString(const model::QualifiedType &QT, llvm::Module &M); /// Return an LLVM IntegerType that has the size of a pointer in the given /// architecture. diff --git a/lib/Backend/CMakeLists.txt b/lib/Backend/CMakeLists.txt index 91e5b6dea..be550bba9 100644 --- a/lib/Backend/CMakeLists.txt +++ b/lib/Backend/CMakeLists.txt @@ -2,20 +2,6 @@ # Copyright rev.ng Labs Srl. See LICENSE.md for details. # -revng_add_analyses_library(revngcVariableScopeAnalysis revngc - VariableScopeAnalysis.cpp) - -target_link_libraries( - revngcVariableScopeAnalysis - revngcRestructureCFG - revngcSupport - revngcTypeNames - revng::revngModel - revng::revngPipes - revng::revngSupport - revng::revngPTML - ${LLVM_LIBRARIES}) - revng_add_analyses_library( revngcBackend revngc CDecompilationPipe.cpp DecompileFunction.cpp DecompiledYAMLToC.cpp DecompiledYAMLToCPipe.cpp) @@ -26,9 +12,9 @@ target_link_libraries( revngcRestructureCFG revngcSupport revngcTypeNames - revngcVariableScopeAnalysis revng::revngABI revng::revngModel revng::revngPipes + revng::revngPTML revng::revngSupport ${LLVM_LIBRARIES}) diff --git a/lib/Backend/DecompileFunction.cpp b/lib/Backend/DecompileFunction.cpp index 49017f39c..54d9e43a1 100644 --- a/lib/Backend/DecompileFunction.cpp +++ b/lib/Backend/DecompileFunction.cpp @@ -3,6 +3,7 @@ // #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" @@ -15,6 +16,7 @@ #include "llvm/IR/InstrTypes.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" +#include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Type.h" #include "llvm/IR/Value.h" #include "llvm/Support/Casting.h" @@ -42,7 +44,6 @@ #include "revng/Support/YAMLTraits.h" #include "revng-c/Backend/DecompileFunction.h" -#include "revng-c/Backend/VariableScopeAnalysis.h" #include "revng-c/InitModelTypes/InitModelTypes.h" #include "revng-c/Pipes/Ranks.h" #include "revng-c/RestructureCFG/ASTNode.h" @@ -95,6 +96,115 @@ static Logger<> Log{ "c-backend" }; static Logger<> VisitLog{ "c-backend-visit-order" }; static Logger<> InlineLog{ "c-backend-inline" }; +/// Visit the node and all its children recursively, checking if a loop +/// variable is needed. +// TODO: This could be precomputed and attached to the SCS node in the GHAST. +static RecursiveCoroutine needsLoopVar(ASTNode *N) { + if (N == nullptr) + rc_return false; + + auto Kind = N->getKind(); + switch (Kind) { + + case ASTNode::NodeKind::NK_Break: + case ASTNode::NodeKind::NK_SwitchBreak: + case ASTNode::NodeKind::NK_Continue: + case ASTNode::NodeKind::NK_Code: + rc_return false; + break; + + case ASTNode::NodeKind::NK_If: { + IfNode *If = cast(N); + + if (nullptr != If->getThen()) + if (rc_recur needsLoopVar(If->getThen())) + rc_return true; + + if (If->hasElse()) + if (rc_recur needsLoopVar(If->getElse())) + rc_return true; + + rc_return false; + } break; + + case ASTNode::NodeKind::NK_Scs: { + ScsNode *LoopBody = cast(N); + rc_return rc_recur needsLoopVar(LoopBody->getBody()); + } break; + + case ASTNode::NodeKind::NK_List: { + SequenceNode *Seq = cast(N); + for (ASTNode *Child : Seq->nodes()) + if (rc_recur needsLoopVar(Child)) + rc_return true; + + rc_return false; + } break; + + case ASTNode::NodeKind::NK_Switch: { + SwitchNode *Switch = cast(N); + llvm::Value *SwitchVar = Switch->getCondition(); + + if (not SwitchVar) + rc_return true; + + for (const auto &[Labels, CaseNode] : Switch->cases()) + if (rc_recur needsLoopVar(CaseNode)) + rc_return true; + + if (auto *Default = Switch->getDefault()) + if (rc_recur needsLoopVar(Default)) + rc_return true; + + rc_return false; + } break; + + case ASTNode::NodeKind::NK_Set: { + rc_return true; + } break; + } +} + +static bool hasLoopDispatchers(const ASTTree &GHAST) { + return needsLoopVar(GHAST.getRoot()); +} + +static InstrSetVec collectTopScopeVariables(const llvm::Function &F) { + InstrSetVec TopScopeVars; + + // We always want to put the stack frame among the top-scope variables, + // since it is is logical for it to appear at the top of the function even + // if it is used only in a later scope. + { + bool Found = false; + for (const BasicBlock &BB : F) { + for (const Instruction &I : BB) { + if (isCallTo(&I, "revng_stack_frame")) { + revng_assert(not Found); + TopScopeVars.insert(&I); + Found = true; + } + } + } + } + + for (const BasicBlock &BB : F) { + for (const Instruction &I : BB) { + // All the others have already been promoted to LocalVariable Copy and + // Assign. + if (isCallToTagged(&I, FunctionTags::QEMU) + or isCallToTagged(&I, FunctionTags::Helper) + or llvm::isa(I)) { + + if (needsTopScopeDeclaration(I)) + TopScopeVars.insert(&I); + } + } + } + + return TopScopeVars; +} + /// Helper function that also writes the logged string as a comment in the C /// file if the corresponding logger is enabled static void decompilerLog(llvm::raw_ostream &Out, const llvm::Twine &Expr) { @@ -577,7 +687,16 @@ CCodeGenerator::addOperandToken(const llvm::Value *Operand) { // Instructions must be visited in reverse-postorder when filling the // TokenMap if (isa(Operand) or isa(Operand)) { - revng_assert(TokenMap.contains(Operand)); + if (auto *CallToCopy = isCallToTagged(Operand, FunctionTags::Copy)) { + if (auto *LocalVar = isCallToTagged(CallToCopy->getArgOperand(0), + FunctionTags::LocalVariable)) { + revng_assert(TokenMap.contains(LocalVar), + dumpToString(LocalVar).c_str()); + TokenMap[Operand] = TokenMap.at(LocalVar); + rc_return true; + } + } + revng_assert(TokenMap.contains(Operand), dumpToString(Operand).c_str()); rc_return false; } @@ -856,53 +975,38 @@ StringToken CCodeGenerator::handleSpecialFunction(const llvm::CallInst *Call) { } else if (FunctionTags::Parentheses.isTagOf(CalledFunc)) { Expression = addAlwaysParentheses(TokenMap.at(Call->getArgOperand(0))); - } else if (FunctionTags::AssignmentMarker.isTagOf(CalledFunc)) { - const llvm::Value *Arg = Call->getArgOperand(0); - - if (not Call->getType()->isAggregateType()) { - const auto VarNames = getOrCreateVarName(Call); - Out << buildAssignmentExpr(TypeMap.at(Call), VarNames, TokenMap.at(Arg)) - << ";\n"; - Expression = VarNames.Use; - } else { - Expression = TokenMap.at(Arg); - } - } else if (FunctionTags::StructInitializer.isTagOf(CalledFunc)) { - // Struct initializers should be used only to pack together return values - // of RawFunctionTypes that return multiple values, therefore they must - // have the same type as the function's return type - llvm::StructType *StructTy = cast(Call->getType()); + // Struct initializers should be used only to pack together return + // values of RawFunctionTypes that return multiple values, therefore + // they must have the same type as the function's return type + auto *StructTy = cast(Call->getType()); revng_assert(Call->getFunction()->getReturnType() == StructTy); - - const auto VarNames = getOrCreateVarName(Call); - - if (VarNames.hasDeclaration()) { - // Emit LHS as a definition - Out << getReturnTypeName(*ModelFunction.Prototype().get()) << " " - << VarNames.Declaration; - } else { - // Emit LHS as a reference - Out << VarNames.Use; - } - - // Emit Assignment - Out << " " << operators::Assign << " "; + revng_assert(LLVMFunction.getReturnType() == StructTy); + auto StrucTypeName = getNamedInstanceOfReturnType(ParentPrototype, ""); + StringToken StructInit = addAlwaysParentheses(StrucTypeName); // Emit RHS - char Separator = '{'; + llvm::StringRef Separator = "{"; for (const auto &Arg : Call->args()) { - Out << Separator << " " << TokenMap.at(Arg); - Separator = ','; + StructInit.append(Separator); + StructInit.append(" "); + StructInit.append(TokenMap.at(Arg)); + Separator = ","; } - Out << "};\n"; + StructInit.append("}\n"); - // Use the name of the assigned variable when referencing this value - Expression = VarNames.Use; + Expression = StructInit; } else if (FunctionTags::LocalVariable.isTagOf(CalledFunc) - or FuncName.startswith("revng_stack_frame") or FuncName.startswith("revng_call_stack_arguments")) { + const auto VarNames = createVarName(Call); + + // Declare a new local variable if it hasn't already been declared + revng_assert(VarNames.hasDeclaration()); + Out << getNamedCInstance(TypeMap.at(Call), VarNames.Declaration) << ";\n"; + Expression = VarNames.Use; + + } else if (FuncName.startswith("revng_stack_frame")) { const auto VarNames = getOrCreateVarName(Call); // Declare a new local variable if it hasn't already been declared @@ -932,33 +1036,29 @@ StringToken CCodeGenerator::handleSpecialFunction(const llvm::CallInst *Call) { // Forward expression Expression = TokenMap.at(Call->getArgOperand(0)); - } else if (FunctionTags::QEMU.isTagOf(CalledFunc) - or FunctionTags::Helper.isTagOf(CalledFunc) - or FunctionTags::OpaqueCSVValue.isTagOf(CalledFunc) - or CalledFunc->isIntrinsic()) { - + } else if (FunctionTags::OpaqueCSVValue.isTagOf(CalledFunc)) { std::string HelperRef = getHelperFunctionLocationReference(CalledFunc); Expression = buildFuncCallExpr(Call, HelperRef, /*prototype=*/nullptr); - // If this call returns an aggregate type, we have to serialize the call - // immediately and declare a local variable for it on-the-fly. This is - // needed because the name of the type returned by this function is not in - // the model: its name is derived from the called function. If we wait for - // `AssignmentMarker` to emit a declaration for it, we will loose - // information on which is the type of the returned struct. - if (Call->getType()->isAggregateType()) { - const auto VarNames = getOrCreateVarName(Call); + } else if (FunctionTags::QEMU.isTagOf(CalledFunc) + or FunctionTags::Helper.isTagOf(CalledFunc) + or CalledFunc->isIntrinsic()) { - // Declare a new local variable if it hasn't already been declared - if (VarNames.hasDeclaration()) - Out << getReturnTypeLocationReference(Call->getCalledFunction()) << " " - << VarNames.Declaration; + if (not Call->getType()->isVoidTy()) { + const auto VarName = getOrCreateVarName(Call); + if (VarName.hasDeclaration()) + Out << getReturnTypeLocationReference(CalledFunc) << " " + << VarName.Declaration; else - Out << VarNames.Use; - - Out << " " << operators::Assign << " " << Expression << ";\n"; - Expression = VarNames.Use; + Out << VarName.Use; + Out << " " << operators::Assign << " "; + Expression = VarName.Use; } + + std::string HelperRef = getHelperFunctionLocationReference(CalledFunc); + auto CallExpr = buildFuncCallExpr(Call, HelperRef, /*prototype=*/nullptr); + Out << CallExpr << ";\n"; + } else if (FunctionTags::HexInteger.isTagOf(CalledFunc)) { const auto Operand = Call->getArgOperand(0); const auto *Value = cast(Operand); @@ -1061,7 +1161,7 @@ StringToken CCodeGenerator::buildExpression(const llvm::Instruction &I) { // The name of such a struct should be derived immediately as postponing // the emission to the next `AssignmentMarker` would result in the loss // of the name of this struct. - const auto VarNames = getOrCreateVarName(Call); + const auto VarNames = createVarName(Call); // Declare a new local variable if it hasn't already been declared if (VarNames.hasDeclaration()) @@ -1079,7 +1179,7 @@ StringToken CCodeGenerator::buildExpression(const llvm::Instruction &I) { "Array return values are only supported when " "the function has `CABIFunctionType`."); - const auto VarNames = getOrCreateVarName(Call); + const auto VarNames = createVarName(Call); // Declare a new local variable if it hasn't already been declared if (VarNames.hasDeclaration()) @@ -1143,7 +1243,7 @@ StringToken CCodeGenerator::buildExpression(const llvm::Instruction &I) { .str(); } else if (auto *Alloca = dyn_cast(&I)) { - auto [VarName, VarDeclaration] = getOrCreateVarName(Alloca); + auto [VarName, VarDeclaration] = createVarName(Alloca); if (!VarDeclaration.empty()) { // Declare a local variable auto AllocaDeclaration = declareAllocaVariable(Alloca, @@ -1226,15 +1326,6 @@ StringToken CCodeGenerator::buildExpression(const llvm::Instruction &I) { const auto *CallReturnsStruct = llvm::cast(AggregateOp); const llvm::Function *Callee = CallReturnsStruct->getCalledFunction(); - // Unwrap potential calls to assignment marker - if (Callee and FunctionTags::AssignmentMarker.isTagOf(Callee)) { - const llvm::Value *FirstArg = CallReturnsStruct->getArgOperand(0); - CallReturnsStruct = llvm::cast(FirstArg); - Callee = CallReturnsStruct->getCalledFunction(); - revng_assert(not Callee - or not FunctionTags::AssignmentMarker.isTagOf(Callee)); - } - const auto CalleePrototype = Cache.getCallSitePrototype(Model, CallReturnsStruct); @@ -1258,6 +1349,38 @@ StringToken CCodeGenerator::buildExpression(const llvm::Instruction &I) { revng_abort("Unexpected instruction found when decompiling"); } + if (Expression.empty()) + return Expression; + + // Clear the TokenMap for operands that only have one use in the same + // BasicBlock, and such that I is the last user in the block. + // They will be never used before, and we don't want those strings to hang + // around, since they can grow quite big. + for (const llvm::Value *Operand : I.operand_values()) { + if (auto *InstructionOp = dyn_cast(Operand)) { + bool UserInDifferentBlock = false; + for (auto *User : InstructionOp->users()) + if (auto *UserInst = cast(User)) + if (UserInst->getParent() != InstructionOp->getParent()) + UserInDifferentBlock = true; + + if (not UserInDifferentBlock) { + llvm::SmallPtrSet UserInstructions; + for (const auto *User : Operand->users()) + UserInstructions.insert(cast(User)); + + bool FoundOtherUser = false; + for (const auto &I : + llvm::make_range(std::next(I.getIterator()), I.getParent()->end())) + if (UserInstructions.contains(&I)) + FoundOtherUser = true; + + if (not FoundOtherUser) + TokenMap.erase(Operand); + } + } + } + return Expression; } @@ -1690,50 +1813,50 @@ void CCodeGenerator::emitFunction(bool NeedsLocalStateVar) { { Scope BraceScope(Out, scopeTags::FunctionBody); - // Declare all variables that have the entire function as a scope - decompilerLog(Out, "Top-Scope Declarations"); - for (const llvm::Instruction *VarToDeclare : TopScopeVariables) { - if (Log.isEnabled() or InlineLog.isEnabled()) { - decompilerLog(Out, "VarToDeclare: " + dumpToString(VarToDeclare)); - } - - VariableTokens VarName = createVarName(VarToDeclare); - - auto VarTypeIt = TypeMap.find(VarToDeclare); - if (VarTypeIt != TypeMap.end()) { - if (auto *Alloca = llvm::dyn_cast(VarToDeclare)) { - // Allocas are special, since the expression associated to them is - // `&var` while the variable allocated is `var` - VarName = declareAllocaVariable(Alloca, VarName); - } else { - Out << getNamedCInstance(TypeMap.at(VarToDeclare), - VarName.Declaration) - << ";\n"; + if (not TopScopeVariables.empty()) { + // Declare all variables that have the entire function as a scope + decompilerLog(Out, "Top-Scope Declarations"); + for (const llvm::Instruction *VarToDeclare : TopScopeVariables) { + if (Log.isEnabled() or InlineLog.isEnabled()) { + decompilerLog(Out, "VarToDeclare: " + dumpToString(VarToDeclare)); } - } else { - // The only types that are allowed to be missing from the TypeMap - // are LLVM aggregates returned by RawFunctionTypes or by helpers - auto *Call = llvm::cast(VarToDeclare); - auto *CalledFunction = Call->getCalledFunction(); - revng_assert(CalledFunction); + VariableTokens VarName = createVarName(VarToDeclare); + + auto VarTypeIt = TypeMap.find(VarToDeclare); + if (VarTypeIt != TypeMap.end()) { + if (auto *Alloca = llvm::dyn_cast(VarToDeclare)) { + // Allocas are special, since the expression associated to them is + // `&var` while the variable allocated is `var` + VarName = declareAllocaVariable(Alloca, VarName); + } else { + Out << getNamedCInstance(TypeMap.at(VarToDeclare), + VarName.Declaration) + << ";\n"; + } - if (FunctionTags::Isolated.isTagOf(CalledFunction)) { - const auto *Prototype = Cache.getCallSitePrototype(Model, Call) - .getConst(); - Out << getNamedInstanceOfReturnType(*Prototype, VarName.Declaration) - << ";\n"; } else { - Out << getReturnTypeLocationReference(CalledFunction) << " " - << VarName.Declaration << ";\n"; + // The only types that are allowed to be missing from the TypeMap + // are LLVM aggregates returned by RawFunctionTypes or by helpers + auto *Call = llvm::cast(VarToDeclare); + if (const auto &Prototype = Cache.getCallSitePrototype(Model, Call); + Prototype.isValid() and not Prototype.empty()) { + const auto *FunctionType = Prototype.getConst(); + Out << getNamedInstanceOfReturnType(*FunctionType, + VarName.Declaration) + << ";\n"; + } else { + auto *CalledFunction = Call->getCalledFunction(); + revng_assert(CalledFunction); + Out << getReturnTypeLocationReference(CalledFunction) << " " + << VarName.Declaration << ";\n"; + } } + + TokenMap[VarToDeclare] = VarName.Use.str().str(); } - - TokenMap[VarToDeclare] = VarName.Use.str().str(); - } - - if (not TopScopeVariables.empty()) decompilerLog(Out, "End of Top-Scope Declarations"); + } // Emit a declaration for the loop state variable, which is used to // redirect control flow inside loops (e.g. if we want to jump in the diff --git a/lib/Backend/VariableScopeAnalysis.cpp b/lib/Backend/VariableScopeAnalysis.cpp deleted file mode 100644 index 39f4bfd14..000000000 --- a/lib/Backend/VariableScopeAnalysis.cpp +++ /dev/null @@ -1,132 +0,0 @@ -// -// Copyright rev.ng Labs Srl. See LICENSE.md for details. -// - -#include "llvm/ADT/PostOrderIterator.h" -#include "llvm/ADT/STLExtras.h" -#include "llvm/ADT/SetVector.h" -#include "llvm/IR/Function.h" -#include "llvm/IR/Instruction.h" -#include "llvm/IR/Instructions.h" -#include "llvm/IR/Value.h" -#include "llvm/Support/Casting.h" - -#include "revng/ADT/RecursiveCoroutine.h" -#include "revng/Support/FunctionTags.h" -#include "revng/Support/IRHelpers.h" - -#include "revng-c/Backend/VariableScopeAnalysis.h" -#include "revng-c/RestructureCFG/ASTNode.h" -#include "revng-c/RestructureCFG/ASTTree.h" -#include "revng-c/Support/FunctionTags.h" - -using InstrSetVec = llvm::SmallSetVector; - -using llvm::BasicBlock; -using llvm::CallInst; -using llvm::Function; -using llvm::Instruction; -using llvm::User; - -using llvm::any_of; -using llvm::cast; - -/// Visit the node and all its children recursively, checking if a loop -/// variable is needed. -// TODO: This could be precomputed and attached to the SCS node in the GHAST. -static RecursiveCoroutine needsLoopVar(ASTNode *N) { - if (N == nullptr) - rc_return false; - - auto Kind = N->getKind(); - switch (Kind) { - - case ASTNode::NodeKind::NK_Break: - case ASTNode::NodeKind::NK_SwitchBreak: - case ASTNode::NodeKind::NK_Continue: - case ASTNode::NodeKind::NK_Code: - rc_return false; - break; - - case ASTNode::NodeKind::NK_If: { - IfNode *If = cast(N); - - if (nullptr != If->getThen()) - if (rc_recur needsLoopVar(If->getThen())) - rc_return true; - - if (If->hasElse()) - if (rc_recur needsLoopVar(If->getElse())) - rc_return true; - - rc_return false; - } break; - - case ASTNode::NodeKind::NK_Scs: { - ScsNode *LoopBody = cast(N); - rc_return rc_recur needsLoopVar(LoopBody->getBody()); - } break; - - case ASTNode::NodeKind::NK_List: { - SequenceNode *Seq = cast(N); - for (ASTNode *Child : Seq->nodes()) - if (rc_recur needsLoopVar(Child)) - rc_return true; - - rc_return false; - } break; - - case ASTNode::NodeKind::NK_Switch: { - SwitchNode *Switch = cast(N); - llvm::Value *SwitchVar = Switch->getCondition(); - - if (not SwitchVar) - rc_return true; - - for (const auto &[Labels, CaseNode] : Switch->cases()) - if (rc_recur needsLoopVar(CaseNode)) - rc_return true; - - if (auto *Default = Switch->getDefault()) - if (rc_recur needsLoopVar(Default)) - rc_return true; - - rc_return false; - } break; - - case ASTNode::NodeKind::NK_Set: { - rc_return true; - } break; - } -} - -bool hasLoopDispatchers(const ASTTree &GHAST) { - return needsLoopVar(GHAST.getRoot()); -} - -InstrSetVec collectTopScopeVariables(const Function &F) { - InstrSetVec TopScopeVars; - - // We always want to put the stack frame among the top-scope variables, - // since it is is logical for it to appear at the top of the function even - // if it is used only in a later scope. - { - bool Found = false; - for (const BasicBlock &BB : F) { - for (const Instruction &I : BB) { - if (isCallTo(&I, "revng_stack_frame")) { - revng_assert(not Found); - TopScopeVars.insert(&I); - Found = true; - } - } - } - } - - for (const BasicBlock &BB : F) - for (const Instruction &I : BB) - if (needsTopScopeDeclaration(I) and not isCallTo(&I, "revng_stack_frame")) - TopScopeVars.insert(&I); - - return TopScopeVars; -} diff --git a/lib/IRCanonicalization/ExitSSAPass.cpp b/lib/IRCanonicalization/ExitSSAPass.cpp index 974086876..f3e78632f 100644 --- a/lib/IRCanonicalization/ExitSSAPass.cpp +++ b/lib/IRCanonicalization/ExitSSAPass.cpp @@ -2,18 +2,13 @@ // Copyright rev.ng Labs Srl. See LICENSE.md for details. // -#include "llvm/ADT/SmallSet.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/IR/Argument.h" #include "llvm/IR/BasicBlock.h" -#include "llvm/IR/Dominators.h" #include "llvm/IR/Function.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" #include "llvm/Pass.h" #include "llvm/Support/Casting.h" -#include "llvm/Support/GenericDomTree.h" #include "revng/ADT/SmallMap.h" #include "revng/Support/Debug.h" @@ -22,18 +17,13 @@ using llvm::AllocaInst; using llvm::AnalysisUsage; -using llvm::Argument; using llvm::BasicBlock; -using llvm::Constant; -using llvm::DominatorTreeBase; using llvm::Function; using llvm::FunctionPass; using llvm::Instruction; using llvm::IRBuilder; using llvm::PHINode; using llvm::RegisterPass; -using llvm::SmallSet; -using llvm::SmallVector; using llvm::Value; static Logger<> Log{ "exit-ssa" }; @@ -51,283 +41,34 @@ public: } }; -using PHIIncomingMap = SmallMap; -using BBPHIMap = SmallMap; - -using DomTree = DominatorTreeBase; - -using IncomingIDSet = SmallSet; -using BlockToIncomingMap = SmallMap; - -using BlockPtrVec = SmallVector; -using IncomingCandidatesVec = SmallVector; - -struct IncomingCandidatesInfoTy { - IncomingCandidatesVec IncomingCandidates; - BlockToIncomingMap BlocksToIncoming; -}; - -static IncomingCandidatesInfoTy -getCandidatesInfo(const PHINode &ThePHI, const DomTree &DT) { - - unsigned NPred = ThePHI.getNumIncomingValues(); - revng_assert(NPred); - revng_assert(NPred > 1 or &ThePHI != ThePHI.getIncomingValue(0)); - - IncomingCandidatesInfoTy Res = { - IncomingCandidatesVec(NPred, {}), // All the candidates are empty - {} // The mapping of candidates to incomings is empty - }; - - for (unsigned K = 0; K < NPred; ++K) { - Value *V = ThePHI.getIncomingValue(K); - if (V == &ThePHI) - continue; - if (not isa(V) and not isa(V) - and not isa(V)) - continue; - - BasicBlock *CandidateB = ThePHI.getIncomingBlock(K); - revng_assert(CandidateB != nullptr); - - BasicBlock *DefBlock = nullptr; - if (auto *Inst = dyn_cast(V)) { - DefBlock = Inst->getParent(); - } else { - revng_assert(isa(V) or isa(V)); - BasicBlock *ParentEntryBlock = &CandidateB->getParent()->getEntryBlock(); - if (auto *Arg = dyn_cast(V)) { - BasicBlock *FunEntryBlock = &Arg->getParent()->getEntryBlock(); - revng_assert(FunEntryBlock == ParentEntryBlock); - } - DefBlock = ParentEntryBlock; - } - revng_assert(DefBlock != nullptr); - - auto *DefBlockNode = DT.getNode(DefBlock); - revng_assert(DefBlockNode != nullptr); - - auto &Candidates = Res.IncomingCandidates[K]; - auto *DTNode = DT.getNode(CandidateB); - revng_assert(DTNode != nullptr); - do { - BasicBlock *B = DTNode->getBlock(); - Candidates.push_back(B); - Res.BlocksToIncoming[B].insert(K); - DTNode = DT.getNode(B)->getIDom(); - } while (DTNode != nullptr and DT.dominates(DefBlockNode, DTNode)); - } - - for (unsigned K = 0; K < NPred; ++K) { - BlockPtrVec &KCandidates = Res.IncomingCandidates[K]; - if (KCandidates.empty()) { - revng_assert(&ThePHI == ThePHI.getIncomingValue(K)); - continue; - } - - BasicBlock *CurrCandidate = KCandidates[0]; - for (unsigned H = 0; H < NPred; ++H) { - if (K == H or ThePHI.getIncomingValue(K) == ThePHI.getIncomingValue(H)) - continue; - BlockPtrVec &HCandidates = Res.IncomingCandidates[H]; - auto HCandidateMatch = std::find(HCandidates.begin(), - HCandidates.end(), - CurrCandidate); - - auto HCandidateIt = HCandidateMatch; - auto HCandidateEnd = HCandidates.end(); - for (; HCandidateIt != HCandidateEnd; ++HCandidateIt) - Res.BlocksToIncoming.at(*HCandidateIt).erase(H); - if (HCandidateMatch != HCandidateEnd) - HCandidates.erase(HCandidateMatch, HCandidateEnd); - } - } - - return Res; -} - -static bool smallerBrokenCount(const std::pair &P, - const std::pair &Q) { - return P.second < Q.second; -} - -static void computePHIVarAssignments(PHINode &ThePHI, - const DomTree &DT, - BBPHIMap &AssignmentBlocks) { - - IncomingCandidatesInfoTy CandidatesInfo = getCandidatesInfo(ThePHI, DT); - IncomingCandidatesVec &IncomingCandidates = CandidatesInfo.IncomingCandidates; - BlockToIncomingMap &BlocksToIncoming = CandidatesInfo.BlocksToIncoming; - - IncomingCandidatesVec::size_type NPred = IncomingCandidates.size(); - - // Compute maximum number of valid candidates across all the incomings. - // Its value is also used later to disable further processing whenever an - // incoming has discarded MaxNumCandidates candidates - size_t MaxNumCandidates = 0; - for (unsigned K = 0; K < NPred; ++K) { - Value *V = ThePHI.getIncomingValue(K); - if (not isa(V) and not isa(V) - and not isa(V)) - continue; - MaxNumCandidates = std::max(MaxNumCandidates, IncomingCandidates[K].size()); - } - - unsigned NumAssigned = 0; - SmallVector NumDiscarded(NPred, 0); - - // Independently of all the other results, we can already assign all the - // incomings that are not Instructions nor Arguments - for (unsigned K = 0; K < NPred; ++K) { - auto &KCandidates = IncomingCandidates[K]; - auto NCandidates = KCandidates.size(); - if (NCandidates <= 1) { - ++NumAssigned; - if (NCandidates != 0) { - AssignmentBlocks[KCandidates.back()][&ThePHI] = K; - revng_log(Log, - "PHI: " << dumpToString(ThePHI) << " incoming: " << K - << " in BB: " << KCandidates.back()); - } else { - revng_assert(&ThePHI == ThePHI.getIncomingValue(K)); - } - NumDiscarded[K] = MaxNumCandidates; // this incoming is complete - KCandidates.clear(); - } - } - - for (size_t NDisc = 0; NDisc < MaxNumCandidates; ++NDisc) { - - SmallVector, 8> BrokenCount; - - for (unsigned K = 0; K < NPred; ++K) { - if (NumDiscarded[K] != NDisc) - continue; - - BrokenCount.push_back({ K, 0 }); - - auto &KCandidates = IncomingCandidates[K]; - - for (unsigned H = 0; H < NPred; ++H) { - if (NumDiscarded[H] != NDisc or H == K - or ThePHI.getIncomingValue(K) == ThePHI.getIncomingValue(H)) - continue; - - // Assigning K breaks H if any of the valid Candidates for K is also a - // valid candidate for H - for (BasicBlock *Candidate : KCandidates) - if (BlocksToIncoming.at(Candidate).count(H)) - BrokenCount.back().second++; - } - } - - std::sort(BrokenCount.begin(), BrokenCount.end(), smallerBrokenCount); - - for (const auto &P : BrokenCount) { - auto IncomingIdx = P.first; - - // update it, marking as completed - NumDiscarded[IncomingIdx] = MaxNumCandidates; - - BlockPtrVec &PCandidates = IncomingCandidates[IncomingIdx]; - Value *NewVal = ThePHI.getIncomingValue(IncomingIdx); - ++NumAssigned; - if (PCandidates.empty()) { - revng_assert(isa(NewVal) and NewVal == &ThePHI); - continue; - } - auto &BlockAssignments = AssignmentBlocks[PCandidates.back()]; - bool New = false; - auto It = BlockAssignments.end(); - std::tie(It, New) = BlockAssignments.insert({ &ThePHI, IncomingIdx }); - bool SameIdx = It->second == IncomingIdx; - Value *OldVal = ThePHI.getIncomingValue(It->second); - bool ExpectedDuplicate = SameIdx or (OldVal == NewVal); - revng_assert(New or ExpectedDuplicate); - if (not New and ExpectedDuplicate) { - PCandidates.clear(); - continue; - } - - // Remove all the candidates in PCandidates from all the other lists of - // candidates for all the other incomings related to a different Value - for (unsigned Other = 0; Other < NPred; ++Other) { - if (Other == IncomingIdx or NewVal == ThePHI.getIncomingValue(Other)) - continue; // don't touch the incoming with the same value - BlockPtrVec &OtherCandidates = IncomingCandidates[Other]; - size_t OtherCandidatesPrevSize = OtherCandidates.size(); - for (BasicBlock *PCand : PCandidates) { - auto OtherIt = std::find(OtherCandidates.begin(), - OtherCandidates.end(), - PCand); - auto OtherEnd = OtherCandidates.end(); - if (OtherIt != OtherEnd) { - OtherCandidates.erase(OtherIt, OtherEnd); - break; - } - } - size_t NewDiscarded = OtherCandidatesPrevSize - OtherCandidates.size(); - if (NewDiscarded != 0) { - NumDiscarded[Other] += NewDiscarded; - revng_assert(NumDiscarded[Other] <= MaxNumCandidates); - } - } - - PCandidates.clear(); - } - } - revng_assert(NumAssigned == NPred); -} - bool ExitSSAPass::runOnFunction(Function &F) { // Skip non-isolated functions if (not FunctionTags::Isolated.isTagOf(&F)) return false; - DomTree DT; - DT.recalculate(F); - - BBPHIMap PHIInfoMap; + IRBuilder<> Builder(F.getContext()); + Builder.SetInsertPoint(&F.getEntryBlock().front()); SmallMap PHIToAlloca; - for (BasicBlock &BB : F) { - for (PHINode &ThePHI : BB.phis()) { - computePHIVarAssignments(ThePHI, DT, PHIInfoMap); - PHIToAlloca[&ThePHI] = nullptr; - } - } + for (BasicBlock &BB : F) + for (PHINode &PHI : BB.phis()) + PHIToAlloca[&PHI] = Builder.CreateAlloca(PHI.getType()); if (PHIToAlloca.empty()) return false; - IRBuilder<> Builder(F.getContext()); for (auto &[PHI, Alloca] : PHIToAlloca) { - BasicBlock *Dominator = nullptr; for (auto &IncomingUse : PHI->incoming_values()) { - Value *IncomingVal = IncomingUse.get(); + llvm::BasicBlock *BB = PHI->getIncomingBlock(IncomingUse); + Builder.SetInsertPoint(BB->getTerminator()); + Value *Incoming = IncomingUse.get(); BasicBlock *IncomingDefBB = &F.getEntryBlock(); - if (auto *I = dyn_cast(IncomingVal)) + if (auto *I = dyn_cast(Incoming)) IncomingDefBB = I->getParent(); revng_assert(IncomingDefBB); - if (not Dominator) - Dominator = IncomingDefBB; - else - Dominator = DT.findNearestCommonDominator(Dominator, IncomingDefBB); - } - Builder.SetInsertPoint(&Dominator->front()); - Alloca = Builder.CreateAlloca(PHI->getType()); - } - - for (auto &[BB, IncomingMap] : PHIInfoMap) { - Builder.SetInsertPoint(BB->getTerminator()); - for (auto &[PHI, IncomingID] : IncomingMap) { - revng_log(Log, - "Creating store for PHI: " << dumpToString(PHI) - << " incoming ID: " << IncomingID); - auto *Incoming = PHI->getIncomingValue(IncomingID); revng_log(Log, "Incoming: " << dumpToString(Incoming)); auto *S = Builder.CreateStore(Incoming, PHIToAlloca.at(PHI)); revng_log(Log, dumpToString(S)); diff --git a/lib/IRCanonicalization/FoldModelGEP.cpp b/lib/IRCanonicalization/FoldModelGEP.cpp index 53227e4f6..ab1a8836a 100644 --- a/lib/IRCanonicalization/FoldModelGEP.cpp +++ b/lib/IRCanonicalization/FoldModelGEP.cpp @@ -16,7 +16,6 @@ #include "revng/Support/Assert.h" #include "revng/Support/YAMLTraits.h" -#include "revng-c/Backend/VariableScopeAnalysis.h" #include "revng-c/Support/DecompilationHelpers.h" #include "revng-c/Support/FunctionTags.h" #include "revng-c/Support/ModelHelpers.h" diff --git a/lib/IRCanonicalization/MarkAssignments/AddAssignmentMarkerPass.cpp b/lib/IRCanonicalization/MarkAssignments/AddAssignmentMarkerPass.cpp index b178dfeec..ce22ef9b6 100644 --- a/lib/IRCanonicalization/MarkAssignments/AddAssignmentMarkerPass.cpp +++ b/lib/IRCanonicalization/MarkAssignments/AddAssignmentMarkerPass.cpp @@ -6,11 +6,13 @@ /// a variable assignment when decompiling to C, and wraps them in special /// marker calls. +#include "llvm/ADT/STLExtras.h" #include "llvm/IR/BasicBlock.h" #include "llvm/IR/Function.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" +#include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Module.h" #include "llvm/Support/Casting.h" @@ -19,8 +21,11 @@ #include "revng/Support/FunctionTags.h" #include "revng/Support/IRHelpers.h" +#include "revng-c/InitModelTypes/InitModelTypes.h" +#include "revng-c/Support/DecompilationHelpers.h" #include "revng-c/Support/FunctionTags.h" #include "revng-c/Support/Mangling.h" +#include "revng-c/Support/ModelHelpers.h" #include "MarkAssignments.h" @@ -32,6 +37,8 @@ public: void getAnalysisUsage(llvm::AnalysisUsage &AU) const override { AU.setPreservesCFG(); + AU.addRequired(); + AU.addRequired(); } bool runOnFunction(llvm::Function &F) override; @@ -47,9 +54,35 @@ bool AddAssignmentMarkersPass::runOnFunction(llvm::Function &F) { MarkAssignments::AssignmentMap Assignments = MarkAssignments::selectAssignments(F); + if (Assignments.empty()) + return false; + + auto &ModelWrapper = getAnalysis().get(); + const TupleTree &Model = ModelWrapper.getReadOnlyModel(); + + auto ModelFunction = llvmToModelFunction(*Model, F); + revng_assert(ModelFunction != nullptr); + auto &Cache = getAnalysis().get(); + + auto TypeMap = initModelTypes(Cache, + F, + ModelFunction, + *Model, + /*PointerOnly*/ false); + llvm::Module *M = F.getParent(); llvm::IRBuilder<> Builder(M->getContext()); bool Changed = false; + + OpaqueFunctionsPool LocalVarPool(M, false); + initLocalVarPool(LocalVarPool); + OpaqueFunctionsPool AssignPool(M, false); + initAssignPool(AssignPool); + OpaqueFunctionsPool CopyPool(M, false); + initCopyPool(CopyPool); + + std::map StructCallToLocalVarType; + for (auto &[I, Flag] : Assignments) { auto *IType = I->getType(); @@ -58,36 +91,73 @@ bool AddAssignmentMarkersPass::runOnFunction(llvm::Function &F) { if (IType->isVoidTy()) continue; + if (IType->isAggregateType()) { + // This is a call to an function that return a struct on llvm + // type system. We cannot handle it like the others, because its return + // type is not on the model (only individual fields are), so we cannot + // serialize its QualifiedType in the LocalVariable. + // + // We'll have to deal with it later in the decompiler backend. + revng_assert(not TypeMap.contains(I)); + continue; + } + + if (isCallToTagged(I, FunctionTags::QEMU) + or isCallToTagged(I, FunctionTags::Helper) + or isa(I)) + continue; + if (bool(Flag)) { - // We should never be adding an assignment marker for a reference, since + // We should never be creating a LocalVariable for a reference, since // we cannot express them in C. revng_assert(not isCallToTagged(I, FunctionTags::IsRef)); - auto *MarkerF = getAssignmentMarker(*M, IType); + // First, we have to declare the LocalVariable, in the correct place, i.e. + // either the entry block or just before I. + if (needsTopScopeDeclaration(*I)) + Builder.SetInsertPoint(&F.getEntryBlock().front()); + else + Builder.SetInsertPoint(I); - // Insert a call to the SCEV barrier right after I. For now the call to - // barrier has an undef argument, that will be fixed later. + auto *LocalVarFunctionType = getLocalVarType(IType); + auto *LocalVarFunction = LocalVarPool.get(IType, + LocalVarFunctionType, + "LocalVariable"); + + // Compute the model type returned from the call. + llvm::Constant *ModelTypeString = serializeToLLVMString(TypeMap.at(I), + *M); + + // Inject call to LocalVariable + auto *LocalVarCall = Builder.CreateCall(LocalVarFunction, + { ModelTypeString }); + + // Then, we have to replace all the uses of I so that they make a Copy + // from the new LocalVariable + for (llvm::Use &U : llvm::make_early_inc_range(I->uses())) { + revng_assert(isa(U.getUser())); + Builder.SetInsertPoint(cast(U.getUser())); + + // Create a Copy to dereference the LocalVariable + auto *CopyFnType = getCopyType(LocalVarCall->getType()); + auto *CopyFunction = CopyPool.get(LocalVarCall->getType(), + CopyFnType, + "Copy"); + auto *CopyCall = Builder.CreateCall(CopyFunction, { LocalVarCall }); + U.set(CopyCall); + } + + // Finally, we have to assign the result of I to the local variable, right + // after I itself. Builder.SetInsertPoint(I->getParent(), std::next(I->getIterator())); - // The first argument for the call for now is undef. We'll fix it up - // later on. - auto *Undef = llvm::UndefValue::get(IType); + // Inject Assign() function + auto *AssignFnType = getAssignFunctionType(IType, + LocalVarCall->getType()); + auto *AssignFunction = AssignPool.get(IType, AssignFnType, "Assign"); - // The second arg operand needs to be true if the assignment is - // required because of side effects. - auto *BoolType = MarkerF->getArg(1)->getType(); - auto *MarkSideEffects = Flag.hasMarkedSideEffects() ? - llvm::ConstantInt::getAllOnesValue(BoolType) : - llvm::ConstantInt::getNullValue(BoolType); - - auto *Call = Builder.CreateCall(MarkerF, { Undef, MarkSideEffects }); - - // Replace all uses of I with the new call. - I->replaceAllUsesWith(Call); - - // Now Fix the call to use I as argument. - Call->setArgOperand(0, I); + Builder.CreateCall(AssignFunction, { I, LocalVarCall }); Changed = true; } diff --git a/lib/IRCanonicalization/MarkAssignments/CMakeLists.txt b/lib/IRCanonicalization/MarkAssignments/CMakeLists.txt index 85a46914b..2ed46c3ed 100644 --- a/lib/IRCanonicalization/MarkAssignments/CMakeLists.txt +++ b/lib/IRCanonicalization/MarkAssignments/CMakeLists.txt @@ -6,5 +6,5 @@ revng_add_analyses_library( revngcMarkAssignments revngc AddAssignmentMarkerPass.cpp LivenessAnalysis.cpp MarkAssignments.cpp) -target_link_libraries(revngcMarkAssignments revngcSupport revng::revngModel - revng::revngSupport ${LLVM_LIBRARIES}) +target_link_libraries(revngcMarkAssignments revngcInitModelTypes revngcSupport + revng::revngModel revng::revngSupport ${LLVM_LIBRARIES}) diff --git a/lib/IRCanonicalization/MarkAssignments/MarkAssignments.cpp b/lib/IRCanonicalization/MarkAssignments/MarkAssignments.cpp index 34c3f8084..988a8310a 100644 --- a/lib/IRCanonicalization/MarkAssignments/MarkAssignments.cpp +++ b/lib/IRCanonicalization/MarkAssignments/MarkAssignments.cpp @@ -15,7 +15,6 @@ #include "revng/Support/IRHelpers.h" #include "revng/Support/MonotoneFramework.h" -#include "revng-c/Backend/VariableScopeAnalysis.h" #include "revng-c/Support/DecompilationHelpers.h" #include "revng-c/Support/FunctionTags.h" @@ -36,27 +35,69 @@ namespace MarkAssignments { using TaintSetT = std::set; -static bool -haveInterferingSideEffects(const llvm::Instruction *InstrWithSideEffects, - const llvm::Instruction &Other, - const TaintSetT &TaintSet) { +static bool haveInterferingSideEffects(const llvm::Instruction *SideEffectful, + const llvm::Instruction &Other, + const TaintSetT &TaintSet) { // Branch instructions never have side effects, so no Other could possibly // interfere with them. - if (isa(InstrWithSideEffects) - or isa(InstrWithSideEffects)) + if (isa(SideEffectful) + or isa(SideEffectful)) return false; - const auto MightInterfere = [](const llvm::Instruction *I) { + const auto MightInterfere = [SideEffectful](const llvm::Instruction *I) { + // AddressOf never has side effects. + if (auto *CallToAddressOf = isCallToTagged(I, FunctionTags::AddressOf)) { + return false; + } + + // Copies from local variables never alias anyone else, except other + // instructions that copy or assign the same local variable + llvm::CallInst *LocalVar = nullptr; + bool IsWrite = false; + if (auto *CallToCopy = isCallToTagged(I, FunctionTags::Copy)) { + LocalVar = isCallToTagged(CallToCopy->getArgOperand(0), + FunctionTags::LocalVariable); + } else if (auto *CallToAssign = isCallToTagged(I, FunctionTags::Assign)) { + LocalVar = isCallToTagged(CallToAssign->getArgOperand(1), + FunctionTags::LocalVariable); + IsWrite = true; + } + + llvm::CallInst *OtherLocalVar = nullptr; + if (auto *OtherCallToCopy = isCallToTagged(SideEffectful, + FunctionTags::Copy)) { + OtherLocalVar = isCallToTagged(OtherCallToCopy->getArgOperand(0), + FunctionTags::LocalVariable); + } else if (auto *OtherCallToAssign = isCallToTagged(SideEffectful, + FunctionTags::Assign)) { + OtherLocalVar = isCallToTagged(OtherCallToAssign->getArgOperand(1), + FunctionTags::LocalVariable); + IsWrite = true; + } + + // If either of the instruction is an access to a local variable, we know + // that only other accesses to the same local variable can have interfering + // side effects + if (LocalVar or OtherLocalVar) { + // If only one accesses a local variable, then the other does not have + // interfering side effect for sure. + if (not LocalVar or not OtherLocalVar) + return false; + // If both access the same local variable and at least one is writing, + // they have interfering side effects + return IsWrite and (LocalVar == OtherLocalVar); + } + if (hasSideEffects(*I)) return true; - // TODO: we could check for aliasing between InstrWithSideEffects and Other + // TODO: we could check for aliasing between SideEffectful and I // here, but it's costly and complicated. We should do that only if // necessary. if (isa(I)) return true; - // TODO: we could check for aliasing between InstrWithSideEffects and Other + // TODO: we could check for aliasing between SideEffectful and I // here, but it's costly and complicated. We should do that only if // necessary. if (isCallToTagged(I, FunctionTags::ReadsMemory)) @@ -312,8 +353,8 @@ public: { // Look at the operands of I. // If some of them is still pending, we want to remove them from - // pending, because at the end of this function we will either mark I - // as assigned, or insert I in pending. + // pending, because at the end of this function we will either mark + // I as assigned, or insert I in pending. revng_log(MarkLog, "Remove operands from pending."); LoggerIndent MoreIndent(MarkLog); @@ -343,18 +384,18 @@ public: } } - // After the new redesign of IRCanonicalization PHINodes shouldn't even - // reach this stage. + // After the new redesign of IRCanonicalization PHINodes shouldn't + // even reach this stage. revng_assert(not isa(I)); - // Instructions only allocating a local variable and integer print + // Instructions only allocating a local variable and integer print // decorators that do not need an assignment. if (isCallToTagged(&I, FunctionTags::AllocatesLocalVariable) || isCallToTagged(&I, FunctionTags::HexInteger) || isCallToTagged(&I, FunctionTags::CharInteger) || isCallToTagged(&I, FunctionTags::BoolInteger)) { - // The OperandTaintSet is discarded here. This is not a problem, because - // it should always be empty. + // The OperandTaintSet is discarded here. This is not a problem, + // because it should always be empty. revng_assert(not hasSideEffects(I)); revng_assert(OperandTaintSet.empty()); continue; @@ -366,44 +407,27 @@ public: revng_log(MarkLog, "Instr HasSideEffects"); } - switch (I.getNumUses()) { - - case 1: { - // Instructions with a single use do not necessarily need to generate - // an assignment. - } break; - - case 0: { + // In principle the condition on the multiple uses can be dropped, but + // removing it causes the backend to allocate a lot of memory because it + // accumulates a lot of strings, and we need to investigate how to fix + // that. + if (I.getNumUses() > 1 or not I.getNumUses() + or needsTopScopeDeclaration(I)) { // Force unused instructions to be assigned. This is done to ease // debugging, and could potentially be dropped in the future. if (not I.getType()->isVoidTy()) { Assignments[&I].set(Reasons::AlwaysAssign); revng_log(MarkLog, "Instr AlwaysAssign"); } - } break; - - default: { - // Instructions with more than one use are always assigned, so all the - // users can re-use the assigned variable. - Assignments[&I].set(Reasons::HasManyUses); - revng_log(MarkLog, "Instr HasManyUses: " << I.getNumUses()); - } break; } - // If an instruction is used outside of the scope in which it appears in - // the LLVM IR, we need to create a local variable for it. - if (needsTopScopeDeclaration(I)) { - Assignments[&I].set(Reasons::HasUsesOutsideBB); - revng_log(MarkLog, - "Instr has uses outside its basic block: " << I.getNumUses()); - } - - // If we've decided to assign I, we need to consider if it might interfere - // with other instructions that are still pending. if (Assignments.contains(&I) or I.getType()->isVoidTy()) { + + // If we've decided to assign I, we need to consider if it might + // interfere with other instructions that are still pending. revng_log(MarkLog, "Assign Pending"); - // We also have to assign all the instructions that are still pending - // and have interfering side effects. + // We also have to assign all the instructions that are still + // pending and have interfering side effects. for (auto PendingIt = Pending.begin(); PendingIt != Pending.end();) { const auto [PendingInstr, TaintSet] = *PendingIt; revng_log(MarkLog, @@ -412,15 +436,14 @@ public: if (haveInterferingSideEffects(&I, *PendingInstr, TaintSet)) { Assignments[PendingInstr].set(Reasons::HasInterferingSideEffects); revng_log(MarkLog, "HasInterferingSideEffects"); - PendingIt = Pending.erase(PendingIt); } else { ++PendingIt; } } } else { - // I is not assigned and it's not void (which are always emitted), so - // we have to track that it's pending. + // I is not assigned and it's not void (which are always emitted), + // so we have to track that it's pending. if (not I.getType()->isVoidTy()) { Pending.insertWithTaint(&I, std::move(OperandTaintSet)); revng_log(MarkLog, @@ -428,13 +451,16 @@ public: } else { // The OperandTaintSet is discarded here. This is not a problem, // because one of the two following cases is always true. - // - The instructions in the taint set were only ever affecting the - // current I. Discarding them means loosing track of them, but given - // that the void instructions are always serialized in C, this does - // not constitute a problem for side effects. - // - The instruction in the taint set were also in the taint set of - // some other instruction J. That could cause J to be assigned later - // for interfering side effects. This would still be correct. + // - The instructions in the taint set were only ever affecting + // the + // current I. Discarding them means loosing track of them, but + // given that the void instructions are always serialized in C, + // this does not constitute a problem for side effects. + // - The instruction in the taint set were also in the taint set + // of + // some other instruction J. That could cause J to be assigned + // later for interfering side effects. This would still be + // correct. revng_log(MarkLog, "void instruction without side effects: '" << &I << "': " << dumpToString(&I)); @@ -452,5 +478,4 @@ AssignmentMap selectAssignments(llvm::Function &F) { Mark.run(); return Mark.takeAssignments(); } - } // end namespace MarkAssignments diff --git a/lib/IRCanonicalization/MarkAssignments/MarkAssignments.h b/lib/IRCanonicalization/MarkAssignments/MarkAssignments.h index 784c71eca..54758f04e 100644 --- a/lib/IRCanonicalization/MarkAssignments/MarkAssignments.h +++ b/lib/IRCanonicalization/MarkAssignments/MarkAssignments.h @@ -38,14 +38,6 @@ enum Values { // This is useful for debug purposes, because it makes dead Instructions show // up in decompiled code. AlwaysAssign = 1 << 2, - // The Instruction has many uses, so we assign it to a variable, so that all - // uses can reference the variable instead of embedding the whole expression - // that represents the computation. - HasManyUses = 1 << 3, - // If an instruction has uses outside its basic block, we might want to - // declare a local variable for it outside its scope. - // TODO: Further refine this when the variable scoping reasoning gets refined - HasUsesOutsideBB = 1 << 4 }; } // end namespace Reasons diff --git a/lib/IRCanonicalization/OperatorPrecedenceResolutionPass.cpp b/lib/IRCanonicalization/OperatorPrecedenceResolutionPass.cpp index 0f454c430..7b0e28f5f 100644 --- a/lib/IRCanonicalization/OperatorPrecedenceResolutionPass.cpp +++ b/lib/IRCanonicalization/OperatorPrecedenceResolutionPass.cpp @@ -7,12 +7,14 @@ #include "llvm/IR/IRBuilder.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" +#include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/PatternMatch.h" #include "llvm/IR/Value.h" #include "llvm/Pass.h" #include "revng/Support/Assert.h" +#include "revng/Support/FunctionTags.h" #include "revng/Support/OpaqueFunctionsPool.h" #include "revng-c/Support/FunctionTags.h" @@ -149,7 +151,6 @@ static bool isCustomOpcode(Instruction *I) { return false; if (FunctionTags::AddressOf.isTagOf(CalledFunc) - || FunctionTags::AssignmentMarker.isTagOf(CalledFunc) || FunctionTags::Assign.isTagOf(CalledFunc) || FunctionTags::ModelCast.isTagOf(CalledFunc) || FunctionTags::ModelGEP.isTagOf(CalledFunc) @@ -167,8 +168,7 @@ static unsigned getCustomOpcode(Instruction *I) { if (FunctionTags::AddressOf.isTagOf(CalledFunc)) return CustomInstruction::AddressOf; - else if (FunctionTags::AssignmentMarker.isTagOf(CalledFunc) - or FunctionTags::Assign.isTagOf(CalledFunc)) + else if (FunctionTags::Assign.isTagOf(CalledFunc)) return CustomInstruction::Assignment; else if (FunctionTags::AllocatesLocalVariable.isTagOf(CalledFunc)) return CustomInstruction::LocalVariable; @@ -285,6 +285,7 @@ bool OPRP::needsParentheses(Instruction *I, Use &U) { case CustomInstruction::AddressOf: case CustomInstruction::Indirection: case CustomInstruction::MemberAccess: + case CustomInstruction::Cast: VerifyParentheses = (U.getOperandNo() == 1); break; case CustomInstruction::Assignment: @@ -292,6 +293,8 @@ bool OPRP::needsParentheses(Instruction *I, Use &U) { case CustomInstruction::Transparent: case CustomInstruction::SegmentRef: return false; + default: + revng_abort("unhandled opcode"); } } diff --git a/lib/IRCanonicalization/PrettyIntFormattingPass.cpp b/lib/IRCanonicalization/PrettyIntFormattingPass.cpp index 3ef746ddb..5b78a9267 100644 --- a/lib/IRCanonicalization/PrettyIntFormattingPass.cpp +++ b/lib/IRCanonicalization/PrettyIntFormattingPass.cpp @@ -110,12 +110,6 @@ std::optional getIntFormat(llvm::Instruction &I, llvm::Use &U) { return std::nullopt; } - // We skip AssignmentMarkers as we require constant bool as a second argument. - // Replacing that constant with something make some assertions failing. - if (isCallToTagged(&I, FunctionTags::AssignmentMarker)) { - return std::nullopt; - } - // Some intrinsic calls require ConstantInt as an argument so we are not able // to pass there any decorated value. if (auto *Intrinsic = llvm::dyn_cast(&I)) { diff --git a/lib/InitModelTypes/InitModelTypes.cpp b/lib/InitModelTypes/InitModelTypes.cpp index 425906a85..692fb9eb6 100644 --- a/lib/InitModelTypes/InitModelTypes.cpp +++ b/lib/InitModelTypes/InitModelTypes.cpp @@ -181,8 +181,7 @@ static TypeVector getReturnTypes(FunctionMetadataCache &Cache, auto *CalledFunc = Call->getCalledFunction(); revng_assert(CalledFunc); - if (FunctionTags::AssignmentMarker.isTagOf(CalledFunc) - || FunctionTags::Parentheses.isTagOf(CalledFunc) + if (FunctionTags::Parentheses.isTagOf(CalledFunc) || FunctionTags::Copy.isTagOf(CalledFunc)) { const llvm::Value *Arg = Call->getArgOperand(0); @@ -344,8 +343,8 @@ ModelTypesMap initModelTypes(FunctionMetadataCache &Cache, // Only Call instructions can return aggregates revng_assert(not InstType->isAggregateType()); - // All InsertValues and ExtractValues should have been assigned when - // handling Call instructions that return an aggregate + // All ExtractValues should have been assigned when handling Call + // instructions that return an aggregate if (isa(&I)) { if (not PointersOnly) revng_assert(TypeMap.contains(&I)); diff --git a/lib/RestructureCFG/BeautifyGHAST.cpp b/lib/RestructureCFG/BeautifyGHAST.cpp index 434759a05..9de8474b8 100644 --- a/lib/RestructureCFG/BeautifyGHAST.cpp +++ b/lib/RestructureCFG/BeautifyGHAST.cpp @@ -129,12 +129,10 @@ static RecursiveCoroutine hasSideEffects(ExprNode *Expr) { } else { // For Instruction with non-void type, the side effects are marked by // the MarkAssignment pass, so we take that in consideration. - if (auto *Call = isCallToTagged(&I, FunctionTags::AssignmentMarker)) { - // If it's a call to an assignment marker, look at the second - // argument. If it's a true constant, than it has side effects. - auto *Arg1 = Call->getArgOperand(1); - auto *HasSideEffects = llvm::cast(Arg1); - if (HasSideEffects->isOne()) + if (auto *Call = isCallToTagged(&I, FunctionTags::Assign)) { + // If it's a call to an @Assign, look at the second argument. + auto *Arg0 = Call->getArgOperand(0); + if (hasSideEffects(llvm::cast(*Arg0))) rc_return true; } } diff --git a/lib/Support/FunctionTags.cpp b/lib/Support/FunctionTags.cpp index 5c3af7a1c..14777e172 100644 --- a/lib/Support/FunctionTags.cpp +++ b/lib/Support/FunctionTags.cpp @@ -16,7 +16,6 @@ #include "revng-c/Support/Mangling.h" static constexpr const char *const ModelGEPName = "ModelGEP"; -static constexpr const char *const MarkerName = "AssignmentMarker"; namespace FunctionTags { Tag AllocatesLocalVariable("AllocatesLocalVariable"); @@ -26,7 +25,6 @@ Tag AddressOf("AddressOf"); Tag ModelCast("ModelCast"); Tag ModelGEP(ModelGEPName); Tag ModelGEPRef("ModelGEPRef"); -Tag AssignmentMarker(MarkerName); Tag OpaqueExtractValue("OpaqueExtractvalue"); Tag Parentheses("Parentheses"); Tag HexInteger("HexInteger"); @@ -79,10 +77,6 @@ static std::string makeModelGEPName(const llvm::Type *RetTy, .str(); } -static std::string makeMarkerName(const llvm::Type *Ty) { - return MarkerName + makeTypeName(Ty); -} - llvm::FunctionType * getAddressOfType(llvm::Type *RetType, llvm::Type *BaseType) { // There are 2 fixed arguments: @@ -251,29 +245,6 @@ getModelGEPRef(llvm::Module &M, llvm::Type *ReturnType, llvm::Type *BaseType) { return ModelGEPFunction; } -llvm::Function *getAssignmentMarker(llvm::Module &M, llvm::Type *T) { - - using namespace llvm; - // Create a function, with T as return type, and 2 arguments. - // The first argument has type T, the second argument is a boolean. - // If the second argument is 'true', it means the marked instructions has - // side effects that need to be taken in consideration for serialization. - auto MarkerCallee = M.getOrInsertFunction(makeMarkerName(T), - T, - T, - IntegerType::get(M.getContext(), - 1)); - - auto *MarkerF = cast(MarkerCallee.getCallee()); - MarkerF->addFnAttr(llvm::Attribute::NoUnwind); - MarkerF->addFnAttr(llvm::Attribute::WillReturn); - MarkerF->addFnAttr(llvm::Attribute::ReadNone); - FunctionTags::AssignmentMarker.addTo(MarkerF); - FunctionTags::Marker.addTo(MarkerF); - - return MarkerF; -} - llvm::FunctionType *getLocalVarType(llvm::Type *ReturnedType) { using namespace llvm; diff --git a/lib/Support/ModelHelpers.cpp b/lib/Support/ModelHelpers.cpp index 17dead246..3eaa335b2 100644 --- a/lib/Support/ModelHelpers.cpp +++ b/lib/Support/ModelHelpers.cpp @@ -161,13 +161,13 @@ deserializeFromLLVMString(llvm::Value *V, const model::Binary &Model) { } llvm::Constant * -serializeToLLVMString(model::QualifiedType &QT, llvm::Module &M) { +serializeToLLVMString(const model::QualifiedType &QT, llvm::Module &M) { // Create a string containing a serialization of the model type std::string SerializedQT; { llvm::raw_string_ostream StringStream(SerializedQT); llvm::yaml::Output YAMLOutput(StringStream); - YAMLOutput << QT; + YAMLOutput << const_cast(QT); } // Build a constant global string containing the serialized type @@ -352,11 +352,23 @@ getStrongModelInfo(FunctionMetadataCache &Cache, if (auto *Call = dyn_cast(Inst)) { - if (FunctionTags::CallToLifted.isTagOf(Call)) { - // Isolated functions have their prototype in the model - auto Prototype = Cache.getCallSitePrototype(Model, Call); - revng_assert(Prototype.isValid()); - ReturnTypes = handleReturnValue(Prototype, Model); + auto Prototype = Cache.getCallSitePrototype(Model, Call); + if (Prototype.isValid() and not Prototype.empty()) { + + auto *CalledFunc = Call->getCalledFunction(); + if (CalledFunc + and CalledFunc->getName().startswith("revng_call_stack_arguments")) { + auto *Arg0Operand = Call->getArgOperand(0); + QualifiedType + CallStackArgumentType = deserializeFromLLVMString(Arg0Operand, Model); + revng_assert(not CallStackArgumentType.isVoid()); + + ReturnTypes.push_back(std::move(CallStackArgumentType)); + } else { + // Isolated functions and dynamic functions have their prototype in the + // model + ReturnTypes = handleReturnValue(Prototype, Model); + } } else { // Non-isolated functions do not have a Prototype in the model, but we can @@ -403,14 +415,8 @@ getStrongModelInfo(FunctionMetadataCache &Cache, ReturnTypes.push_back(QualifiedType{ StackType, {} }); - } else if (FuncName.startswith("revng_call_stack_arguments")) { - - auto *Arg0Operand = Call->getArgOperand(0); - QualifiedType - CallStackArgumentType = deserializeFromLLVMString(Arg0Operand, Model); - revng_assert(not CallStackArgumentType.isVoid()); - - ReturnTypes.push_back(std::move(CallStackArgumentType)); + } else { + revng_assert(not FuncName.startswith("revng_call_stack_arguments")); } } } else if (auto *EV = llvm::dyn_cast(Inst)) { diff --git a/lib/TypeNames/LLVMTypeNames.cpp b/lib/TypeNames/LLVMTypeNames.cpp index f42c9f2a6..d7b0c650e 100644 --- a/lib/TypeNames/LLVMTypeNames.cpp +++ b/lib/TypeNames/LLVMTypeNames.cpp @@ -114,7 +114,7 @@ static std::string getReturnTypeLocation(const llvm::Function *F) { // Isolated functions' return types must be converted using model types revng_assert(not FunctionTags::Isolated.isTagOf(F)); - if (RetType->isStructTy()) { + if (RetType->isAggregateType()) { std::string StructName = getReturnedStructIdentifier(F); return ptml::tokenTag(StructName, tokens::Type) .addAttribute(ptml::locationAttribute(IsDefinition), diff --git a/lib/ValueManipulationAnalysis/TypeFlowGraph.cpp b/lib/ValueManipulationAnalysis/TypeFlowGraph.cpp index 164895ace..d84812c86 100644 --- a/lib/ValueManipulationAnalysis/TypeFlowGraph.cpp +++ b/lib/ValueManipulationAnalysis/TypeFlowGraph.cpp @@ -413,7 +413,6 @@ static bool connect(TypeFlowNode *N1, TypeFlowNode *N2) { // These opcodes are transparent if (FunctionTags::Parentheses.isTagOf(Callee) - or FunctionTags::AssignmentMarker.isTagOf(Callee) or FunctionTags::Copy.isTagOf(Callee)) return AddBidirectionalEdge(UseNode, ValNode, ALL_COLORS); } diff --git a/tests/unit/MarkAssignmentsTest.cpp b/tests/unit/MarkAssignmentsTest.cpp index 99f042aac..ce94ace8b 100644 --- a/tests/unit/MarkAssignmentsTest.cpp +++ b/tests/unit/MarkAssignmentsTest.cpp @@ -139,7 +139,7 @@ BOOST_AUTO_TEST_CASE(ManyUses) { ExpectedFlagsType ExpectedFlags{ BBAssignmentFlags{ "initial_block", - { HasManyUses, AlwaysAssign, AlwaysAssign, None } }, + { AlwaysAssign, AlwaysAssign, AlwaysAssign, None } }, }; runTestOnFunctionWithExpected(Body, ExpectedFlags); @@ -174,7 +174,7 @@ BOOST_AUTO_TEST_CASE(Interfering) { ExpectedFlagsType ExpectedFlags{ BBAssignmentFlags{ "initial_block", - { HasManyUses, + { AlwaysAssign, HasInterferingSideEffects, HasSideEffects, HasSideEffects, @@ -195,7 +195,7 @@ BOOST_AUTO_TEST_CASE(NonInterfering) { ExpectedFlagsType ExpectedFlags{ BBAssignmentFlags{ "initial_block", - { HasManyUses, None, HasSideEffects, None } }, + { AlwaysAssign, None, HasSideEffects, None } }, }; runTestOnFunctionWithExpected(Body, ExpectedFlags); @@ -214,7 +214,7 @@ BOOST_AUTO_TEST_CASE(ComplexNonInterfering) { ExpectedFlagsType ExpectedFlags{ BBAssignmentFlags{ /*.BBName =*/"initial_block", - /*.InstrFlags =*/{ HasManyUses, + /*.InstrFlags =*/{ AlwaysAssign, None, None, None, @@ -297,7 +297,7 @@ BOOST_AUTO_TEST_CASE(ConditionalIsNotAssigned1) { BBAssignmentFlags{ /*.BBName =*/"initial_block", /*.InstrFlags =*/ { - HasManyUses, + AlwaysAssign, None, None, None, @@ -343,7 +343,7 @@ BOOST_AUTO_TEST_CASE(ConditionalIsNotAssigned2) { BBAssignmentFlags{ /*.BBName =*/"initial_block", /*.InstrFlags =*/ { - HasManyUses, + AlwaysAssign, None, None, None, @@ -484,14 +484,14 @@ BOOST_AUTO_TEST_CASE(Loop) { BBAssignmentFlags{ /*.BBName =*/"initial_block", /*.InstrFlags =*/ { - HasManyUses, + AlwaysAssign, None, None, HasSideEffects, None, } }, BBAssignmentFlags{ /*.BBName =*/"head", - /*.InstrFlags =*/{ HasUsesOutsideBB, None } }, + /*.InstrFlags =*/{ AlwaysAssign, None } }, BBAssignmentFlags{ /*.BBName =*/"tail", /*.InstrFlags =*/{ None, None, None, HasSideEffects, None } }, diff --git a/tests/unit/llvm-lit-tests/ExitSSA.ll b/tests/unit/llvm-lit-tests/ExitSSA.ll deleted file mode 100644 index e467ab452..000000000 --- a/tests/unit/llvm-lit-tests/ExitSSA.ll +++ /dev/null @@ -1,156 +0,0 @@ -; -; Copyright rev.ng Labs Srl. See LICENSE.md for details. -; - -; RUN: %revngopt %s --exit-ssa -S -o - | FileCheck %s - -; CHECK: define i1 @basicphi -define i1 @basicphi (i1 %x) !revng.tags !0 { - ; The phi node below becomes a single alloca - ; CHECK: %1 = alloca i1 - ; CHECK-NOT: alloca - ; The ExitSSAPass.cpp decides that it's safe to push the store up to here. - ; CHECK: store i1 true, i1* %1 - ; CHECK-NEXT: br i1 %x, label %then, label %else - br i1 %x, label %then, label %else - -then: - ; This block stays empty, because the associated store has been pushed above. - ; CHECK: then: - ; CHECK-NEXT: br label %tail - br label %tail - -else: - ; This block now contains a store - ; CHECK: else: - ; CHECK-NEXT: store i1 false, i1* %1 - ; CHECK-NEXT: br label %tail - br label %tail - -tail: - ; The following phi has become an alloca, so now here we don't have any phis - ; anymore, we just have a load from the alloca and the ret. - ; CHECK: tail: - ; CHECK-NEXT: %2 = load i1, i1* %1 - ; CHECK-NEXT: ret i1 %2 - %res = phi i1 [true, %then], [false, %else] - ret i1 %res -} - -; CHECK: define i1 @nestedifwithglobals -define i1 @nestedifwithglobals (i1 %x) !revng.tags !0 { - ; CHECK: %1 = alloca i1 - ; CHECK-NEXT: %2 = alloca i1 - ; CHECK-NEXT: store i1 true, i1* %2 - ; CHECK-NEXT: store i1 true, i1* %1 - ; CHECK-NEXT: br i1 %x, label %then, label %else - br i1 %x, label %then, label %else - -then: - ; This block stays empty, because the associated store has been pushed above. - ; CHECK: then: - ; CHECK-NEXT: br label %tail - br label %tail - -else: - ; This block stays empty, because the associated store has been pushed above. - ; CHECK: else: - ; CHECK-NEXT: br i1 %x, label %then2, label %else2 - br i1 %x, label %then2, label %else2 - -then2: - ; This block stays empty, because the associated store has been pushed above. - ; CHECK: then2: - ; CHECK-NEXT: br label %tail2 - br label %tail2 - -else2: - ; This block now contains a store - ; CHECK: else2: - ; CHECK-NEXT: store i1 false, i1* %2 - ; CHECK-NEXT: br label %tail2 - br label %tail2 - -tail2: - ; The following phi has become an alloca, so now here we don't have any phis - ; anymore, we just have a load from the alloca. Then we have a store in the - ; other alloca, associated with the second phi. - ; Finally we branch to tail - ; CHECK: tail2: - ; CHECK-NEXT: %3 = load i1, i1* %2 - ; CHECK-NEXT: store i1 %3, i1* %1 - ; CHECK-NEXT: br label %tail - %a = phi i1 [true, %then2], [false, %else2] - br label %tail - -tail: - ; The following phi has become an alloca, so now here we don't have any phis - ; anymore, we just have a load from the alloca and the ret. - ; CHECK: tail: - ; CHECK-NEXT: %4 = load i1, i1* %1 - ; CHECK-NEXT: ret i1 %4 - %res = phi i1 [true, %then], [%a, %tail2] - ret i1 %res -} - -; CHECK: define i1 @nestedifwithlocals -define i1 @nestedifwithlocals (i1 %x) !revng.tags !0 { - ; CHECK: %1 = alloca i1 - ; CHECK-NEXT: store i1 true, i1* %1 - ; CHECK-NEXT: br i1 %x, label %then, label %else - br i1 %x, label %then, label %else - -then: - ; This block stays empty, because the associated store has been pushed above. - ; CHECK: then: - ; CHECK-NEXT: br label %tail - br label %tail - -else: - ; This block stays empty, because the associated store has been pushed above. - ; CHECK: else: - ; CHECK-NEXT: %2 = alloca i1 - ; CHECK-NEXT: %local1 = and i1 %x, %x - ; CHECK-NEXT: store i1 %local1, i1* %2 - ; CHECK-NEXT: br i1 %x, label %then2, label %else2 - %local1 = and i1 %x, %x - br i1 %x, label %then2, label %else2 - -then2: - ; This block stays empty, because the associated store has been pushed above. - ; CHECK: then2: - ; CHECK-NEXT: br label %tail2 - br label %tail2 - -else2: - ; This block now contains a store - ; CHECK: else2: - ; CHECK-NEXT: %local2 = xor i1 %x, %x - ; CHECK-NEXT: store i1 %local2, i1* %2 - ; CHECK-NEXT: br label %tail2 - %local2 = xor i1 %x, %x - br label %tail2 - -tail2: - ; The following phi has become an alloca, so now here we don't have any phis - ; anymore, we just have a load from the alloca. Then we have a store in the - ; other alloca, associated with the second phi. - ; Finally we branch to tail - ; CHECK: tail2: - ; CHECK-NEXT: %3 = load i1, i1* %2 - ; CHECK-NEXT: store i1 %3, i1* %1 - ; CHECK-NEXT: br label %tail - %a = phi i1 [%local1, %then2], [%local2, %else2] - br label %tail - -tail: - ; The following phi has become an alloca, so now here we don't have any phis - ; anymore, we just have a load from the alloca and the ret. - ; CHECK: tail: - ; CHECK-NEXT: %4 = load i1, i1* %1 - ; CHECK-NEXT: ret i1 %4 - %res = phi i1 [true, %then], [%a, %tail2] - ret i1 %res -} - -!0 = !{!"Isolated"}