Files
revng-revng/lib/CliftTransforms/FieldAccessReplacement.cpp
Pietro Fezzardi 197bdd346d FieldAccessReplacement: fix type of array indices
When emitting the synthesized integer index for an access whose
LinearCombination contains a variable, use the variable's own type
for the constant `[0]` immediate (and for the rest of the index
arithmetic) instead of always using a generic pointer-sized integer.
With model-sized pointers in the picture the two integer widths can
differ, and using the wrong one trips Clift's type checking
downstream.
2026-05-08 11:37:09 +02:00

549 lines
24 KiB
C++

//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include "llvm/ADT/APInt.h"
#include "revng/Clift/Clift.h"
#include "revng/Clift/CliftTypes.h"
#include "BestTraversal.h"
#include "FieldAccessReplacement.h"
#include "PointerArithmetic.h"
using namespace clift;
namespace {
/// Helper to unwrap a `clift` `Type` from a potential `PointerType` wrapper,
/// returning the unwrapped `Type` and whether indirection was needed
template<typename CliftType>
static std::pair<CliftType, bool>
getAccessedTypeInfo(mlir::Value CurrentValue) {
if (auto P = clift::unwrapped_dyn_cast<PointerType>(CurrentValue.getType())) {
return { clift::unwrapped_cast<CliftType>(P.getPointeeType()), true };
}
return { clift::unwrapped_cast<CliftType>(CurrentValue.getType()), false };
}
// =============================================================================
// `Replacement` struct definition
// =============================================================================
/// `Replacement` is used as a builder class to perform the rewrite of the
/// pointer arithmetic with (multiple) `clift` `operation`s equivalent to the
/// elected `BestTraversal`
struct Replacement {
/// `FieldAccessInfo` represent the atomic element of the `Replacement`. It
/// can represent an access to `union`, `struct`, `array`, accompanied by the
/// relative `Index`
struct FieldAccessInfo {
enum Kind {
Union,
Struct,
Array
} TheKind;
// We need to have the possibility to represent an `Index` with both a
// constant and a variable component (in order to represent access like
// `[i + 4]`)
struct IndexInfo {
mlir::Value Variable;
uint64_t Constant;
} Index;
};
/// We store the sequence of needed `FieldAccess`es here
llvm::SmallVector<FieldAccessInfo> FieldAccesses;
/// `LeftoverOffset` holds the eventual portion of the access that is not
/// captured by the `BestTraversal`
PointerArithmetic::OffsetExpression LeftoverOffset;
/// Bit width of the pointer type being rewritten
unsigned PointerBitWidth;
/// This `static` method prepares the description of the `Replacement`, that
/// will be later applied
static Replacement make(unsigned PointerBitWidth,
const PointerArithmetic &Arithmetic,
const Traversal &BestTraversal);
/// This method performs the actual `clift` IR rewriting. Returns `true` if
/// a type was propagated through an indirection.
bool replace(ExpressionOpInterface PointerToReplace,
const PointerArithmetic &Arithmetic) const;
};
// =============================================================================
// `Replacement` methods implementation
// =============================================================================
/// Factory `make` constructor method
Replacement Replacement::make(unsigned PointerBitWidth,
const PointerArithmetic &Arithmetic,
const Traversal &BestTraversal) {
auto BaseType = deriveBaseType(Arithmetic.BasePointer);
// Start with an empty `Replacement` object, which will be populated in this
// routine
Replacement Result = {
.LeftoverOffset = PointerArithmetic::OffsetExpression(PointerBitWidth),
.PointerBitWidth = PointerBitWidth
};
// Copy the starting `BestTraversal` and `Offset`, we will consume them in the
// current phase
// We initialize the `LeftoverTraversal` with the `BestTraversal` we
// identified during phase 2, and we consume it until the whole `Replacement`
// is produced
Traversal LeftoverTraversal = BestTraversal;
// We initialize the `LeftoverOffset` with the offset expression that was
// produced in the `PointerArithmetic` during phase 1
PointerArithmetic::OffsetExpression LeftoverOffset = Arithmetic.Offset;
// We perform an iterator-based traversal of the fields, going over each
// component in the selected `Traversal` and building the `Replacement`
auto FieldIt = LeftoverTraversal.TraversedFields.begin();
auto FieldEnd = LeftoverTraversal.TraversedFields.end();
auto ArrayIt = LeftoverTraversal.TraversedArrays.begin();
auto ArrayEnd = LeftoverTraversal.TraversedArrays.end();
while (FieldIt != FieldEnd or ArrayIt != ArrayEnd) {
// Inspect the `TypedefType` and cast to a known `clift` `Type`
if (auto TypedefType = mlir::dyn_cast<clift::TypedefType>(BaseType)) {
BaseType = mlir::cast<mlir::Type>(TypedefType.getUnderlyingType());
continue;
}
// We should never reach these `Type`s by construction
if (isa<FunctionType, PointerType, PrimitiveType, EnumType>(BaseType)) {
revng_abort("Invalid type in traversal");
}
// Inspect `struct` or `union` (both implement ClassType)
if (auto ClassType = mlir::dyn_cast<clift::ClassType>(BaseType)) {
auto Kind = mlir::isa<clift::StructType>(BaseType) ?
FieldAccessInfo::Struct :
FieldAccessInfo::Union;
unsigned FieldIndex = *FieldIt++;
Result.FieldAccesses.push_back({ .TheKind = Kind,
.Index = { mlir::Value(),
FieldIndex } });
// Look up the field by positional index. Subtract the field's byte
// offset from LeftoverOffset (for unions, getOffset() returns 0)
const FieldAttr &Field = ClassType.getFields()[FieldIndex];
BaseType = mlir::cast<mlir::Type>(Field.getType());
LeftoverOffset.BaseOffset -= Field.getOffset();
continue;
}
// Inspect the `array`
if (auto ArrayType = mlir::dyn_cast<clift::ArrayType>(BaseType)) {
ArrayShape CurrentArray = *ArrayIt++;
// When reaching this iteration, if there was an array traversal in the
// original traversal with a larger stride than the current, it must have
// been already processed in a previous iteration
if (not LeftoverOffset.LinearCombination.empty()) {
revng_assert(LeftoverOffset.LinearCombination.front()
.Stride.ule(CurrentArray.Stride));
}
// We decide if we consume the offset from the `BaseOffset` or the
// `LinearCombination`
llvm::APInt NumFixedConsumedElements = llvm::APInt(PointerBitWidth, 0);
if (LeftoverOffset.BaseOffset.uge(CurrentArray.Stride)) {
NumFixedConsumedElements = LeftoverOffset.BaseOffset
.udiv(CurrentArray.Stride);
LeftoverOffset.BaseOffset -= CurrentArray.Stride
* NumFixedConsumedElements;
}
mlir::Value DynamicElementId = {};
const auto &LinearCombination = LeftoverOffset.LinearCombination;
if (not LinearCombination.empty()
and LinearCombination.front().Stride == CurrentArray.Stride) {
auto &FrontTerm = LeftoverOffset.LinearCombination.front();
DynamicElementId = FrontTerm.Idx.Variable;
// If the Idx also has a constant component, add it to the fixed
// consumed elements count
if (FrontTerm.Idx.Constant.getBoolValue()) {
NumFixedConsumedElements += FrontTerm.Idx.Constant;
}
LeftoverOffset.LinearCombination
.erase(LeftoverOffset.LinearCombination.begin());
}
Result.FieldAccesses
.push_back({ .TheKind = FieldAccessInfo::Array,
.Index = { DynamicElementId,
static_cast<uint64_t>(NumFixedConsumedElements
.getZExtValue()) } });
// Move to the `array` element `Type`
BaseType = ArrayType.getElementType();
continue;
}
}
// We pass over the remaining `LeftoverOffset`
Result.LeftoverOffset = LeftoverOffset;
return Result;
}
bool Replacement::replace(ExpressionOpInterface PointerToReplace,
const PointerArithmetic &Arithmetic) const {
bool PropagatedThroughIndirection = false;
// We need the `PointerSize` in order to generate the `ImmediateOp`s used to
// access the `struct` fields and `array` members, and to generate the
// `AddressOp` at the end of the field access substitution. We extract it
// from the `PointerToReplace` we are processing.
auto PointerSize = clift::unwrapped_cast<PointerType>(PointerToReplace
->getResult(0)
.getType())
.getPointerSize();
// Set insertion point right before the `PointerToReplace`
mlir::OpBuilder Builder(PointerToReplace);
mlir::Value CurrentValue = Arithmetic.BasePointer;
// Every new `Operation` created in this phase will retain the `Location` of
// the original `PointerToReplace`.
// TODO: possible improvement for building the `PointerToReplaceLocation`.
// We could consider merging all the locations of all the
// `ExpressionOp`s that contributed to the computation of the
// `PointerArithmetic`. That would be much more accurate and probably
// give better results.
mlir::Location PointerToReplaceLoc = PointerToReplace.getLoc();
// Apply each field access in sequence
// Iterate over every `FieldAccess` in `Replacement`, and materialize the
// `clift` `Operation`s needed to perform such access
for (const FieldAccessInfo &Access : FieldAccesses) {
switch (Access.TheKind) {
case FieldAccessInfo::Kind::Struct: {
auto Index = Access.Index.Constant;
auto [Type,
IsIndirect] = getAccessedTypeInfo<clift::StructType>(CurrentValue);
mlir::Type FieldType = Type.getFields()[Index].getType();
CurrentValue = Builder.create<AccessOp>(PointerToReplaceLoc,
FieldType,
CurrentValue,
IsIndirect,
Index);
break;
}
case FieldAccessInfo::Kind::Union: {
auto Index = Access.Index.Constant;
auto [Type,
IsIndirect] = getAccessedTypeInfo<clift::UnionType>(CurrentValue);
mlir::Type FieldType = Type.getFields()[Index].getType();
CurrentValue = Builder.create<AccessOp>(PointerToReplaceLoc,
FieldType,
CurrentValue,
IsIndirect,
Index);
break;
}
case FieldAccessInfo::Kind::Array: {
// We may need to unwrap the `ArrayType` from a `PointerType`, and emit
// the needed `IndirectionOp` and `Decay` cast accordingly.
mlir::Type ArrayElementType;
// We need to explicitly handle the `pointer as array` case, where
// `CurrentValue` is not a `ptr<T>` of `ArrayType` (we virtually wrap it
// ourselves), so the `indirection` and `cast<decay>` is not needed.
if (auto P = unwrapped_dyn_cast<PointerType>(CurrentValue.getType());
P and not unwrapped_isa<ArrayType>(P.getPointeeType())) {
ArrayElementType = P.getPointeeType();
} else {
// Standard path emitting `indirection` and `cast<decay>` as needed
auto [ArrayType,
IsIndirect] = getAccessedTypeInfo<clift::ArrayType>(CurrentValue);
if (IsIndirect) {
CurrentValue = Builder.create<IndirectionOp>(PointerToReplaceLoc,
CurrentValue);
}
ArrayElementType = ArrayType.getElementType();
auto DecayType = PointerType::get(ArrayElementType, PointerSize);
CurrentValue = Builder.create<DecayOp>(PointerToReplaceLoc,
DecayType,
CurrentValue);
}
// Emit the `mlir::Value` representing the `Index` access.
// We declare all the possible components (constant and variable parts)
// as uninitialized here, and later fill only the components that we
// need to emit.
mlir::Value FixedIndexValue = {};
mlir::Value DynamicIndexValue = {};
mlir::Value IndexValue = {};
// If present, we emit a new `mlir::Value` representing the constant
// component of the `Index` access. If we do not have a `DynamicIndex`
// component, we still emit a `imm 0` to represent the access to `[0]`.
if (Access.Index.Constant != 0 or not Access.Index.Variable) {
auto Index = Access.Index.Constant;
auto IntegerType = Access.Index.Variable ?
Access.Index.Variable.getType() :
clift::IntegerType::get(Builder.getContext(),
IntegerKind::Generic,
PointerSize);
FixedIndexValue = Builder.create<ImmediateOp>(PointerToReplaceLoc,
IntegerType,
Index);
}
// If present, we emit a new `mlir::Value` representing the variable
// component of the `Index` access
if (Access.Index.Variable) {
DynamicIndexValue = Access.Index.Variable;
}
// We compose the constant and variable components of the `Index` access
// depending on whether they are present
if (FixedIndexValue and DynamicIndexValue) {
IndexValue = Builder.create<AddOp>(PointerToReplaceLoc,
FixedIndexValue,
DynamicIndexValue);
} else if (FixedIndexValue) {
IndexValue = FixedIndexValue;
} else {
IndexValue = DynamicIndexValue;
}
// The `SubscriptOp` requires its `Pointer` operand to be a `PointerType`.
// In case `CurrentValue` is a `PointerType`, wrapped into a
// `TypeDefType`, we need to cast it to the underlying `PointerType`
// first.
if (not mlir::isa<clift::PointerType>(CurrentValue.getType())) {
auto PtrType = unwrapped_cast<PointerType>(CurrentValue.getType());
CurrentValue = Builder.create<BitCastOp>(PointerToReplaceLoc,
PtrType,
CurrentValue);
}
// Finally, we emit the `SubscriptOp` using as `Index` the `mlir::Value`
// constructed above
CurrentValue = Builder.create<SubscriptOp>(PointerToReplaceLoc,
CurrentValue,
IndexValue);
break;
}
}
}
// Take address of the result, since we always start the replacement from a
// `PointerType`, we want to get back to it
auto CurrentValuePointerType = PointerType::get(CurrentValue.getType(),
PointerSize);
CurrentValue = Builder.create<AddressofOp>(PointerToReplaceLoc,
CurrentValuePointerType,
CurrentValue);
// After we emit the `addressof`, we save the resulting `RichValue`, which may
// contain a _rich_ type information of the emitted access. We collect this
// here before the subsequent `cast` strips it of the type information`. We
// then propagate the type information into `indirection` uses.
mlir::Value RichValue = CurrentValue;
// If there is a non-null `LeftoverOffset`, we add it as integer arithmetic
if (not LeftoverOffset.BaseOffset.isZero()
or not LeftoverOffset.LinearCombination.empty()) {
// Cast pointer to integer
auto IntPtrType = clift::IntegerType::get(Builder.getContext(),
IntegerKind::Generic,
PointerSize);
CurrentValue = Builder.create<BitCastOp>(PointerToReplaceLoc,
IntPtrType,
CurrentValue);
// Add base offset
if (!LeftoverOffset.BaseOffset.isZero()) {
auto LeftoverOffsetValue = LeftoverOffset.BaseOffset.getSExtValue();
auto AddOperandValue = Builder.create<ImmediateOp>(PointerToReplaceLoc,
IntPtrType,
LeftoverOffsetValue);
CurrentValue = Builder.create<AddOp>(PointerToReplaceLoc,
CurrentValue,
AddOperandValue);
}
// Add strided terms
for (const auto &Term : LeftoverOffset.LinearCombination) {
// Multiply stride by index
auto IndexValue = Builder.create<ImmediateOp>(PointerToReplaceLoc,
IntPtrType,
Term.Idx.Constant
.getSExtValue());
auto StrideValue = Builder
.create<ImmediateOp>(PointerToReplaceLoc,
IntPtrType,
Term.Stride.getSExtValue());
auto StridedValue = Builder.create<clift::MulOp>(PointerToReplaceLoc,
IndexValue,
StrideValue);
CurrentValue = Builder.create<clift::AddOp>(PointerToReplaceLoc,
CurrentValue,
StridedValue);
}
// Cast back to pointer
auto PointerType = PointerType::get(IntPtrType, PointerSize);
CurrentValue = Builder.create<BitCastOp>(PointerToReplaceLoc,
PointerType,
CurrentValue);
}
// If the result type differs from PointerToReplace's type, prepare a cast
// to make the replacement fit the replaced `PointerToReplace` type.
if (CurrentValue.getType() != PointerToReplace->getResult(0).getType()) {
CurrentValue = Builder.create<BitCastOp>(PointerToReplaceLoc,
PointerToReplace->getResult(0)
.getType(),
CurrentValue);
}
// Replace all the `Use`s of `PointerToReplace`, handling `IndirectionOp`s
// specially: instead of giving them the "type-erased" `CurrentValue` (through
// the `BitCast`) we give the `RichValue` directly so their result type
// reflects the actual pointed-to type.
if (CurrentValue != Arithmetic.BasePointer) {
// Collect the `Use`s before the iteration, to avoid invalidation
llvm::SmallVector<mlir::OpOperand *> Uses;
for (auto &Use : PointerToReplace->getResult(0).getUses()) {
Uses.push_back(&Use);
}
for (auto *Use : Uses) {
// Check if we can propagate the _rich_ type through `indirection` uses.
// We are interested in the `ptr<ptr<...>>` pattern, where the
// `indirection` loads a pointer through which further field accesses can
// be chained.
// If we are inspecting a non-`IndirectionOp`, or if the propagation is
// not applicable, we just redirect the `Use` to the type-matched
// `CurrentValue`
auto Indirection = mlir::dyn_cast<IndirectionOp>(Use->getOwner());
auto RichPointerType = unwrapped_dyn_cast<PointerType>(RichValue
.getType());
bool CanPropagate = RichPointerType
and unwrapped_isa<PointerType>(RichPointerType
.getPointeeType());
if (not Indirection or not CanPropagate) {
Use->set(CurrentValue);
continue;
}
auto RichPointeeType = RichPointerType.getPointeeType();
auto IndirectionResultType = Indirection.getResult().getType();
// Size guard: the rich `pointee` must have the same byte size as the
// `IndirectionOp` result for the retyping to be safe
auto RichPointeeSize = getObjectSizeOrZero(RichPointeeType);
auto IndirectionResultSize = getObjectSizeOrZero(IndirectionResultType);
if (RichPointeeSize != IndirectionResultSize) {
Use->set(CurrentValue);
continue;
}
// Types already match — no propagation needed
if (RichPointeeType == IndirectionResultType) {
Use->set(CurrentValue);
continue;
}
// Create a new typed `IndirectionOp` with the `RichValue` as operand,
// so its result type is inferred from the `RichPointeeType`
Builder.setInsertionPoint(Indirection);
auto NewIndirection = Builder.create<IndirectionOp>(PointerToReplaceLoc,
RichValue);
// Insert a `FixupCast` from the typed result to the old untyped result,
// so existing users that expect the original type still verify
auto FixupCast = Builder.create<BitCastOp>(PointerToReplaceLoc,
IndirectionResultType,
NewIndirection);
// Redirect the old `IndirectionOp`'s uses to the `FixupCast`.
// Lvalue uses (assign LHS) stay on the old `IndirectionOp`: a `BitCastOp`
// is not an lvalue, and changing the LHS type would break the assign's
// type constraints. The old `IndirectionOp` is left for DCE.
llvm::SmallVector<mlir::OpOperand *> IndirectionUses;
for (auto &IndirUse : Indirection.getResult().getUses()) {
if (mlir::isa<AssignOp>(IndirUse.getOwner())
and IndirUse.getOperandNumber() == 0) {
continue;
}
IndirectionUses.push_back(&IndirUse);
}
for (auto *IndirectionUse : IndirectionUses) {
IndirectionUse->set(FixupCast);
}
if (not IndirectionUses.empty()) {
PropagatedThroughIndirection = true;
}
// The old indirection's operand stays as-is (for assign LHS uses)
Use->set(CurrentValue);
}
}
// At this point, we are left in the `clift` IR with a set of dead `Value`s
// representing the old `PointerArithmetic`. We rely on a subsequent DCE
// pass to clean up all the dead `Value`s.
// We signal at the above level that we propagated the `Type` info through an
// `IndirectionOp`, which requires another iteration to find new rewriting
// opportunities
return PropagatedThroughIndirection;
}
} // namespace
/// Entry point function to perform the replacement of the pointer arithmetic
/// access (`PointerToReplace`), with operations equivalent to the
/// `BestTraversal` elected in the previous steps.
/// Returns `true` if a type was propagated through an `indirection`.
bool replaceFieldAccess(ExpressionOpInterface PointerToReplace,
const PointerArithmetic &Arithmetic,
const Traversal &BestTraversal) {
// Derive the `PointerBitWidth` from the `PointerToReplace` type
unsigned PointerBitWidth = clift::unwrapped_cast<PointerType>(PointerToReplace
->getResult(0)
.getType())
.getPointerSize()
* 8;
// We prepare the `Replacement`, which describes the `Traversal` in a way that
// can easily be converted into a series of `clift` `operation`s
auto R = Replacement::make(PointerBitWidth, Arithmetic, BestTraversal);
// We actually perform the replacement
return R.replace(PointerToReplace, Arithmetic);
}