diff --git a/kotlin-native/runtime/src/main/cpp/Alignment.hpp b/kotlin-native/runtime/src/main/cpp/Alignment.hpp index 49e4febc007..2bd5ac641a0 100644 --- a/kotlin-native/runtime/src/main/cpp/Alignment.hpp +++ b/kotlin-native/runtime/src/main/cpp/Alignment.hpp @@ -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; } diff --git a/kotlin-native/runtime/src/main/cpp/TypeLayout.hpp b/kotlin-native/runtime/src/main/cpp/TypeLayout.hpp new file mode 100644 index 00000000000..0a0fa823a00 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/TypeLayout.hpp @@ -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 +#include +#include +#include +#include + +#include "Alignment.hpp" +#include "KAssert.h" +#include "RawPtr.hpp" + +namespace kotlin::type_layout { + +namespace internal { + +template +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(baseOffset, head.alignment()); + if constexpr (index == 0) { + return baseOffset; + } else { + return fieldOffsetFromBase(baseOffset + head.size(), tail...); + } + } + (baseOffset, fieldDescriptors...); + } +} + +template +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 +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 +struct has_descriptor : std::false_type {}; + +template +struct has_descriptor> : std::true_type {}; + +} // namespace internal + +template ::value> +struct descriptor { + using type = typename T::descriptor; +}; + +template +struct descriptor { + 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`. +template +using descriptor_t = typename descriptor::type; + +// Descriptor for a composite type `T` consisting of `Fields...`. +// Common usage would be: +// ``` +// struct MyClass { +// using descriptor = Composite; +// // Helper methods for accessing fields and constructing from fields +// // that forward to Composite::field and Composite::fromField +// private: +// MyClass() = delete; +// ~MyClass() = delete; +// }; +// ``` +template +class Composite { +public: + using value_type = T; + + constexpr Composite() noexcept = default; + + constexpr explicit Composite(descriptor_t... 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...>, 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()) { + return AlignUp(offset, alignment()); + } + return 0; + } + + // Construct this at address `ptr`. + constexpr value_type* construct(uint8_t* ptr) noexcept { + std::apply(internal::construct...>, std::tuple_cat(std::make_tuple(ptr), fields_)); + return reinterpret_cast(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 + constexpr uint64_t fieldOffset() const noexcept { + return std::apply( + internal::fieldOffsetFromBase...>, std::tuple_cat(std::make_tuple(0), fields_)); + } + + template + using FieldDescriptor = std::tuple_element_t...>>; + + // Get descriptor of field at `index` and its address given address of this. + template + constexpr std::pair, typename FieldDescriptor::value_type*> field(value_type* ptr) noexcept { + auto offset = fieldOffset(); + auto field = std::get(fields_); + return {field, reinterpret_cast(reinterpret_cast(ptr) + offset)}; + } + + // Get address of this given address of a field at `index`. + template + constexpr value_type* fromField(typename FieldDescriptor::value_type* ptr) noexcept { + auto offset = fieldOffset(); + return reinterpret_cast(reinterpret_cast(ptr) - offset); + } + +private: + std::tuple...> fields_; +}; + +template +class Composite { +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 + constexpr uint64_t fieldOffset() const noexcept { + static_assert(index == 0); + return 0; + } +}; + +} // namespace kotlin::type_layout + +#endif diff --git a/kotlin-native/runtime/src/main/cpp/TypeLayoutTest.cpp b/kotlin-native/runtime/src/main/cpp/TypeLayoutTest.cpp new file mode 100644 index 00000000000..d1a16a38c60 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/TypeLayoutTest.cpp @@ -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() == 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; +}; + +static_assert(type_layout::descriptor_t().alignment() == alignof(TEmpty)); +static_assert(type_layout::descriptor_t().size() == 0); + +struct T323232 { + using descriptor = type_layout::Composite; + + int32_t f0; + int32_t f1; + int32_t f2; +}; +CHECK_DESCRIPTOR(type_layout::descriptor_t, 0, 1, 2); + +struct T643232 { + using descriptor = type_layout::Composite; + + int64_t f0; + int32_t f1; + int32_t f2; +}; +CHECK_DESCRIPTOR(type_layout::descriptor_t, 0, 1, 2); + +struct T326432 { + using descriptor = type_layout::Composite; + + int32_t f0; + int64_t f1; + int32_t f2; +}; +CHECK_DESCRIPTOR(type_layout::descriptor_t, 0, 1, 2); + +struct T323264 { + using descriptor = type_layout::Composite; + + int32_t f0; + int32_t f1; + int64_t f2; +}; +CHECK_DESCRIPTOR(type_layout::descriptor_t, 0, 1, 2); + +struct TEmpty326432 { + using descriptor = type_layout::Composite; + + [[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, 1, 2, 3); + +struct T32Empty6432 { + using descriptor = type_layout::Composite; + + 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, 0, 2, 3); + +struct T3264Empty32 { + using descriptor = type_layout::Composite; + + 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, 0, 1, 3); + +struct T326432Empty { + using descriptor = type_layout::Composite; + + 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, 0, 1, 2); + +struct TUndefined { + static testing::MockFunction* 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(ptr); + } + }; + +private: + TUndefined() = delete; + ~TUndefined() = delete; +}; + +// static +testing::MockFunction* TUndefined::ctorMock = nullptr; + +struct TDynamic { + static testing::MockFunction* 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(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* TDynamic::ctorMock = nullptr; + +struct THeader { + using descriptor = type_layout::Composite; + + 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; + + 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& ctorMock() noexcept { return ctorMock_; } + +private: + testing::StrictMock> 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(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(dataHeader), ptr + expectedDataHeaderOffset); + + auto* flags = dataHeader->flags(); + EXPECT_THAT(THeader::fromFlags(flags), dataHeader); + EXPECT_THAT(reinterpret_cast(flags), ptr + expectedFlagsOffset); + + auto* header = dataHeader->header(); + EXPECT_THAT(THeader::fromHeader(header), dataHeader); + EXPECT_THAT(reinterpret_cast(header), ptr + expectedHeaderOffset); + + auto* data = instance->data(vlaDescriptor); + EXPECT_THAT(TVLA::fromData(vlaDescriptor, data), instance); + EXPECT_THAT(reinterpret_cast(data), ptr + expectedDataOffset); + + auto* footer = instance->footer(vlaDescriptor); + EXPECT_THAT(TVLA::fromFooter(vlaDescriptor, footer), instance); + EXPECT_THAT(reinterpret_cast(footer), ptr + expectedFooterOffset); + + std_support::aligned_free(ptr); +} + +TEST_F(TypeLayoutTest, VLAVeryLarge) { + constexpr size_t vlaAlignment = 1; + constexpr uint64_t vlaSize = std::numeric_limits::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::max())); +}