Files
revng-revng/lib/Model/ConvertFunctionType.cpp
T
2022-02-14 13:35:36 +01:00

625 lines
24 KiB
C++

/// \file ConvertFunctionType.cpp
/// \brief
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include "revng/ABI/Trait.h"
#include "revng/ADT/SmallMap.h"
#include "revng/Model/Binary.h"
#include "revng/Model/ConvertFunctionType.h"
#include "revng/Model/Register.h"
#include "revng/Model/VerifyHelper.h"
#include "revng/Support/EnumSwitch.h"
namespace model {
template<size_t Size>
using RegisterArray = std::array<model::Register::Values, Size>;
template<model::Architecture::Values Architecture,
typename RegisterType,
size_t RegisterCount>
bool verify(const SortedVector<RegisterType> &UsedRegisters,
const RegisterArray<RegisterCount> &AllowedRegisters) {
for (const model::Register::Values &Register : AllowedRegisters) {
// Verify the architecture of allowed registers.
if (model::Register::getArchitecture(Register) != Architecture)
revng_abort();
// Verify that there are no duplicate allowed registers.
if (llvm::count(AllowedRegisters, Register) != 1)
revng_abort();
}
for (const RegisterType &Register : UsedRegisters) {
// Verify the architecture of used registers.
if (model::Register::getArchitecture(Register.Location) != Architecture)
return false;
}
// Verify that every used register is also allowed.
for (const RegisterType &Register : UsedRegisters)
if (llvm::count(AllowedRegisters, Register.Location) != 1)
return false;
return true;
}
constexpr static model::PrimitiveTypeKind::Values
selectTypeKind(model::Register::Values) {
// TODO: implement a way to determine the register type. At the very least
// we should be able to differentiate GPRs from the vector registers.
return model::PrimitiveTypeKind::PointerOrNumber;
}
static model::QualifiedType
buildType(model::Register::Values Register, model::Binary &TheBinary) {
model::PrimitiveTypeKind::Values Kind = selectTypeKind(Register);
size_t Size = model::Register::getSize(Register);
return model::QualifiedType(TheBinary.getPrimitiveType(Kind, Size), {});
}
static model::QualifiedType
buildGenericType(model::Register::Values Register, model::Binary &TheBinary) {
constexpr auto Kind = model::PrimitiveTypeKind::Generic;
size_t Size = model::Register::getSize(Register);
return model::QualifiedType(TheBinary.getPrimitiveType(Kind, Size), {});
}
static std::optional<model::QualifiedType>
buildDoubleType(model::Register::Values UpperRegister,
model::Register::Values LowerRegister,
model::PrimitiveTypeKind::Values CustomKind,
model::Binary &TheBinary) {
model::PrimitiveTypeKind::Values UpperKind = selectTypeKind(UpperRegister);
model::PrimitiveTypeKind::Values LowerKind = selectTypeKind(LowerRegister);
if (UpperKind != LowerKind)
return std::nullopt;
size_t UpperSize = model::Register::getSize(UpperRegister);
size_t LowerSize = model::Register::getSize(LowerRegister);
return model::QualifiedType(TheBinary.getPrimitiveType(CustomKind,
UpperSize + LowerSize),
{});
}
static model::QualifiedType getTypeOrDefault(const model::QualifiedType &Type,
model::Register::Values Register,
model::Binary &Binary) {
if (Type.UnqualifiedType.get() != nullptr)
return Type;
else
return buildType(Register, Binary);
}
template<model::ABI::Values ABI>
class ConvertionHelper {
using AT = abi::Trait<ABI>;
using IndexType = decltype(model::Argument::Index);
using RegisterList = llvm::SmallVector<model::Register::Values, 1>;
struct DistributedArgument {
RegisterList Registers = {};
size_t Size = 0, SizeOnStack = 0;
};
using DistributedArguments = llvm::SmallVector<DistributedArgument, 4>;
using ArgumentContainer = SortedVector<model::Argument>;
public:
static std::optional<model::CABIFunctionType>
toCABI(const model::RawFunctionType &Function, model::Binary &TheBinary) {
static constexpr auto Arch = model::ABI::getArchitecture(ABI);
if (!verify<Arch>(Function.Arguments, AT::GeneralPurposeArgumentRegisters))
return std::nullopt;
if (!verify<Arch>(Function.ReturnValues,
AT::GeneralPurposeReturnValueRegisters))
return std::nullopt;
constexpr model::Register::Values PTCRR = AT::ReturnValueLocationRegister;
if (PTCRR != model::Register::Invalid)
revng_assert(model::Register::getArchitecture(PTCRR) == Arch);
for (auto &SavedRegister : AT::CalleeSavedRegisters)
revng_assert(model::Register::getArchitecture(SavedRegister) == Arch);
model::CABIFunctionType Result;
Result.CustomName = Function.CustomName;
Result.ABI = ABI;
auto ArgumentList = convertArguments(Function.Arguments,
AT::GeneralPurposeArgumentRegisters,
TheBinary);
if (ArgumentList == std::nullopt)
return std::nullopt;
for (auto &Argument : *ArgumentList)
Result.Arguments.insert(Argument);
using C = AT;
auto ReturnValue = convertReturnValue(Function.ReturnValues,
C::GeneralPurposeReturnValueRegisters,
C::ReturnValueLocationRegister,
TheBinary);
if (ReturnValue == std::nullopt)
return std::nullopt;
Result.ReturnType = *ReturnValue;
return Result;
}
static std::optional<model::RawFunctionType>
toRaw(const model::CABIFunctionType &Function, model::Binary &TheBinary) {
auto Arguments = distributeArguments(Function.Arguments);
model::RawFunctionType Result;
Result.CustomName = Function.CustomName;
for (size_t ArgumentIdx = 0; ArgumentIdx < Arguments.size();
++ArgumentIdx) {
auto &ArgumentStorage = Arguments[ArgumentIdx];
const auto &ArgumentType = Function.Arguments.at(ArgumentIdx).Type;
if (!ArgumentStorage.Registers.empty()) {
// Handle the registers
auto OriginalName = Function.Arguments.at(ArgumentIdx).CustomName;
for (size_t Index = 0; auto Register : ArgumentStorage.Registers) {
auto FinalName = OriginalName;
if (ArgumentStorage.Registers.size() > 1 && !FinalName.empty())
FinalName += "_part_" + std::to_string(++Index) + "_out_of_"
+ std::to_string(ArgumentStorage.Registers.size());
model::NamedTypedRegister Argument(Register);
Argument.Type = chooseArgumentType(ArgumentType,
Register,
ArgumentStorage.Registers,
TheBinary);
Argument.CustomName = FinalName;
Result.Arguments.insert(Argument);
}
}
if (ArgumentStorage.SizeOnStack != 0) {
// Handle the stack
auto ArgumentIterator = Function.Arguments.find(ArgumentIdx);
revng_assert(ArgumentIterator != Function.Arguments.end());
const model::Argument &Argument = *ArgumentIterator;
/// TODO: handle stack arguments properly.
/// \note: different ABIs could use different stack types.
/// \sa: `clrcall` ABI.
}
}
if (!Function.ReturnType.isVoid()) {
auto ReturnValue = distributeReturnValue(Function.ReturnType);
if (ReturnValue == std::nullopt)
return std::nullopt;
if (!ReturnValue->Registers.empty()) {
// Handle a register-based return value.
for (model::Register::Values Register : ReturnValue->Registers) {
model::TypedRegister ReturnValueRegister;
ReturnValueRegister.Location = Register;
ReturnValueRegister.Type = chooseArgumentType(Function.ReturnType,
Register,
ReturnValue->Registers,
TheBinary);
Result.ReturnValues.insert(std::move(ReturnValueRegister));
}
// Try and recover types from the struct if possible
if (Function.ReturnType.Qualifiers.empty()) {
const model::Type *Type = Function.ReturnType.UnqualifiedType.get();
const auto *Struct = llvm::dyn_cast<model::StructType>(Type);
if (Struct && Struct->Fields.size() == Result.ReturnValues.size()) {
using RegisterEnum = model::Register::Values;
SmallMap<RegisterEnum, model::QualifiedType, 4> RecoveredTypes;
size_t StructOffset = 0;
for (size_t Index = 0; Index < Struct->Fields.size(); ++Index) {
if (Index >= AT::GeneralPurposeReturnValueRegisters.size())
break;
auto Register = AT::GeneralPurposeReturnValueRegisters[Index];
auto TypedRegisterIterator = Result.ReturnValues.find(Register);
if (TypedRegisterIterator == Result.ReturnValues.end())
break;
const model::StructField &Field = Struct->Fields.at(StructOffset);
auto MaybeFieldSize = Field.Type.size();
revng_assert(MaybeFieldSize != std::nullopt);
auto MaybeRegisterSize = TypedRegisterIterator->Type.size();
revng_assert(MaybeRegisterSize != std::nullopt);
if (MaybeFieldSize.value() != MaybeRegisterSize.value())
break;
auto Tie = std::tie(Register, Field.Type);
auto [Iterator, Success] = RecoveredTypes.insert(std::move(Tie));
revng_assert(Success);
StructOffset += MaybeFieldSize.value();
}
if (RecoveredTypes.size() == Result.ReturnValues.size())
for (auto [Register, Type] : RecoveredTypes)
Result.ReturnValues.at(Register).Type = Type;
}
}
} else {
// Handle a pointer-based return value.
if (AT::GeneralPurposeReturnValueRegisters.empty())
return std::nullopt;
auto Register = AT::GeneralPurposeReturnValueRegisters[0];
auto RegisterSize = model::Register::getSize(Register);
auto PointerQualifier = model::Qualifier::createPointer(RegisterSize);
auto MaybeReturnValueSize = Function.ReturnType.size();
if (MaybeReturnValueSize == std::nullopt)
return std::nullopt;
if (ReturnValue->Size != *MaybeReturnValueSize)
return std::nullopt;
model::QualifiedType ReturnType = Function.ReturnType;
ReturnType.Qualifiers.emplace_back(PointerQualifier);
model::TypedRegister ReturnPointer(Register);
ReturnPointer.Type = std::move(ReturnType);
Result.ReturnValues.insert(std::move(ReturnPointer));
}
}
// Populate the list of preserved registers
for (model::Register::Values Register : AT::CalleeSavedRegisters)
Result.PreservedRegisters.insert(Register);
return Result;
}
private:
template<typename RegisterType, size_t RegisterCount>
static std::optional<llvm::SmallVector<model::Argument, 8>>
convertArguments(const SortedVector<RegisterType> &UsedRegisters,
const RegisterArray<RegisterCount> &AllowedRegisters,
model::Binary &TheBinary) {
llvm::SmallVector<model::Argument, 8> Result;
bool MustUseTheNextOne = false;
auto AllowedRange = llvm::enumerate(llvm::reverse(AllowedRegisters));
for (auto Pair : AllowedRange) {
size_t Index = AllowedRegisters.size() - Pair.index() - 1;
model::Register::Values Register = Pair.value();
bool IsUsed = UsedRegisters.find(Register) != UsedRegisters.end();
if (IsUsed) {
model::Argument Temporary;
Temporary.Type = getTypeOrDefault(UsedRegisters.at(Register).Type,
Register,
TheBinary);
Temporary.CustomName = UsedRegisters.at(Register).CustomName;
Result.emplace_back(Temporary);
} else if (MustUseTheNextOne) {
if constexpr (!AT::OnlyStartDoubleArgumentsFromAnEvenRegister) {
return std::nullopt;
} else if ((Index & 1) == 0) {
return std::nullopt;
} else if (Result.size() > 1 && Index > 1) {
auto &First = Result[Result.size() - 1];
auto &Second = Result[Result.size() - 2];
if (!First.CustomName.empty() || !Second.CustomName.empty()) {
if (First.CustomName.empty())
First.CustomName = "unnamed";
if (Second.CustomName.empty())
Second.CustomName = "unnamed";
First.CustomName.append(("+" + Second.CustomName).str());
}
auto NewType = buildDoubleType(AllowedRegisters.at(Index - 2),
AllowedRegisters.at(Index - 1),
model::PrimitiveTypeKind::Generic,
TheBinary);
if (NewType == std::nullopt)
return std::nullopt;
First.Type = *NewType;
Result.pop_back();
} else {
return std::nullopt;
}
}
MustUseTheNextOne = MustUseTheNextOne || IsUsed;
}
for (auto Pair : llvm::enumerate(llvm::reverse(Result)))
Pair.value().Index = Pair.index();
return Result;
}
template<typename RegisterType, size_t RegisterCount>
static std::optional<model::QualifiedType>
convertReturnValue(const SortedVector<RegisterType> &UsedRegisters,
const RegisterArray<RegisterCount> &AllowedRegisters,
const model::Register::Values PointerToCopyLocation,
model::Binary &TheBinary) {
if (UsedRegisters.size() == 0) {
auto Void = TheBinary.getPrimitiveType(model::PrimitiveTypeKind::Void, 0);
return model::QualifiedType{ Void, {} };
}
if (UsedRegisters.size() == 1) {
if (UsedRegisters.begin()->Location == PointerToCopyLocation) {
return getTypeOrDefault(UsedRegisters.begin()->Type,
PointerToCopyLocation,
TheBinary);
} else {
if constexpr (RegisterCount == 0)
return std::nullopt;
if (AllowedRegisters.front() == UsedRegisters.begin()->Location) {
return getTypeOrDefault(UsedRegisters.begin()->Type,
UsedRegisters.begin()->Location,
TheBinary);
} else {
return std::nullopt;
}
}
} else {
model::UpcastableType Result = model::makeType<model::StructType>();
auto ReturnStruct = llvm::dyn_cast<model::StructType>(Result.get());
bool MustUseTheNextOne = false;
auto AllowedRange = llvm::enumerate(llvm::reverse(AllowedRegisters));
for (auto Pair : AllowedRange) {
size_t Index = AllowedRegisters.size() - Pair.index() - 1;
model::Register::Values Register = Pair.value();
auto UsedIterator = UsedRegisters.find(Register);
bool IsCurrentRegisterUsed = UsedIterator != UsedRegisters.end();
if (IsCurrentRegisterUsed) {
model::StructField CurrentField;
CurrentField.Offset = ReturnStruct->Size;
CurrentField.Type = getTypeOrDefault(UsedIterator->Type,
UsedIterator->Location,
TheBinary);
ReturnStruct->Fields.insert(std::move(CurrentField));
ReturnStruct->Size += model::Register::getSize(Register);
} else if (MustUseTheNextOne) {
if constexpr (!AT::OnlyStartDoubleArgumentsFromAnEvenRegister)
return std::nullopt;
else if ((Index & 1) == 0 || ReturnStruct->Fields.size() <= 1
|| Index <= 1)
return std::nullopt;
}
MustUseTheNextOne = MustUseTheNextOne || IsCurrentRegisterUsed;
}
revng_assert(ReturnStruct->Size != 0 && !ReturnStruct->Fields.empty());
auto ReturnStructTypePath = TheBinary.recordNewType(std::move(Result));
revng_assert(ReturnStructTypePath.isValid());
return model::QualifiedType{ ReturnStructTypePath, {} };
}
return std::nullopt;
}
static DistributedArguments
distributePositionBasedArguments(const ArgumentContainer &Arguments) {
DistributedArguments Result;
for (const model::Argument &Argument : Arguments) {
if (Result.size() <= Argument.Index)
Result.resize(Argument.Index + 1);
auto &Distributed = Result[Argument.Index];
auto MaybeSize = Argument.Type.size();
revng_assert(MaybeSize.has_value());
Distributed.Size = *MaybeSize;
if (Argument.Type.isFloat()) {
if (Argument.Index < AT::VectorArgumentRegisters.size()) {
auto Register = AT::VectorArgumentRegisters[Argument.Index];
Distributed.Registers.emplace_back(Register);
} else {
Distributed.SizeOnStack = Distributed.Size;
}
} else {
if (Argument.Index < AT::GeneralPurposeArgumentRegisters.size()) {
auto Reg = AT::GeneralPurposeArgumentRegisters[Argument.Index];
Distributed.Registers.emplace_back(Reg);
} else {
Distributed.SizeOnStack = Distributed.Size;
}
}
}
return Result;
}
template<size_t RegisterCount>
static std::pair<DistributedArgument, size_t>
considerRegisters(size_t Size,
size_t AllowedRegisterLimit,
size_t OccupiedRegisterCount,
const RegisterArray<RegisterCount> &AllowedRegisters,
bool AllowPuttingPartOfAnArgumentOnStack) {
size_t RegisterLimit = OccupiedRegisterCount + AllowedRegisterLimit;
size_t ConsideredRegisterCounter = OccupiedRegisterCount;
size_t SizeCounter = 0;
const size_t ARC = AllowedRegisters.size();
if (ARC > 0) {
size_t &CRC = ConsideredRegisterCounter;
while (SizeCounter < Size && CRC < ARC && CRC < RegisterLimit) {
size_t RegisterIndex = ConsideredRegisterCounter++;
auto CurrentRegister = AllowedRegisters[RegisterIndex];
SizeCounter += model::Register::getSize(CurrentRegister);
}
}
DistributedArgument DA;
DA.Size = Size;
if constexpr (AT::OnlyStartDoubleArgumentsFromAnEvenRegister) {
if (ConsideredRegisterCounter - OccupiedRegisterCount == 2) {
if ((OccupiedRegisterCount & 1) != 0) {
++OccupiedRegisterCount;
++ConsideredRegisterCounter;
}
}
}
if (SizeCounter >= Size) {
for (size_t I = OccupiedRegisterCount; I < ConsideredRegisterCounter; ++I)
DA.Registers.emplace_back(AllowedRegisters[I]);
DA.SizeOnStack = 0;
} else if (AllowPuttingPartOfAnArgumentOnStack) {
for (size_t I = OccupiedRegisterCount; I < ConsideredRegisterCounter; ++I)
DA.Registers.emplace_back(AllowedRegisters[I]);
DA.SizeOnStack = DA.Size - SizeCounter;
} else {
DA.SizeOnStack = DA.Size;
ConsideredRegisterCounter = OccupiedRegisterCount;
}
return { DA, ConsideredRegisterCounter };
}
static DistributedArguments
distributeNonPositionBasedArguments(const ArgumentContainer &Arguments) {
DistributedArguments Result;
size_t UsedGeneralPurposeRegisterCounter = 0;
size_t UsedVectorRegisterCounter = 0;
for (const model::Argument &Argument : Arguments) {
auto MaybeSize = Argument.Type.size();
revng_assert(MaybeSize.has_value());
constexpr bool CanSplit = AT::ArgumentsCanBeSplitBetweenRegistersAndStack;
if (Argument.Type.isFloat()) {
static constexpr auto &Registers = AT::VectorArgumentRegisters;
size_t &Counter = UsedVectorRegisterCounter;
const size_t Limit = 1;
auto [Distributed, NextIndex] = considerRegisters(*MaybeSize,
Limit,
Counter,
Registers,
CanSplit);
if (Result.size() <= Argument.Index)
Result.resize(Argument.Index + 1);
Result[Argument.Index] = Distributed;
Counter = NextIndex;
} else {
static constexpr auto &Registers = AT::GeneralPurposeArgumentRegisters;
size_t &Counter = UsedGeneralPurposeRegisterCounter;
if (Argument.Type.isScalar()) {
const size_t Limit = AT::MaximumGPRsPerScalarArgument;
auto [Distributed, NextIndex] = considerRegisters(*MaybeSize,
Limit,
Counter,
Registers,
CanSplit);
if (Result.size() <= Argument.Index)
Result.resize(Argument.Index + 1);
Result[Argument.Index] = Distributed;
Counter = NextIndex;
} else {
const size_t Limit = AT::MaximumGPRsPerAggregateArgument;
auto [Distributed, NextIndex] = considerRegisters(*MaybeSize,
Limit,
Counter,
Registers,
CanSplit);
if (Result.size() <= Argument.Index)
Result.resize(Argument.Index + 1);
Result[Argument.Index] = Distributed;
Counter = NextIndex;
}
}
}
return Result;
}
static DistributedArguments
distributeArguments(const ArgumentContainer &Arguments) {
if constexpr (AT::ArgumentsArePositionBased)
return distributePositionBasedArguments(Arguments);
else
return distributeNonPositionBasedArguments(Arguments);
}
static std::optional<DistributedArgument>
distributeReturnValue(const model::QualifiedType &ReturnValueType) {
auto MaybeSize = ReturnValueType.size();
revng_assert(MaybeSize.has_value());
if (ReturnValueType.isFloat()) {
const auto &Registers = AT::VectorReturnValueRegisters;
return considerRegisters(*MaybeSize, 1, 0, Registers, false).first;
} else {
const auto &Registers = AT::GeneralPurposeReturnValueRegisters;
if (ReturnValueType.isScalar()) {
const size_t L = AT::MaximumGPRsPerScalarReturnValue;
return considerRegisters(*MaybeSize, L, 0, Registers, false).first;
} else {
const size_t L = AT::MaximumGPRsPerAggregateReturnValue;
return considerRegisters(*MaybeSize, L, 0, Registers, false).first;
}
}
}
static model::QualifiedType
chooseArgumentType(const model::QualifiedType &ArgumentType,
model::Register::Values Register,
const RegisterList &RegisterList,
model::Binary &TheBinary) {
if (RegisterList.size() > 1) {
return buildGenericType(Register, TheBinary);
} else {
auto ResultType = ArgumentType;
auto MaybeSize = ArgumentType.size();
auto TargetSize = model::Register::getSize(Register);
if (!MaybeSize.has_value()) {
return buildType(Register, TheBinary);
} else if (*MaybeSize > TargetSize) {
auto Qualifier = model::Qualifier::createPointer(TargetSize);
ResultType.Qualifiers.emplace_back(Qualifier);
} else if (!ResultType.isScalar()) {
return buildGenericType(Register, TheBinary);
}
return ResultType;
}
}
};
std::optional<model::CABIFunctionType>
convertToCABIFunctionType(const model::RawFunctionType &Function,
model::Binary &TheBinary,
std::optional<model::ABI::Values> MaybeABI) {
if (!MaybeABI.has_value())
MaybeABI = TheBinary.DefaultABI;
revng_assert(*MaybeABI != model::ABI::Invalid);
return skippingEnumSwitch<1>(*MaybeABI, [&]<model::ABI::Values A>() {
return ConvertionHelper<A>::toCABI(Function, TheBinary);
});
}
std::optional<model::RawFunctionType>
convertToRawFunctionType(const model::CABIFunctionType &Function,
model::Binary &TheBinary) {
revng_assert(Function.ABI != model::ABI::Invalid);
return skippingEnumSwitch<1>(Function.ABI, [&]<model::ABI::Values A>() {
return ConvertionHelper<A>::toRaw(Function, TheBinary);
});
}
} // namespace model