Files
revng-revng/lib/CliftTransforms/PointerArithmetic.cpp
T
Andrea Gussoni 02cc6923d0 EmitFieldAccesses: implement pass
The `EmitFieldAccesses` pass transforms `clift` by taking pointer-typed
expressions computed via integerr arithmetic with type-safe field
accesses and array accesses.

The transformation is split in three main phases:
1) `PointerArithmetic` computation.
2) `BestTraversal` computation.
3) `FieldAccess` `clift` rewrite.

The high level driver is implemented in the `EmitFieldAccesses` header
and cpp, while the nested 3 phases are implemented respectively in
`PointerArithmetic`, `BestTraversal` and `FieldAccessReplacement`.

The `computerPointerArithmetic` phase is concerned with taking a
pointer-typed `ExpressionOp`, called `PointerToReplace`, and expressing
it in a `BasePointer+Offset` form.

The `computeBestTraversal` phase is concerned with computing the best
traversal of the type pointed to by `BasePointer`, that can be used to
rewrite the pointer arithmetic in `clift` with just field accesses and
array subscripts.

The `replaceFieldAccess` phase takes the `Traversal` computed at the
previous step, and actually rewrites in `clift` the `PointerToReplace`
in terms of field accesses and array accesses w.r.t. the `BasePointer`.
2026-03-18 17:25:13 +01:00

503 lines
17 KiB
C++

//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include <compare>
#include <optional>
#include "revng/ADT/RecursiveCoroutine.h"
#include "revng/Clift/Clift.h"
#include "EmitFieldAccesses.h"
#include "PointerArithmetic.h"
namespace clift = mlir::clift;
using namespace clift;
static Logger Log("pointer-arithmetic");
// =============================================================================
// `PointerArithmetic` class methods
// =============================================================================
PointerArithmetic::OffsetExpression::OffsetExpression(unsigned BitWidth) :
BaseOffset(BitWidth, 0) {
}
PointerArithmetic::OffsetExpression::OffsetExpression(llvm::APInt Offset) :
BaseOffset(Offset) {
}
bool PointerArithmetic::isNumeric() const {
return !BasePointer;
}
bool PointerArithmetic::isAddress() const {
return static_cast<bool>(BasePointer);
}
bool PointerArithmetic::verify() const {
const auto &LinearCombination = Offset.LinearCombination;
// Check that strides are strictly positive. Negative or null strides do not
// make sense
for (const auto &Term : LinearCombination) {
if (Term.Stride.isNegative() or Term.Stride.isZero()) {
return false;
}
}
// Check that we do not have duplicated strides, and that they are in
// descending order
for (size_t I = 0; I < LinearCombination.size(); I++) {
for (size_t J = I + 1; J < LinearCombination.size(); J++) {
if (LinearCombination[I].Stride == LinearCombination[J].Stride) {
return false;
}
if (LinearCombination[I].Stride.ult(LinearCombination[J].Stride)) {
return false;
}
}
}
// If we pass all the previous checks, we have a valid `PointerArithmetic`
return true;
}
void PointerArithmetic::StridedTerm::dump() const {
Log << " Stride: ";
Log << Stride.getZExtValue() << "\n";
Log << " Dynamic Index: ";
if (Idx.Variable) {
mlir::Value ValueToPrint = Idx.Variable;
ValueToPrint.print(*Log.getAsLLVMStream());
} else {
Log << "(null)";
}
Log << "\n";
Log << " Fixed Offset: ";
Log << Idx.Constant.getSExtValue() << "\n";
}
void PointerArithmetic::OffsetExpression::dump() const {
Log << " Base Offset: ";
Log << BaseOffset.getSExtValue() << "\n";
Log << " Linear Combinations (" << LinearCombination.size() << " terms):\n";
for (size_t I = 0; I < LinearCombination.size(); ++I) {
Log << " Term #" << I << ":\n";
LinearCombination[I].dump();
}
}
void PointerArithmetic::dump() const {
Log << "Dumping PointerArithmetic object:\n";
Log << " BasePointer: ";
if (BasePointer) {
mlir::Value ValueToPrint = BasePointer;
ValueToPrint.print(*Log.getAsLLVMStream());
Log << "\n";
} else {
Log << "(null)\n";
}
Offset.dump();
Log << "\n";
Log.flush();
}
// =============================================================================
// `PointerArithmeticBuilder` class implementation, which contains the logic
// used to compute the resulting `PointerArithmetic`
// =============================================================================
/// The `PointerArithmeticBuilder` class is the main helper class used for
/// computing the `PointerArithmetic` object starting from the
/// `PointerToReplace`
class PointerArithmeticBuilder {
unsigned PointerBitWidth = 0;
public:
PointerArithmeticBuilder() = default;
// Compute pointer arithmetic for a given `PointerToReplace`
std::optional<PointerArithmetic>
computePointerArithmetic(mlir::clift::ExpressionOpInterface PointerToReplace);
private:
// Traversal function to inspect the operands. Uses RecursiveCoroutine
// for stack safety, since traverse and compose* are mutually recursive
// over user input expressions.
RecursiveCoroutine<std::optional<PointerArithmetic>> traverse(mlir::Value V);
// Return the `PointerArithmetic` for the leaf nodes
PointerArithmetic createLeaf(mlir::Value V);
// Methods which are used to compose the currently computed
// `PointerArithmetic` with different `clift` `Operation`s that want to
// traverse during our exploration
RecursiveCoroutine<std::optional<PointerArithmetic>>
composeBitcast(CastOp Bitcast);
RecursiveCoroutine<std::optional<PointerArithmetic>> composeAdd(AddOp Add);
RecursiveCoroutine<std::optional<PointerArithmetic>>
composePtrAdd(PtrAddOp PtrAdd);
RecursiveCoroutine<std::optional<PointerArithmetic>> composeMul(MulOp Mul);
RecursiveCoroutine<std::optional<PointerArithmetic>>
composeShl(ShiftLeftOp ShiftLeft);
// Helper function used to multiply all the `PointerArithmetic` strides and
// offset by a constant. Clears the BasePointer since the result of a
// multiplication is no longer a pointer.
PointerArithmetic multiplyByConstant(PointerArithmetic PA,
const llvm::APInt &Multiplier);
// Helper function used to sort linear combination by stride, in descending
// order
void sortLinearCombination(PointerArithmetic &PA);
};
// =============================================================================
// Static helper standalone functions
// =============================================================================
/// Helper function used to verify if an `ExpressionOpInterface` is
/// `PointerType`d
static bool isPointerTyped(ExpressionOpInterface Expr) {
return Expr->getNumResults() > 0
and isPointerType(Expr->getResult(0).getType());
}
/// Helper function used to extract the underlying constant value from an
/// `Value`
static std::optional<llvm::APInt> getConstantValue(mlir::Value V,
unsigned BitWidth) {
if (auto Immediate = V.getDefiningOp<ImmediateOp>()) {
return llvm::APInt(BitWidth, Immediate.getValue());
}
return std::nullopt;
}
// =============================================================================
// `PointerArithmeticBuilder` class methods
// =============================================================================
std::optional<PointerArithmetic>
PointerArithmeticBuilder::computePointerArithmetic(ExpressionOpInterface
PointerToReplace) {
// We skip every non pointer-typed `PointerToReplace`
if (not isPointerTyped(PointerToReplace)) {
return std::nullopt;
}
// Derive the pointer bit size from the PointerToReplace type
auto PtrType = getPointerType(PointerToReplace->getResult(0).getType());
PointerBitWidth = PtrType.getPointerSize() * 8;
// We traverse upwards the dataflow starting from the
auto Result = rc_eval(traverse(PointerToReplace->getResult(0)));
// If we got a numeric result, discard it, since we cannot use it
if (Result and Result->isNumeric()) {
return std::nullopt;
}
// Verify invariants for the obtained `PointerArithmetic`
revng_assert(not Result or Result->verify());
// Log the resulting `PointerArithmetic`, together with the initial
// `PointerToReplace` it was produced from
if (Log.isEnabled() and PointerToReplace and Result) {
Log << "PointerArithmetic Relative to operation:\n";
PointerToReplace.print(*Log.getAsLLVMStream());
Log << "\n";
Result->dump();
}
// If we reach this point, it means that we successfully computed the
// `PointerArithmetic` object, so we can return it, so that the rewrite can
// happen in later phases
return Result;
}
RecursiveCoroutine<std::optional<PointerArithmetic>>
PointerArithmeticBuilder::traverse(mlir::Value V) {
// Every time we find a compatible `Expression`, we traverse it in order to
// compose its operands
if (auto Expr = V.getDefiningOp<ExpressionOpInterface>()) {
if (auto Cast = mlir::dyn_cast<CastOp>(Expr.getOperation())) {
if (Cast.getKind() == CastKind::Bitcast) {
rc_return rc_recur composeBitcast(Cast);
}
} else if (auto Add = mlir::dyn_cast<AddOp>(Expr.getOperation())) {
rc_return rc_recur composeAdd(Add);
} else if (auto PtrAdd = mlir::dyn_cast<PtrAddOp>(Expr.getOperation())) {
rc_return rc_recur composePtrAdd(PtrAdd);
} else if (auto Mul = mlir::dyn_cast<MulOp>(Expr.getOperation())) {
rc_return rc_recur composeMul(Mul);
} else if (auto Shl = mlir::dyn_cast<ShiftLeftOp>(Expr.getOperation())) {
rc_return rc_recur composeShl(Shl);
}
}
// If we do not traverse V, we create the leaf
rc_return createLeaf(V);
}
PointerArithmetic PointerArithmeticBuilder::createLeaf(mlir::Value V) {
using OffsetExpression = PointerArithmetic::OffsetExpression;
PointerArithmetic PA{ .Offset = OffsetExpression(PointerBitWidth) };
if (isPointerType(V.getType())) {
// Pointer typed expression
PA.BasePointer = V;
PA.Offset = OffsetExpression(llvm::APInt(PointerBitWidth, 0));
} else if (auto ConstantValue = getConstantValue(V, PointerBitWidth)) {
// Integer Constant
PA.Offset = OffsetExpression(*ConstantValue);
} else {
// We want to handle `ExpressionOpInterface`s and `mlir::BlockArgument`s for
// building leaf `PA` containing a `LinearCombination`. These can be the
// `Index` component of an array access.
// Generic offset expression - strided 1 term.
PA.Offset = OffsetExpression(llvm::APInt(PointerBitWidth, 0));
PA.Offset.LinearCombination.emplace_back(llvm::APInt(PointerBitWidth, 1),
PointerArithmetic::Index{
V,
llvm::APInt(PointerBitWidth,
0) });
}
return PA;
}
RecursiveCoroutine<std::optional<PointerArithmetic>>
PointerArithmeticBuilder::composeBitcast(CastOp Cast) {
// Retrieve the `bitcast` single `Operand`, and recursively forward the
// `PointerArithmetic` produced from it
auto Operand = Cast->getOperand(0);
auto OperandPA = rc_recur traverse(Operand);
// Potentially, every `bitcast` could be treated as a `PointerOperand` leaf in
// the exploration, if it was a `PointerType`. This can be useful in cases
// where the exploration through the `Operand` produces a `Numeric`
// `PointerArithmetic` (not reaching a `PointerType`). In such cases, we use
// the current `bitcast` as a `BasePointer`. Better than nothing.
if (OperandPA->isNumeric() and isPointerTyped(Cast)) {
auto CastPA = createLeaf(Cast);
rc_return CastPA;
}
rc_return OperandPA;
}
RecursiveCoroutine<std::optional<PointerArithmetic>>
PointerArithmeticBuilder::composeAdd(AddOp Add) {
auto LHS = Add.getLhs();
auto RHS = Add.getRhs();
auto LHSPA = rc_recur traverse(LHS);
auto RHSPA = rc_recur traverse(RHS);
// It may happen that either the LHS or the RHS traversal produced an invalid
// `PointerArithmetic` result. In such case, we need to propagate upward the
// failure.
if (not LHSPA or not RHSPA) {
rc_return std::nullopt;
}
// It may happen that both `LHSPA` or `RHSPA` have an address
// `PointerArithmetic`. In such situation, we cannot know which one is the
// `BasePointer`, so we bail out from the construction of the
// `PointerArithmetic` result.
if (LHSPA->isAddress() and RHSPA->isAddress()) {
rc_return std::nullopt;
}
// Add the two `PointerArithmetic`s together
using OffsetExpression = PointerArithmetic::OffsetExpression;
PointerArithmetic Result{ .Offset = OffsetExpression(PointerBitWidth) };
// Add the `BaseOffsets`
Result.Offset.BaseOffset = LHSPA->Offset.BaseOffset
+ RHSPA->Offset.BaseOffset;
// Append the linear combinations of `LHS` and `RHS`
Result.Offset.LinearCombination = LHSPA->Offset.LinearCombination;
Result.Offset.LinearCombination
.append(RHSPA->Offset.LinearCombination.begin(),
RHSPA->Offset.LinearCombination.end());
// After we combined the `LinearCombination`s coming from the operands, we
// sort them in descending order so that larger `Stride`s come before shorter
// ones. This is the expected form derived from a nested array access, where
// the stride of the _outer_ level is greater than the _inner_ one by
// construction. Duplicate strides are checked in the verify phase as an
// invariant.
sortLinearCombination(Result);
// After merging and sorting, if both sides contributed terms with the same
// `Stride`, the `Result` would have duplicate `Stride`s and not pass verify.
// In this situation, we return `nullopt` in order to soft fail the
// `PointerArithmetic` construction, instead of not verifying.
for (size_t I = 1; I < Result.Offset.LinearCombination.size(); ++I) {
const auto &LinearCombination = Result.Offset.LinearCombination;
if (LinearCombination[I].Stride == LinearCombination[I - 1].Stride) {
rc_return std::nullopt;
}
}
// We propagate as the `BasePointer` of the result the one corresponding to
// the _address_ part of the traversal
if (LHSPA->isAddress()) {
Result.BasePointer = LHSPA->BasePointer;
} else if (RHSPA->isAddress()) {
Result.BasePointer = RHSPA->BasePointer;
}
rc_return Result;
}
RecursiveCoroutine<std::optional<PointerArithmetic>>
PointerArithmeticBuilder::composePtrAdd(PtrAddOp Add) {
mlir::Value PointerOperand = Add.getPointer();
mlir::Value OffsetOperand = Add.getOffset();
// Compute the `PointerArithmetic` for both the pointer and offset operands.
auto PointerOperandPA = rc_recur traverse(PointerOperand);
auto OffsetOperandPA = rc_recur traverse(OffsetOperand);
// We should check that the pointer and offset operands are not both `numeric`
// or `address`
if (not PointerOperandPA or not OffsetOperandPA) {
rc_return std::nullopt;
}
revng_assert(not(PointerOperandPA->isAddress()
and OffsetOperandPA->isAddress()));
revng_assert(not(PointerOperandPA->isNumeric()
and OffsetOperandPA->isNumeric()));
// We expect that the offset operand is indeed a numeric `PointerArithmetic`.
// We need this so that we can multiply the size of the clift pointee type by
// the value that the numeric `PointerArithmetic` brings with it.
revng_assert(OffsetOperandPA->isNumeric());
// We multiply the size of the `PointeeType` of the `PointerOperand` operand
// by the `OffsetOperand` `BaseOffset`. The multiplication factor is contained
// into the `BaseOffset` of the numeric operand.
auto PointerOperandType = getPointerType(PointerOperand.getType());
auto PointeeSize = PointerOperandType.getPointeeType().getByteSize();
PointerOperandPA->Offset.BaseOffset += OffsetOperandPA->Offset.BaseOffset
* PointeeSize;
rc_return PointerOperandPA;
}
RecursiveCoroutine<std::optional<PointerArithmetic>>
PointerArithmeticBuilder::composeMul(MulOp Mul) {
auto LHS = Mul.getLhs();
auto RHS = Mul.getRhs();
// Categorize the `Mul` operands constant and non constant, so that we can
// choose which one to traverse
std::optional<llvm::APInt> Constant;
mlir::Value Variable;
if (auto C = getConstantValue(LHS, PointerBitWidth)) {
Constant = C;
Variable = RHS;
} else if (auto C = getConstantValue(RHS, PointerBitWidth)) {
Constant = C;
Variable = LHS;
} else {
// Neither operand is constant, bail out
rc_return std::nullopt;
}
// Traverse the variable operand
auto VarPA = rc_recur traverse(Variable);
if (not VarPA) {
rc_return std::nullopt;
}
// Multiply the result (`multiplyByConstant` also clears `BasePointer`)
rc_return multiplyByConstant(*VarPA, *Constant);
}
RecursiveCoroutine<std::optional<PointerArithmetic>>
PointerArithmeticBuilder::composeShl(ShiftLeftOp Shl) {
// Handling is similar to `MulOp`, shift by N is multiplication by 2^N
auto LHS = Shl.getLhs();
auto RHS = Shl.getRhs();
auto ShiftAmount = getConstantValue(RHS, PointerBitWidth);
// We do not have a constant amount to perform the shift, so we bail out
if (not ShiftAmount) {
rc_return std::nullopt;
}
// We craft multiplication factor equivalent to the shift amount
llvm::APInt Multiplier = llvm::APInt(PointerBitWidth, 1).shl(*ShiftAmount);
// We traverse the variable operand
auto LHSPA = rc_recur traverse(LHS);
if (not LHSPA) {
rc_return std::nullopt;
}
// Multiply the result (`multiplyByConstant` also clears `BasePointer`)
rc_return multiplyByConstant(*LHSPA, Multiplier);
}
PointerArithmetic
PointerArithmeticBuilder::multiplyByConstant(PointerArithmetic PA,
const llvm::APInt &Multiplier) {
// Multiply the base offset
PA.Offset.BaseOffset = PA.Offset.BaseOffset * Multiplier;
// Multiply all the strides contained in the `LinearCombination`s
for (auto &Term : PA.Offset.LinearCombination) {
Term.Stride = Term.Stride * Multiplier;
}
// Multiplying implicitly returns something that is not a pointer
PA.BasePointer = mlir::Value();
return PA;
}
void PointerArithmeticBuilder::sortLinearCombination(PointerArithmetic &PA) {
std::sort(PA.Offset.LinearCombination.begin(),
PA.Offset.LinearCombination.end(),
[](const auto &First, const auto &Second) {
return First.Stride.ugt(Second.Stride);
});
}
std::optional<PointerArithmetic>
computePointerArithmetic(mlir::clift::ExpressionOpInterface PointerToReplace) {
auto Impl = PointerArithmeticBuilder();
return Impl.computePointerArithmetic(PointerToReplace);
}