Files
revng-revng/tests/unit/Alignment.cpp
2026-05-02 17:21:47 +03:00

339 lines
14 KiB
C++

//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include <bit>
#define BOOST_TEST_MODULE TypeAlignment
bool init_unit_test();
#include "boost/test/unit_test.hpp"
#include "revng/ABI/Definition.h"
#include "revng/ADT/Concepts.h"
#include "revng/Model/Binary.h"
static std::string printAlignment(uint64_t Alignment) {
return Alignment != 0 ? std::to_string(Alignment) : "undefined";
}
struct Expected {
const abi::Definition &ABI;
uint64_t Alignment;
explicit Expected(model::ABI::Values ABIName, uint64_t Alignment) :
ABI(abi::Definition::get(ABIName)), Alignment(Alignment) {}
};
template<typename... Types>
requires(std::same_as<Types, Expected> && ...)
void testAlignment(model::UpcastableType &&Type, const Types &...TestCases) {
for (auto &&[ABI, Expected] : std::array{ TestCases... }) {
std::optional<uint64_t> TestResult = ABI.alignment(*Type);
if (TestResult.value_or(0) != Expected) {
std::string Error = "Alignment run failed for type:\n" + toString(Type)
+ "ABI (`" + toString(ABI.ABI())
+ "`) reports the alignment of `"
+ printAlignment(TestResult.value_or(0)) + "`, "
+ "while the expected value is `"
+ printAlignment(Expected) + "`.\n";
revng_abort(Error.c_str());
}
}
}
static bool ABIhasIntsOfSizes(const abi::Definition &ABI,
std::initializer_list<uint64_t> Values) {
return std::ranges::all_of(Values, [&ABI](uint64_t Value) {
return ABI.findIntegerType(Value) != nullptr;
});
}
static bool ABIhasFloatsOfSizes(const abi::Definition &ABI,
std::initializer_list<uint64_t> Values) {
return std::ranges::all_of(Values, [&ABI](uint64_t Value) {
return ABI.findFloatingPointType(Value) != nullptr;
});
}
BOOST_AUTO_TEST_CASE(GenericPrimitiveTypes) {
TupleTree<model::Binary> Binary;
testAlignment(model::PrimitiveType::makeVoid(),
Expected(model::ABI::AAPCS64, 0),
Expected(model::ABI::AAPCS, 0),
Expected(model::ABI::SystemZ_s390x, 0),
Expected(model::ABI::SystemV_x86, 0));
testAlignment(model::PrimitiveType::makeGeneric(1),
Expected(model::ABI::AAPCS64, 1),
Expected(model::ABI::AAPCS, 1),
Expected(model::ABI::SystemZ_s390x, 1),
Expected(model::ABI::SystemV_x86, 1));
testAlignment(model::PrimitiveType::makeGeneric(2),
Expected(model::ABI::AAPCS64, 2),
Expected(model::ABI::AAPCS, 2),
Expected(model::ABI::SystemZ_s390x, 2),
Expected(model::ABI::SystemV_x86, 2));
testAlignment(model::PrimitiveType::makeGeneric(4),
Expected(model::ABI::AAPCS64, 4),
Expected(model::ABI::AAPCS, 4),
Expected(model::ABI::SystemZ_s390x, 4),
Expected(model::ABI::SystemV_x86, 4));
testAlignment(model::PrimitiveType::makeGeneric(8),
Expected(model::ABI::AAPCS64, 8),
Expected(model::ABI::AAPCS, 8),
Expected(model::ABI::SystemZ_s390x, 8),
Expected(model::ABI::SystemV_x86, 4));
testAlignment(model::PrimitiveType::makeGeneric(16),
Expected(model::ABI::AAPCS64, 16),
Expected(model::ABI::SystemZ_s390x, 8),
Expected(model::ABI::SystemV_x86_64, 16));
}
BOOST_AUTO_TEST_CASE(FloatingPointPrimitiveTypes) {
TupleTree<model::Binary> Binary;
testAlignment(model::PrimitiveType::makeFloat(2),
Expected(model::ABI::AAPCS64, 2),
Expected(model::ABI::AAPCS, 2),
Expected(model::ABI::SystemV_x86_64, 2));
testAlignment(model::PrimitiveType::makeFloat(4),
Expected(model::ABI::AAPCS64, 4),
Expected(model::ABI::AAPCS, 4),
Expected(model::ABI::SystemZ_s390x, 4),
Expected(model::ABI::SystemV_x86, 4));
testAlignment(model::PrimitiveType::makeFloat(8),
Expected(model::ABI::AAPCS64, 8),
Expected(model::ABI::AAPCS, 8),
Expected(model::ABI::SystemZ_s390x, 8),
Expected(model::ABI::SystemV_x86, 4));
testAlignment(model::PrimitiveType::makeFloat(16),
Expected(model::ABI::AAPCS64, 16),
Expected(model::ABI::SystemZ_s390x, 8),
Expected(model::ABI::SystemV_x86, 16),
Expected(model::ABI::SystemV_x86_64, 16));
}
constexpr std::array TestedABIs{ model::ABI::AAPCS64,
model::ABI::AAPCS,
model::ABI::SystemZ_s390x,
model::ABI::SystemV_x86 };
static void compareTypeAlignments(const abi::Definition &ABI,
const model::UpcastableType &LHS,
const model::UpcastableType &RHS) {
std::optional<uint64_t> Left = ABI.alignment(*LHS);
std::optional<uint64_t> Right = ABI.alignment(*RHS);
if (Left != Right) {
std::string Error = "Alignment comparison run failed for types:\n"
+ toString(LHS) + "and\n" + toString(RHS) + "ABI (`"
+ toString(ABI.ABI()) + "`) reports the alignment of `"
+ printAlignment(Left.value_or(0))
+ "` for the first one, and `"
+ printAlignment(Right.value_or(0))
+ "` for the second one.\n";
revng_abort(Error.c_str());
}
}
BOOST_AUTO_TEST_CASE(RemainingPrimitiveTypes) {
TupleTree<model::Binary> Binary;
constexpr std::array<model::PrimitiveKind::Values, 5> RemainingTypes{
model::PrimitiveKind::Unsigned,
model::PrimitiveKind::Signed,
model::PrimitiveKind::Number,
model::PrimitiveKind::PointerOrNumber
};
for (model::ABI::Values ABIName : TestedABIs) {
const abi::Definition &ABI = abi::Definition::get(ABIName);
for (const auto &PKind : RemainingTypes)
for (uint64_t Size = 1; Size <= 16; Size *= 2)
if (ABIhasIntsOfSizes(ABI, { Size }))
compareTypeAlignments(ABI,
model::PrimitiveType::makeGeneric(Size),
model::PrimitiveType::make(PKind, Size));
}
}
BOOST_AUTO_TEST_CASE(UnionTypes) {
TupleTree<model::Binary> Binary;
auto Int16 = model::PrimitiveType::makeSigned(2);
auto Int32 = model::PrimitiveType::makeSigned(4);
auto Int64 = model::PrimitiveType::makeSigned(8);
auto Float = model::PrimitiveType::makeFloat(4);
auto Double = model::PrimitiveType::makeFloat(8);
auto LongDouble = model::PrimitiveType::makeFloat(16);
auto WeirdLD = model::PrimitiveType::makeFloat(12);
for (model::ABI::Values ABIName : TestedABIs) {
const abi::Definition &ABI = abi::Definition::get(ABIName);
auto &&[SimpleDefinition, Simple] = Binary->makeUnionDefinition();
SimpleDefinition.addField(Int32.copy());
SimpleDefinition.addField(Int64.copy());
if (ABIhasIntsOfSizes(ABI, { 4, 8 }))
compareTypeAlignments(ABI, Int64, Simple);
auto &&[SmallFloatDefinition, SmallFloat] = Binary->makeUnionDefinition();
SmallFloatDefinition.addField(Int32.copy());
SmallFloatDefinition.addField(Float.copy());
if (ABIhasIntsOfSizes(ABI, { 4 }) && ABIhasFloatsOfSizes(ABI, { 4 })) {
compareTypeAlignments(ABI, Int32, SmallFloat);
compareTypeAlignments(ABI, Float, SmallFloat);
}
auto &&[BigFloatDefinition, BigFloat] = Binary->makeUnionDefinition();
BigFloatDefinition.addField(LongDouble.copy());
BigFloatDefinition.addField(Int64.copy());
if (ABIhasIntsOfSizes(ABI, { 8 }) && ABIhasFloatsOfSizes(ABI, { 16 }))
compareTypeAlignments(ABI, LongDouble, BigFloat);
auto &&[WeirdFloatDefinition, WeirdFloat] = Binary->makeUnionDefinition();
WeirdFloatDefinition.addField(WeirdLD.copy());
WeirdFloatDefinition.addField(Int32.copy());
if (ABIhasIntsOfSizes(ABI, { 4 }) && ABIhasFloatsOfSizes(ABI, { 12 }))
compareTypeAlignments(ABI, WeirdLD, WeirdFloat);
// Test the case where on top of the float field, there's also another
// stricter-aligned field, which "eclipses" the float one.
auto &&[EclipsedFloatDefinition,
EclipsedFl] = Binary->makeUnionDefinition();
EclipsedFloatDefinition.addField(Float.copy());
EclipsedFloatDefinition.addField(Int64.copy());
if (ABIhasIntsOfSizes(ABI, { 8 }) && ABIhasFloatsOfSizes(ABI, { 4 }))
compareTypeAlignments(ABI, Int64, EclipsedFl);
auto &&[NestedDefinition, Nested] = Binary->makeUnionDefinition();
NestedDefinition.addField(SmallFloat.copy());
NestedDefinition.addField(Int16.copy());
if (ABIhasIntsOfSizes(ABI, { 2, 4 }) && ABIhasFloatsOfSizes(ABI, { 4 })) {
compareTypeAlignments(ABI, Int32, Nested);
compareTypeAlignments(ABI, Float, Nested);
compareTypeAlignments(ABI, SmallFloat, Nested);
}
auto &&[EclipsedNestedDefinition,
EclipsedN] = Binary->makeUnionDefinition();
EclipsedNestedDefinition.addField(SmallFloat.copy());
EclipsedNestedDefinition.addField(Int64.copy());
if (ABIhasIntsOfSizes(ABI, { 4, 8 }) && ABIhasFloatsOfSizes(ABI, { 4 }))
compareTypeAlignments(ABI, Int64, EclipsedN);
}
}
BOOST_AUTO_TEST_CASE(StructTypes) {
TupleTree<model::Binary> Binary;
auto Int16 = model::PrimitiveType::makeSigned(2);
auto Int32 = model::PrimitiveType::makeSigned(4);
auto Int64 = model::PrimitiveType::makeSigned(8);
auto Float = model::PrimitiveType::makeFloat(4);
auto Double = model::PrimitiveType::makeFloat(8);
auto LongDouble = model::PrimitiveType::makeFloat(16);
auto WeirdLD = model::PrimitiveType::makeFloat(12);
for (model::ABI::Values ABIName : TestedABIs) {
const abi::Definition &ABI = abi::Definition::get(ABIName);
auto &&[SimpleDefinition, Simple] = Binary->makeStructDefinition();
SimpleDefinition.addField(0, Int32.copy());
SimpleDefinition.addField(8, Int64.copy());
if (ABIhasIntsOfSizes(ABI, { 4, 8 }))
compareTypeAlignments(ABI, Int64, Simple);
auto &&[SmallFloatDefinition, SmallFloat] = Binary->makeStructDefinition();
SmallFloatDefinition.addField(0, Int32.copy());
SmallFloatDefinition.addField(4, Float.copy());
if (ABIhasIntsOfSizes(ABI, { 4 }) && ABIhasFloatsOfSizes(ABI, { 4 })) {
compareTypeAlignments(ABI, Int32, SmallFloat);
compareTypeAlignments(ABI, Float, SmallFloat);
}
auto &&[BigFloatDefinition, BigFloat] = Binary->makeStructDefinition();
BigFloatDefinition.addField(0, LongDouble.copy());
BigFloatDefinition.addField(16, Int64.copy());
if (ABIhasIntsOfSizes(ABI, { 8 }) && ABIhasFloatsOfSizes(ABI, { 16 }))
compareTypeAlignments(ABI, LongDouble, BigFloat);
auto &&[WeirdFloatDefinition, WeirdFloat] = Binary->makeStructDefinition();
WeirdFloatDefinition.addField(0, WeirdLD.copy());
WeirdFloatDefinition.addField(12, Int32.copy());
if (ABIhasIntsOfSizes(ABI, { 4 }) && ABIhasFloatsOfSizes(ABI, { 12 }))
compareTypeAlignments(ABI, WeirdLD, WeirdFloat);
// Test the case where on top of the float field, there's also another
// stricter-aligned field, which "eclipses" the float one.
auto &&[EclipsedFloatDefinition,
EclipsedFl] = Binary->makeStructDefinition();
EclipsedFloatDefinition.addField(0, Float.copy());
EclipsedFloatDefinition.addField(8, Int64.copy());
if (ABIhasIntsOfSizes(ABI, { 8 }) && ABIhasFloatsOfSizes(ABI, { 4 }))
compareTypeAlignments(ABI, Int64, EclipsedFl);
auto &&[NestedDefinition, Nested] = Binary->makeStructDefinition();
NestedDefinition.addField(0, SmallFloat.copy());
NestedDefinition.addField(8, Int16.copy());
if (ABIhasIntsOfSizes(ABI, { 2, 4 }) && ABIhasFloatsOfSizes(ABI, { 4 })) {
compareTypeAlignments(ABI, Int32, Nested);
compareTypeAlignments(ABI, Float, Nested);
compareTypeAlignments(ABI, SmallFloat, Nested);
}
auto &&[EclipsedNestedDefinition,
EclipsedN] = Binary->makeStructDefinition();
EclipsedNestedDefinition.addField(0, SmallFloat.copy());
EclipsedNestedDefinition.addField(8, Int64.copy());
if (ABIhasIntsOfSizes(ABI, { 4, 8 }) && ABIhasFloatsOfSizes(ABI, { 4 }))
compareTypeAlignments(ABI, Int64, EclipsedN);
}
}
BOOST_AUTO_TEST_CASE(ArraysAndPointers) {
TupleTree<model::Binary> Binary;
auto Int32 = model::PrimitiveType::makeSigned(4);
auto Int64 = model::PrimitiveType::makeSigned(8);
auto Double = model::PrimitiveType::makeFloat(8);
for (model::ABI::Values ABIName : TestedABIs) {
const abi::Definition &ABI = abi::Definition::get(ABIName);
auto IntPointer = model::PointerType::make(Int32.copy(),
ABI.getPointerSize());
if (ABI.getPointerSize() == 8)
compareTypeAlignments(ABI, Int64, IntPointer);
else
compareTypeAlignments(ABI, Int32, IntPointer);
auto IntArray = model::ArrayType::make(Int32.copy(), 100);
compareTypeAlignments(ABI, Int32, IntArray);
auto ConstInt = Int32.copy();
ConstInt->IsConst() = true;
compareTypeAlignments(ABI, Int32, ConstInt);
auto DoublePointer = model::PointerType::make(Double.copy(),
ABI.getPointerSize());
compareTypeAlignments(ABI, IntPointer, DoublePointer);
auto DoubleArray = model::ArrayType::make(Double.copy(), 100);
compareTypeAlignments(ABI, Double, DoubleArray);
auto ConstDouble = Double.copy();
ConstInt->IsConst() = true;
compareTypeAlignments(ABI, Double, ConstDouble);
}
}