[K/N] Add a helper for objects with custom layout ^KT-60928

This commit is contained in:
Alexander Shabalin
2023-07-27 21:09:54 +02:00
committed by Space Team
parent 70e98c9c2b
commit d956a5504d
3 changed files with 480 additions and 1 deletions
@@ -28,7 +28,7 @@ constexpr inline bool IsValidAlignment(size_t alignment) {
return alignment != 0 && (alignment & (alignment - 1)) == 0;
}
constexpr inline bool IsAligned(size_t size, size_t alignment) {
constexpr inline bool IsAligned(uint64_t size, size_t alignment) {
return size % alignment == 0;
}
@@ -0,0 +1,198 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#pragma once
#ifndef KONAN_WASM
#include <algorithm>
#include <cinttypes>
#include <cstddef>
#include <cstdint>
#include <tuple>
#include "Alignment.hpp"
#include "KAssert.h"
#include "RawPtr.hpp"
namespace kotlin::type_layout {
namespace internal {
template <size_t index, typename... FieldDescriptors>
constexpr uint64_t fieldOffsetFromBase(uint64_t baseOffset, FieldDescriptors... fieldDescriptors) noexcept {
constexpr auto fieldsDescriptorsSize = sizeof...(FieldDescriptors);
static_assert(index <= fieldsDescriptorsSize);
if constexpr (fieldsDescriptorsSize == 0) {
return baseOffset;
} else {
return [](uint64_t baseOffset, auto head, auto... tail) constexpr noexcept {
baseOffset = AlignUp<uint64_t>(baseOffset, head.alignment());
if constexpr (index == 0) {
return baseOffset;
} else {
return fieldOffsetFromBase<index - 1>(baseOffset + head.size(), tail...);
}
}
(baseOffset, fieldDescriptors...);
}
}
template <typename... FieldDescriptors>
constexpr size_t alignment(FieldDescriptors... fieldDescriptors) noexcept {
if constexpr (sizeof...(FieldDescriptors) == 0) {
return 1;
} else {
return [](auto head, auto... tail) constexpr noexcept { return std::max(head.alignment(), alignment(tail...)); }
(fieldDescriptors...);
}
}
template <typename... FieldDescriptors>
constexpr void construct(uint8_t* ptr, FieldDescriptors... fieldDescriptors) noexcept {
if constexpr (sizeof...(FieldDescriptors) == 0) {
return;
} else {
[](uint8_t * ptr, auto head, auto... tail) constexpr noexcept {
head.construct(ptr);
auto offset = fieldOffsetFromBase<1>(0, head, tail...);
construct(ptr + offset, tail...);
}
(ptr, fieldDescriptors...);
}
}
template <typename T, typename = void>
struct has_descriptor : std::false_type {};
template <typename T>
struct has_descriptor<T, std::void_t<typename T::descriptor>> : std::true_type {};
} // namespace internal
template <typename T, bool = internal::has_descriptor<T>::value>
struct descriptor {
using type = typename T::descriptor;
};
template <typename T>
struct descriptor<T, false> {
struct type {
using value_type = T;
static constexpr size_t alignment() noexcept { return alignof(T); }
static constexpr size_t size() noexcept { return sizeof(T) == 0 ? 0 : AlignUp(sizeof(T), alignment()); }
static constexpr value_type* construct(uint8_t* ptr) noexcept { return new (ptr) T(); }
};
};
// Get descriptor of type `T`.
// If `T` has `descriptor` member type, it'll be used as a descriptor.
// Implementations may also add their own specializations of `descriptor<T>`.
template <typename T>
using descriptor_t = typename descriptor<T>::type;
// Descriptor for a composite type `T` consisting of `Fields...`.
// Common usage would be:
// ```
// struct MyClass {
// using descriptor = Composite<MyClass, Fields...>;
// // Helper methods for accessing fields and constructing from fields
// // that forward to Composite::field and Composite::fromField
// private:
// MyClass() = delete;
// ~MyClass() = delete;
// };
// ```
template <typename T, typename... Fields>
class Composite {
public:
using value_type = T;
constexpr Composite() noexcept = default;
constexpr explicit Composite(descriptor_t<Fields>... fields) noexcept : fields_(fields...) {}
// This will be the maximum alignment across all fields.
// Alignment of an empty composite type is 1.
constexpr size_t alignment() const noexcept { return std::apply(internal::alignment<descriptor_t<Fields>...>, fields_); }
// The size of an empty composite type will be 0, unlike C++ where it's 1.
constexpr uint64_t size() const noexcept {
if (auto offset = fieldOffset<sizeof...(Fields)>()) {
return AlignUp<uint64_t>(offset, alignment());
}
return 0;
}
// Construct this at address `ptr`.
constexpr value_type* construct(uint8_t* ptr) noexcept {
std::apply(internal::construct<descriptor_t<Fields>...>, std::tuple_cat(std::make_tuple(ptr), fields_));
return reinterpret_cast<value_type*>(ptr);
}
// Get offset of field at `index` from start of this.
// `index` can be equal to count of `Fields...` This will point to
// right after the last field.
template <size_t index>
constexpr uint64_t fieldOffset() const noexcept {
return std::apply(
internal::fieldOffsetFromBase<index, descriptor_t<Fields>...>, std::tuple_cat(std::make_tuple<uint64_t>(0), fields_));
}
template <size_t index>
using FieldDescriptor = std::tuple_element_t<index, std::tuple<descriptor_t<Fields>...>>;
// Get descriptor of field at `index` and its address given address of this.
template <size_t index>
constexpr std::pair<FieldDescriptor<index>, typename FieldDescriptor<index>::value_type*> field(value_type* ptr) noexcept {
auto offset = fieldOffset<index>();
auto field = std::get<index>(fields_);
return {field, reinterpret_cast<typename decltype(field)::value_type*>(reinterpret_cast<uint8_t*>(ptr) + offset)};
}
// Get address of this given address of a field at `index`.
template <size_t index>
constexpr value_type* fromField(typename FieldDescriptor<index>::value_type* ptr) noexcept {
auto offset = fieldOffset<index>();
return reinterpret_cast<value_type*>(reinterpret_cast<uint8_t*>(ptr) - offset);
}
private:
std::tuple<descriptor_t<Fields>...> fields_;
};
template <typename T>
class Composite<T> {
public:
using value_type = T;
constexpr Composite() noexcept = default;
// This will be the maximum alignment across all fields.
// Alignment of an empty composite type is 1.
constexpr size_t alignment() const noexcept { return 1; }
// The size of an empty composite type will be 0, unlike C++ where it's 1.
constexpr uint64_t size() const noexcept { return 0; }
// Construct this at address `ptr`.
constexpr value_type* construct(uint8_t* ptr) noexcept {}
// Get offset of field at `index` from start of this.
// `index` can be equal to count of `Fields...` This will point to
// right after the last field.
template <size_t index>
constexpr uint64_t fieldOffset() const noexcept {
static_assert(index == 0);
return 0;
}
};
} // namespace kotlin::type_layout
#endif
@@ -0,0 +1,281 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#include "TypeLayout.hpp"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "std_support/CStdlib.hpp"
using namespace kotlin;
namespace {
#define CHECK_OVERALL_DESCRIPTOR(DESCRIPTOR) \
static_assert(DESCRIPTOR().alignment() == alignof(DESCRIPTOR::value_type)); \
static_assert(DESCRIPTOR().size() == sizeof(DESCRIPTOR::value_type))
#define CHECK_FIELD_DESCRIPTOR(DESCRIPTOR, FIELD) \
static_assert(DESCRIPTOR().fieldOffset<FIELD>() == offsetof(DESCRIPTOR::value_type, f##FIELD))
#define CHECK_DESCRIPTOR(DESCRIPTOR, F0, F1, F2) \
CHECK_OVERALL_DESCRIPTOR(DESCRIPTOR); \
CHECK_FIELD_DESCRIPTOR(DESCRIPTOR, F0); \
CHECK_FIELD_DESCRIPTOR(DESCRIPTOR, F1); \
CHECK_FIELD_DESCRIPTOR(DESCRIPTOR, F2)
struct TEmpty {
using descriptor = type_layout::Composite<TEmpty>;
};
static_assert(type_layout::descriptor_t<TEmpty>().alignment() == alignof(TEmpty));
static_assert(type_layout::descriptor_t<TEmpty>().size() == 0);
struct T323232 {
using descriptor = type_layout::Composite<T323232, int32_t, int32_t, int32_t>;
int32_t f0;
int32_t f1;
int32_t f2;
};
CHECK_DESCRIPTOR(type_layout::descriptor_t<T323232>, 0, 1, 2);
struct T643232 {
using descriptor = type_layout::Composite<T643232, int64_t, int32_t, int32_t>;
int64_t f0;
int32_t f1;
int32_t f2;
};
CHECK_DESCRIPTOR(type_layout::descriptor_t<T643232>, 0, 1, 2);
struct T326432 {
using descriptor = type_layout::Composite<T326432, int32_t, int64_t, int32_t>;
int32_t f0;
int64_t f1;
int32_t f2;
};
CHECK_DESCRIPTOR(type_layout::descriptor_t<T326432>, 0, 1, 2);
struct T323264 {
using descriptor = type_layout::Composite<T323264, int32_t, int32_t, int64_t>;
int32_t f0;
int32_t f1;
int64_t f2;
};
CHECK_DESCRIPTOR(type_layout::descriptor_t<T323264>, 0, 1, 2);
struct TEmpty326432 {
using descriptor = type_layout::Composite<TEmpty326432, TEmpty, int32_t, int64_t, int32_t>;
[[no_unique_address]] TEmpty f0;
int32_t f1;
int64_t f2;
int32_t f3;
};
// Offset of an empty field serves no purpose, skipping it.
CHECK_DESCRIPTOR(type_layout::descriptor_t<TEmpty326432>, 1, 2, 3);
struct T32Empty6432 {
using descriptor = type_layout::Composite<T32Empty6432, int32_t, TEmpty, int64_t, int32_t>;
int32_t f0;
[[no_unique_address]] TEmpty f1;
int64_t f2;
int32_t f3;
};
// Offset of an empty field serves no purpose, skipping it.
CHECK_DESCRIPTOR(type_layout::descriptor_t<T32Empty6432>, 0, 2, 3);
struct T3264Empty32 {
using descriptor = type_layout::Composite<T3264Empty32, int32_t, int64_t, TEmpty, int32_t>;
int32_t f0;
int64_t f1;
[[no_unique_address]] TEmpty f2;
int32_t f3;
};
// Offset of an empty field serves no purpose, skipping it.
CHECK_DESCRIPTOR(type_layout::descriptor_t<T3264Empty32>, 0, 1, 3);
struct T326432Empty {
using descriptor = type_layout::Composite<T326432Empty, int32_t, int64_t, int32_t, TEmpty>;
int32_t f0;
int64_t f1;
int32_t f2;
[[no_unique_address]] TEmpty f3;
};
// Offset of an empty field serves no purpose, skipping it.
CHECK_DESCRIPTOR(type_layout::descriptor_t<T326432Empty>, 0, 1, 2);
struct TUndefined {
static testing::MockFunction<void(uint8_t*)>* ctorMock;
struct descriptor {
using value_type = TUndefined;
static constexpr size_t alignment() noexcept { return 2 * alignof(uint64_t); }
static constexpr size_t size() noexcept { return 4 * sizeof(uint64_t); }
static value_type* construct(uint8_t* ptr) noexcept {
ctorMock->Call(ptr);
return reinterpret_cast<value_type*>(ptr);
}
};
private:
TUndefined() = delete;
~TUndefined() = delete;
};
// static
testing::MockFunction<void(uint8_t*)>* TUndefined::ctorMock = nullptr;
struct TDynamic {
static testing::MockFunction<void(uint8_t*)>* ctorMock;
struct descriptor {
using value_type = TDynamic;
descriptor(uint64_t size, size_t alignment) noexcept : size_(size), alignment_(alignment) {}
size_t alignment() noexcept { return alignment_; }
uint64_t size() noexcept { return size_; }
static value_type* construct(uint8_t* ptr) noexcept {
ctorMock->Call(ptr);
return reinterpret_cast<value_type*>(ptr);
}
private:
uint64_t size_;
size_t alignment_;
};
static descriptor make_descriptor(uint64_t size, size_t alignment) noexcept { return descriptor(size, alignment); }
private:
TDynamic() = delete;
~TDynamic() = delete;
};
// static
testing::MockFunction<void(uint8_t*)>* TDynamic::ctorMock = nullptr;
struct THeader {
using descriptor = type_layout::Composite<THeader, T323232, TUndefined>;
T323232* flags() noexcept { return descriptor().field<0>(this).second; }
TUndefined* header() noexcept { return descriptor().field<1>(this).second; }
static THeader* fromFlags(T323232* flags) noexcept { return descriptor().fromField<0>(flags); }
static THeader* fromHeader(TUndefined* header) noexcept { return descriptor().fromField<1>(header); }
private:
THeader() = delete;
~THeader() = delete;
};
struct TVLA {
using descriptor = type_layout::Composite<TVLA, THeader, TDynamic, T326432>;
static descriptor make_descriptor(uint64_t size, size_t alignment) noexcept {
return descriptor({}, TDynamic::descriptor(size, alignment), {});
}
THeader* dataHeader(descriptor descriptor) noexcept { return descriptor.field<0>(this).second; }
TDynamic* data(descriptor descriptor) noexcept { return descriptor.field<1>(this).second; }
T326432* footer(descriptor descriptor) noexcept { return descriptor.field<2>(this).second; }
static TVLA* fromDataHeader(descriptor descriptor, THeader* header) noexcept { return descriptor.fromField<0>(header); }
static TVLA* fromData(descriptor descriptor, TDynamic* data) noexcept { return descriptor.fromField<1>(data); }
static TVLA* fromFooter(descriptor descriptor, T326432* footer) noexcept { return descriptor.fromField<2>(footer); }
private:
TVLA() = delete;
~TVLA() = delete;
};
} // namespace
class TypeLayoutTest : public ::testing::Test {
public:
TypeLayoutTest() noexcept {
TUndefined::ctorMock = &ctorMock_;
TDynamic::ctorMock = &ctorMock_;
}
~TypeLayoutTest() {
TUndefined::ctorMock = nullptr;
TDynamic::ctorMock = nullptr;
}
testing::MockFunction<void(uint8_t*)>& ctorMock() noexcept { return ctorMock_; }
private:
testing::StrictMock<testing::MockFunction<void(uint8_t*)>> ctorMock_;
};
TEST_F(TypeLayoutTest, VLA) {
constexpr size_t vlaAlignment = 1;
constexpr size_t vlaSize = 100;
constexpr size_t expectedDataHeaderOffset = 0;
constexpr size_t expectedFlagsOffset = expectedDataHeaderOffset;
constexpr size_t expectedHeaderOffset = expectedFlagsOffset + 2 * sizeof(uint64_t);
constexpr size_t expectedDataOffset = expectedHeaderOffset + 4 * sizeof(uint64_t);
constexpr size_t expectedFooterOffset = AlignUp(expectedDataOffset + vlaSize, alignof(T326432));
constexpr size_t expectedAlignment = 16;
constexpr size_t expectedSize = AlignUp(expectedFooterOffset + sizeof(T326432), expectedAlignment);
auto vlaDescriptor = TVLA::make_descriptor(vlaSize, vlaAlignment);
EXPECT_THAT(vlaDescriptor.size(), expectedSize);
EXPECT_THAT(vlaDescriptor.alignment(), expectedAlignment);
auto* ptr = reinterpret_cast<uint8_t*>(std_support::aligned_malloc(vlaDescriptor.alignment(), vlaDescriptor.size()));
uint8_t* expectedHeader = ptr + 2 * sizeof(uint64_t);
uint8_t* expectedData = ptr + 6 * sizeof(uint64_t);
{
testing::InSequence seq;
EXPECT_CALL(ctorMock(), Call(expectedHeader));
EXPECT_CALL(ctorMock(), Call(expectedData));
}
TVLA* instance = vlaDescriptor.construct(ptr);
testing::Mock::VerifyAndClear(&ctorMock());
auto* dataHeader = instance->dataHeader(vlaDescriptor);
EXPECT_THAT(TVLA::fromDataHeader(vlaDescriptor, dataHeader), instance);
EXPECT_THAT(reinterpret_cast<uint8_t*>(dataHeader), ptr + expectedDataHeaderOffset);
auto* flags = dataHeader->flags();
EXPECT_THAT(THeader::fromFlags(flags), dataHeader);
EXPECT_THAT(reinterpret_cast<uint8_t*>(flags), ptr + expectedFlagsOffset);
auto* header = dataHeader->header();
EXPECT_THAT(THeader::fromHeader(header), dataHeader);
EXPECT_THAT(reinterpret_cast<uint8_t*>(header), ptr + expectedHeaderOffset);
auto* data = instance->data(vlaDescriptor);
EXPECT_THAT(TVLA::fromData(vlaDescriptor, data), instance);
EXPECT_THAT(reinterpret_cast<uint8_t*>(data), ptr + expectedDataOffset);
auto* footer = instance->footer(vlaDescriptor);
EXPECT_THAT(TVLA::fromFooter(vlaDescriptor, footer), instance);
EXPECT_THAT(reinterpret_cast<uint8_t*>(footer), ptr + expectedFooterOffset);
std_support::aligned_free(ptr);
}
TEST_F(TypeLayoutTest, VLAVeryLarge) {
constexpr size_t vlaAlignment = 1;
constexpr uint64_t vlaSize = std::numeric_limits<uint64_t>::max() / 2;
auto vlaDescriptor = TVLA::make_descriptor(vlaSize, vlaAlignment);
// Checking that no unsigned integer overflow happened.
EXPECT_THAT(vlaDescriptor.size(), testing::Gt(std::numeric_limits<uint32_t>::max()));
}