mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
431 lines
16 KiB
C++
431 lines
16 KiB
C++
#pragma once
|
|
|
|
//
|
|
// This file is distributed under the MIT License. See LICENSE.md for details.
|
|
//
|
|
|
|
#include <vector>
|
|
|
|
#include "revng/ABI/FunctionType/Support.h"
|
|
#include "revng/ABI/RegisterState.h"
|
|
#include "revng/ABI/ScalarType.h"
|
|
#include "revng/ADT/SortedVector.h"
|
|
#include "revng/Model/ABI.h"
|
|
#include "revng/Model/RawFunctionType.h"
|
|
#include "revng/Model/Register.h"
|
|
#include "revng/Support/Debug.h"
|
|
#include "revng/TupleTree/TupleTree.h"
|
|
#include "revng/TupleTree/TupleTreeDiff.h"
|
|
|
|
/* TUPLE-TREE-YAML
|
|
|
|
name: Definition
|
|
type: struct
|
|
fields:
|
|
- name: ABI
|
|
doc: |
|
|
Indicates the model::ABI this definition is for.
|
|
type: model::ABI::Values
|
|
|
|
- name: ArgumentsArePositionBased
|
|
doc: |
|
|
States whether the ABI is focused on preserving the argument order.
|
|
|
|
Here's an example function:
|
|
```
|
|
struct Big; // Is really big, so it can only be passed in memory.
|
|
void function(Big, signed, float, unsigned);
|
|
```
|
|
|
|
If the ABI is position based, the arguments would be passed in
|
|
- memory for the `Big` argument (and a pointer to it in the first GPR).
|
|
- the second GPR register for the `signed` argument.
|
|
- the third vector register for the `float` argument.
|
|
- the forth GPR register for the `unsigned` argument.
|
|
|
|
If the ABI is NOT position based, the arguments would be passed in
|
|
- memory for the `Big` argument.
|
|
- the first GPR register for the `signed` argument.
|
|
- the first vector register for the `float` argument.
|
|
- the second GPR register for the `unsigned` argument.
|
|
|
|
A typical example of a position-based ABI is `Microsoft_x86_64`,
|
|
a non-position-based one - `SystemV_x86_64`.
|
|
type: bool
|
|
|
|
- name: OnlyStartDoubleArgumentsFromAnEvenRegister
|
|
doc: |
|
|
States whether an object that needs two GPRs to fit (5-8 bytes on 32-bit
|
|
architectures and 9-16 bytes on 64-bit systems) is only allowed to start
|
|
from a register with an even index.
|
|
|
|
Here's an example function:
|
|
```
|
|
void function(uint32_t, uint64_t);
|
|
```
|
|
On a system with 32-bit wide GPRs, the first argument (`uint32_t`) is
|
|
passed using the first allowed GPR (say `r0`).
|
|
|
|
The second argument (`uint64_t`) requires two register to fit, so it's
|
|
passed using second and third registers if the ABI allows starting double
|
|
arguments from any register (`r1` and `r2` in this example), or third and
|
|
forth if it only allows starting them from even registers (`r2` and `r3`
|
|
in this example, since `r1` is considered an odd register (the count
|
|
starts from 0, much like C array indexing).
|
|
|
|
\note this option is only applicable for non-position based ABIs
|
|
(if `ArgumentsArePositionBased` is `false`).
|
|
type: bool
|
|
|
|
- name: ArgumentsCanBeSplitBetweenRegistersAndStack
|
|
doc: |
|
|
States whether the ABI allows a single object that wouldn't fit into
|
|
a single GPR (9+ bytes on 32-bit systems and 17+ bytes on 64-bit ones)
|
|
to be partially passed in registers with the remainder placed on the stack
|
|
if there are not enough registers to fit the entirety of it.
|
|
|
|
As an example, let's say that there is a big object of type `Big` such
|
|
that `sizeof(Big)` is equal to 16 bytes. On 32-bit system it would mean
|
|
having to use four GPRs (`16 == 4 * 4`) to fit it.
|
|
|
|
Let's look at an ABI that allocates four registers for passing function
|
|
arguments (`r0-r3`). Then, for a function like
|
|
```
|
|
void function(uint32_t, Big);
|
|
```
|
|
the `uint32_t` argument would be passed in the first GPR (`r0`).
|
|
But that would also mean that the remaining three available GPRs are not
|
|
enough to fit the entirety of the `Big` object, meaning it needs to
|
|
either be split between the registers and the memory, or passed using
|
|
the stack. That's exactly what this option states.
|
|
|
|
\note this option is only applicable for non-position based ABIs
|
|
(if `ArgumentsArePositionBased` is `false`).
|
|
type: bool
|
|
|
|
- name: NoRegisterArgumentsCanComeAfterStackOnes
|
|
doc: |
|
|
States whether ABI allows a stack argument (mainly one too big
|
|
to be placed in the registers) to precede other register arguments.
|
|
|
|
For example, if there is a `Big` struct that has to use the stack, and
|
|
a function like
|
|
```
|
|
void function(Big, uint32_t);
|
|
```
|
|
if this value is set to true, both argument will be passed on stack,
|
|
otherwise, only the struct will.
|
|
type: bool
|
|
optional: true
|
|
|
|
- name: UsePointerToCopyForStackArguments
|
|
doc:
|
|
States how the stack arguments are passed.
|
|
If `UsePointerToCopyForStackArguments` is true, pointers-to-copy are used,
|
|
otherwise - the whole argument is copied onto the stack.
|
|
|
|
\note this only affects the arguments with size exceeding the size of
|
|
a single stack "slot" (which is equal to the GPR size for the architecture
|
|
in question).
|
|
type: bool
|
|
|
|
- name: CalleeIsResponsibleForStackCleanup
|
|
doc: |
|
|
Specifies who is responsible for cleaning the stack after the function
|
|
call. If equal to `true`, it's the callee, otherwise it the caller.
|
|
type: bool
|
|
|
|
- name: StackAlignment
|
|
doc: |
|
|
States the required alignment of the stack at the point of a function
|
|
call in bytes.
|
|
|
|
\note states minimum value for ABIs supporting multiple different stack
|
|
alignment values, for example, if the ABI requires the stack to be aligned
|
|
on 4 bytes for internal calls but on 8 bytes for interfaces (like 32-bit
|
|
ARM ABI), the value of `StackAlignment` should be equal to 4.
|
|
type: uint64_t
|
|
|
|
- name: MaximumGPRsPerAggregateArgument
|
|
doc: |
|
|
States the maximum number of GPRs available to pass a single aggregate
|
|
(a struct, a union, etc.) argument, meaning that it can only be passed in
|
|
the GPRs if `MaximumGPRsPerAggregateArgument` is less than or equal to
|
|
the number of the registers required to fit the object including padding.
|
|
|
|
\note If `MaximumGPRsPerAggregateArgument` is equal to 0, it means that
|
|
the ABI does not allow aggregate arguments to use GPRs.
|
|
|
|
\note If an argument doesn't fit into the specified registers or
|
|
uses irregular padding, the registers are not used and the object is
|
|
passed using the memory (stack, pointer-to-copy, etc.).
|
|
type: uint64_t
|
|
|
|
- name: MaximumGPRsPerAggregateReturnValue
|
|
doc: |
|
|
States the maximum number of GPRs available to return a single aggregate
|
|
(a struct, a union, etc.) value, meaning that it can only be returned
|
|
in the GPRs if `MaximumGPRsPerAggregateReturnValue` is less than or equal
|
|
to the number of the registers required to fit the object including
|
|
padding.
|
|
|
|
\note If `MaximumGPRsPerAggregateReturnValue` is equal to 0, it means
|
|
that the ABI does not allow aggregate return values to use GPRs.
|
|
|
|
\note If a return value doesn't fit into the specified registers or
|
|
uses irregular padding, the registers are not used and the object is
|
|
passed using the memory (stack, pointer-to-copy, etc.).
|
|
type: uint64_t
|
|
|
|
- name: MaximumGPRsPerScalarArgument
|
|
doc: |
|
|
States the maximum number of GPRs available to pass a single scalar
|
|
(`int`, `__int128`, pointer, etc.) argument, meaning that it can only be
|
|
passed in the GPRs if `MaximumGPRsPerScalarArgument` is less than or
|
|
equal to the number of the registers required to fit the object.
|
|
|
|
\note If `MaximumGPRsPerScalarArgument` is equal to 0, it means that
|
|
the ABI does not allow scalar arguments to use GPRs.
|
|
|
|
\note If an argument doesn't fit into the specified registers or
|
|
uses irregular padding, the registers are not used and the object is
|
|
passed using the memory (stack, pointer-to-copy, etc.).
|
|
type: uint64_t
|
|
|
|
- name: MaximumGPRsPerScalarReturnValue
|
|
doc: |
|
|
States the maximum number of GPRs available to return a single scalar
|
|
(`int`, `__int128`, pointer, etc.) value, meaning that it can only be
|
|
returned in the GPRs if `MaximumGPRsPerScalarReturnValue` is less than
|
|
or equal to the number of the registers required to fit the object
|
|
including padding.
|
|
|
|
\note If `MaximumGPRsPerScalarReturnValue` is equal to 0, it means
|
|
that the ABI does not allow scalar return values to use GPRs.
|
|
|
|
\note If a return value doesn't fit into the specified registers or
|
|
uses irregular padding, the registers are not used and the object is
|
|
passed using the memory (stack, pointer-to-copy, etc.).
|
|
type: uint64_t
|
|
|
|
- name: GeneralPurposeArgumentRegisters
|
|
doc: |
|
|
Stores the list of general purpose registers allowed to be used for
|
|
passing arguments and the order they are to be used in.
|
|
sequence:
|
|
type: std::vector
|
|
elementType: model::Register::Values
|
|
optional: true
|
|
- name: GeneralPurposeReturnValueRegisters
|
|
doc: |
|
|
Stores the list of general purpose registers allowed to be used for
|
|
returning values and the order they are to be used in.
|
|
sequence:
|
|
type: std::vector
|
|
elementType: model::Register::Values
|
|
optional: true
|
|
- name: VectorArgumentRegisters
|
|
doc: |
|
|
Stores the list of vector registers allowed to be used for passing
|
|
arguments and the order they are to be used in.
|
|
sequence:
|
|
type: std::vector
|
|
elementType: model::Register::Values
|
|
optional: true
|
|
- name: VectorReturnValueRegisters
|
|
doc: |
|
|
Stores the list of vector registers allowed to be used for returning
|
|
values and the order they are to be used in.
|
|
sequence:
|
|
type: std::vector
|
|
elementType: model::Register::Values
|
|
optional: true
|
|
- name: CalleeSavedRegisters
|
|
doc: |
|
|
Stores the list of registers for which the ABI requires the callee to
|
|
preserve the value, meaning that when the callee returns, the value of
|
|
those registers must be the same as it was when the function was called.
|
|
sequence:
|
|
type: std::vector
|
|
elementType: model::Register::Values
|
|
optional: true
|
|
|
|
- name: ReturnValueLocationRegister
|
|
doc: |
|
|
Specifies a register to be used for returning (or even passing,
|
|
depending on ABI) the pointer to the memory used for returning
|
|
copies of big aggregate objects.
|
|
|
|
Can be `model::Register::Invalid` for ABIs that do not support returning
|
|
values by 'pointer-to-copy'.
|
|
type: model::Register::Values
|
|
optional: true
|
|
|
|
- name: ReturnValueLocationOnStack
|
|
doc: |
|
|
Specifies whether stack is used to pass the return value location.
|
|
|
|
This is only relevant if `ReturnValueLocationRegister` is set to `Invalid`
|
|
type: bool
|
|
optional: true
|
|
|
|
- name: ScalarTypes
|
|
doc: |
|
|
This provides a way to introduce some type-specific constraint information
|
|
to ABI definition, e.g. how types get aligned based on their size.
|
|
sequence:
|
|
type: SortedVector
|
|
elementType: ScalarType
|
|
|
|
- name: FloatingPointScalarTypes
|
|
doc: |
|
|
This provides a way to introduce some type-specific constraint information
|
|
to ABI definition, e.g. how types get aligned based on their size.
|
|
sequence:
|
|
type: SortedVector
|
|
elementType: ScalarType
|
|
|
|
key:
|
|
- ABI
|
|
|
|
TUPLE-TREE-YAML */
|
|
|
|
#include "revng/ABI/Generated/Early/Definition.h"
|
|
|
|
namespace abi {
|
|
|
|
class Definition : public generated::Definition {
|
|
public:
|
|
using generated::Definition::Definition;
|
|
|
|
public:
|
|
static const Definition &get(model::ABI::Values ABI);
|
|
|
|
public:
|
|
std::string_view getName() const { return model::ABI::getName(ABI()); }
|
|
std::size_t getPointerSize() const {
|
|
return model::ABI::getPointerSize(ABI());
|
|
}
|
|
|
|
/// Make sure current definition is valid.
|
|
bool verify() const debug_function;
|
|
|
|
/// Checks whether a given function data does not contradict this ABI
|
|
///
|
|
/// \note this is not an exhaustive check, so if it returns `false`,
|
|
/// the function definitely is NOT compatible, but if it returns `true`
|
|
/// it might either be compatible or not.
|
|
///
|
|
/// \note this also asserts \ref isValid
|
|
///
|
|
/// \tparam Register The type representing the registers, example of valid
|
|
/// values include \ref model::TypedRegister and
|
|
/// \ref model::NamedTypedRegister
|
|
///
|
|
/// \param ArgumentRegisters The list of registers used for passing arguments
|
|
/// of the function in question
|
|
/// \param ReturnValueRegisters The list of registers used for returning
|
|
/// values of the function in question
|
|
///
|
|
/// \return `false` if the function is definitely NOT compatible with the ABI,
|
|
/// `true` if it might be compatible.
|
|
bool isIncompatibleWith(const model::RawFunctionType &Function) const;
|
|
|
|
/// Compute the natural alignment of the type in accordance with
|
|
/// the current ABI
|
|
///
|
|
/// \note It mirrors, `model::Type::size()` pretty closely, see documentation
|
|
/// related to it (and usage of the coroutines inside this codebase
|
|
/// in general) for more details on how it works.
|
|
///
|
|
/// \param Type The type to compute the alignment of.
|
|
/// \param ABI The ABI used to determine alignment of the primitive components
|
|
/// of the type
|
|
///
|
|
/// \return either an alignment or a `std::nullopt` when it's not applicable.
|
|
inline std::optional<uint64_t>
|
|
alignment(const model::QualifiedType &Type) const {
|
|
model::VerifyHelper VH;
|
|
return alignment(VH, Type);
|
|
}
|
|
|
|
std::optional<uint64_t> alignment(model::VerifyHelper &VH,
|
|
const model::QualifiedType &Type) const;
|
|
|
|
std::uint64_t alignedOffset(std::uint64_t Offset,
|
|
const model::QualifiedType &Type) const {
|
|
const std::uint64_t Alignment = *alignment(Type);
|
|
if (Offset % Alignment != 0)
|
|
return Offset + Alignment - Offset % Alignment;
|
|
|
|
return Offset;
|
|
}
|
|
|
|
public:
|
|
std::optional<abi::RegisterState::Map>
|
|
tryDeducingRegisterState(const abi::RegisterState::Map &State) const;
|
|
|
|
abi::RegisterState::Map
|
|
enforceRegisterState(const abi::RegisterState::Map &State) const;
|
|
|
|
template<ranges::sized_range Container>
|
|
llvm::SmallVector<model::Register::Values, 8>
|
|
sortArguments(const Container &Registers) const {
|
|
SortedVector<model::Register::Values> Lookup;
|
|
{
|
|
auto Inserter = Lookup.batch_insert();
|
|
for (auto &&Register : Registers)
|
|
Inserter.insert(Register);
|
|
}
|
|
|
|
llvm::SmallVector<model::Register::Values, 8> Result;
|
|
for (auto Register : GeneralPurposeArgumentRegisters())
|
|
if (Lookup.contains(Register))
|
|
Result.emplace_back(Register);
|
|
for (auto Register : VectorArgumentRegisters())
|
|
if (Lookup.contains(Register))
|
|
Result.emplace_back(Register);
|
|
|
|
revng_assert(Result.size() == std::size(Registers));
|
|
return Result;
|
|
}
|
|
|
|
template<typename Container>
|
|
llvm::SmallVector<model::Register::Values, 8>
|
|
sortReturnValues(const Container &Registers) const {
|
|
SortedVector<model::Register::Values> Lookup;
|
|
{
|
|
auto Inserter = Lookup.batch_insert();
|
|
for (auto &&Register : Registers)
|
|
Inserter.insert(Register);
|
|
}
|
|
|
|
llvm::SmallVector<model::Register::Values, 8> Result;
|
|
for (auto Register : GeneralPurposeReturnValueRegisters())
|
|
if (Lookup.contains(Register))
|
|
Result.emplace_back(Register);
|
|
for (auto Register : VectorReturnValueRegisters())
|
|
if (Lookup.contains(Register))
|
|
Result.emplace_back(Register);
|
|
|
|
revng_assert(Result.size() == std::size(Registers));
|
|
return Result;
|
|
}
|
|
|
|
/// Takes care of extending (padding) the size of a stack argument.
|
|
///
|
|
/// \note This only accounts for the post-padding (extension).
|
|
/// Pre-padding (offset) needs to be taken care of separately.
|
|
///
|
|
/// \param Size The size of the argument without the padding.
|
|
///
|
|
/// \return The size of the argument with the padding.
|
|
uint64_t paddedSizeOnStack(uint64_t Size) const {
|
|
return FunctionType::paddedSizeOnStack(Size, getPointerSize());
|
|
}
|
|
};
|
|
|
|
} // namespace abi
|
|
|
|
#include "revng/ABI/Generated/Late/Definition.h"
|