[K/N] Add a helper for objects with custom layout ^KT-60928
This commit is contained in:
committed by
Space Team
parent
70e98c9c2b
commit
d956a5504d
@@ -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()));
|
||||
}
|
||||
Reference in New Issue
Block a user