Merge branch 'feature/segretate-aggregates'

This commit is contained in:
Alessandro Di Federico
2026-06-12 09:40:02 +02:00
45 changed files with 3103 additions and 1641 deletions
+69 -7
View File
@@ -11,6 +11,7 @@
#include "revng/ABI/Definition.h"
#include "revng/ADT/STLExtras.h"
#include "revng/Model/Binary.h"
#include "revng/Support/YAMLTraits.h"
namespace abi::FunctionType {
@@ -88,12 +89,11 @@ public:
public:
struct StackSpan {
uint64_t Offset;
uint64_t Size;
StackSpan operator+(uint64_t Offset) const {
return { this->Offset + Offset, Size };
}
/// The offset should be interpreted as an offset within the struct
/// containing the stack arguments. It's not an offset from some reference
/// stack pointer value.
uint64_t Offset = 0;
uint64_t Size = 0;
};
public:
@@ -196,9 +196,25 @@ public:
}
public:
void dump() const debug_function;
void dump() const debug_function { dump(dbg); }
template<typename T>
void dump(T &Stream) const {
// TODO: accept an arbitrary stream
serialize(Stream, *this);
}
};
inline Layout::Argument::StackSpan
operator+(const Layout::Argument::StackSpan &This, uint64_t Offset) {
return { This.Offset + Offset, This.Size };
}
inline Layout::Argument::StackSpan
operator+(uint64_t Offset, const Layout::Argument::StackSpan &This) {
return { This.Offset + Offset, This.Size };
}
inline std::span<const model::Register::Values>
calleeSavedRegisters(const model::CABIFunctionDefinition &Prototype) {
return abi::Definition::get(Prototype.ABI()).CalleeSavedRegisters();
@@ -276,3 +292,49 @@ inline UsedRegisters usedRegisters(const model::UpcastableType &FunctionType) {
}
} // namespace abi::FunctionType
using FTL = abi::FunctionType::Layout;
namespace FTAK = abi::FunctionType::ArgumentKind;
template<>
struct llvm::yaml::ScalarEnumerationTraits<FTAK::Values>
: public NamedEnumScalarTraits<FTAK::Values> {};
template<>
struct llvm::yaml::MappingTraits<FTL::Argument::StackSpan> {
static void mapping(IO &IO, FTL::Argument::StackSpan &SS) {
IO.mapRequired("Offset", SS.Offset);
IO.mapRequired("Size", SS.Size);
}
};
LLVM_YAML_IS_SEQUENCE_VECTOR(FTL::Argument::StackSpan)
template<>
struct llvm::yaml::MappingTraits<FTL::ReturnValue> {
static void mapping(IO &IO, FTL::ReturnValue &RV) {
IO.mapRequired("Type", RV.Type);
IO.mapRequired("Registers", RV.Registers);
}
};
LLVM_YAML_IS_SEQUENCE_VECTOR(FTL::ReturnValue)
template<>
struct llvm::yaml::MappingTraits<FTL::Argument> {
static void mapping(IO &IO, FTL::Argument &A) {
IO.mapRequired("Type", A.Type);
IO.mapRequired("Kind", A.Kind);
IO.mapRequired("Registers", A.Registers);
IO.mapOptional("Stack", A.Stack);
}
};
LLVM_YAML_IS_SEQUENCE_VECTOR(FTL::Argument)
template<>
struct llvm::yaml::MappingTraits<FTL> {
static void mapping(IO &IO, FTL &L) {
IO.mapRequired("Arguments", L.Arguments);
IO.mapRequired("ReturnValues", L.ReturnValues);
IO.mapRequired("CalleeSavedRegisters", L.CalleeSavedRegisters);
IO.mapRequired("FinalStackOffset", L.FinalStackOffset);
}
};
+4 -8
View File
@@ -120,14 +120,10 @@ layoutToLLVMFunctionType(llvm::LLVMContext &Context,
if constexpr (LegacyLocalVariables) {
ReturnType = TargetPointerSizedInteger;
} else {
if (Layout.hasSPTAR()) {
ReturnType = TargetPointerSizedInteger;
} else {
const model::Type &ReturnAggregate = Layout.returnValueAggregateType();
size_t ReturnSize = *ReturnAggregate.size();
auto *Int8 = llvm::IntegerType::getInt8Ty(Context);
ReturnType = llvm::ArrayType::get(Int8, ReturnSize);
}
const model::Type &ReturnAggregate = Layout.returnValueAggregateType();
size_t ReturnSize = *ReturnAggregate.size();
auto *Int8 = llvm::IntegerType::getInt8Ty(Context);
ReturnType = llvm::ArrayType::get(Int8, ReturnSize);
}
} break;
+3 -2
View File
@@ -1615,8 +1615,9 @@ struct GraphTraits<llvm::Inverse<T *>>
typename T::nodes_iterator>;
static NodeRef getEntryNode(llvm::Inverse<T *> Inv) {
// TODO: we might want to consider an option of having optional
// `ExitNode`s as well, for consistency.
// NOTE: this is clearly misguided, however it's coherent with what happens
// for Inverse<llvm::Function *>.
// Don't use this.
return Inv.Graph->getEntryNode();
}
@@ -16,8 +16,9 @@ class ReversePostOrderTraversalExt {
NodeVec Blocks; // Block list in normal RPO order
void initialize(GraphT G, SetType &WhiteList) {
std::copy(po_ext_begin(G, WhiteList),
po_ext_end(G, WhiteList),
using ExtIter = llvm::po_ext_iterator<GraphT, SetType, GT>;
std::copy(ExtIter::begin(G, WhiteList),
ExtIter::end(G, WhiteList),
std::back_inserter(Blocks));
}
@@ -14,11 +14,16 @@ class StructInitializers {
private:
OpaqueFunctionsPool<llvm::StructType *> Pool;
llvm::LLVMContext &Context;
bool EmitBody = true;
public:
StructInitializers(llvm::Module *M);
StructInitializers(llvm::Module *M, bool EmitBody = true);
public:
llvm::CallInst *createCall(revng::IRBuilder &Builder,
llvm::StructType *ReturnType,
llvm::ArrayRef<llvm::Value *> Values);
llvm::Instruction *createReturn(revng::IRBuilder &Builder,
llvm::ArrayRef<llvm::Value *> Values);
};
+7 -7
View File
@@ -13,9 +13,9 @@ namespace llvm {
class Instruction;
}
template<MFP::MonotoneFrameworkInstance MFI>
struct llvm::DOTGraphTraits<const MFP::Graph<MFI> *> {
using GraphType = const MFP::Graph<MFI> *;
template<mfp::MonotoneFrameworkInstance MFI>
struct llvm::DOTGraphTraits<const mfp::Graph<MFI> *> {
using GraphType = const mfp::Graph<MFI> *;
using UnderlyingGraphType = MFI::GraphType;
using UnderlyingDOTGraphTraits = llvm::DOTGraphTraits<UnderlyingGraphType>;
using NodeRef = llvm::GraphTraits<GraphType>::NodeRef;
@@ -63,7 +63,7 @@ struct llvm::DOTGraphTraits<const MFP::Graph<MFI> *> {
llvm::raw_string_ostream Stream(Result);
Stream << Name.str() << " value:"
<< "\n";
MFP::dump(Stream, 1, ToDump);
mfp::dump(Stream, 1, ToDump);
}
replaceAll(Result, "\n", Newline);
@@ -141,6 +141,6 @@ struct llvm::DOTGraphTraits<const MFP::Graph<MFI> *> {
}
};
template<MFP::MonotoneFrameworkInstance MFI>
struct llvm::DOTGraphTraits<MFP::Graph<MFI> *>
: llvm::DOTGraphTraits<const MFP::Graph<MFI> *> {};
template<mfp::MonotoneFrameworkInstance MFI>
struct llvm::DOTGraphTraits<mfp::Graph<MFI> *>
: llvm::DOTGraphTraits<const mfp::Graph<MFI> *> {};
+8 -8
View File
@@ -6,7 +6,7 @@
#include "revng/MFP/MFP.h"
namespace MFP {
namespace mfp {
template<MonotoneFrameworkInstance MFI>
class Graph {
@@ -28,12 +28,12 @@ public:
const auto &results() const { return Results; }
};
} // namespace MFP
} // namespace mfp
/// \note This implementation of GraphTraits forwards everything 1-to-1
template<MFP::MonotoneFrameworkInstance MFI>
struct llvm::GraphTraits<MFP::Graph<MFI> *> {
using GraphType = MFP::Graph<MFI> *;
template<mfp::MonotoneFrameworkInstance MFI>
struct llvm::GraphTraits<mfp::Graph<MFI> *> {
using GraphType = mfp::Graph<MFI> *;
using UnderlyingGraphTraits = llvm::GraphTraits<typename MFI::GraphType>;
using NodeRef = typename UnderlyingGraphTraits::NodeRef;
using EdgeRef = typename UnderlyingGraphTraits::EdgeRef;
@@ -76,9 +76,9 @@ struct llvm::GraphTraits<MFP::Graph<MFI> *> {
};
/// \note This implementation of GraphTraits forwards everything 1-to-1
template<MFP::MonotoneFrameworkInstance MFI>
struct llvm::GraphTraits<const MFP::Graph<MFI> *> {
using GraphType = const MFP::Graph<MFI> *;
template<mfp::MonotoneFrameworkInstance MFI>
struct llvm::GraphTraits<const mfp::Graph<MFI> *> {
using GraphType = const mfp::Graph<MFI> *;
using UnderlyingGraphTraits = llvm::GraphTraits<typename MFI::GraphType>;
using NodeRef = const typename UnderlyingGraphTraits::NodeRef;
using EdgeRef = typename UnderlyingGraphTraits::EdgeRef;
+341 -109
View File
@@ -1,5 +1,8 @@
#pragma once
#include "revng/ADT/STLExtras.h"
#include "revng/Support/Debug.h"
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
@@ -9,8 +12,13 @@
#include <map>
#include <queue>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <variant>
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/GraphTraits.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/PostOrderIterator.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/iterator_range.h"
@@ -20,22 +28,133 @@
#include "revng/ADT/GenericGraph.h"
#include "revng/ADT/ReversePostOrderTraversal.h"
namespace MFP {
namespace mfp {
inline Logger NullLogger("");
template<typename T>
void dump(llvm::raw_ostream &Stream, unsigned Indent, const T &Element) {
/// @{
/// Tag types selecting how `MFPConfiguration::EntryLabels` is computed when
/// the caller does not provide an explicit `std::vector<Label>`.
/// Use `GT::getEntryNode(Flow)` as the only entry label. Default. Appropriate
/// for forward analyses where the graph has a single entry node.
///
/// WARNING: Do not use with `llvm::Inverse<...>` graphs. By LLVM convention
/// `GraphTraits<Inverse<llvm::Function*>>::getEntryNode` still returns the
/// forward entry node (and `GenericGraph` follows the same convention), so
/// RPOT seeded from that node in the inverse direction reaches nothing
/// useful. This is unfortunate, but it is what it is. Use `All` instead.
struct Entry {};
/// Enumerate every node via `GT::nodes_begin..nodes_end` and use them all as
/// entry labels. Appropriate for backward analyses on `llvm::Inverse<...>`,
/// where `getEntryNode` is unreliable.
struct All {};
/// @}
template<typename Label>
using EntryLabelsOptions = std::variant<Entry,
All,
/* non-nullptr */
const std::vector<Label> *>;
template<typename T, typename StreamT>
void dump(StreamT &Stream, unsigned Indent, const T &Element) {
for (unsigned I = 0; I < Indent; ++I)
Stream << " ";
Stream << "(not implemented)\n";
}
template<typename T>
void dumpLabel(llvm::raw_ostream &Stream, const T &Element) {
template<typename T, typename StreamT>
void dumpLabel(StreamT &Stream, const T &Element) {
Stream << "(not implemented)";
}
/// Position relative to a `Key` recorded into an `ExtraState`
enum class Position : unsigned char {
Before,
After
};
/// An hashmap that enables transfer functions to attach a LatticeElement to
/// sub-Label entities (e.g., instructions in a basic block), depending to what
/// the user is interested in
template<typename KeyT, typename LatticeElement>
class ExtraState {
public:
using Key = KeyT;
using KeyAndPosition = std::pair<Key, Position>;
private:
struct Hash {
size_t operator()(const KeyAndPosition &P) const noexcept {
return llvm::hash_combine(P.first, static_cast<int>(P.second));
}
};
private:
/// The presence of a `(Key, Position)` entry marks it as interesting; the
/// stored value is the most recently recorded one.
std::unordered_map<KeyAndPosition, LatticeElement, Hash> Map;
public:
/// @{
/// Mark a `(Key, Position)` pair as interesting. Caller-side.
void registerAsInterestingBefore(const Key &K) {
Map.try_emplace({ K, Position::Before });
}
void registerAsInterestingAfter(const Key &K) {
Map.try_emplace({ K, Position::After });
}
/// @}
/// @{
/// Record `Value` for `(K, Position)`. No-op if not interesting. Called
/// from inside `applyTransferFunction`.
void registerBefore(const Key &K, const LatticeElement &Value) {
auto It = Map.find({ K, Position::Before });
if (It != Map.end())
It->second = Value;
}
void registerAfter(const Key &K, const LatticeElement &Value) {
auto It = Map.find({ K, Position::After });
if (It != Map.end())
It->second = Value;
}
/// @}
/// @{
/// Retrieve the recorded value. Caller-side, after `getMaximalFixedPoint`.
const LatticeElement &getBefore(const Key &K) const {
auto It = Map.find({ K, Position::Before });
revng_assert(It != Map.end());
return It->second;
}
const LatticeElement &getAfter(const Key &K) const {
auto It = Map.find({ K, Position::After });
revng_assert(It != Map.end());
return It->second;
}
/// @}
public:
template<typename T>
void dump(T &Stream) const {
Stream << Map.size() << " elements:\n";
for (const auto &[Key, Element] : Map) {
Stream << " " << (Key.second == Position::Before ? "Before " : "After ");
mfp::dumpLabel(Stream, Key.first);
Stream << "\n";
mfp::dump<LatticeElement>(Stream, 2, Element);
}
}
};
template<typename LatticeElement>
struct MFPResult {
LatticeElement InValue;
@@ -48,11 +167,40 @@ auto successors(typename GT::NodeRef From) {
return llvm::make_range(GT::child_begin(From), GT::child_end(From));
}
/// Placeholder struct to be employed when an MFI does not need to track extra
/// state
struct NoExtraState {};
template<typename MFI>
concept HasExtraStateKey = requires { typename MFI::ExtraStateKey; };
template<typename MFI>
struct GetExtraStateKey {
using type = typename MFI::ExtraStateKey;
};
template<typename T>
struct Identity {
using type = T;
};
template<typename MFI>
using ExtraStateKey = std::conditional_t<HasExtraStateKey<MFI>,
GetExtraStateKey<MFI>,
Identity<void *>>::type;
template<typename MFI>
using ExtraStateType = std::conditional_t<
HasExtraStateKey<MFI>,
ExtraState<ExtraStateKey<MFI>, typename MFI::LatticeElement>,
NoExtraState>;
template<typename MFI, typename LatticeElement = typename MFI::LatticeElement>
concept MonotoneFrameworkInstance = requires(const MFI &I,
LatticeElement E1,
LatticeElement E2,
typename MFI::Label L) {
typename MFI::Label L,
ExtraStateType<MFI> &ES) {
/// To compute the reverse post order traversal of the graph starting from
/// the extremal nodes, we need that the nodes also represent a subgraph
typename llvm::GraphTraits<typename MFI::Label>::NodeRef;
@@ -61,7 +209,13 @@ concept MonotoneFrameworkInstance = requires(const MFI &I,
typename llvm::GraphTraits<typename MFI::GraphType>::NodeRef>;
{ I.combineValues(E1, E2) } -> std::same_as<LatticeElement>;
{ I.isLessOrEqual(E1, E2) } -> std::same_as<bool>;
{ I.applyTransferFunction(L, E2) } -> std::same_as<LatticeElement>;
{ I.applyTransferFunction(L, E2, ES) } -> std::same_as<LatticeElement>;
};
template<typename GT>
concept HasNodeRange = requires() {
{ GT::nodes_begin };
{ GT::nodes_end };
};
template<typename Label, typename LatticeElement>
@@ -71,6 +225,44 @@ template<MonotoneFrameworkInstance MFI>
using MFIResultMap = ResultMap<typename MFI::Label,
typename MFI::LatticeElement>;
template<MonotoneFrameworkInstance MFIType>
struct MFPConfiguration {
/// The monotone framework instance
const MFIType *Instance = nullptr;
/// The graph on which the monotone framework will run
typename MFIType::GraphType Flow;
/// The value that will be used to initialize all the non-extremal nodes.
/// Defaults to default constructor.
const typename MFIType::LatticeElement *Bottom = nullptr;
/// The value that will be used to initialize all the extremal nodes
/// Defaults to default constructor.
const typename MFIType::LatticeElement *ExtremalValue = nullptr;
/// The list of extremal labels
/// Defaults to empty.
const std::vector<typename MFIType::Label> *ExtremalLabels = nullptr;
/// How to seed the worklist (priority RPOT). Defaults to `Entry{}`. Pass
/// `All{}` to enumerate every node (required for backward analyses on
/// `llvm::Inverse<...>` graphs, where `getEntryNode` returns the forward
/// entry and is therefore unreliable). Pass `&vec` (non-null) for full
/// control.
EntryLabelsOptions<typename MFIType::Label> EntryLabels = Entry{};
/// The extra state to populate during the analysis
/// Defaults to empty.
ExtraStateType<MFIType> *ExtraState = nullptr;
/// A logger where the advancement of the MFP algorithm should be reported
/// Defaults to NullLogger.
Logger *Logger = nullptr;
};
/// Compute the solution to the given instance of a monotone framework.
///
/// Compute the maximum fixed points of an instance of monotone framework GT an
/// instance of llvm::GraphTraits that tells us how to visit the graph LGT a
/// graph type that tells us how to visit the subgraph induced by a node in the
@@ -78,22 +270,80 @@ using MFIResultMap = ResultMap<typename MFI::Label,
/// Inverse<...>) the nodes don't necessary carry all the information that
/// GraphType has.
template<MonotoneFrameworkInstance MFIType,
typename GT = llvm::GraphTraits<typename MFIType::GraphType>,
typename LGT = typename MFIType::Label>
typename GT = llvm::GraphTraits<typename MFIType::GraphType>>
MFIResultMap<MFIType>
getMaximalFixedPoint(const MFIType &MFI,
typename MFIType::GraphType Flow,
typename MFIType::LatticeElement InitialValue,
typename MFIType::LatticeElement ExtremalValue,
const std::vector<typename MFIType::Label> &ExtremalLabels,
const std::vector<typename MFIType::Label> &InitialNodes,
Logger &Logger = NullLogger) {
getMaximalFixedPointImpl(MFPConfiguration<MFIType> &Configuration) {
using Label = typename MFIType::Label;
using LatticeElement = typename MFIType::LatticeElement;
std::map<Label, LatticeElement> PartialAnalysis;
auto &Instance = notNull(Configuration.Instance);
auto &Bottom = notNull(Configuration.Bottom);
auto &ExtremalValue = notNull(Configuration.ExtremalValue);
auto &ExtremalLabels = notNull(Configuration.ExtremalLabels);
auto &ExtraState = notNull(Configuration.ExtraState);
auto &Logger = notNull(Configuration.Logger);
// Resolve the EntryLabels variant into a concrete list of seeds.
std::vector<Label> EntryLabels;
auto ResolveEntryLabels = [&](const auto &Option) {
using T = std::decay_t<decltype(Option)>;
if constexpr (std::is_same_v<T, Entry>) {
auto Seed = GT::getEntryNode(Configuration.Flow);
if (Seed != typename GT::NodeRef{}) {
EntryLabels.push_back(Seed);
}
} else if constexpr (std::is_same_v<T, All>) {
if constexpr (HasNodeRange<GT>) {
auto &Flow = Configuration.Flow;
for (auto Node :
llvm::make_range(GT::nodes_begin(Flow), GT::nodes_end(Flow))) {
EntryLabels.push_back(Node);
}
} else {
revng_abort();
}
} else {
static_assert(std::is_same_v<T, const std::vector<Label> *>);
EntryLabels = notNull(Option);
}
};
std::visit(ResolveEntryLabels, Configuration.EntryLabels);
if (Logger.isEnabled()) {
revng_log(Logger, "Initializing extremal labels");
LoggerIndent Indent(Logger);
Logger << "Extremal value:\n";
mfp::dump(*Logger.getAsLLVMStream(), 1, Configuration.ExtremalValue);
Logger << DoLog;
Logger << "Extremal labels:" << DoLog;
LoggerIndent Indent2(Logger);
for (Label ExtremalLabel : ExtremalLabels) {
mfp::dumpLabel(*Logger.getAsLLVMStream(), ExtremalLabel);
Logger << DoLog;
}
revng_log(Logger, "Initializing initial nodes");
LoggerIndent Indent3(Logger);
Logger << "Initial value:\n";
mfp::dump(*Logger.getAsLLVMStream(), 1, Bottom);
Logger << DoLog;
Logger << "Initial labels:" << DoLog;
LoggerIndent Indent4(Logger);
for (Label InitialNode : EntryLabels) {
mfp::dumpLabel(*Logger.getAsLLVMStream(), InitialNode);
Logger << DoLog;
}
}
std::map<Label, MFPResult<LatticeElement>> AnalysisResult;
// Initialize the state of the analysis: associate extremal labels to extremal
// values
for (Label ExtremalLabel : ExtremalLabels)
AnalysisResult[ExtremalLabel].InValue = ExtremalValue;
struct WorklistItem {
size_t Priority;
Label Item;
@@ -101,63 +351,32 @@ getMaximalFixedPoint(const MFIType &MFI,
std::weak_ordering operator<=>(const WorklistItem &) const = default;
};
std::set<WorklistItem> Worklist;
llvm::SmallSet<Label, 8> Visited{};
std::map<Label, size_t> LabelPriority;
//
// Initialize the worklist and extremal labels
// Initialize the worklist with the nodes in reverse post order.
// Also, record the visit order as priority.
//
// If the graph has multiple initial nodes, we perform a reverse post order
// visit from each initial node, sharing the list of visited nodes with
// previous visits.
{
using NodeSet = llvm::SmallSet<Label, 8>;
NodeSet Visited;
for (Label Start : EntryLabels) {
if (Logger.isEnabled()) {
revng_log(Logger, "Initializing extremal labels");
LoggerIndent Indent(Logger);
Logger << "Extremal value:\n";
MFP::dump(*Logger.getAsLLVMStream(), 1, ExtremalValue);
Logger << DoLog;
if (Visited.contains(Start))
continue;
Logger << "Extremal labels:" << DoLog;
LoggerIndent Indent2(Logger);
for (Label ExtremalLabel : ExtremalLabels) {
MFP::dumpLabel(*Logger.getAsLLVMStream(), ExtremalLabel);
Logger << DoLog;
}
}
ReversePostOrderTraversalExt<Label, GT, NodeSet> RPOT(Start, Visited);
for (Label Node : RPOT) {
LabelPriority[Node] = LabelPriority.size();
Worklist.insert({ LabelPriority.at(Node), Node });
for (Label ExtremalLabel : ExtremalLabels)
AnalysisResult[ExtremalLabel].InValue = ExtremalValue;
if (Logger.isEnabled()) {
revng_log(Logger, "Initializing initial nodes");
LoggerIndent Indent(Logger);
Logger << "Initial value:\n";
MFP::dump(*Logger.getAsLLVMStream(), 1, InitialValue);
Logger << DoLog;
Logger << "Initial labels:" << DoLog;
LoggerIndent Indent2(Logger);
for (Label InitialNode : InitialNodes) {
MFP::dumpLabel(*Logger.getAsLLVMStream(), InitialNode);
Logger << DoLog;
}
}
for (Label Start : InitialNodes) {
if (Visited.contains(Start))
continue;
// Fill the worklist with nodes in reverse post order launching a visit
// from each remaining node
ReversePostOrderTraversalExt<LGT, GT, llvm::SmallSet<Label, 8>>
RPOTE(Start, Visited);
for (Label Node : RPOTE) {
LabelPriority[Node] = LabelPriority.size();
Worklist.insert({ LabelPriority.at(Node), Node });
// Initialize the analysis value for non extremal nodes
if (!AnalysisResult.contains(Node))
AnalysisResult[Node].InValue = InitialValue;
// Initialize the analysis value for non extremal nodes
if (not AnalysisResult.contains(Node))
AnalysisResult[Node].InValue = Bottom;
}
}
}
@@ -176,7 +395,7 @@ getMaximalFixedPoint(const MFIType &MFI,
if (Logger.isEnabled()) {
Logger << "Iteration #" << IterationIndex << " on ";
MFP::dumpLabel(*Logger.getAsLLVMStream(), Start);
mfp::dumpLabel(*Logger.getAsLLVMStream(), Start);
Logger << DoLog;
}
@@ -184,24 +403,26 @@ getMaximalFixedPoint(const MFIType &MFI,
if (Logger.isEnabled()) {
Logger << "Initial value:\n";
MFP::dump(*Logger.getAsLLVMStream(), 1, LabelAnalysis.InValue);
mfp::dump(*Logger.getAsLLVMStream(), 1, LabelAnalysis.InValue);
Logger << DoLog;
Logger << "Final value:\n";
MFP::dump(*Logger.getAsLLVMStream(), 1, LabelAnalysis.OutValue);
mfp::dump(*Logger.getAsLLVMStream(), 1, LabelAnalysis.OutValue);
Logger << DoLog;
}
// Run the transfer function
// Run the transfer function.
revng_log(Logger, "Running the transfer function");
Logger.indent();
const auto &New = MFI.applyTransferFunction(Start, LabelAnalysis.InValue);
const auto New = Instance.applyTransferFunction(Start,
LabelAnalysis.InValue,
ExtraState);
Logger.unindent();
if (Logger.isEnabled()) {
LoggerIndent Indent(Logger);
Logger << "New final value:\n";
MFP::dump(*Logger.getAsLLVMStream(), 1, New);
mfp::dump(*Logger.getAsLLVMStream(), 1, New);
Logger << DoLog;
}
@@ -218,28 +439,29 @@ getMaximalFixedPoint(const MFIType &MFI,
if (Logger.isEnabled()) {
Logger << "Considering successor ";
MFP::dumpLabel(*Logger.getAsLLVMStream(), Successor);
mfp::dumpLabel(*Logger.getAsLLVMStream(), Successor);
Logger << DoLog;
Logger << "Initial value:\n";
LoggerIndent Indent(Logger);
Logger << DoLog;
MFP::dump(*Logger.getAsLLVMStream(), 1, SuccessorResults.InValue);
mfp::dump(*Logger.getAsLLVMStream(), 1, SuccessorResults.InValue);
Logger << DoLog;
}
LoggerIndent Indent(Logger);
if (not MFI.isLessOrEqual(LabelAnalysis.OutValue,
SuccessorResults.InValue)) {
if (not Instance.isLessOrEqual(LabelAnalysis.OutValue,
SuccessorResults.InValue)) {
// We need to re-enqueue
// Combine the old value with the new incoming value and update it
SuccessorResults.InValue = MFI.combineValues(SuccessorResults.InValue,
LabelAnalysis.OutValue);
SuccessorResults.InValue = Instance
.combineValues(SuccessorResults.InValue,
LabelAnalysis.OutValue);
if (Logger.isEnabled()) {
Logger << "Enqueuing. New initial value:\n";
MFP::dump(*Logger.getAsLLVMStream(), 1, SuccessorResults.InValue);
mfp::dump(*Logger.getAsLLVMStream(), 1, SuccessorResults.InValue);
Logger << DoLog;
}
@@ -256,36 +478,46 @@ getMaximalFixedPoint(const MFIType &MFI,
return AnalysisResult;
}
template<MonotoneFrameworkInstance MFI,
typename GT = llvm::GraphTraits<typename MFI::GraphType>,
typename LGT = typename MFI::Label>
MFIResultMap<MFI>
getMaximalFixedPoint(const MFI &Instance,
typename MFI::GraphType Flow,
typename MFI::LatticeElement InitialValue,
typename MFI::LatticeElement ExtremalValue,
const std::vector<typename MFI::Label> &ExtremalLabels,
Logger &Logger = NullLogger) {
using Label = typename MFI::Label;
std::vector<Label> InitialNodes(ExtremalLabels);
template<MonotoneFrameworkInstance MFIType,
typename GT = llvm::GraphTraits<typename MFIType::GraphType>>
MFIResultMap<MFIType>
getMaximalFixedPoint(MFPConfiguration<MFIType> Configuration) {
std::optional<MFIType> DefaultInstance;
if constexpr (std::is_default_constructible_v<MFIType>) {
if (Configuration.Instance == nullptr)
Configuration.Instance = &DefaultInstance.emplace();
} else {
revng_assert(Configuration.Instance != nullptr);
}
// Handle the special case that the graph has a single entry node
if (GT::getEntryNode(Flow) != typename GT::NodeRef{}) {
InitialNodes.push_back(GT::getEntryNode(Flow));
using LatticElement = typename MFIType::LatticeElement;
std::optional<LatticElement> DefaultBottom;
std::optional<typename MFIType::LatticeElement> DefaultExtremalValue;
if constexpr (std::is_default_constructible_v<LatticElement>) {
if (Configuration.Bottom == nullptr)
Configuration.Bottom = &DefaultBottom.emplace();
if (Configuration.ExtremalValue == nullptr)
Configuration.ExtremalValue = &DefaultExtremalValue.emplace();
} else {
revng_assert(Configuration.Bottom != nullptr);
revng_assert(Configuration.ExtremalValue != nullptr);
}
// Start visits for nodes that we still haven't visited
// prioritizing extremal nodes
for (Label Node :
llvm::make_range(GT::nodes_begin(Flow), GT::nodes_end(Flow))) {
InitialNodes.push_back(Node);
}
return getMaximalFixedPoint<MFI, GT, LGT>(Instance,
Flow,
InitialValue,
ExtremalValue,
ExtremalLabels,
InitialNodes,
Logger);
std::vector<typename MFIType::Label> DefaultExtremalLabels;
if (Configuration.ExtremalLabels == nullptr)
Configuration.ExtremalLabels = &DefaultExtremalLabels;
if (Configuration.Logger == nullptr)
Configuration.Logger = &NullLogger;
ExtraStateType<MFIType> DefaultExtraState;
if (Configuration.ExtraState == nullptr)
Configuration.ExtraState = &DefaultExtraState;
return getMaximalFixedPointImpl<MFIType, GT>(Configuration);
}
} // namespace MFP
} // namespace mfp
@@ -18,7 +18,12 @@ private:
public:
using LatticeElement = Set;
using GraphType = llvm::Inverse<const Function *>;
// Backward analysis. We store the forward graph here (by value, so no
// dangling-reference traps); callers must override the `GT` template
// parameter of `getMaximalFixedPoint` to
// `llvm::GraphTraits<llvm::Inverse<const Function *>>` to actually walk
// backwards.
using GraphType = const Function *;
using Label = const BlockNode *;
private:
@@ -54,7 +59,8 @@ public:
}
RegisterSet applyTransferFunction(const BlockNode *Block,
const RegisterSet &InitialState) const {
const RegisterSet &InitialState,
mfp::NoExtraState &) const {
RegisterSet Result = InitialState;
for (const Operation &Operation :
@@ -80,6 +86,6 @@ public:
}
};
static_assert(MFP::MonotoneFrameworkInstance<Liveness>);
static_assert(mfp::MonotoneFrameworkInstance<Liveness>);
} // namespace rua
@@ -64,6 +64,7 @@ public:
using LatticeElement = WritersSet;
using GraphType = Function *;
using Label = BlockNode *;
using ExtraStateType = mfp::NoExtraState;
private:
llvm::DenseMap<const Operation *, uint8_t> WriteToIndex;
@@ -131,7 +132,8 @@ public:
}
WritersSet applyTransferFunction(const Block *Block,
const WritersSet &InitialState) const {
const WritersSet &InitialState,
mfp::NoExtraState &) const {
WritersSet Result = InitialState;
for (const Operation &Operation : *Block) {
@@ -158,6 +160,6 @@ public:
}
};
static_assert(MFP::MonotoneFrameworkInstance<ReachingDefinitions>);
static_assert(mfp::MonotoneFrameworkInstance<ReachingDefinitions>);
} // namespace rua
@@ -715,8 +715,11 @@ struct ReachableExitsAnalysis
using LatticeElement = typename SetUnionLattice<
std::set<BasicBlockNode<NodeT> *>>::LatticeElement;
using ExtraStateType = mfp::NoExtraState;
static LatticeElement applyTransferFunction(const Label &L,
const LatticeElement E) {
const LatticeElement E,
mfp::NoExtraState &) {
const auto IsInlined = [](const auto &NodeLabelPair) {
return NodeLabelPair.second.Inlined;
@@ -761,10 +764,10 @@ inline bool RegionCFG<NodeT>::inflate() {
using REA = ReachableExitsAnalysis<NodeT>;
using Inverse = llvm::Inverse<typename REA::GraphType>;
auto ReachableExits = MFP::getMaximalFixedPoint<
REA,
llvm::GraphTraits<Inverse>,
llvm::Inverse<BasicBlockNode<NodeT> *>>({}, &Graph, {}, {}, {}, Exits);
using GraphTraits = llvm::GraphTraits<Inverse>;
auto GetMaximalFixedPoint = mfp::getMaximalFixedPoint<REA, GraphTraits>;
auto ReachableExits = GetMaximalFixedPoint({ .Flow = &Graph,
.EntryLabels = &Exits });
// Refresh information of dominator and postdominator trees.
DT.recalculate(Graph);
+4 -1
View File
@@ -1573,7 +1573,10 @@ std::optional<T> getConstantArg(llvm::CallInst *Call, unsigned Index) {
using namespace llvm;
if (auto *CI = dyn_cast<ConstantInt>(Call->getArgOperand(Index))) {
return CI->getLimitedValue();
if constexpr (std::is_signed_v<T>)
return CI->getSExtValue();
else
return CI->getZExtValue();
} else {
return {};
}
@@ -25,8 +25,9 @@ public:
using LatticeElement = std::map<llvm::Instruction *, ConstantRangeSet>;
using GraphType = const ControlFlowEdgesGraph *;
using Label = const ControlFlowEdgesGraph::Node *;
using ResultsMap = std::map<Label, MFP::MFPResult<LatticeElement>>;
using ResultsMap = std::map<Label, mfp::MFPResult<LatticeElement>>;
using InstructionsSet = llvm::SmallPtrSetImpl<llvm::Instruction *>;
using ExtraStateType = mfp::NoExtraState;
private:
llvm::LazyValueInfo &LVI;
@@ -57,13 +58,15 @@ public:
bool isLessOrEqual(const LatticeElement &LHS,
const LatticeElement &RHS) const;
LatticeElement applyTransferFunction(Label L, const LatticeElement &E) const;
LatticeElement applyTransferFunction(Label L,
const LatticeElement &E,
mfp::NoExtraState &) const;
public:
static void dump(GraphType CFEG, const ResultsMap &AllResults);
};
static_assert(MFP::MonotoneFrameworkInstance<AdvancedValueInfoMFI>);
static_assert(mfp::MonotoneFrameworkInstance<AdvancedValueInfoMFI>);
/// \p DFG the data flow graph containing the instructions we're interested in.
/// \p Context the position in the function for the current query.
@@ -71,7 +74,7 @@ std::tuple<
std::map<llvm::Instruction *, ConstantRangeSet>,
ControlFlowEdgesGraph,
std::map<const ForwardNode<ControlFlowEdgesNode> *,
MFP::MFPResult<std::map<llvm::Instruction *, ConstantRangeSet>>>>
mfp::MFPResult<std::map<llvm::Instruction *, ConstantRangeSet>>>>
runAVI(const DataFlowGraph &DFG,
llvm::Instruction *Context,
const llvm::DominatorTree &DT,
@@ -80,10 +83,10 @@ runAVI(const DataFlowGraph &DFG,
bool ZeroExtendConstraints);
template<>
void MFP::dump(llvm::raw_ostream &Stream,
void mfp::dump(llvm::raw_ostream &Stream,
unsigned Indent,
const std::map<llvm::Instruction *, ConstantRangeSet> &Element);
template<>
void MFP::dumpLabel(llvm::raw_ostream &Stream,
void mfp::dumpLabel(llvm::raw_ostream &Stream,
const ControlFlowEdgesGraph::Node *const &Label);
@@ -58,7 +58,7 @@ private:
DataFlowGraph DataFlowGraph;
ConstraintsMap OracleConstraints;
std::map<const ForwardNode<ControlFlowEdgesNode> *,
MFP::MFPResult<std::map<llvm::Instruction *, ConstantRangeSet>>>
mfp::MFPResult<std::map<llvm::Instruction *, ConstantRangeSet>>>
MFIResults;
std::optional<MaterializedValues> Values;
ControlFlowEdgesGraph CFEG;
-52
View File
@@ -12,7 +12,6 @@
#include "revng/Model/Binary.h"
#include "revng/Model/Helpers.h"
#include "revng/Support/Debug.h"
#include "revng/Support/YAMLTraits.h"
#include "revng/TupleTree/NamedEnumScalarTraits.h"
#include "ValueDistributor.h"
@@ -707,54 +706,3 @@ UsedRegisters usedRegisters(const model::CABIFunctionDefinition &Function) {
}
} // namespace abi::FunctionType
using FTL = abi::FunctionType::Layout;
namespace FTAK = abi::FunctionType::ArgumentKind;
template<>
struct llvm::yaml::ScalarEnumerationTraits<FTAK::Values>
: public NamedEnumScalarTraits<FTAK::Values> {};
template<>
struct llvm::yaml::MappingTraits<FTL::Argument::StackSpan> {
static void mapping(IO &IO, FTL::Argument::StackSpan &SS) {
IO.mapRequired("Offset", SS.Offset);
IO.mapRequired("Size", SS.Size);
}
};
LLVM_YAML_IS_SEQUENCE_VECTOR(FTL::Argument::StackSpan)
template<>
struct llvm::yaml::MappingTraits<FTL::ReturnValue> {
static void mapping(IO &IO, FTL::ReturnValue &RV) {
IO.mapRequired("Type", RV.Type);
IO.mapRequired("Registers", RV.Registers);
}
};
LLVM_YAML_IS_SEQUENCE_VECTOR(FTL::ReturnValue)
template<>
struct llvm::yaml::MappingTraits<FTL::Argument> {
static void mapping(IO &IO, FTL::Argument &A) {
IO.mapRequired("Type", A.Type);
IO.mapRequired("Kind", A.Kind);
IO.mapRequired("Registers", A.Registers);
IO.mapOptional("Stack", A.Stack);
}
};
LLVM_YAML_IS_SEQUENCE_VECTOR(FTL::Argument)
template<>
struct llvm::yaml::MappingTraits<FTL> {
static void mapping(IO &IO, FTL &L) {
IO.mapRequired("Arguments", L.Arguments);
IO.mapRequired("ReturnValues", L.ReturnValues);
IO.mapRequired("CalleeSavedRegisters", L.CalleeSavedRegisters);
IO.mapRequired("FinalStackOffset", L.FinalStackOffset);
}
};
void FTL::dump() const {
// TODO: accept an arbitrary stream
serialize(dbg, *this);
}
+4 -4
View File
@@ -866,11 +866,12 @@ private:
LocalValue<> &Pointer = Pointers[K];
// Skip globals because they have no local operands.
llvm::Value *V = Pointer.value();
if (isGlobal(V))
continue;
revng_assert(isLocal(V));
auto *I = dyn_cast<llvm::Instruction>(V);
if (I) {
if (auto *I = dyn_cast<llvm::Instruction>(V)) {
for (llvm::Use &U : I->operands()) {
propagatePointersBackwards(U);
}
@@ -893,8 +894,7 @@ private:
};
static bool foldPointerCasts(llvm::Function &F) {
using WTVH = llvm::WeakTrackingVH;
llvm::SmallVector<WTVH, 8> Dead;
llvm::SmallVector<llvm::WeakTrackingVH, 8> Dead;
for (llvm::Instruction &I : llvm::instructions(F)) {
+2 -1
View File
@@ -11,7 +11,7 @@ revng_add_analyses_library_internal(
ExitSSAPass.cpp
ExtractValueToGEP.cpp
FoldModelGEP.cpp
HoistStructPhis.cpp
SplitStructPhis.cpp
ImplicitModelCastPass.cpp
LoopRewriteWithCanonicalIV.cpp
MakeLocalVariables.cpp
@@ -33,6 +33,7 @@ revng_add_analyses_library_internal(
target_link_libraries(
revngCanonicalize
revngFunctionIsolation
revngInitModelTypes
revngTypeNames
revngSupport
-155
View File
@@ -1,155 +0,0 @@
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include "llvm/Passes/PassBuilder.h"
#include "revng/Model/FunctionTags.h"
#include "revng/Support/Debug.h"
#include "revng/Support/IRHelpers.h"
using namespace llvm;
static bool isLastBeforeTerminator(Instruction *I) {
auto It = I->getIterator();
++It;
auto End = I->getParent()->end();
return It != End && ++It == End;
}
/// This pass hoists calls turns phis of calls returning a `StructType` into
/// a single call (in place of the original phi) whose arguments are phis of the
/// arguments of the original call.
/// This is currently necessary since the backend does not handle well
// `StructType`.
///
/// It turns:
///
/// a:
/// %x1 = call x(1, 2)
/// br c
/// b:
/// %x2 = call x(3, 4)
/// br c
/// c:
/// %x3 = phi [(a, %x1), (b, %x2)]
///
/// Into:
/// c:
/// %arg1 = phi [(a, 1), (b, 3)]
/// %arg2 = phi [(a, 2), (b, 4)]
/// %x3 = call x(%arg1, %arg2)
///
/// \note This can be done only when x does not have side-effects.
class HoistStructPhis : public llvm::FunctionPass {
public:
static char ID;
HoistStructPhis() : llvm::FunctionPass(ID) {}
bool runOnFunction(llvm::Function &F) override {
llvm::SmallVector<PHINode *, 16> ToFix;
// Collect phis that need fixing
for (BasicBlock &BB : F)
for (Instruction &I : BB)
if (auto *Phi = dyn_cast<PHINode>(&I))
if (isa<StructType>(I.getType()))
ToFix.push_back(Phi);
if (ToFix.size() == 0)
return false;
for (PHINode *Phi : ToFix)
handlePhi(Phi);
return true;
}
void handlePhi(PHINode *Phi) {
auto PhiSize = Phi->getNumIncomingValues();
if (PhiSize == 1) {
Phi->replaceAllUsesWith(Phi->getIncomingValue(0));
return;
}
// Create all the phis
CallInst *FirstCall = nullptr;
Value *CalledValue = nullptr;
llvm::SmallVector<Value *, 2> Phis;
llvm::SmallVector<CallInst *, 2> Calls;
for (auto &V : Phi->incoming_values()) {
if (auto *Call = dyn_cast<CallInst>(V.get())) {
if (CalledValue == nullptr) {
// This is the first call we see, make some extra checks
CalledValue = Call->getCalledOperand();
FirstCall = Call;
Function *Callee = getCalledFunction(Call);
// Ignore isolated functions, they are not side-effect free
if (Callee == nullptr or FunctionTags::Isolated.isTagOf(Callee))
return;
revng_assert(isLastBeforeTerminator(Call)
or Callee->onlyReadsMemory());
// First iteration, create phis
for (Type *ArgumentType : Call->getFunctionType()->params()) {
llvm::Instruction *NewPhi = PHINode::Create(ArgumentType,
PhiSize,
"",
Phi);
NewPhi->setDebugLoc(Phi->getDebugLoc());
Phis.push_back(NewPhi);
}
Calls.push_back(Call);
} else {
// Ensure all the incomings are calls to the same function
revng_assert(CalledValue == Call->getCalledOperand());
}
}
}
for (auto &&[V, Predecessor] : zip(Phi->incoming_values(), Phi->blocks())) {
if (isa<UndefValue>(V)) {
for (auto *NewPhi : Phis)
cast<PHINode>(NewPhi)->addIncoming(UndefValue::get(NewPhi->getType()),
Predecessor);
} else if (isa<PoisonValue>(V)) {
for (auto *NewPhi : Phis) {
auto *Poison = PoisonValue::get(NewPhi->getType());
cast<PHINode>(NewPhi)->addIncoming(Poison, Predecessor);
}
} else if (auto *Call = dyn_cast<CallInst>(V)) {
revng_assert(Call->arg_size() == Phis.size());
for (auto &&[Argument, NewPhi] : zip(Call->args(), Phis))
cast<PHINode>(NewPhi)->addIncoming(Argument, Predecessor);
}
}
// Create a new function call
Instruction *InsertionPoint = Phi->getParent()->getFirstNonPHI();
auto *NewCall = CallInst::Create({ FirstCall->getFunctionType(),
CalledValue },
ArrayRef<Value *>(Phis),
ArrayRef<OperandBundleDef>{},
"",
InsertionPoint);
// Steal metadata and replace original phi
NewCall->copyMetadata(*FirstCall);
revng_assert(NewCall->getType() == Phi->getType());
Phi->replaceAllUsesWith(NewCall);
Phi->eraseFromParent();
for (CallInst *Call : Calls)
eraseFromParent(Call);
}
};
char HoistStructPhis::ID;
static RegisterPass<HoistStructPhis> R("hoist-struct-phis", "", false, false);
+244
View File
@@ -0,0 +1,244 @@
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instructions.h"
#include "llvm/Passes/PassBuilder.h"
#include "revng/FunctionIsolation/StructInitializers.h"
#include "revng/Model/FunctionTags.h"
#include "revng/Support/Debug.h"
#include "revng/Support/IRBuilder.h"
#include "revng/Support/IRHelpers.h"
#include "revng/Support/OpaqueFunctionsPool.h"
using namespace llvm;
/// This pass eliminates phi nodes of `StructType` by splitting them into one
/// phi per struct field.
///
/// For each phi `%p : {T0, ..., Tn}`, it inserts an `OpaqueExtractvalue` for
/// each field at the end of every predecessor block, then creates `n+1`
/// scalar phis, one per field. Uses of the original phi that are themselves
/// `OpaqueExtractvalue` calls get rewired to the matching scalar phi; chained
/// struct phis are handled by reusing the per-field phis of the producer
/// directly (so no extraction is needed across the chain). Any other kind of
/// use causes the pass to abort.
///
/// It turns:
///
/// a:
/// %x1 = call <{i64, i64}> @f(...)
/// br c
/// b:
/// %x2 = call <{i64, i64}> @f(...)
/// br c
/// c:
/// %x3 = phi <{i64, i64}> [(a, %x1), (b, %x2)]
/// %y0 = call i64 @OpaqueExtractvalue(%x3, i64 0)
/// %y1 = call i64 @OpaqueExtractvalue(%x3, i64 1)
///
/// Into:
///
/// a:
/// %x1 = call <{i64, i64}> @f(...)
/// %x1.0 = call i64 @OpaqueExtractvalue(%x1, i64 0)
/// %x1.1 = call i64 @OpaqueExtractvalue(%x1, i64 1)
/// br c
/// b:
/// %x2 = call <{i64, i64}> @f(...)
/// %x2.0 = call i64 @OpaqueExtractvalue(%x2, i64 0)
/// %x2.1 = call i64 @OpaqueExtractvalue(%x2, i64 1)
/// br c
/// c:
/// %y0 = phi i64 [(a, %x1.0), (b, %x2.0)]
/// %y1 = phi i64 [(a, %x1.1), (b, %x2.1)]
class SplitStructPhis : public llvm::FunctionPass {
public:
static char ID;
SplitStructPhis() : llvm::FunctionPass(ID) {}
bool runOnFunction(llvm::Function &F) override {
SmallVector<PHINode *, 16> SingleIncoming;
SmallVector<PHINode *, 16> MultiIncoming;
for (BasicBlock &BB : F) {
for (Instruction &I : BB) {
auto *Phi = dyn_cast<PHINode>(&I);
if (Phi == nullptr or not isa<StructType>(Phi->getType()))
continue;
if (Phi->getNumIncomingValues() == 1)
SingleIncoming.push_back(Phi);
else
MultiIncoming.push_back(Phi);
}
}
bool Changed = not SingleIncoming.empty() or not MultiIncoming.empty();
// Trivially eliminate single-incoming struct phis.
for (PHINode *Phi : SingleIncoming) {
Phi->replaceAllUsesWith(Phi->getIncomingValue(0));
Phi->eraseFromParent();
}
if (MultiIncoming.empty())
return Changed;
auto OpaqueEVPool = FunctionTags::OpaqueExtractValue
.getPool(*F.getParent());
LLVMContext &Ctx = F.getContext();
Type *Int64Ty = IntegerType::getInt64Ty(Ctx);
// Step 1: pre-create empty per-field phis for every struct phi, so we can
// resolve cross-references (including cycles) between chained struct phis.
DenseMap<PHINode *, SmallVector<PHINode *, 2>> PerFieldPhis;
for (PHINode *Phi : MultiIncoming) {
auto *ST = cast<StructType>(Phi->getType());
unsigned NumIncoming = Phi->getNumIncomingValues();
SmallVector<PHINode *, 2> Fields;
for (Type *FieldType : ST->elements()) {
auto *NewPhi = PHINode::Create(FieldType, NumIncoming, "", Phi);
NewPhi->setDebugLoc(Phi->getDebugLoc());
Fields.push_back(NewPhi);
}
PerFieldPhis[Phi] = std::move(Fields);
}
// Step 2: fill in the incoming values for each per-field phi.
for (PHINode *Phi : MultiIncoming) {
auto *ST = cast<StructType>(Phi->getType());
auto &Fields = PerFieldPhis[Phi];
for (unsigned I = 0, N = Phi->getNumIncomingValues(); I < N; ++I) {
Value *Incoming = Phi->getIncomingValue(I);
BasicBlock *Pred = Phi->getIncomingBlock(I);
// Undef / poison: propagate the same to every per-field phi.
if (isa<UndefValue>(Incoming) or isa<PoisonValue>(Incoming)) {
for (auto &&[FieldType, NewPhi] : zip(ST->elements(), Fields)) {
Value *V = isa<PoisonValue>(Incoming) ?
cast<Value>(PoisonValue::get(FieldType)) :
UndefValue::get(FieldType);
NewPhi->addIncoming(V, Pred);
}
continue;
}
// Chained struct phi: reuse the producer's per-field phis directly.
if (auto *IncomingPhi = dyn_cast<PHINode>(Incoming)) {
auto It = PerFieldPhis.find(IncomingPhi);
if (It != PerFieldPhis.end()) {
for (auto &&[Src, Dst] : zip(It->second, Fields))
Dst->addIncoming(Src, Pred);
continue;
}
}
// If the incoming is a `struct_initializer` call, use its arguments
// directly instead of materializing `OpaqueExtractvalue` calls that
// would just undo the packing. The `struct_initializer` call becomes
// dead afterwards and DCE will clean it up.
if (auto *Call = dyn_cast<CallInst>(Incoming);
Call != nullptr
and isCallToTagged(Call, FunctionTags::StructInitializer)) {
revng_assert(Call->arg_size() == Fields.size());
for (auto &&[Arg, FieldNewPhi] : zip(Call->args(), Fields))
FieldNewPhi->addIncoming(Arg.get(), Pred);
continue;
}
// General case: materialize one OpaqueExtractvalue per field at the
// end of the predecessor block.
Instruction *InsertBefore = Pred->getTerminator();
IRBuilder<> Builder(InsertBefore);
for (auto &&[Idx, FieldNewPhi] : llvm::enumerate(Fields)) {
Type *FieldType = ST->getElementType(Idx);
auto *FT = FunctionType::get(FieldType,
{ Incoming->getType(), Int64Ty },
false);
FunctionTags::TypePair Key = { FieldType, Incoming->getType() };
auto *EVFn = OpaqueEVPool.get(Key, FT, "OpaqueExtractvalue");
auto *Index = ConstantInt::get(Int64Ty, Idx);
CallInst *Extract = Builder.CreateCall(EVFn, { Incoming, Index });
Extract->setDebugLoc(InsertBefore->getDebugLoc());
FieldNewPhi->addIncoming(Extract, Pred);
}
}
}
// Step 3: rewrite uses of the original struct phis.
// - `OpaqueExtractvalue` users get rewired to the matching per-field phi.
// - Chained struct phis already point at our per-field phis (Step 2), so
// we leave their use of the original phi alone (it'll be dropped in
// Step 4 when the chained phi itself is erased).
// - Anything else: reconstruct the struct via `struct_initializer` at the
// join block and redirect the use to the reconstruction.
// remove-lifting-artifacts has already purged the bodies of all
// non-isolated functions before this point, so don't put one back.
StructInitializers Initializers(F.getParent(), /* EmitBody */ false);
for (PHINode *Phi : MultiIncoming) {
auto &Fields = PerFieldPhis[Phi];
for (User *U : llvm::make_early_inc_range(Phi->users())) {
auto *Call = dyn_cast<CallInst>(U);
if (Call != nullptr
and isCallToTagged(Call, FunctionTags::OpaqueExtractValue)) {
auto *IndexConst = cast<ConstantInt>(Call->getArgOperand(1));
uint64_t Index = IndexConst->getZExtValue();
revng_assert(Index < Fields.size());
Call->replaceAllUsesWith(Fields[Index]);
Call->eraseFromParent();
}
}
// After OpaqueExtractvalue rewriting, any remaining non-chained use
// needs the original struct value. Reconstruct it via struct_initializer.
bool NeedsReconstruction = false;
for (User *U : Phi->users()) {
if (auto *OtherPhi = dyn_cast<PHINode>(U))
if (PerFieldPhis.find(OtherPhi) != PerFieldPhis.end())
continue;
NeedsReconstruction = true;
break;
}
if (NeedsReconstruction) {
Instruction *InsertionPoint = Phi->getParent()->getFirstNonPHI();
revng::IRBuilder ReconstructBuilder(Ctx);
ReconstructBuilder.SetInsertPoint(InsertionPoint, Phi->getDebugLoc());
SmallVector<Value *, 4> FieldValues(Fields.begin(), Fields.end());
auto *ST = cast<StructType>(Phi->getType());
CallInst *Reconstructed = Initializers.createCall(ReconstructBuilder,
ST,
FieldValues);
for (User *U : llvm::make_early_inc_range(Phi->users())) {
if (auto *OtherPhi = dyn_cast<PHINode>(U))
if (PerFieldPhis.find(OtherPhi) != PerFieldPhis.end())
continue;
U->replaceUsesOfWith(Phi, Reconstructed);
}
}
}
// Step 4: erase the original struct phis. Any remaining cross-references
// between them (in chained-phi operand lists) are dropped via undef so
// that the erases can happen in any order.
for (PHINode *Phi : MultiIncoming)
Phi->replaceAllUsesWith(UndefValue::get(Phi->getType()));
for (PHINode *Phi : MultiIncoming)
Phi->eraseFromParent();
return Changed;
}
};
char SplitStructPhis::ID;
static RegisterPass<SplitStructPhis> R("split-struct-phis", "", false, false);
+17 -11
View File
@@ -494,7 +494,8 @@ public:
}
LatticeElement applyTransferFunction(ProgramPointNode *L,
const LatticeElement &E) const;
const LatticeElement &E,
mfp::NoExtraState &ExtraState) const;
private:
void applyTransferFunctionImpl(Instruction *I, LatticeElement &E) const;
@@ -509,7 +510,7 @@ template<bool IsLegacy>
using LatticeElement = AEMFP<IsLegacy>::LatticeElement;
template<bool IsLegacy>
using AvailableExpressionsMap = MFP::MFIResultMap<AEMFP<IsLegacy>>;
using AvailableExpressionsMap = mfp::MFIResultMap<AEMFP<IsLegacy>>;
static bool legacyLocalVariablesNoAlias(const Instruction *I,
const Instruction *J) {
@@ -644,8 +645,8 @@ void AEMFP<IsLegacy>::applyTransferFunctionImpl(Instruction *I,
template<bool IsLegacy>
AEMFP<IsLegacy>::LatticeElement
AEMFP<IsLegacy>::applyTransferFunction(ProgramPointNode *ProgramPoint,
const AEMFP<IsLegacy>::LatticeElement &E)
const {
const AEMFP<IsLegacy>::LatticeElement &E,
mfp::NoExtraState &ExtraState) const {
Instruction *I = ProgramPoint->TheInstruction;
@@ -955,18 +956,23 @@ static AEResult<IsLegacy> getAvailableExpressions(Function &F,
}
}
AvailableSet Empty{};
ProgramPointsCFG *Graph = &Result.ProgramPointsGraph;
ProgramPointNode *Entry = Graph->getEntryNode();
AEMFP<IsLegacy> AvailableExpressionsMF{ AA, MST };
using AEMFP = AEMFP<IsLegacy>;
AEMFP AvailableExpressionsMF{ AA, MST };
std::vector Entries = { Entry };
mfp::MFPConfiguration<AEMFP> Configuration{
.Instance = &AvailableExpressionsMF,
.Flow = Graph,
.Bottom = &Bottom,
.ExtremalLabels = &Entries,
.EntryLabels = &Entries
};
// std::exchange here is only needed to make revng check-conventions happy.
std::exchange(Result.AvailableExpressions,
MFP::getMaximalFixedPoint<>(AvailableExpressionsMF,
Graph,
Bottom,
Empty,
{ Entry }));
mfp::getMaximalFixedPoint<AEMFP>(Configuration));
return Result;
}
-2
View File
@@ -98,8 +98,6 @@ public:
//===---------------------------- Expressions ---------------------------===//
RecursiveCoroutine<void> emitUndefExpression(mlir::Value V) {
revng_assert(isScalarType(V.getType()));
Tokens.emitLiteralIdentifier("undef");
Tokens.emitOperator(CTE::Operator::LeftParenthesis);
emitType(V.getType());
+6 -7
View File
@@ -44,14 +44,13 @@ public:
BitwiseOrOp,
BitwiseXorOp,
ShiftLeftOp,
ShiftRightOp,
CmpEqOp,
CmpNeOp,
CmpLtOp,
CmpGtOp,
CmpLeOp,
CmpGeOp>(Op))
ShiftRightOp>(Op))
return elideArithmeticCasts(Op);
if (mlir::isa<CmpEqOp, CmpNeOp, CmpLtOp, CmpGtOp, CmpLeOp, CmpGeOp>(Op)) {
if (clift::unwrapped_isa<IntegralType>(Op->getOperand(0).getType()))
elideArithmeticCasts(Op);
}
}
private:
+3 -43
View File
@@ -1621,7 +1621,7 @@ private:
Builder.create<GotoOp>(Loc, Iterator->second.Label);
}
bool isCallToSPTAR(const llvm::CallInst *Call) {
bool returnsAggregate(const llvm::CallInst *Call) {
if (not Call->hasMetadata(PrototypeMDName))
return false;
@@ -1629,7 +1629,7 @@ private:
auto Layout = abi::FunctionType::Layout::make(*ModelCallType);
namespace ReturnMethod = abi::FunctionType::ReturnMethod;
return Layout.hasSPTAR();
return Layout.returnMethod() == ReturnMethod::ModelAggregate;
}
// This function emits a single basic block as part of a larger C scope.
@@ -1751,7 +1751,7 @@ private:
continue;
// Some function calls are emitted in local variable initializers.
if (isCallToSPTAR(Call)) {
if (returnsAggregate(Call)) {
mlir::Location Loc = C.getLocation(Call);
auto Op = Builder.create<ExpressionStatementOp>(Loc);
@@ -1801,10 +1801,6 @@ private:
mlir::Type FuncReturnType = FunctionType.getReturnType();
mlir::Type LLVMReturnType = FuncReturnType;
// In SPTAR functions, values are returned by address. In this case
if (FunctionLayout.hasSPTAR())
LLVMReturnType = C.getPointerType(LLVMReturnType);
// Emit the expression tree rooted at the return instruction directly
// into the expression region of the newly created return operation:
emitExpressionTreeImpl(Op.getResult(), [&]() {
@@ -1830,47 +1826,11 @@ private:
ReturnValue);
}
if (FunctionLayout.hasSPTAR()) {
// TODO: This may happen when the pointer size of the Model doesn't
// match the pointer size on LLVM IR, due to mismatching DataLayout.
// SPTAR functions return model-pointer-sized integers, with the
// semantic is to actually return the pointee by copy.
// Given that the return value is model-pointer-sized, it's size
// doesn't necessarily match the pointer size in LLVM's DataLayout.
// Until we don't solve the broader issue of LLVM's DataLayout
// mismatching the pointer size of the input binary and the Model,
// we'll have to deal with this corner case.
// Another option would be to change SPTAR function to return
// llvm-pointer-sized integers or even LLVM's pointers, instead of
// model-pointer-sized integers.
uint64_t
LLVMPointerSize = unwrapped_cast<PointerType>(LLVMReturnType)
.getObjectSize();
uint64_t ModelPointerSize = C.getModelPointerSize();
uint64_t OperandSize = unwrapped_cast<IntegerType>(ReturnValue
.getType())
.getObjectSize();
revng_assert(ModelPointerSize == OperandSize);
if (ModelPointerSize != LLVMPointerSize)
ReturnValue = emitIntegerCast(TerminalLoc,
ReturnValue,
LLVMPointerSize);
}
// Emit an implicit cast to the required return type if necessary:
ReturnValue = emitImplicitBitcast(TerminalLoc,
ReturnValue,
LLVMReturnType);
// In the case of SPTAR, because in the LLVM IR the return is by
// address, but in Clift the return is by value as usual, a final
// indirection is needed to convert the LLVM IR pointer to a value:
if (FunctionLayout.hasSPTAR()) {
ReturnValue = Builder.create<IndirectionOp>(TerminalLoc,
FuncReturnType,
ReturnValue);
}
return ReturnValue;
});
}
@@ -261,11 +261,26 @@ RUAResults analyzeRegisterUsage(Function *F,
// Run the liveness analysis
revng_log(Log, "Running Liveness");
rua::Liveness Liveness(Function.Function);
auto AnalysisResult = MFP::getMaximalFixedPoint(Liveness,
&Function.Function,
Liveness.defaultValue(),
Liveness.defaultValue(),
{ Function.ReturnNode });
auto DefaultValue = Liveness.defaultValue();
std::vector<const rua::BlockNode *> ExtremalLabels{ Function.ReturnNode };
// Backward analysis: `getEntryNode(Inverse<...>)` is unreliable (returns
// the forward entry), and seeding only from the return node would skip
// no-return blocks (e.g., calls to non-returning functions). Use `All` to
// seed RPOT from every node.
mfp::MFPConfiguration<rua::Liveness> Configuration{
.Instance = &Liveness,
.Flow = &Function.Function,
.Bottom = &DefaultValue,
.ExtremalValue = &DefaultValue,
.ExtremalLabels = &ExtremalLabels,
.EntryLabels = mfp::All{}
};
using namespace mfp;
using InverseGT = llvm::GraphTraits<llvm::Inverse<const rua::Function *>>;
auto GetMaximalFixedPoint = getMaximalFixedPoint<rua::Liveness, InverseGT>;
auto AnalysisResult = GetMaximalFixedPoint(Configuration);
// Collect registers alive at the entry
revng_log(Log, "Registers alive at the entry of the function:");
@@ -303,11 +318,24 @@ RUAResults analyzeRegisterUsage(Function *F,
rua::ReachingDefinitions ReachingDefinitions(Function.Function);
auto DefaultValue = ReachingDefinitions.defaultValue();
auto *EntryNode = Function.Function.getEntryNode();
auto AnalysisResult = MFP::getMaximalFixedPoint(ReachingDefinitions,
&Function.Function,
DefaultValue,
DefaultValue,
{ EntryNode });
std::vector ExtremalLabels{ EntryNode };
// Seed RPOT from every node: callers read `AnalysisResult.at(...)` for
// nodes (e.g. `ReturnNode`) that may not be forward-reachable from the
// entry (functions with no return paths). With `Entry{}` those nodes
// would be absent from the result map.
mfp::MFPConfiguration<rua::ReachingDefinitions> Configuration{
.Instance = &ReachingDefinitions,
.Flow = &Function.Function,
.Bottom = &DefaultValue,
.ExtremalValue = &DefaultValue,
.ExtremalLabels = &ExtremalLabels,
.EntryLabels = mfp::All{}
};
using namespace mfp;
auto GetMaximalFixedPoint = getMaximalFixedPoint<rua::ReachingDefinitions>;
auto AnalysisResult = GetMaximalFixedPoint(Configuration);
auto Compute = [&AnalysisResult, &Function](rua::Function::Node *Node,
bool Before) {
+1
View File
@@ -381,6 +381,7 @@ void EnforceABI::handleRegularFunctionCall(const MetaAddress &CallerAddress,
const auto *Prototype = Binary.prototypeOrDefault(ModelFunc.prototype());
revng_assert(Prototype != nullptr);
auto UsedRegisters = abi::FunctionType::usedRegisters(*Prototype);
Callee = getOrCreateNewFunction(*Callee, UsedRegisters);
}
+6 -8
View File
@@ -20,7 +20,7 @@
#include "revng/Support/IRHelpers.h"
using namespace llvm;
using namespace MFP;
using namespace mfp;
// TODO: switch from CallInst to CallBase
@@ -450,9 +450,11 @@ static bool needsWrapper(Function *F) {
struct UsedRegistersMFI : public SetUnionLattice<FunctionNodeData::UsedCSVSet> {
using Label = FunctionNode *;
using GraphType = GenericCallGraph *;
using ExtraStateType = mfp::NoExtraState;
static LatticeElement applyTransferFunction(Label L,
const LatticeElement &Value) {
const LatticeElement &Value,
mfp::NoExtraState &) {
return combineValues(L->UsedCSVs, Value);
}
};
@@ -542,12 +544,8 @@ CSVsUsageMap PromoteCSVs::getUsedCSVs(ArrayRef<CallInst *> CallsRange) {
}
}
auto AnalysisResult = getMaximalFixedPoint<UsedRegistersMFI>({},
&CallGraph,
{},
{},
{},
{});
auto GetMaximalFixedPoint = getMaximalFixedPoint<UsedRegistersMFI>;
auto AnalysisResult = GetMaximalFixedPoint({ .Flow = &CallGraph });
// Populate results set
for (auto &[Label, Value] : AnalysisResult) {
+19 -11
View File
@@ -9,8 +9,8 @@ using namespace llvm;
const char *StructInitializerPrefix = "struct_initializer";
StructInitializers::StructInitializers(llvm::Module *M) :
Pool(M, false), Context(M->getContext()) {
StructInitializers::StructInitializers(llvm::Module *M, bool EmitBody) :
Pool(M, false), Context(M->getContext()), EmitBody(EmitBody) {
Pool.setMemoryEffects(MemoryEffects::none());
Pool.addFnAttribute(Attribute::NoUnwind);
Pool.addFnAttribute(Attribute::WillReturn);
@@ -21,12 +21,9 @@ StructInitializers::StructInitializers(llvm::Module *M) :
Pool.initializeFromReturnType(FunctionTags::StructInitializer);
}
Instruction *StructInitializers::createReturn(revng::IRBuilder &Builder,
ArrayRef<Value *> Values) {
// Obtain return StructType
auto *FT = Builder.GetInsertBlock()->getParent()->getFunctionType();
auto *ReturnType = cast<StructType>(FT->getReturnType());
CallInst *StructInitializers::createCall(revng::IRBuilder &Builder,
StructType *ReturnType,
ArrayRef<Value *> Values) {
SmallVector<Type *, 8> Types;
llvm::copy(ReturnType->elements(), std::back_inserter(Types));
@@ -36,8 +33,10 @@ Instruction *StructInitializers::createReturn(revng::IRBuilder &Builder,
Types,
StructInitializerPrefix);
// Lazily populate its body
if (Initializer->isDeclaration()) {
// Lazily populate its body, unless the caller opted out (e.g., the body
// would otherwise persist past `remove-lifting-artifacts` and confuse later
// canonicalize passes that expect non-isolated functions to be declarations).
if (EmitBody and Initializer->isDeclaration()) {
auto *Entry = BasicBlock::Create(Context, "", Initializer);
// TODO: the checks should be enabled conditionally based on the user.
@@ -51,5 +50,14 @@ Instruction *StructInitializers::createReturn(revng::IRBuilder &Builder,
}
// Emit a call in the caller
return Builder.CreateRet(Builder.CreateCall(Initializer, Values));
return cast<CallInst>(Builder.CreateCall(Initializer, Values));
}
Instruction *StructInitializers::createReturn(revng::IRBuilder &Builder,
ArrayRef<Value *> Values) {
// Obtain return StructType
auto *FT = Builder.GetInsertBlock()->getParent()->getFunctionType();
auto *ReturnType = cast<StructType>(FT->getReturnType());
return Builder.CreateRet(createCall(Builder, ReturnType, Values));
}
-1
View File
@@ -536,7 +536,6 @@ void CodeGenerator::translate(LibTcg &LibTcg,
} // End loop over instructions
TranslateTask.complete();
TranslateTask.advance("Finalization", true);
Variables.closeTranslationBlock();
+1 -3
View File
@@ -104,9 +104,7 @@ FunctionPoolTag<TypePair>
llvm::Attribute::NoMerge,
llvm::Attribute::NoUnwind,
llvm::Attribute::WillReturn },
// The following is necessary to prevent the optimizer to
// move these around.
llvm::MemoryEffects::inaccessibleMemOnly(),
llvm::MemoryEffects::none(),
{ &FunctionTags::UniquedByPrototype },
[](OpaqueFunctionsPool<TypePair> &Pool,
llvm::Module &M,
@@ -3,6 +3,7 @@
//
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/Progress.h"
#include "revng/Model/Importer/DebugInfo/DwarfImporter.h"
@@ -297,6 +298,12 @@ void DwarfToModelConverter::createType(const DWARFDie &Die) {
return;
}
if (Kind == model::PrimitiveKind::Float and Size > 8) {
reportIgnoredDie(Die, "Ignoring floating-point primitives larger than 8");
createInvalidPrimitivePlaceholder(Die);
return;
}
record(Die, model::PrimitiveType::make(Kind, Size));
} break;
@@ -301,7 +301,7 @@ void DetectStackSize::electFunctionStackFrameSize(FunctionStackInfo &FSI) {
// If we have call site, the stack size is the highest value of the
// following expression:
//
// StackSizeAtCallSite - CallSiteStackArgumentsSize
// StackSizeAtCallSite - CallSiteStackArgumentsSize
//
for (const CallSite &CallSite : FSI.CallSites) {
auto MaybeNewCandidate = handleCallSite(CallSite);
@@ -322,6 +322,15 @@ void DetectStackSize::electFunctionStackFrameSize(FunctionStackInfo &FSI) {
auto EmptyStruct = Binary->makeStructDefinition(*StackSize).second;
ModelFunction.StackFrame().Type() = std::move(EmptyStruct);
} else {
if (Log.isEnabled()) {
Log << "No valid stack size: ";
if (StackSize.has_value())
Log << "(none)";
else
Log << *StackSize;
Log << DoLog;
}
}
}
File diff suppressed because it is too large Load Diff
+29 -14
View File
@@ -14,6 +14,7 @@
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/BitVector.h"
#include "llvm/ADT/GraphTraits.h"
#include "llvm/IR/AssemblyAnnotationWriter.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/Instruction.h"
@@ -30,12 +31,12 @@
namespace TypeShrinking {
class BitLivenwssAnnotatedWriter : public llvm::AssemblyAnnotationWriter {
class BitLivenessAnnotatedWriter : public llvm::AssemblyAnnotationWriter {
private:
const BitLivenessAnalysisResults &Results;
public:
BitLivenwssAnnotatedWriter(const BitLivenessAnalysisResults &Results) :
BitLivenessAnnotatedWriter(const BitLivenessAnalysisResults &Results) :
Results(Results) {}
void emitInstructionAnnot(const llvm::Instruction *I,
@@ -49,7 +50,7 @@ public:
};
void BitLivenessWrapperPass::dump(llvm::Function &F) const {
BitLivenwssAnnotatedWriter Annotator(Result);
BitLivenessAnnotatedWriter Annotator(Result);
llvm::raw_os_ostream Stream(dbg);
F.print(Stream, &Annotator);
}
@@ -61,7 +62,8 @@ struct BitLivenessAnalysis {
using GraphType = GenericGraph<DataFlowNode> *;
using LatticeElement = uint32_t;
using Label = DataFlowNode *;
using MFPResult = MFP::MFPResult<BitLivenessAnalysis::LatticeElement>;
using MFPResult = mfp::MFPResult<BitLivenessAnalysis::LatticeElement>;
using ExtraStateType = mfp::NoExtraState;
uint32_t combineValues(const uint32_t &LHS, const uint32_t &RHS) const {
return std::max(LHS, RHS);
@@ -71,7 +73,9 @@ struct BitLivenessAnalysis {
return LHS <= RHS;
}
uint32_t applyTransferFunction(DataFlowNode *L, const uint32_t E) const;
uint32_t applyTransferFunction(DataFlowNode *L,
const uint32_t E,
mfp::NoExtraState &) const;
};
using BitVector = llvm::BitVector;
@@ -239,7 +243,8 @@ static uint32_t transferZExt(Instruction *Ins, const uint32_t &Element) {
}
uint32_t BitLivenessAnalysis::applyTransferFunction(DataFlowNode *L,
const uint32_t E) const {
const uint32_t E,
mfp::NoExtraState &) const {
auto *Ins = L->Instruction;
switch (Ins->getOpcode()) {
case Instruction::And:
@@ -277,13 +282,23 @@ BitLivenessPass::Result BitLivenessPass::run(llvm::Function &F,
}
}
auto MFPRes = MFP::getMaximalFixedPoint<BitLivenessAnalysis>({},
&DataFlowGraph,
0,
Top,
ExtremalLabels);
// The data-flow graph has no designated entry node and the analysis is
// backward-shaped (extremals are sinks). Seed RPOT from every node.
mfp::MFPConfiguration<BitLivenessAnalysis> Configuration{
.Flow = &DataFlowGraph,
.ExtremalValue = &Top,
.ExtremalLabels = &ExtremalLabels,
.EntryLabels = mfp::All{}
};
auto Results = mfp::getMaximalFixedPoint<BitLivenessAnalysis>(Configuration);
using GraphType = typename BitLivenessAnalysis::GraphType;
using GraphTraits = llvm::GraphTraits<GraphType>;
static_assert(mfp::HasNodeRange<GraphTraits>);
BitLivenessPass::Result Result;
for (auto &[Label, MFPResult] : MFPRes) {
for (auto &[Label, MFPResult] : Results) {
auto &Entry = Result[Label->Instruction];
Entry.Result = MFPResult.InValue;
Entry.Operands = MFPResult.OutValue;
@@ -292,7 +307,7 @@ BitLivenessPass::Result BitLivenessPass::run(llvm::Function &F,
if (llvm::Error Error = DataFlowGraph.verify())
revng_abort(revng::unwrapError(std::move(Error)).c_str());
MFP::Graph<BitLivenessAnalysis> MFPGraph(&DataFlowGraph, MFPRes);
mfp::Graph<BitLivenessAnalysis> MFPGraph(&DataFlowGraph, Results);
return Result;
}
@@ -306,7 +321,7 @@ bool BitLivenessWrapperPass::runOnFunction(llvm::Function &F) {
} // namespace TypeShrinking
template<>
void MFP::dump(llvm::raw_ostream &Stream,
void mfp::dump(llvm::raw_ostream &Stream,
unsigned Indent,
const unsigned &Value) {
Stream << Value;
+20 -15
View File
@@ -78,7 +78,8 @@ bool AdvancedValueInfoMFI::isLessOrEqual(const LatticeElement &LHS,
AdvancedValueInfoMFI::LatticeElement
AdvancedValueInfoMFI::applyTransferFunction(Label L,
const LatticeElement &E) const {
const LatticeElement &E,
mfp::NoExtraState &) const {
revng_log(AVILogger, " " << L->toString());
LoggerIndent Indent(AVILogger);
@@ -190,7 +191,7 @@ AdvancedValueInfoMFI::applyTransferFunction(Label L,
}
void AdvancedValueInfoMFI::dump(GraphType CFEG, const ResultsMap &AllResults) {
MFP::Graph<AdvancedValueInfoMFI> MFPGraph(CFEG, AllResults);
mfp::Graph<AdvancedValueInfoMFI> MFPGraph(CFEG, AllResults);
llvm::WriteGraph(&MFPGraph, "cfeg");
}
@@ -199,7 +200,7 @@ void AdvancedValueInfoMFI::dump(GraphType CFEG, const ResultsMap &AllResults) {
std::tuple<std::map<llvm::Instruction *, ConstantRangeSet>,
ControlFlowEdgesGraph,
map<const ForwardNode<ControlFlowEdgesNode> *,
MFP::MFPResult<map<llvm::Instruction *, ConstantRangeSet>>>>
mfp::MFPResult<map<llvm::Instruction *, ConstantRangeSet>>>>
runAVI(const DataFlowGraph &DFG,
llvm::Instruction *Context,
const llvm::DominatorTree &DT,
@@ -237,7 +238,7 @@ runAVI(const DataFlowGraph &DFG,
std::map<llvm::Instruction *, ConstantRangeSet>{},
ControlFlowEdgesGraph(),
map<const ForwardNode<ControlFlowEdgesNode> *,
MFP::MFPResult<map<llvm::Instruction *, ConstantRangeSet>>>{}
mfp::MFPResult<map<llvm::Instruction *, ConstantRangeSet>>>{}
};
}
@@ -315,13 +316,17 @@ runAVI(const DataFlowGraph &DFG,
ExtremalValue[I] = ConstantRangeSet(I->getType()->getIntegerBitWidth(),
true);
auto AllResults = MFP::getMaximalFixedPoint(AVIMFI,
&CFEG,
{},
ExtremalValue,
InitialNodes,
InitialNodes,
AVILogger);
mfp::MFPConfiguration<AdvancedValueInfoMFI> Configuration{
.Instance = &AVIMFI,
.Flow = &CFEG,
.ExtremalValue = &ExtremalValue,
.ExtremalLabels = &InitialNodes,
.EntryLabels = &InitialNodes,
.Logger = &AVILogger
};
auto GetMaximalFixedPoint = mfp::getMaximalFixedPoint<AdvancedValueInfoMFI>;
auto AllResults = GetMaximalFixedPoint(Configuration);
if (AVILogger.isEnabled()) {
AVILogger << "Dumping MFP results:" << DoLog;
@@ -329,9 +334,9 @@ runAVI(const DataFlowGraph &DFG,
for (const auto &[Node, AnalysisResults] : AllResults) {
AVILogger << Node->toString() << ":\n";
AVILogger << " Initial value:\n";
MFP::dump(*AVILogger.getAsLLVMStream().get(), 2, AnalysisResults.InValue);
mfp::dump(*AVILogger.getAsLLVMStream().get(), 2, AnalysisResults.InValue);
AVILogger << " Final value:\n";
MFP::dump(*AVILogger.getAsLLVMStream().get(),
mfp::dump(*AVILogger.getAsLLVMStream().get(),
2,
AnalysisResults.OutValue);
}
@@ -344,7 +349,7 @@ runAVI(const DataFlowGraph &DFG,
}
template<>
void MFP::dump(llvm::raw_ostream &Stream,
void mfp::dump(llvm::raw_ostream &Stream,
unsigned Indent,
const std::map<llvm::Instruction *, ConstantRangeSet> &Element) {
for (const auto &[I, Range] : Element) {
@@ -357,7 +362,7 @@ void MFP::dump(llvm::raw_ostream &Stream,
}
template<>
void MFP::dumpLabel(llvm::raw_ostream &Stream,
void mfp::dumpLabel(llvm::raw_ostream &Stream,
const ControlFlowEdgesGraph::Node *const &Label) {
Stream << Label->toString();
}
@@ -87,6 +87,8 @@ class ModelOverrideByName(Command):
return 1
for base_function in base_model["Functions"]:
if "Name" not in base_function:
continue
if base_function["Name"] == function_name:
function_to_override["Entry"] = base_function["Entry"]
function_to_override["Name"] = base_function["Name"]
+2 -2
View File
@@ -261,7 +261,7 @@ branches:
- pipe: pure-llvm-passes-pipe
arguments: [llvm-functions]
configuration:
passes: [hoist-struct-phis]
passes: [split-struct-phis]
- pipe: legacy-segregate-stack-accesses
arguments: [llvm-functions]
- pipe: pure-llvm-passes-pipe
@@ -312,7 +312,7 @@ branches:
arguments: [llvm-functions]
configuration:
passes:
- hoist-struct-phis
- split-struct-phis
- pipe: segregate-stack-accesses
arguments: [llvm-functions]
- pipe: pure-llvm-passes-pipe
+1
View File
@@ -26,6 +26,7 @@ USAGE: revng-artifact [options] <artifact> <binary>
emit-model-header - text/x.c+ptml
emit-type-definitions - text/x.c+tar+gz
cleanup-ir - application/x.llvm.bc+zstd
segregate-stack-accesses - application/x.llvm.bc+zstd
emit-c - text/x.c+ptml+tar+gz
emit-c-as-single-file - text/x.c+ptml
```
+2 -2
View File
@@ -216,9 +216,9 @@ static_assert(sizeof(float128_t) == 16, "");
// Undefined values
//
extern uintmax_t undef_value(void);
extern void const *undef_value(size_t size);
#define undef(T) ((T) undef_value())
#define undef(T) (*(__typeof__(T) *) undef_value(sizeof(T)))
//
// Break and continue
+8 -3
View File
@@ -420,7 +420,7 @@ Branches:
- Type: llvm-pipe
UsedContainers: [functions.bc.zstd]
Passes:
- hoist-struct-phis
- split-struct-phis
- legacy-segregate-stack-accesses
- cleanup-stack-size-markers
- dce
@@ -483,7 +483,7 @@ Branches:
- Type: llvm-pipe
UsedContainers: [functions.bc.zstd]
Passes:
- hoist-struct-phis
- split-struct-phis
- remove-llvmassume-calls
- dce
- remove-pointer-casts
@@ -648,7 +648,7 @@ Branches:
- Type: llvm-pipe
UsedContainers: [functions.bc.zstd]
Passes:
- hoist-struct-phis
- split-struct-phis
- segregate-stack-accesses
- cleanup-stack-size-markers
- dce
@@ -673,6 +673,11 @@ Branches:
- strip-dead-prototypes
- split-overflow-intrinsics
- dce
Artifacts:
Container: functions.bc.zstd
Kind: stack-accesses-segregated
SingleTargetFilename: clean-ir.ll
Docs: ""
- Name: emit-c
Pipes:
- Type: llvm-pipe
@@ -24,7 +24,7 @@ commands:
revng analyze --resume "$OUTPUT" detect-stack-size "$INPUT" -o /dev/null;
revng artifact --resume "$OUTPUT" make-segment-ref "$INPUT" |
revng artifact --resume "$OUTPUT" segregate-stack-accesses "$INPUT" |
revng opt -S | FileCheck ${SOURCE}.filecheck.ll;
revng artifact --resume "$OUTPUT" emit-c "$INPUT" -o /dev/null;
@@ -7,6 +7,10 @@ CHECK-DAG: add i64 [[IGN:.*]]%[[ARG1]]
CHECK-DAG: add i64 [[IGN:.*]]%[[ARG2]]
CHECK: }
CHECK: define i64 @local_call_raw_primitives_on_registers() [[IGN:.*]] {
CHECK-DAG: = call i64 @local_raw_primitives_on_registers(i64 2, i64 1)
CHECK: }
CHECK: define i64 @local_raw_pointers_on_registers(i64 %[[ARG1:.*]], i64 %[[ARG2:.*]]) [[IGN:.*]] {
CHECK-DAG: %[[ARG1_PTR:.*]] = inttoptr i64 %[[ARG1]] to ptr
CHECK-DAG: load i64, ptr %[[ARG1_PTR:.*]]
@@ -14,64 +18,147 @@ CHECK-DAG: %[[ARG2_PTR:.*]] = inttoptr i64 %[[ARG2]] to ptr
CHECK-DAG: load i64, ptr %[[ARG2_PTR]]
CHECK: }
CHECK: define i64 @local_call_raw_pointers_on_registers() [[IGN:.*]] {
CHECK-DAG: = call i64 @local_raw_pointers_on_registers(i64 [[ARG:.*]], i64 [[ARG]])
CHECK: }
CHECK: define i64 @local_raw_primitives_on_stack(i64 %[[IGN:.*]], i64 %[[IGN:.*]], i64 %[[IGN:.*]], i64 %[[IGN:.*]], i64 %[[IGN:.*]], i64 %[[IGN:.*]], i64 %[[STACK_ARG:.*]]) [[IGN:.*]] {
CHECK-DAG: %[[STACK_ARG_AO:.*]] = call i64 @AddressOf([[IGN:.*]]i64 %[[STACK_ARG]])
CHECK-DAG: %[[STACK_ARG8:.*]] = add i64 %[[STACK_ARG_AO]], 8
CHECK-DAG: %[[STACK_ARG8:.*]] = add i64 %[[STACK_ARG]], 8
CHECK-DAG: %[[STACK_ARG8_PTR:.*]] = inttoptr i64 %[[STACK_ARG8]] to ptr
CHECK-DAG: load i64, ptr %[[STACK_ARG8_PTR:.*]]
CHECK-DAG: %[[STACK_ARG_PTR:.*]] = inttoptr i64 %[[STACK_ARG_AO]] to ptr
CHECK-DAG: %[[STACK_ARG_PTR:.*]] = inttoptr i64 %[[STACK_ARG]] to ptr
CHECK-DAG: load i64, ptr %[[STACK_ARG_PTR]]
CHECK: }
CHECK: define i64 @local_call_raw_primitives_on_stack() [[IGN:.*]] {
CHECK-DAG: %[[STACK:.*]] = alloca [16 x i8]
CHECK-DAG: %[[STACK_INT:.*]] = ptrtoint ptr %[[STACK]] to i64
CHECK-DAG: %[[STACK_INT_8:.*]] = add i64 %[[STACK_INT]], 8
CHECK-DAG: %[[STACK_8:.*]] = inttoptr i64 %[[STACK_INT_8]] to ptr
CHECK-DAG: store i64 8, ptr %[[STACK_8]]
CHECK-DAG: store i64 7, ptr %[[STACK]]
CHECK-DAG: = call i64 @local_raw_primitives_on_stack(i64 4, i64 3, i64 2, i64 1, i64 5, i64 6, i64 %[[STACK_INT]])
CHECK: }
CHECK: define i64 @local_cabi_primitives_on_registers(i64 %[[ARG1:.*]], i64 %[[ARG2:.*]]) [[IGN:.*]] {
CHECK-DAG: add i64 [[IGN:.*]]%[[ARG1]]
CHECK-DAG: add i64 [[IGN:.*]]%[[ARG2]]
CHECK: }
CHECK: define i64 @local_call_cabi_primitives_on_registers() [[IGN:.*]] {
CHECK-DAG: = call i64 @local_cabi_primitives_on_registers(i64 1, i64 2)
CHECK: }
CHECK: define i64 @local_cabi_primitives_on_stack(i64 %[[IGN:.*]], i64 %[[IGN:.*]], i64 %[[IGN:.*]], i64 %[[IGN:.*]], i64 %[[IGN:.*]], i64 %[[IGN:.*]], i64 %[[STACK_ARG1:.*]], i64 %[[STACK_ARG2:.*]]) [[IGN:.*]] {
CHECK-DAG: %[[IGN:.*]] = add i64 %[[IGN:.*]]%[[STACK_ARG1]]
CHECK-DAG: %[[IGN:.*]] = add i64 %[[IGN:.*]]%[[STACK_ARG2]]
CHECK: }
CHECK: define i64 @local_call_cabi_primitives_on_stack() [[IGN:.*]] {
CHECK-DAG: = call i64 @local_cabi_primitives_on_stack(i64 1, i64 2, i64 3, i64 4, i64 5, i64 6, i64 [[SCALAR1:.*]], i64 [[SCALAR2:.*]])
CHECK: }
CHECK: define i64 @local_cabi_aggregate_on_registers(i64 %[[ARG1:.*]]) [[IGN:.*]] {
CHECK-DAG: %[[ARG1_AO:.*]] = call i64 @AddressOf([[IGN:.*]]i64 %[[ARG1]])
CHECK-DAG: %[[FIELD1_PTR:.*]] = inttoptr i64 %[[ARG1_AO]] to ptr
CHECK-DAG: %[[FIELD1_PTR:.*]] = inttoptr i64 %[[ARG1]] to ptr
CHECK-DAG: load i64, ptr %[[FIELD1_PTR]]
CHECK-DAG: %[[FIELD2_ADDR:.*]] = add i64 %[[ARG1_AO]], 8
CHECK-DAG: %[[FIELD2_ADDR:.*]] = add i64 %[[ARG1]], 8
CHECK-DAG: %[[FIELD2_PTR:.*]] = inttoptr i64 %[[FIELD2_ADDR]] to ptr
CHECK-DAG: load i64, ptr %[[FIELD2_PTR]], align 8
CHECK: }
CHECK: define i64 @local_call_cabi_aggregate_on_registers() [[IGN:.*]] {
CHECK-DAG: %[[STACK:.*]] = alloca [16 x i8]
CHECK-DAG: %[[STACK_INT:.*]] = ptrtoint ptr %[[STACK]] to i64
CHECK-DAG: store i64 1, ptr %[[STACK]]
CHECK-DAG: %[[STACK_INT_8:.*]] = add i64 %[[STACK_INT]], 8
CHECK-DAG: %[[STACK_8:.*]] = inttoptr i64 %[[STACK_INT_8]] to ptr
CHECK-DAG: store i64 2, ptr %[[STACK_8]]
CHECK-DAG: = call i64 @local_cabi_aggregate_on_registers(i64 %[[STACK_INT]])
CHECK: }
CHECK: define i64 @local_cabi_aggregate_on_stack(i64 %[[IGN:.*]], i64 %[[IGN:.*]], i64 %[[IGN:.*]], i64 %[[IGN:.*]], i64 %[[IGN:.*]], i64 %[[IGN:.*]], i64 %[[STACK_ARG:.*]]) [[IGN:.*]] {
CHECK-DAG: %[[STACK_ARG_AO:.*]] = call i64 @AddressOf([[IGN:.*]]i64 %[[STACK_ARG]])
CHECK-DAG: %[[FIELD1_PTR:.*]] = inttoptr i64 %[[STACK_ARG_AO]] to ptr
CHECK-DAG: %[[FIELD1_PTR:.*]] = inttoptr i64 %[[STACK_ARG]] to ptr
CHECK-DAG: load i64, ptr %[[FIELD1_PTR]]
CHECK-DAG: %[[FIELD2_ADDR:.*]] = add i64 %[[STACK_ARG_AO]], 8
CHECK-DAG: %[[FIELD2_ADDR:.*]] = add i64 %[[STACK_ARG]], 8
CHECK-DAG: %[[FIELD2_PTR:.*]] = inttoptr i64 %[[FIELD2_ADDR]] to ptr
CHECK-DAG: load i64, ptr %[[FIELD2_PTR]]
CHECK: }
CHECK: define i64 @local_call_cabi_aggregate_on_stack() [[IGN:.*]] {
CHECK-DAG: %[[STACK:.*]] = alloca [16 x i8]
CHECK-DAG: %[[STACK_INT:.*]] = ptrtoint ptr %[[STACK]] to i64
CHECK-DAG: store i64 1, ptr %[[STACK]]
CHECK-DAG: %[[STACK_INT_8:.*]] = add i64 %[[STACK_INT]], 8
CHECK-DAG: %[[STACK_8:.*]] = inttoptr i64 %[[STACK_INT_8]] to ptr
CHECK-DAG: store i64 2, ptr %[[STACK_8]]
CHECK-DAG: = call i64 @local_cabi_aggregate_on_stack(i64 1, i64 2, i64 3, i64 4, i64 5, i64 6, i64 %[[STACK_INT]])
CHECK: }
CHECK: define i64 @local_cabi_aggregate_on_stack_and_registers(i64 %[[IGN:.*]], i64 %[[IGN:.*]], i64 %[[IGN:.*]], i64 %[[IGN:.*]], i64 %[[IGN:.*]], i64 %[[STACK_ARG:.*]]) [[IGN:.*]] {
CHECK-DAG: %[[STACK_ARG_AO:.*]] = call i64 @AddressOf([[IGN:.*]]i64 %[[STACK_ARG]])
CHECK-DAG: %[[FIELD1_PTR:.*]] = inttoptr i64 %[[STACK_ARG_AO]] to ptr
CHECK-DAG: %[[FIELD1_PTR:.*]] = inttoptr i64 %[[STACK_ARG]] to ptr
CHECK-DAG: load i64, ptr %[[FIELD1_PTR]]
CHECK-DAG: %[[FIELD2_ADDR:.*]] = add i64 %[[STACK_ARG_AO]], 8
CHECK-DAG: %[[FIELD2_ADDR:.*]] = add i64 %[[STACK_ARG]], 8
CHECK-DAG: %[[FIELD2_PTR:.*]] = inttoptr i64 %[[FIELD2_ADDR]] to ptr
CHECK-DAG: load i64, ptr %[[FIELD2_PTR]]
CHECK: }
CHECK: define i64 @local_caller() [[IGN:.*]] {
CHECK-DAG: = call i64 @local_raw_primitives_on_registers(i64 2, i64 1)
CHECK-DAG: = call i64 @local_raw_pointers_on_registers(i64 %[[ARG:.*]], i64 %[[ARG]])
CHECK-DAG: %[[STACK:.*]] = call i64 @revng_call_stack_arguments([[IGN:.*]], i64 16)
CHECK-DAG: = call i64 @local_raw_primitives_on_stack(i64 4, i64 3, i64 2, i64 1, i64 5, i64 6, i64 %[[STACK]])
CHECK-DAG: = call i64 @local_cabi_primitives_on_registers(i64 1, i64 2)
TODO: devise a pipeline that highlights both arguments as immediates
CHECK-DAG: = call i64 @local_cabi_primitives_on_stack(i64 1, i64 2, i64 3, i64 4, i64 5, i64 6, i64 [[SCALAR1:.*]], i64 [[SCALAR2:.*]])
CHECK-DAG: %[[AGGREGATE:.*]] = call i64 @revng_call_stack_arguments([[IGN:.*]], i64 16)
CHECK-DAG: = call i64 @local_cabi_aggregate_on_registers(i64 %[[AGGREGATE]])
CHECK-DAG: %[[AGGREGATE:.*]] = call i64 @revng_call_stack_arguments([[IGN:.*]], i64 16)
CHECK-DAG: = call i64 @local_cabi_aggregate_on_stack(i64 1, i64 2, i64 3, i64 4, i64 5, i64 6, i64 %[[AGGREGATE]])
CHECK-DAG: %[[AGGREGATE:.*]] = call i64 @revng_call_stack_arguments([[IGN:.*]], i64 16)
CHECK-DAG: = call i64 @local_cabi_aggregate_on_stack_and_registers(i64 1, i64 2, i64 3, i64 4, i64 5, i64 %[[AGGREGATE]])
CHECK: define i64 @local_call_cabi_aggregate_on_stack_and_registers() [[IGN:.*]] {
CHECK-DAG: %[[STACK:.*]] = alloca [16 x i8]
CHECK-DAG: %[[STACK_INT:.*]] = ptrtoint ptr %[[STACK]] to i64
CHECK-DAG: store i64 1, ptr %[[STACK]]
CHECK-DAG: %[[STACK_INT_8:.*]] = add i64 %[[STACK_INT]], 8
CHECK-DAG: %[[STACK_8:.*]] = inttoptr i64 %[[STACK_INT_8]] to ptr
CHECK-DAG: store i64 2, ptr %[[STACK_8]]
CHECK-DAG: = call i64 @local_cabi_aggregate_on_stack_and_registers(i64 1, i64 2, i64 3, i64 4, i64 5, i64 %[[STACK_INT]])
CHECK: }
CHECK: define <{ i64, i64 }> @local_raw_return_small_aggregate() [[IGN:.*]] {
CHECK-DAG: %[[RESULT:.*]] = call <{ i64, i64 }> @struct_initializer(i64 124, i64 123)
CHECK-DAG: ret <{ i64, i64 }> %[[RESULT]]
CHECK: }
CHECK: define i64 @local_call_raw_return_small_aggregate() [[IGN:.*]] {
CHECK: %[[RESULT:.*]] = call <{ i64, i64 }> @local_raw_return_small_aggregate()
CHECK-DAG: call i64 @OpaqueExtractvalue(<{ i64, i64 }> %[[RESULT]], i64 1)
CHECK: }
CHECK: define [16 x i8] @local_cabi_return_small_aggregate() [[IGN:.*]] {
CHECK-DAG: %[[RETURN_ALLOCA:.*]] = alloca [16 x i8]
CHECK-DAG: %[[RETURN_ALLOCA_INT:.*]] = ptrtoint ptr %[[RETURN_ALLOCA]] to i64
CHECK-DAG: %[[RETURN_ALLOCA_INT_8:.*]] = add i64 %[[RETURN_ALLOCA_INT]], 8
CHECK-DAG: %[[RETURN_ALLOCA_8:.*]] = inttoptr i64 %[[RETURN_ALLOCA_INT_8]] to ptr
CHECK-DAG: store i64 124, ptr %[[RETURN_ALLOCA]]
CHECK-DAG: store i64 123, ptr %[[RETURN_ALLOCA_8]]
CHECK-DAG: %[[TO_RETURN:.*]] = load [16 x i8], ptr %[[RETURN_ALLOCA]]
CHECK-DAG: ret [16 x i8] %[[TO_RETURN]]
CHECK: }
CHECK: define i64 @local_call_cabi_return_small_aggregate() [[IGN:.*]] {
CHECK-DAG: %[[RETURN_ALLOCA:.*]] = alloca [16 x i8]
CHECK-DAG: %[[RETURN_ALLOCA_INT:.*]] = ptrtoint ptr %[[RETURN_ALLOCA]] to i64
CHECK-DAG: %[[RETURN_VALUE:.*]] = call [16 x i8] @local_cabi_return_small_aggregate()
CHECK-DAG: store [16 x i8] %[[RETURN_VALUE]], ptr %[[RETURN_ALLOCA]]
CHECK-DAG: %[[RETURN_ALLOCA_INT_8:.*]] = add i64 %[[RETURN_ALLOCA_INT]], 8
CHECK-DAG: %[[RETURN_ALLOCA_8:.*]] = inttoptr i64 %[[RETURN_ALLOCA_INT_8]] to ptr
CHECK-DAG: %[[TO_RETURN:.*]] = load i64, ptr %[[RETURN_ALLOCA_8]]
CHECK: }
CHECK: define [64 x i8] @local_cabi_return_big_aggregate() [[IGN:.*]] {
CHECK-DAG: %[[RETURN_ALLOCA:.*]] = alloca [64 x i8]
CHECK-DAG: %[[RETURN_ALLOCA_INT:.*]] = ptrtoint ptr %[[RETURN_ALLOCA]] to i64
CHECK-DAG: %[[RETURN_ALLOCA_INT_16:.*]] = add i64 %[[RETURN_ALLOCA_INT]], 16
CHECK-DAG: %[[RETURN_ALLOCA_16:.*]] = inttoptr i64 %[[RETURN_ALLOCA_INT_16]] to ptr
CHECK-DAG: store i64 123, ptr %[[RETURN_ALLOCA_16]]
CHECK-DAG: %[[TO_RETURN:.*]] = load [64 x i8], ptr %[[RETURN_ALLOCA]]
CHECK-DAG: ret [64 x i8] %[[TO_RETURN]]
CHECK: }
CHECK: define i64 @local_call_cabi_return_big_aggregate() [[IGN:.*]] {
CHECK-DAG: %[[RETURN_ALLOCA:.*]] = alloca [64 x i8]
CHECK-DAG: %[[RETURN_ALLOCA_INT:.*]] = ptrtoint ptr %[[RETURN_ALLOCA]] to i64
CHECK-DAG: %[[RETURN_VALUE:.*]] = call [64 x i8] @local_cabi_return_big_aggregate()
CHECK-DAG: store [64 x i8] %[[RETURN_VALUE]], ptr %[[RETURN_ALLOCA]]
CHECK-DAG: %[[RETURN_ALLOCA_INT_16:.*]] = add i64 %[[RETURN_ALLOCA_INT]], 16
CHECK-DAG: %[[RETURN_ALLOCA_16:.*]] = inttoptr i64 %[[RETURN_ALLOCA_INT_16]] to ptr
CHECK-DAG: %[[TO_RETURN:.*]] = load i64, ptr %[[RETURN_ALLOCA_16]]
CHECK: }
@@ -37,10 +37,18 @@ Functions:
Prototype:
Kind: DefinedType
Definition: "/TypeDefinitions/100008-CABIFunctionDefinition"
- Name: cabi_return_big_aggregate
- Name: cabi_return_small_aggregate
Prototype:
Kind: DefinedType
Definition: "/TypeDefinitions/100010-CABIFunctionDefinition"
- Name: cabi_return_big_aggregate
Prototype:
Kind: DefinedType
Definition: "/TypeDefinitions/100012-CABIFunctionDefinition"
- Name: raw_return_small_aggregate
Prototype:
Kind: DefinedType
Definition: "/TypeDefinitions/100013-RawFunctionDefinition"
TypeDefinitions:
- Kind: StructDefinition
ID: 100003
@@ -310,6 +318,28 @@ TypeDefinitions:
Definition: "/TypeDefinitions/100003-StructDefinition"
- Kind: StructDefinition
ID: 100009
Size: 16
Fields:
- Offset: 0
Type:
Kind: PrimitiveType
PrimitiveKind: Generic
Size: 8
- Offset: 8
Type:
Kind: PrimitiveType
PrimitiveKind: Generic
Size: 8
- Kind: CABIFunctionDefinition
ID: 100010
ABI: SystemV_x86_64
ReturnType:
Kind: DefinedType
Definition: "/TypeDefinitions/100009-StructDefinition"
Arguments: []
- Kind: StructDefinition
ID: 100011
Size: 64
Fields:
- Offset: 0
Type:
@@ -351,11 +381,24 @@ TypeDefinitions:
Kind: PrimitiveType
PrimitiveKind: Generic
Size: 8
Size: 64
- Kind: CABIFunctionDefinition
ID: 100010
ID: 100012
ABI: SystemV_x86_64
ReturnType:
Kind: DefinedType
Definition: "/TypeDefinitions/100009-StructDefinition"
Definition: "/TypeDefinitions/100011-StructDefinition"
Arguments: []
- Kind: RawFunctionDefinition
ID: 100013
Architecture: x86_64
ReturnValues:
- Location: rax_x86_64
Type:
Kind: PrimitiveType
PrimitiveKind: Generic
Size: 8
- Location: rdx_x86_64
Type:
Kind: PrimitiveType
PrimitiveKind: Generic
Size: 8
+12
View File
@@ -484,6 +484,18 @@ revng_add_test(
"${CMAKE_BINARY_DIR}/bin/revng opt -S -early-type-shrinking -type-shrinking -instcombine ${SRC}/TypeShrinking.ll | FileCheck ${SRC}/TypeShrinking.ll"
)
#
# test_mfp
#
revng_add_test_executable(test_mfp "${SRC}/MFP.cpp")
target_compile_definitions(test_mfp PRIVATE "BOOST_TEST_DYN_LINK=1")
target_include_directories(test_mfp PRIVATE "${CMAKE_SOURCE_DIR}")
target_link_libraries(test_mfp revngSupport revngUnitTestHelpers
Boost::unit_test_framework ${LLVM_LIBRARIES})
revng_add_test(NAME test_mfp COMMAND test_mfp)
set_tests_properties(test_mfp PROPERTIES LABELS "unit;mfp")
#
# test_combingpass
#
+207
View File
@@ -0,0 +1,207 @@
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#define BOOST_TEST_MODULE MFPExtraState
bool init_unit_test();
#include <set>
#include <string>
#include <vector>
#include "boost/test/unit_test.hpp"
#include "llvm/ADT/GraphTraits.h"
#include "revng/ADT/GenericGraph.h"
#include "revng/MFP/MFP.h"
#include "revng/MFP/SetLattices.h"
// Each node has a name and an ordered list of integer "operations" that the
// transfer function adds to a `std::set<int>` lattice. The `ExtraState`
// recording surface uses `(NodeName, OperationIndex)` as its key, so each
// individual operation can be observed before/after.
struct OperationNodeData {
std::string Name;
std::vector<int> Operations;
};
using OperationNode = ForwardNode<OperationNodeData>;
using OperationGraph = GenericGraph<OperationNode>;
using OperationKey = std::pair<std::string, size_t>;
using IntSet = std::set<int>;
struct OperationsMFI : public SetUnionLattice<IntSet> {
using Label = OperationNode *;
using GraphType = OperationGraph *;
using LatticeElement = IntSet;
using ExtraStateKey = OperationKey;
using ExtraStateType = mfp::ExtraState<OperationKey, LatticeElement>;
LatticeElement applyTransferFunction(Label Node,
const LatticeElement &In,
ExtraStateType &State) const {
LatticeElement Current = In;
for (size_t I = 0; I < Node->Operations.size(); ++I) {
OperationKey K{ Node->Name, I };
State.registerBefore(K, Current);
Current.insert(Node->Operations[I]);
State.registerAfter(K, Current);
}
return Current;
}
};
static_assert(mfp::MonotoneFrameworkInstance<OperationsMFI>);
namespace {
struct DiamondGraph {
// A diamond:
//
// Entry:[1]
// / \
// Left:[2] Right:[3]
// \ /
// Tail:[4]
//
OperationGraph Graph;
OperationNode *Entry = nullptr;
OperationNode *Left = nullptr;
OperationNode *Right = nullptr;
OperationNode *Tail = nullptr;
DiamondGraph() {
Entry = Graph.addNode(OperationNodeData{ "Entry", { 1 } });
Left = Graph.addNode(OperationNodeData{ "Left", { 2 } });
Right = Graph.addNode(OperationNodeData{ "Right", { 3 } });
Tail = Graph.addNode(OperationNodeData{ "Tail", { 4 } });
Graph.setEntryNode(Entry);
Entry->addSuccessor(Left);
Entry->addSuccessor(Right);
Left->addSuccessor(Tail);
Right->addSuccessor(Tail);
}
};
} // namespace
// The fixed point of a simple set-union analysis on a diamond graph is the
// union of every reachable predecessor's contributions. After convergence
// every node downstream of `Entry` must observe `1` on entry; `Tail` must see
// the contributions of both `Left` and `Right`.
BOOST_AUTO_TEST_CASE(DiamondMFP) {
DiamondGraph G;
OperationsMFI MFI;
IntSet Bottom;
IntSet ExtremalValue;
std::vector<OperationNode *> ExtremalLabels{ G.Entry };
mfp::MFPConfiguration<OperationsMFI> Configuration{
.Instance = &MFI,
.Flow = &G.Graph,
.Bottom = &Bottom,
.ExtremalValue = &ExtremalValue,
.ExtremalLabels = &ExtremalLabels,
};
auto Result = mfp::getMaximalFixedPoint<OperationsMFI>(Configuration);
BOOST_TEST(Result.at(G.Entry).InValue == IntSet{});
BOOST_TEST(Result.at(G.Entry).OutValue == IntSet({ 1 }));
BOOST_TEST(Result.at(G.Left).InValue == IntSet({ 1 }));
BOOST_TEST(Result.at(G.Left).OutValue == IntSet({ 1, 2 }));
BOOST_TEST(Result.at(G.Right).InValue == IntSet({ 1 }));
BOOST_TEST(Result.at(G.Right).OutValue == IntSet({ 1, 3 }));
// `Tail` joins both branches, so its incoming value must be `{1, 2, 3}` and
// its outgoing value must additionally contain its own contribution `4`.
BOOST_TEST(Result.at(G.Tail).InValue == IntSet({ 1, 2, 3 }));
BOOST_TEST(Result.at(G.Tail).OutValue == IntSet({ 1, 2, 3, 4 }));
}
// Verify the analysis reaches the same fixed point on a graph with a back
// edge (and a multi-step node), exercising the worklist.
BOOST_AUTO_TEST_CASE(LoopMFP) {
// A:[1] --> B:[2,3] --> C:[4]
// ^ /
// \________/
OperationGraph Graph;
auto *A = Graph.addNode(OperationNodeData{ "A", { 1 } });
auto *B = Graph.addNode(OperationNodeData{ "B", { 2, 3 } });
auto *C = Graph.addNode(OperationNodeData{ "C", { 4 } });
Graph.setEntryNode(A);
A->addSuccessor(B);
B->addSuccessor(C);
C->addSuccessor(B);
OperationsMFI MFI;
IntSet Bottom;
IntSet ExtremalValue;
std::vector<OperationNode *> ExtremalLabels{ A };
mfp::MFPConfiguration<OperationsMFI> Configuration{
.Instance = &MFI,
.Flow = &Graph,
.Bottom = &Bottom,
.ExtremalValue = &ExtremalValue,
.ExtremalLabels = &ExtremalLabels,
};
auto Result = mfp::getMaximalFixedPoint<OperationsMFI>(Configuration);
// The loop forces B and C's incoming values to include {1, 2, 3, 4} once
// the analysis converges.
BOOST_TEST(Result.at(A).OutValue == IntSet({ 1 }));
BOOST_TEST(Result.at(B).InValue == IntSet({ 1, 2, 3, 4 }));
BOOST_TEST(Result.at(B).OutValue == IntSet({ 1, 2, 3, 4 }));
BOOST_TEST(Result.at(C).InValue == IntSet({ 1, 2, 3, 4 }));
BOOST_TEST(Result.at(C).OutValue == IntSet({ 1, 2, 3, 4 }));
}
// Same diamond as the first case, but the caller hands in an `ExtraState`
// pre-populated with a few interesting `(operation, position)` pairs and
// confirms the recorded values match the per-operation lattice values at the
// fixed point. The ExtraState observation is a check on top of the regular
// MFP result, not the focus of the test.
BOOST_AUTO_TEST_CASE(ExtraStateRecordingOnDiamond) {
DiamondGraph G;
using State = mfp::ExtraState<OperationKey, IntSet>;
State S;
// Mark a single point per node.
OperationKey EntryOp0{ "Entry", 0 };
OperationKey LeftOp0{ "Left", 0 };
OperationKey TailOp0{ "Tail", 0 };
S.registerAsInterestingBefore(EntryOp0);
S.registerAsInterestingAfter(LeftOp0);
S.registerAsInterestingBefore(TailOp0);
OperationsMFI MFI;
IntSet Bottom;
IntSet ExtremalValue;
std::vector<OperationNode *> ExtremalLabels{ G.Entry };
mfp::MFPConfiguration<OperationsMFI> Configuration{
.Instance = &MFI,
.Flow = &G.Graph,
.Bottom = &Bottom,
.ExtremalValue = &ExtremalValue,
.ExtremalLabels = &ExtremalLabels,
.ExtraState = &S
};
auto Result = mfp::getMaximalFixedPoint<OperationsMFI>(Configuration);
// Sanity: the fixed-point lattice values must match the diamond test above.
BOOST_TEST(Result.at(G.Tail).OutValue == IntSet({ 1, 2, 3, 4 }));
// The recorded values agree with the labels' InValue / partial OutValue.
BOOST_TEST(S.getBefore(EntryOp0) == IntSet{});
BOOST_TEST(S.getAfter(LeftOp0) == IntSet({ 1, 2 }));
BOOST_TEST(S.getBefore(TailOp0) == IntSet({ 1, 2, 3 }));
}
+27 -10
View File
@@ -128,11 +128,20 @@ createNoReturn(rua::Block::OperationsVector &&Header,
BOOST_AUTO_TEST_CASE(LivenessTest) {
auto RunAnalysis = [](rua::Function &Function, BlockNode *Entry) {
Liveness LA(Function);
return MFP::getMaximalFixedPoint(LA,
&Function,
LA.defaultValue(),
LA.defaultValue(),
{ Entry });
auto DefaultValue = LA.defaultValue();
std::vector<const BidirectionalNode<Block> *> ExtremalLabels{ Entry };
// Backward analysis: `getEntryNode(Inverse<...>)` is unreliable (returns
// the forward entry), and a single exit doesn't reach no-return blocks.
// Use `All` to seed RPOT from every node.
using InverseGT = llvm::GraphTraits<llvm::Inverse<const rua::Function *>>;
auto GetMaximalFixedPoint = mfp::getMaximalFixedPoint<Liveness, InverseGT>;
return GetMaximalFixedPoint(mfp::MFPConfiguration<Liveness>{
.Instance = &LA,
.Flow = &Function,
.Bottom = &DefaultValue,
.ExtremalValue = &DefaultValue,
.ExtremalLabels = &ExtremalLabels,
.EntryLabels = mfp::All{} });
};
auto RunOnSingleNode =
@@ -274,11 +283,19 @@ BOOST_AUTO_TEST_CASE(LivenessTest) {
BOOST_AUTO_TEST_CASE(ReachingDefinitionsTest) {
auto RunAnalysis = [](TestAnalysisResult &&F) {
ReachingDefinitions RD(F.Function);
auto Results = MFP::getMaximalFixedPoint(RD,
&F.Function,
RD.defaultValue(),
RD.defaultValue(),
{ F.Entry });
auto DefaultValue = RD.defaultValue();
std::vector ExtremalLabels{ F.Entry };
mfp::MFPConfiguration<ReachingDefinitions> Configuration{
.Instance = &RD,
.Flow = &F.Function,
.Bottom = &DefaultValue,
.ExtremalValue = &DefaultValue,
.ExtremalLabels = &ExtremalLabels
};
using namespace mfp;
auto Results = getMaximalFixedPoint<ReachingDefinitions>(Configuration);
return ReachingDefinitions::compute(Results[F.Exit].OutValue,
Results[F.Sink].OutValue);
};