mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
Merge branch 'feature/segretate-aggregates'
This commit is contained in:
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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> *> {};
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -536,7 +536,6 @@ void CodeGenerator::translate(LibTcg &LibTcg,
|
||||
|
||||
} // End loop over instructions
|
||||
|
||||
TranslateTask.complete();
|
||||
TranslateTask.advance("Finalization", true);
|
||||
|
||||
Variables.closeTranslationBlock();
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
+113
-26
@@ -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: }
|
||||
|
||||
+47
-4
@@ -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
|
||||
|
||||
@@ -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
|
||||
#
|
||||
|
||||
@@ -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 }));
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user