Files
revng-revng/tests/unit/Alignment.cpp
Ivan Krysak 823e561806 TTG: rework polymorphic serialization
For now, the only serialization trait we were verifying
a polymorphic TTG type to have was the wrong (the one
that only printed base class's fields).

This commit explicitly disables said serializer and
ensures it's never used.

Here's an illustration of the impact of the changes:
```
model::UpcastableType MyType = getTypeFromSomewhere();
model::Type &View = *MyType;
model::PointerType &Pointer = MyType->asPointer();

serialize(MyType); // Good
serialize(Pointer); // Good
serialize(View); // new: explicit error
                 // old: only print base type's fields
```
2024-06-27 11:05:51 +02:00

338 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(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"
+ serializeToString(Type) + "ABI ('"
+ serializeToString(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.ScalarTypes().contains(Value);
});
}
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.FloatingPointScalarTypes().contains(Value);
});
}
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"
+ serializeToString(LHS) + "and\n"
+ serializeToString(RHS) + "ABI ('"
+ serializeToString(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);
}
}