diff options
author | Devtools Arcadia <arcadia-devtools@yandex-team.ru> | 2022-02-07 18:08:42 +0300 |
---|---|---|
committer | Devtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net> | 2022-02-07 18:08:42 +0300 |
commit | 1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch) | |
tree | e26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/yt | |
download | ydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz |
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/yt')
110 files changed, 17347 insertions, 0 deletions
diff --git a/library/cpp/yt/assert/assert.cpp b/library/cpp/yt/assert/assert.cpp new file mode 100644 index 0000000000..095357cdfa --- /dev/null +++ b/library/cpp/yt/assert/assert.cpp @@ -0,0 +1,29 @@ +#include "assert.h" + +#include <util/system/yassert.h> +#include <util/system/compiler.h> + +namespace NYT::NDetail { + +//////////////////////////////////////////////////////////////////////////////// + +Y_WEAK void AssertTrapImpl( + TStringBuf trapType, + TStringBuf expr, + TStringBuf file, + int line, + TStringBuf function) +{ + // Map to Arcadia assert, poorly... + ::NPrivate::Panic( + ::NPrivate::TStaticBuf(file.data(), file.length()), + line, + function.data(), + expr.data(), + "%s", + trapType.data()); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NDetail diff --git a/library/cpp/yt/assert/assert.h b/library/cpp/yt/assert/assert.h new file mode 100644 index 0000000000..7a9e761a3a --- /dev/null +++ b/library/cpp/yt/assert/assert.h @@ -0,0 +1,76 @@ +#pragma once + +#include <util/system/compiler.h> +#include <util/system/src_root.h> + +#include <util/generic/strbuf.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +namespace NDetail { + +[[noreturn]] +void AssertTrapImpl( + TStringBuf trapType, + TStringBuf expr, + TStringBuf file, + int line, + TStringBuf function); + +} // namespace NDetail + +#ifdef __GNUC__ + #define YT_BUILTIN_TRAP() __builtin_trap() +#else + #define YT_BUILTIN_TRAP() std::terminate() +#endif + +#define YT_ASSERT_TRAP(trapType, expr) \ + ::NYT::NDetail::AssertTrapImpl(TStringBuf(trapType), TStringBuf(expr), __SOURCE_FILE_IMPL__.As<TStringBuf>(), __LINE__, TStringBuf(__FUNCTION__)); \ + Y_UNREACHABLE() \ + +#ifdef NDEBUG + #define YT_ASSERT(expr) \ + do { \ + if (false) { \ + (void) (expr); \ + } \ + } while (false) +#else + #define YT_ASSERT(expr) \ + do { \ + if (Y_UNLIKELY(!(expr))) { \ + YT_ASSERT_TRAP("YT_ASSERT", #expr); \ + } \ + } while (false) +#endif + +//! Same as |YT_ASSERT| but evaluates and checks the expression in both release and debug mode. +#define YT_VERIFY(expr) \ + do { \ + if (Y_UNLIKELY(!(expr))) { \ + YT_ASSERT_TRAP("YT_VERIFY", #expr); \ + } \ + } while (false) + +//! Fatal error code marker. Abnormally terminates the current process. +#ifdef YT_COMPILING_UDF + #define YT_ABORT() __YT_BUILTIN_ABORT() +#else + #define YT_ABORT() \ + do { \ + YT_ASSERT_TRAP("YT_ABORT", ""); \ + } while (false) +#endif + +//! Unimplemented code marker. Abnormally terminates the current process. +#define YT_UNIMPLEMENTED() \ + do { \ + YT_ASSERT_TRAP("YT_UNIMPLEMENTED", ""); \ + } while (false) + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/assert/ya.make b/library/cpp/yt/assert/ya.make new file mode 100644 index 0000000000..df74a4f1fa --- /dev/null +++ b/library/cpp/yt/assert/ya.make @@ -0,0 +1,9 @@ +LIBRARY() + +OWNER(g:yt) + +SRCS( + assert.cpp +) + +END() diff --git a/library/cpp/yt/coding/unittests/varint_ut.cpp b/library/cpp/yt/coding/unittests/varint_ut.cpp new file mode 100644 index 0000000000..ed83ab5c92 --- /dev/null +++ b/library/cpp/yt/coding/unittests/varint_ut.cpp @@ -0,0 +1,134 @@ +#include <library/cpp/testing/gtest/gtest.h> + +#include <library/cpp/yt/coding/varint.h> + +#include <util/random/random.h> + +#include <util/string/escape.h> + +#include <tuple> + +namespace NYT { +namespace { + +using ::testing::Values; + +//////////////////////////////////////////////////////////////////////////////// + +class TWriteVarIntTest: public ::testing::TestWithParam<std::tuple<ui64, TString> > +{ }; + +TEST_P(TWriteVarIntTest, Serialization) +{ + ui64 value = std::get<0>(GetParam()); + TString rightAnswer = std::get<1>(GetParam()); + + TStringStream outputStream; + WriteVarUint64(&outputStream, value); + EXPECT_EQ(rightAnswer, outputStream.Str()); +} + +//////////////////////////////////////////////////////////////////////////////// + +class TReadVarIntTest: public ::testing::TestWithParam<std::tuple<ui64, TString> > +{ }; + +TEST_P(TReadVarIntTest, Serialization) +{ + ui64 rightAnswer = std::get<0>(GetParam()); + TString input = std::get<1>(GetParam()); + + TStringInput inputStream(input); + ui64 value; + ReadVarUint64(&inputStream, &value); + EXPECT_EQ(rightAnswer, value); +} + +TEST(TReadVarIntTest, Overflow) +{ + TString input("\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x01", 11); + TStringInput inputStream(input); + ui64 value; + EXPECT_ANY_THROW(ReadVarUint64(&inputStream, &value)); +} + +//////////////////////////////////////////////////////////////////////////////// + +auto ValuesForVarIntTests = Values( + // Simple cases. + std::make_tuple(0x0ull, TString("\x00", 1)), + std::make_tuple(0x1ull, TString("\x01", 1)), + std::make_tuple(0x2ull, TString("\x02", 1)), + std::make_tuple(0x3ull, TString("\x03", 1)), + std::make_tuple(0x4ull, TString("\x04", 1)), + + // The following "magic numbers" are critical points for varint encoding. + std::make_tuple((1ull << 7) - 1, TString("\x7f", 1)), + std::make_tuple((1ull << 7), TString("\x80\x01", 2)), + std::make_tuple((1ull << 14) - 1, TString("\xff\x7f", 2)), + std::make_tuple((1ull << 14), TString("\x80\x80\x01", 3)), + std::make_tuple((1ull << 21) - 1, TString("\xff\xff\x7f", 3)), + std::make_tuple((1ull << 21), TString("\x80\x80\x80\x01", 4)), + std::make_tuple((1ull << 28) - 1, TString("\xff\xff\xff\x7f", 4)), + std::make_tuple((1ull << 28), TString("\x80\x80\x80\x80\x01", 5)), + std::make_tuple((1ull << 35) - 1, TString("\xff\xff\xff\xff\x7f", 5)), + std::make_tuple((1ull << 35), TString("\x80\x80\x80\x80\x80\x01", 6)), + std::make_tuple((1ull << 42) - 1, TString("\xff\xff\xff\xff\xff\x7f", 6)), + std::make_tuple((1ull << 42), TString("\x80\x80\x80\x80\x80\x80\x01", 7)), + std::make_tuple((1ull << 49) - 1, TString("\xff\xff\xff\xff\xff\xff\x7f", 7)), + std::make_tuple((1ull << 49), TString("\x80\x80\x80\x80\x80\x80\x80\x01", 8)), + std::make_tuple((1ull << 56) - 1, TString("\xff\xff\xff\xff\xff\xff\xff\x7f", 8)), + std::make_tuple((1ull << 56), TString("\x80\x80\x80\x80\x80\x80\x80\x80\x01", 9)), + std::make_tuple((1ull << 63) - 1, TString("\xff\xff\xff\xff\xff\xff\xff\xff\x7f", 9)), + std::make_tuple((1ull << 63), TString("\x80\x80\x80\x80\x80\x80\x80\x80\x80\x01", 10)), + + // Boundary case. + std::make_tuple(static_cast<ui64>(-1), TString("\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01", 10)) +); + +INSTANTIATE_TEST_SUITE_P(ValueParametrized, TWriteVarIntTest, + ValuesForVarIntTests); + +INSTANTIATE_TEST_SUITE_P(ValueParametrized, TReadVarIntTest, + ValuesForVarIntTests); + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TVarInt32Test, RandomValues) +{ + srand(100500); // Set seed + const int numberOfValues = 10000; + + TStringStream stream; + for (int i = 0; i < numberOfValues; ++i) { + i32 expected = static_cast<i32>(RandomNumber<ui32>()); + WriteVarInt32(&stream, expected); + i32 actual; + ReadVarInt32(&stream, &actual); + EXPECT_EQ(expected, actual) + << "Encoded Variant: " << EscapeC(stream.Str()); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TVarInt64Test, RandomValues) +{ + srand(100500); // Set seed + const int numberOfValues = 10000; + + TStringStream stream; + for (int i = 0; i < numberOfValues; ++i) { + i64 expected = static_cast<i64>(RandomNumber<ui64>()); + WriteVarInt64(&stream, expected); + i64 actual; + ReadVarInt64(&stream, &actual); + EXPECT_EQ(expected, actual) + << "Encoded Variant: " << EscapeC(stream.Str()); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace +} // namespace NYT diff --git a/library/cpp/yt/coding/unittests/ya.make b/library/cpp/yt/coding/unittests/ya.make new file mode 100644 index 0000000000..e0622db22d --- /dev/null +++ b/library/cpp/yt/coding/unittests/ya.make @@ -0,0 +1,15 @@ +GTEST() + +OWNER(g:yt) + +SRCS( + zig_zag_ut.cpp + varint_ut.cpp +) + +PEERDIR( + library/cpp/yt/coding + library/cpp/testing/gtest +) + +END() diff --git a/library/cpp/yt/coding/unittests/zig_zag_ut.cpp b/library/cpp/yt/coding/unittests/zig_zag_ut.cpp new file mode 100644 index 0000000000..fae4e63064 --- /dev/null +++ b/library/cpp/yt/coding/unittests/zig_zag_ut.cpp @@ -0,0 +1,57 @@ +#include <library/cpp/testing/gtest/gtest.h> + +#include <library/cpp/yt/coding/zig_zag.h> + +namespace NYT { +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TZigZagTest, Encode32) +{ + EXPECT_EQ(0u, ZigZagEncode32( 0)); + EXPECT_EQ(1u, ZigZagEncode32(-1)); + EXPECT_EQ(2u, ZigZagEncode32( 1)); + EXPECT_EQ(3u, ZigZagEncode32(-2)); + // ... + EXPECT_EQ(std::numeric_limits<ui32>::max() - 1, ZigZagEncode32(std::numeric_limits<i32>::max())); + EXPECT_EQ(std::numeric_limits<ui32>::max(), ZigZagEncode32(std::numeric_limits<i32>::min())); +} + +TEST(TZigZagTest, Decode32) +{ + EXPECT_EQ( 0, ZigZagDecode32(0)); + EXPECT_EQ(-1, ZigZagDecode32(1)); + EXPECT_EQ( 1, ZigZagDecode32(2)); + EXPECT_EQ(-2, ZigZagDecode32(3)); + // ... + EXPECT_EQ(std::numeric_limits<i32>::max(), ZigZagDecode32(std::numeric_limits<ui32>::max() - 1)); + EXPECT_EQ(std::numeric_limits<i32>::min(), ZigZagDecode32(std::numeric_limits<ui32>::max())); +} + +TEST(TZigZagTest, Encode64) +{ + EXPECT_EQ(0ull, ZigZagEncode64( 0)); + EXPECT_EQ(1ull, ZigZagEncode64(-1)); + EXPECT_EQ(2ull, ZigZagEncode64( 1)); + EXPECT_EQ(3ull, ZigZagEncode64(-2)); + // ... + EXPECT_EQ(std::numeric_limits<ui64>::max() - 1, ZigZagEncode64(std::numeric_limits<i64>::max())); + EXPECT_EQ(std::numeric_limits<ui64>::max(), ZigZagEncode64(std::numeric_limits<i64>::min())); +} + +TEST(TZigZagTest, Decode64) +{ + EXPECT_EQ(ZigZagDecode64(0), 0ll); + EXPECT_EQ(ZigZagDecode64(1), -1ll); + EXPECT_EQ(ZigZagDecode64(2), 1ll); + EXPECT_EQ(ZigZagDecode64(3), -2ll); + // ... + EXPECT_EQ(std::numeric_limits<i64>::max(), ZigZagDecode64(std::numeric_limits<ui64>::max() - 1)); + EXPECT_EQ(std::numeric_limits<i64>::min(), ZigZagDecode64(std::numeric_limits<ui64>::max())); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace +} // namespace NYT diff --git a/library/cpp/yt/coding/varint-inl.h b/library/cpp/yt/coding/varint-inl.h new file mode 100644 index 0000000000..f0a09e9d30 --- /dev/null +++ b/library/cpp/yt/coding/varint-inl.h @@ -0,0 +1,240 @@ +#ifndef VARINT_INL_H_ +#error "Direct inclusion of this file is not allowed, include varint.h" +// For the sake of sane code completion. +#include "varint.h" +#endif + +#include "zig_zag.h" + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template <class TWriteCallback> +Y_FORCE_INLINE int WriteVarUint64Impl(TWriteCallback doWrite, ui64 value) +{ + bool stop = false; + int bytesWritten = 0; + while (!stop) { + ++bytesWritten; + ui8 byte = static_cast<ui8>(value | 0x80); + value >>= 7; + if (value == 0) { + stop = true; + byte &= 0x7F; + } + doWrite(byte); + } + return bytesWritten; +} + +// These are optimized versions of these Read/Write functions in protobuf/io/coded_stream.cc. +Y_FORCE_INLINE int WriteVarUint64(IOutputStream* output, ui64 value) +{ + return WriteVarUint64Impl([&] (ui8 byte) { + output->Write(byte); + }, value); +} + +Y_FORCE_INLINE int WriteVarUint64(char* output, ui64 value) +{ + return WriteVarUint64Impl([&] (ui8 byte) { + *output++ = byte; + }, value); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class TOutput> +Y_FORCE_INLINE int WriteVarUint32Impl(TOutput output, ui32 value) +{ + return WriteVarUint64(output, static_cast<ui64>(value)); +} + +Y_FORCE_INLINE int WriteVarUint32(IOutputStream* output, ui32 value) +{ + return WriteVarUint32Impl(output, value); +} + +Y_FORCE_INLINE int WriteVarUint32(char* output, ui32 value) +{ + return WriteVarUint32Impl(output, value); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class TOutput> +Y_FORCE_INLINE int WriteVarInt32Impl(TOutput output, i32 value) +{ + return WriteVarUint64(output, static_cast<ui64>(ZigZagEncode32(value))); +} + +Y_FORCE_INLINE int WriteVarInt32(IOutputStream* output, i32 value) +{ + return WriteVarInt32Impl(output, value); +} + +Y_FORCE_INLINE int WriteVarInt32(char* output, i32 value) +{ + return WriteVarInt32Impl(output, value); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class TOutput> +Y_FORCE_INLINE int WriteVarInt64Impl(TOutput output, i64 value) +{ + return WriteVarUint64(output, static_cast<ui64>(ZigZagEncode64(value))); +} + +Y_FORCE_INLINE int WriteVarInt64(IOutputStream* output, i64 value) +{ + return WriteVarInt64Impl(output, value); +} + +Y_FORCE_INLINE int WriteVarInt64(char* output, i64 value) +{ + return WriteVarInt64Impl(output, value); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class TReadCallback> +Y_FORCE_INLINE int ReadVarUint64Impl(TReadCallback doRead, ui64* value) +{ + size_t count = 0; + ui64 result = 0; + + ui8 byte; + do { + if (7 * count > 8 * sizeof(ui64) ) { + throw TSimpleException("Value is too big for varuint64"); + } + byte = doRead(); + result |= (static_cast<ui64> (byte & 0x7F)) << (7 * count); + ++count; + } while (byte & 0x80); + + *value = result; + return count; +} + +Y_FORCE_INLINE int ReadVarUint64(IInputStream* input, ui64* value) +{ + return ReadVarUint64Impl([&] () { + char byte; + if (input->Read(&byte, 1) != 1) { + throw TSimpleException("Premature end of stream while reading varuint64"); + } + return byte; + }, value); +} + +Y_FORCE_INLINE int ReadVarUint64(const char* input, ui64* value) +{ + return ReadVarUint64Impl([&] () { + char byte = *input; + ++input; + return byte; + }, value); +} + +Y_FORCE_INLINE int ReadVarUint64(const char* input, const char* end, ui64* value) +{ + return ReadVarUint64Impl([&] () { + if (input == end) { + throw TSimpleException("Premature end of data while reading varuint64"); + } + char byte = *input; + ++input; + return byte; + }, value); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class... Args> +Y_FORCE_INLINE int ReadVarUint32Impl(ui32* value, Args... args) +{ + ui64 varInt; + int bytesRead = ReadVarUint64(args..., &varInt); + if (varInt > std::numeric_limits<ui32>::max()) { + throw TSimpleException("Value is too big for varuint32"); + } + *value = static_cast<ui32>(varInt); + return bytesRead; +} + +Y_FORCE_INLINE int ReadVarUint32(IInputStream* input, ui32* value) +{ + return ReadVarUint32Impl(value, input); +} + +Y_FORCE_INLINE int ReadVarUint32(const char* input, ui32* value) +{ + return ReadVarUint32Impl(value, input); +} + +Y_FORCE_INLINE int ReadVarUint32(const char* input, const char* end, ui32* value) +{ + return ReadVarUint32Impl(value, input, end); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class... Args> +Y_FORCE_INLINE int ReadVarInt32Impl(i32* value, Args... args) +{ + ui64 varInt; + int bytesRead = ReadVarUint64(args..., &varInt); + if (varInt > std::numeric_limits<ui32>::max()) { + throw TSimpleException("Value is too big for varint32"); + } + *value = ZigZagDecode32(static_cast<ui32>(varInt)); + return bytesRead; +} + +Y_FORCE_INLINE int ReadVarInt32(IInputStream* input, i32* value) +{ + return ReadVarInt32Impl(value, input); +} + +Y_FORCE_INLINE int ReadVarInt32(const char* input, i32* value) +{ + return ReadVarInt32Impl(value, input); +} + +Y_FORCE_INLINE int ReadVarInt32(const char* input, const char* end, i32* value) +{ + return ReadVarInt32Impl(value, input, end); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class... Args> +Y_FORCE_INLINE int ReadVarInt64Impl(i64* value, Args... args) +{ + ui64 varInt; + int bytesRead = ReadVarUint64(args..., &varInt); + *value = ZigZagDecode64(varInt); + return bytesRead; +} + +Y_FORCE_INLINE int ReadVarInt64(IInputStream* input, i64* value) +{ + return ReadVarInt64Impl(value, input); +} + +Y_FORCE_INLINE int ReadVarInt64(const char* input, i64* value) +{ + return ReadVarInt64Impl(value, input); +} + +Y_FORCE_INLINE int ReadVarInt64(const char* input, const char* end, i64* value) +{ + return ReadVarInt64Impl(value, input, end); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/coding/varint.h b/library/cpp/yt/coding/varint.h new file mode 100644 index 0000000000..c5399f8b06 --- /dev/null +++ b/library/cpp/yt/coding/varint.h @@ -0,0 +1,56 @@ +#pragma once + +#include <library/cpp/yt/exception/exception.h> + +#include <util/system/defaults.h> + +#include <util/stream/input.h> +#include <util/stream/output.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +constexpr size_t MaxVarInt64Size = (8 * sizeof(ui64) - 1) / 7 + 1; +constexpr size_t MaxVarUint64Size = (8 * sizeof(ui64) - 1) / 7 + 1; + +constexpr size_t MaxVarInt32Size = (8 * sizeof(ui32) - 1) / 7 + 1; +constexpr size_t MaxVarUint32Size = (8 * sizeof(ui32) - 1) / 7 + 1; + +// Various functions to read/write varints. + +// Returns the number of bytes written. +int WriteVarUint64(IOutputStream* output, ui64 value); +int WriteVarUint32(IOutputStream* output, ui32 value); +int WriteVarInt32(IOutputStream* output, i32 value); +int WriteVarInt64(IOutputStream* output, i64 value); + +int WriteVarUint64(char* output, ui64 value); +int WriteVarUint32(char* output, ui32 value); +int WriteVarInt32(char* output, i32 value); +int WriteVarInt64(char* output, i64 value); + +// Returns the number of bytes read. +int ReadVarUint64(IInputStream* input, ui64* value); +int ReadVarUint32(IInputStream* input, ui32* value); +int ReadVarInt32(IInputStream* input, i32* value); +int ReadVarInt64(IInputStream* input, i64* value); + +int ReadVarUint64(const char* input, ui64* value); +int ReadVarUint32(const char* input, ui32* value); +int ReadVarInt32(const char* input, i32* value); +int ReadVarInt64(const char* input, i64* value); + +// Throws exception if integer is not complete when `end' is reached. +int ReadVarUint64(const char* input, const char* end, ui64* value); +int ReadVarUint32(const char* input, const char* end, ui32* value); +int ReadVarInt32(const char* input, const char* end, i32* value); +int ReadVarInt64(const char* input, const char* end, i64* value); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define VARINT_INL_H_ +#include "varint-inl.h" +#undef VARINT_INL_H_ diff --git a/library/cpp/yt/coding/ya.make b/library/cpp/yt/coding/ya.make new file mode 100644 index 0000000000..3dae919e57 --- /dev/null +++ b/library/cpp/yt/coding/ya.make @@ -0,0 +1,12 @@ +LIBRARY() + +SRCS( +) + +PEERDIR( + library/cpp/yt/exception +) + +END() + +RECURSE_FOR_TESTS(unittests) diff --git a/library/cpp/yt/coding/zig_zag-inl.h b/library/cpp/yt/coding/zig_zag-inl.h new file mode 100644 index 0000000000..c611f7e1d4 --- /dev/null +++ b/library/cpp/yt/coding/zig_zag-inl.h @@ -0,0 +1,40 @@ +#ifndef ZIG_ZAG_INL_H_ +#error "Direct inclusion of this file is not allowed, include zig_zag.h" +// For the sake of sane code completion. +#include "zig_zag.h" +#endif +#undef ZIG_ZAG_INL_H_ + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +inline ui32 ZigZagEncode32(i32 n) +{ + // Note: the right-shift must be arithmetic. + // Note: left shift must be unsigned because of overflow. + return (static_cast<ui32>(n) << 1) ^ static_cast<ui32>(n >> 31); +} + +inline i32 ZigZagDecode32(ui32 n) +{ + // Note: using unsigned types prevent undefined behavior. + return static_cast<i32>((n >> 1) ^ (~(n & 1) + 1)); +} + +inline ui64 ZigZagEncode64(i64 n) +{ + // Note: the right-shift must be arithmetic. + // Note: left shift must be unsigned because of overflow. + return (static_cast<ui64>(n) << 1) ^ static_cast<ui64>(n >> 63); +} + +inline i64 ZigZagDecode64(ui64 n) +{ + // Note: using unsigned types prevent undefined behavior. + return static_cast<i64>((n >> 1) ^ (~(n & 1) + 1)); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/coding/zig_zag.h b/library/cpp/yt/coding/zig_zag.h new file mode 100644 index 0000000000..aa6d425a1c --- /dev/null +++ b/library/cpp/yt/coding/zig_zag.h @@ -0,0 +1,24 @@ +#pragma once + +#include <util/system/types.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +// These Functions provide coding of integers with property: 0 <= f(x) <= 2 * |x| +// Actually taken 'as is' from protobuf/wire_format_lite.h + +ui32 ZigZagEncode32(i32 n); +i32 ZigZagDecode32(ui32 n); + +ui64 ZigZagEncode64(i64 n); +i64 ZigZagDecode64(ui64 n); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define ZIG_ZAG_INL_H_ +#include "zig_zag-inl.h" +#undef ZIG_ZAG_INL_H_ diff --git a/library/cpp/yt/exception/exception.cpp b/library/cpp/yt/exception/exception.cpp new file mode 100644 index 0000000000..1059d497e8 --- /dev/null +++ b/library/cpp/yt/exception/exception.cpp @@ -0,0 +1,48 @@ +#include "exception.h" + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +TSimpleException::TSimpleException(TString message) + : Message_(std::move(message)) +{ } + +const TString& TSimpleException::GetMesage() const +{ + return Message_; +} + +const char* TSimpleException::what() const noexcept +{ + return Message_.c_str(); +} + +//////////////////////////////////////////////////////////////////////////////// + +TCompositeException::TCompositeException(TString message) + : TSimpleException(std::move(message)) + , What_(Message_) +{ } + +TCompositeException::TCompositeException( + const std::exception& exception, + TString message) + : TSimpleException(message) + , InnerException_(std::current_exception()) + , What_(message + "\n" + exception.what()) +{ } + +const std::exception_ptr& TCompositeException::GetInnerException() const +{ + return InnerException_; +} + +const char* TCompositeException::what() const noexcept +{ + return What_.c_str(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/exception/exception.h b/library/cpp/yt/exception/exception.h new file mode 100644 index 0000000000..7d35637d60 --- /dev/null +++ b/library/cpp/yt/exception/exception.h @@ -0,0 +1,45 @@ +#pragma once + +#include <util/generic/string.h> + +#include <exception> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// +// These are poor man's versions of NYT::TErrorException to be used in +// a limited subset of core libraries that are needed to implement NYT::TError. + +class TSimpleException + : public std::exception +{ +public: + explicit TSimpleException(TString message); + + const TString& GetMesage() const; + const char* what() const noexcept override; + +protected: + const TString Message_; +}; + +class TCompositeException + : public TSimpleException +{ +public: + explicit TCompositeException(TString message); + TCompositeException( + const std::exception& exception, + TString message); + + const std::exception_ptr& GetInnerException() const; + const char* what() const noexcept override; + +private: + const std::exception_ptr InnerException_; + const TString What_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/exception/ya.make b/library/cpp/yt/exception/ya.make new file mode 100644 index 0000000000..0c89c31dc3 --- /dev/null +++ b/library/cpp/yt/exception/ya.make @@ -0,0 +1,7 @@ +LIBRARY() + +SRCS( + exception.cpp +) + +END() diff --git a/library/cpp/yt/malloc/malloc.cpp b/library/cpp/yt/malloc/malloc.cpp new file mode 100644 index 0000000000..808afacdfb --- /dev/null +++ b/library/cpp/yt/malloc/malloc.cpp @@ -0,0 +1,19 @@ +#include "malloc.h" + +#include <util/system/compiler.h> + +//////////////////////////////////////////////////////////////////////////////// + +Y_WEAK extern "C" size_t nallocx(size_t size, int /* flags */) noexcept +{ + return size; +} + +#ifndef _win_ +Y_WEAK extern "C" size_t malloc_usable_size(void* /* ptr */) noexcept +{ + return 0; +} +#endif + +//////////////////////////////////////////////////////////////////////////////// diff --git a/library/cpp/yt/malloc/malloc.h b/library/cpp/yt/malloc/malloc.h new file mode 100644 index 0000000000..b3c16d7849 --- /dev/null +++ b/library/cpp/yt/malloc/malloc.h @@ -0,0 +1,8 @@ +#include <cstddef> + +//////////////////////////////////////////////////////////////////////////////// + +extern "C" size_t malloc_usable_size(void* ptr) noexcept; +extern "C" size_t nallocx(size_t size, int flags) noexcept; + +//////////////////////////////////////////////////////////////////////////////// diff --git a/library/cpp/yt/malloc/ya.make b/library/cpp/yt/malloc/ya.make new file mode 100644 index 0000000000..0a07d93b36 --- /dev/null +++ b/library/cpp/yt/malloc/ya.make @@ -0,0 +1,7 @@ +LIBRARY() + +SRCS( + malloc.cpp +) + +END() diff --git a/library/cpp/yt/memory/blob.cpp b/library/cpp/yt/memory/blob.cpp new file mode 100644 index 0000000000..86000b033b --- /dev/null +++ b/library/cpp/yt/memory/blob.cpp @@ -0,0 +1,224 @@ +#include "blob.h" +#include "ref.h" + +#include <library/cpp/ytalloc/api/ytalloc.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +static constexpr size_t InitialBlobCapacity = 16; +static constexpr double BlobCapacityMultiplier = 1.5; + +TBlob::TBlob( + TRefCountedTypeCookie tagCookie, + size_t size, + bool initiailizeStorage, + bool pageAligned) + : PageAligned_(pageAligned) +{ + SetTagCookie(tagCookie); + if (size == 0) { + Reset(); + } else { + Allocate(std::max(size, InitialBlobCapacity)); + Size_ = size; + if (initiailizeStorage) { + ::memset(Begin_, 0, Size_); + } + } +} + +TBlob::TBlob( + TRefCountedTypeCookie tagCookie, + TRef data, + bool pageAligned) + : PageAligned_(pageAligned) +{ + SetTagCookie(tagCookie); + Reset(); + Append(data); +} + +TBlob::TBlob(const TBlob& other) + : PageAligned_(other.PageAligned_) +{ + SetTagCookie(other); + if (other.Size_ == 0) { + Reset(); + } else { + Allocate(std::max(InitialBlobCapacity, other.Size_)); + ::memcpy(Begin_, other.Begin_, other.Size_); + Size_ = other.Size_; + } +} + +TBlob::TBlob(TBlob&& other) noexcept + : Begin_(other.Begin_) + , Size_(other.Size_) + , Capacity_(other.Capacity_) + , PageAligned_(other.PageAligned_) +{ + SetTagCookie(other); + other.Reset(); +} + +TBlob::~TBlob() +{ + Free(); +} + +void TBlob::Reserve(size_t newCapacity) +{ + if (newCapacity > Capacity_) { + Reallocate(newCapacity); + } +} + +void TBlob::Resize(size_t newSize, bool initializeStorage /*= true*/) +{ + if (newSize > Size_) { + if (newSize > Capacity_) { + size_t newCapacity; + if (Capacity_ == 0) { + newCapacity = std::max(InitialBlobCapacity, newSize); + } else { + newCapacity = std::max(static_cast<size_t>(Capacity_ * BlobCapacityMultiplier), newSize); + } + Reallocate(newCapacity); + } + if (initializeStorage) { + ::memset(Begin_ + Size_, 0, newSize - Size_); + } + } + Size_ = newSize; +} + +TBlob& TBlob::operator = (const TBlob& rhs) +{ + if (this != &rhs) { + this->~TBlob(); + new(this) TBlob(rhs); + } + return *this; +} + +TBlob& TBlob::operator = (TBlob&& rhs) noexcept +{ + if (this != &rhs) { + this->~TBlob(); + new(this) TBlob(std::move(rhs)); + } + return *this; +} + +void TBlob::Append(const void* data, size_t size) +{ + if (Size_ + size > Capacity_) { + Resize(Size_ + size, false); + ::memcpy(Begin_ + Size_ - size, data, size); + } else { + ::memcpy(Begin_ + Size_, data, size); + Size_ += size; + } +} + +void TBlob::Append(TRef ref) +{ + Append(ref.Begin(), ref.Size()); +} + +void TBlob::Append(char ch) +{ + if (Size_ + 1 > Capacity_) { + Resize(Size_ + 1, false); + Begin_[Size_ - 1] = ch; + } else { + Begin_[Size_++] = ch; + } +} + +void TBlob::Reset() +{ + Begin_ = nullptr; + Size_ = Capacity_ = 0; +} + +char* TBlob::DoAllocate(size_t size) +{ + return static_cast<char*>(PageAligned_ + ? NYTAlloc::AllocatePageAligned(size) + : NYTAlloc::Allocate(size)); +} + +void TBlob::Allocate(size_t newCapacity) +{ + YT_VERIFY(!Begin_); + Begin_ = DoAllocate(newCapacity); + Capacity_ = newCapacity; +#ifdef YT_ENABLE_REF_COUNTED_TRACKING + TRefCountedTrackerFacade::AllocateTagInstance(TagCookie_); + TRefCountedTrackerFacade::AllocateSpace(TagCookie_, newCapacity); +#endif +} + +void TBlob::Reallocate(size_t newCapacity) +{ + if (!Begin_) { + Allocate(newCapacity); + return; + } + char* newBegin = DoAllocate(newCapacity); + ::memcpy(newBegin, Begin_, Size_); + NYTAlloc::FreeNonNull(Begin_); +#ifdef YT_ENABLE_REF_COUNTED_TRACKING + TRefCountedTrackerFacade::AllocateSpace(TagCookie_, newCapacity); + TRefCountedTrackerFacade::FreeSpace(TagCookie_, Capacity_); +#endif + Begin_ = newBegin; + Capacity_ = newCapacity; +} + +void TBlob::Free() +{ + if (!Begin_) { + return; + } + NYTAlloc::FreeNonNull(Begin_); +#ifdef YT_ENABLE_REF_COUNTED_TRACKING + TRefCountedTrackerFacade::FreeTagInstance(TagCookie_); + TRefCountedTrackerFacade::FreeSpace(TagCookie_, Capacity_); +#endif + Reset(); +} + +void TBlob::SetTagCookie(TRefCountedTypeCookie tagCookie) +{ +#ifdef YT_ENABLE_REF_COUNTED_TRACKING + TagCookie_ = tagCookie; +#endif +} + +void TBlob::SetTagCookie(const TBlob& other) +{ +#ifdef YT_ENABLE_REF_COUNTED_TRACKING + TagCookie_ = other.TagCookie_; +#endif +} + +void swap(TBlob& left, TBlob& right) +{ + if (&left != &right) { + std::swap(left.Begin_, right.Begin_); + std::swap(left.Size_, right.Size_); + std::swap(left.Capacity_, right.Capacity_); + std::swap(left.PageAligned_, right.PageAligned_); +#ifdef YT_ENABLE_REF_COUNTED_TRACKING + std::swap(left.TagCookie_, right.TagCookie_); +#endif + } +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/memory/blob.h b/library/cpp/yt/memory/blob.h new file mode 100644 index 0000000000..99441fb8c9 --- /dev/null +++ b/library/cpp/yt/memory/blob.h @@ -0,0 +1,221 @@ +#pragma once + +#include "ref.h" +#include "ref_counted.h" + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +//! Default memory tag for TBlob. +struct TDefaultBlobTag +{ }; + +//! A home-grown optimized replacement for |std::vector<char>| suitable for carrying +//! large chunks of data. +/*! + * Compared to |std::vector<char>|, this class supports uninitialized allocations + * when explicitly requested to. + */ +class TBlob +{ +public: + //! Constructs a blob with a given size. + TBlob( + TRefCountedTypeCookie tagCookie, + size_t size, + bool initiailizeStorage = true, + bool pageAligned = false); + + //! Copies a chunk of memory into a new instance. + TBlob( + TRefCountedTypeCookie tagCookie, + TRef data, + bool pageAligned = false); + + //! Constructs an empty blob. + template <class TTag = TDefaultBlobTag> + explicit TBlob(TTag tag = {}) + : TBlob(tag, 0, true, false) + { } + + //! Constructs a blob with a given size. + template <class TTag> + explicit TBlob( + TTag, + size_t size, + bool initiailizeStorage = true, + bool pageAligned = false) + : TBlob( + GetRefCountedTypeCookie<TTag>(), + size, + initiailizeStorage, + pageAligned) + { } + + //! Copies a chunk of memory into a new instance. + template <class TTag> + TBlob( + TTag, + TRef data, + bool pageAligned = false) + : TBlob( + GetRefCountedTypeCookie<TTag>(), + data, + pageAligned) + { } + + //! Remind user about the tag argument. + TBlob(i32 size, bool initiailizeStorage = true) = delete; + TBlob(i64 size, bool initiailizeStorage = true) = delete; + TBlob(ui32 size, bool initiailizeStorage = true) = delete; + TBlob(ui64 size, bool initiailizeStorage = true) = delete; + template <typename T, typename U> + TBlob(const T*, U) = delete; + + //! Copies the data. + TBlob(const TBlob& other); + + //! Moves the data (takes the ownership). + TBlob(TBlob&& other) noexcept; + + //! Reclaims the memory. + ~TBlob(); + + //! Ensures that capacity is at least #capacity. + void Reserve(size_t newCapacity); + + //! Changes the size to #newSize. + /*! + * If #size exceeds the current capacity, + * we make sure the new capacity grows exponentially. + * Hence calling #Resize N times to increase the size by N only + * takes amortized O(1) time per call. + */ + void Resize(size_t newSize, bool initializeStorage = true); + + //! Returns the start pointer. + Y_FORCE_INLINE const char* Begin() const + { + return Begin_; + } + + //! Returns the start pointer. + Y_FORCE_INLINE char* Begin() + { + return Begin_; + } + + //! Returns the end pointer. + Y_FORCE_INLINE const char* End() const + { + return Begin_ + Size_; + } + + //! Returns the end pointer. + Y_FORCE_INLINE char* End() + { + return Begin_ + Size_; + } + + //! Returns the size. + Y_FORCE_INLINE size_t size() const + { + return Size_; + } + + //! Returns the size. + Y_FORCE_INLINE size_t Size() const + { + return Size_; + } + + //! Returns the capacity. + Y_FORCE_INLINE size_t Capacity() const + { + return Capacity_; + } + + //! Returns the TStringBuf instance for the occupied part of the blob. + Y_FORCE_INLINE TStringBuf ToStringBuf() const + { + return TStringBuf(Begin_, Size_); + } + + //! Returns the TRef instance for the occupied part of the blob. + Y_FORCE_INLINE TRef ToRef() const + { + return TRef(Begin_, Size_); + } + + //! Provides by-value access to the underlying storage. + Y_FORCE_INLINE char operator [] (size_t index) const + { + return Begin_[index]; + } + + //! Provides by-ref access to the underlying storage. + Y_FORCE_INLINE char& operator [] (size_t index) + { + return Begin_[index]; + } + + //! Clears the instance but does not reclaim the memory. + Y_FORCE_INLINE void Clear() + { + Size_ = 0; + } + + //! Returns |true| if size is zero. + Y_FORCE_INLINE bool IsEmpty() const + { + return Size_ == 0; + } + + //! Overwrites the current instance. + TBlob& operator = (const TBlob& rhs); + + //! Takes the ownership. + TBlob& operator = (TBlob&& rhs) noexcept; + + //! Appends a chunk of memory to the end. + void Append(const void* data, size_t size); + + //! Appends a chunk of memory to the end. + void Append(TRef ref); + + //! Appends a single char to the end. + void Append(char ch); + + //! Swaps the current and other instances + void Swap(TBlob& other); + + friend void swap(TBlob& left, TBlob& right); + +private: + char* Begin_ = nullptr; + size_t Size_ = 0; + size_t Capacity_ = 0; + bool PageAligned_ = false; + +#ifdef YT_ENABLE_REF_COUNTED_TRACKING + TRefCountedTypeCookie TagCookie_ = NullRefCountedTypeCookie; +#endif + + char* DoAllocate(size_t newCapacity); + void Allocate(size_t newCapacity); + void Reallocate(size_t newCapacity); + void Free(); + + void Reset(); + + void SetTagCookie(TRefCountedTypeCookie tagCookie); + void SetTagCookie(const TBlob& other); +}; + +void swap(TBlob& left, TBlob& right); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + diff --git a/library/cpp/yt/memory/intrusive_ptr.h b/library/cpp/yt/memory/intrusive_ptr.h new file mode 100644 index 0000000000..3dead7db1d --- /dev/null +++ b/library/cpp/yt/memory/intrusive_ptr.h @@ -0,0 +1,360 @@ +#pragma once + +#include "ref_counted.h" + +#include <util/generic/hash.h> +#include <util/generic/utility.h> + +#include <utility> +#include <type_traits> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +class TIntrusivePtr +{ +public: + typedef T TUnderlying; + + constexpr TIntrusivePtr() noexcept + { } + + constexpr TIntrusivePtr(std::nullptr_t) noexcept + { } + + //! Constructor from an unqualified reference. + /*! + * Note that this constructor could be racy due to unsynchronized operations + * on the object and on the counter. + * + * Note that it notoriously hard to make this constructor explicit + * given the current amount of code written. + */ + TIntrusivePtr(T* obj, bool addReference = true) noexcept + : T_(obj) + { + if (T_ && addReference) { + Ref(T_); + } + } + + //! Copy constructor. + TIntrusivePtr(const TIntrusivePtr& other) noexcept + : T_(other.Get()) + { + if (T_) { + Ref(T_); + } + } + + //! Copy constructor with an upcast. + template <class U, class = typename std::enable_if_t<std::is_convertible_v<U*, T*>>> + TIntrusivePtr(const TIntrusivePtr<U>& other) noexcept + : T_(other.Get()) + { + static_assert( + std::is_base_of_v<TRefCountedBase, T>, + "Cast allowed only for types derived from TRefCountedBase"); + if (T_) { + Ref(T_); + } + } + + //! Move constructor. + TIntrusivePtr(TIntrusivePtr&& other) noexcept + : T_(other.Get()) + { + other.T_ = nullptr; + } + + //! Move constructor with an upcast. + template <class U, class = typename std::enable_if_t<std::is_convertible_v<U*, T*>>> + TIntrusivePtr(TIntrusivePtr<U>&& other) noexcept + : T_(other.Get()) + { + static_assert( + std::is_base_of_v<TRefCountedBase, T>, + "Cast allowed only for types derived from TRefCountedBase"); + other.T_ = nullptr; + } + + //! Destructor. + ~TIntrusivePtr() + { + if (T_) { + Unref(T_); + } + } + + //! Copy assignment operator. + TIntrusivePtr& operator=(const TIntrusivePtr& other) noexcept + { + TIntrusivePtr(other).Swap(*this); + return *this; + } + + //! Copy assignment operator with an upcast. + template <class U> + TIntrusivePtr& operator=(const TIntrusivePtr<U>& other) noexcept + { + static_assert( + std::is_convertible_v<U*, T*>, + "U* must be convertible to T*"); + static_assert( + std::is_base_of_v<TRefCountedBase, T>, + "Cast allowed only for types derived from TRefCountedBase"); + TIntrusivePtr(other).Swap(*this); + return *this; + } + + //! Move assignment operator. + TIntrusivePtr& operator=(TIntrusivePtr&& other) noexcept + { + TIntrusivePtr(std::move(other)).Swap(*this); + return *this; + } + + //! Move assignment operator with an upcast. + template <class U> + TIntrusivePtr& operator=(TIntrusivePtr<U>&& other) noexcept + { + static_assert( + std::is_convertible_v<U*, T*>, + "U* must be convertible to T*"); + static_assert( + std::is_base_of_v<TRefCountedBase, T>, + "Cast allowed only for types derived from TRefCountedBase"); + TIntrusivePtr(std::move(other)).Swap(*this); + return *this; + } + + //! Drop the pointer. + void Reset() // noexcept + { + TIntrusivePtr().Swap(*this); + } + + //! Replace the pointer with a specified one. + void Reset(T* p) // noexcept + { + TIntrusivePtr(p).Swap(*this); + } + + //! Returns the pointer. + T* Get() const noexcept + { + return T_; + } + + //! Returns the pointer and releases the ownership. + T* Release() noexcept + { + auto* p = T_; + T_ = nullptr; + return p; + } + + T& operator*() const noexcept + { + YT_ASSERT(T_); + return *T_; + } + + T* operator->() const noexcept + { + YT_ASSERT(T_); + return T_; + } + + explicit operator bool() const noexcept + { + return T_ != nullptr; + } + + //! Swap the pointer with the other one. + void Swap(TIntrusivePtr& r) noexcept + { + DoSwap(T_, r.T_); + } + +private: + template <class U> + friend class TIntrusivePtr; + + T* T_ = nullptr; +}; + +//////////////////////////////////////////////////////////////////////////////// + +//! Creates a strong pointer wrapper for a given raw pointer. +//! Compared to |TIntrusivePtr<T>::ctor|, type inference enables omitting |T|. +template <class T> +TIntrusivePtr<T> MakeStrong(T* p) +{ + return TIntrusivePtr<T>(p); +} + +//! Tries to obtain an intrusive pointer for an object that may had +//! already lost all of its references and, thus, is about to be deleted. +/*! + * You may call this method at any time provided that you have a valid + * raw pointer to an object. The call either returns an intrusive pointer + * for the object (thus ensuring that the object won't be destroyed until + * you're holding this pointer) or NULL indicating that the last reference + * had already been lost and the object is on its way to heavens. + * All these steps happen atomically. + * + * Under all circumstances it is caller's responsibility the make sure that + * the object is not destroyed during the call to #DangerousGetPtr. + * Typically this is achieved by keeping a (lock-protected) collection of + * raw pointers, taking a lock in object's destructor, and unregistering + * its raw pointer from the collection there. + */ + +template <class T> +Y_FORCE_INLINE TIntrusivePtr<T> DangerousGetPtr(T* object) +{ + return object->TryRef() + ? TIntrusivePtr<T>(object, false) + : TIntrusivePtr<T>(); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class T, class U> +TIntrusivePtr<T> StaticPointerCast(const TIntrusivePtr<U>& ptr) +{ + return {static_cast<T*>(ptr.Get())}; +} + +template <class T, class U> +TIntrusivePtr<T> StaticPointerCast(TIntrusivePtr<U>&& ptr) +{ + return {static_cast<T*>(ptr.Release()), false}; +} + +template <class T, class U> +TIntrusivePtr<T> ConstPointerCast(const TIntrusivePtr<U>& ptr) +{ + return {const_cast<T*>(ptr.Get())}; +} + +template <class T, class U> +TIntrusivePtr<T> ConstPointerCast(TIntrusivePtr<U>&& ptr) +{ + return {const_cast<T*>(ptr.Release()), false}; +} + +template <class T, class U> +TIntrusivePtr<T> DynamicPointerCast(const TIntrusivePtr<U>& ptr) +{ + return {dynamic_cast<T*>(ptr.Get())}; +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +bool operator<(const TIntrusivePtr<T>& lhs, const TIntrusivePtr<T>& rhs) +{ + return lhs.Get() < rhs.Get(); +} + +template <class T> +bool operator>(const TIntrusivePtr<T>& lhs, const TIntrusivePtr<T>& rhs) +{ + return lhs.Get() > rhs.Get(); +} + +template <class T, class U> +bool operator==(const TIntrusivePtr<T>& lhs, const TIntrusivePtr<U>& rhs) +{ + static_assert( + std::is_convertible_v<U*, T*>, + "U* must be convertible to T*"); + return lhs.Get() == rhs.Get(); +} + +template <class T, class U> +bool operator!=(const TIntrusivePtr<T>& lhs, const TIntrusivePtr<U>& rhs) +{ + static_assert( + std::is_convertible_v<U*, T*>, + "U* must be convertible to T*"); + return lhs.Get() != rhs.Get(); +} + +template <class T, class U> +bool operator==(const TIntrusivePtr<T>& lhs, U* rhs) +{ + static_assert( + std::is_convertible_v<U*, T*>, + "U* must be convertible to T*"); + return lhs.Get() == rhs; +} + +template <class T, class U> +bool operator!=(const TIntrusivePtr<T>& lhs, U* rhs) +{ + static_assert( + std::is_convertible_v<U*, T*>, + "U* must be convertible to T*"); + return lhs.Get() != rhs; +} + +template <class T, class U> +bool operator==(T* lhs, const TIntrusivePtr<U>& rhs) +{ + static_assert( + std::is_convertible_v<U*, T*>, + "U* must be convertible to T*"); + return lhs == rhs.Get(); +} + +template <class T, class U> +bool operator!=(T* lhs, const TIntrusivePtr<U>& rhs) +{ + static_assert( + std::is_convertible_v<U*, T*>, + "U* must be convertible to T*"); + return lhs != rhs.Get(); +} + +template <class T> +bool operator==(std::nullptr_t, const TIntrusivePtr<T>& rhs) +{ + return nullptr == rhs.Get(); +} + +template <class T> +bool operator!=(std::nullptr_t, const TIntrusivePtr<T>& rhs) +{ + return nullptr != rhs.Get(); +} + +template <class T> +bool operator==(const TIntrusivePtr<T>& lhs, std::nullptr_t) +{ + return nullptr == lhs.Get(); +} + +template <class T> +bool operator!=(const TIntrusivePtr<T>& lhs, std::nullptr_t) +{ + return nullptr != lhs.Get(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} //namespace NYT + +//! A hasher for TIntrusivePtr. +template <class T> +struct THash<NYT::TIntrusivePtr<T>> +{ + Y_FORCE_INLINE size_t operator () (const NYT::TIntrusivePtr<T>& ptr) const + { + return THash<T*>()(ptr.Get()); + } +}; diff --git a/library/cpp/yt/memory/leaky_ref_counted_singleton-inl.h b/library/cpp/yt/memory/leaky_ref_counted_singleton-inl.h new file mode 100644 index 0000000000..a68ec5ed6a --- /dev/null +++ b/library/cpp/yt/memory/leaky_ref_counted_singleton-inl.h @@ -0,0 +1,43 @@ +#ifndef LEAKY_REF_COUNTED_SINGLETON_INL_H_ +#error "Direct inclusion of this file is not allowed, include leaky_ref_counted_singleton.h" +// For the sake of sane code completion. +#include "leaky_ref_counted_singleton.h" +#endif + +#include "new.h" + +#include <atomic> +#include <mutex> + +#include <util/system/compiler.h> +#include <util/system/sanitizers.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +TIntrusivePtr<T> LeakyRefCountedSingleton() +{ + static std::atomic<T*> Ptr; + auto* ptr = Ptr.load(std::memory_order_acquire); + if (Y_LIKELY(ptr)) { + return ptr; + } + + static std::once_flag Initialized; + std::call_once(Initialized, [] { + auto ptr = New<T>(); + Ref(ptr.Get()); + Ptr.store(ptr.Get()); +#if defined(_asan_enabled_) + NSan::MarkAsIntentionallyLeaked(ptr.Get()); +#endif + }); + + return Ptr.load(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/memory/leaky_ref_counted_singleton.h b/library/cpp/yt/memory/leaky_ref_counted_singleton.h new file mode 100644 index 0000000000..1d5761bd9d --- /dev/null +++ b/library/cpp/yt/memory/leaky_ref_counted_singleton.h @@ -0,0 +1,18 @@ +#pragma once + +#include "intrusive_ptr.h" + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +TIntrusivePtr<T> LeakyRefCountedSingleton(); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define LEAKY_REF_COUNTED_SINGLETON_INL_H_ +#include "leaky_ref_counted_singleton-inl.h" +#undef LEAKY_REF_COUNTED_SINGLETON_INL_H_ diff --git a/library/cpp/yt/memory/leaky_singleton-inl.h b/library/cpp/yt/memory/leaky_singleton-inl.h new file mode 100644 index 0000000000..932747c921 --- /dev/null +++ b/library/cpp/yt/memory/leaky_singleton-inl.h @@ -0,0 +1,34 @@ +#ifndef LEAKY_SINGLETON_INL_H_ +#error "Direct inclusion of this file is not allowed, include leaky_singleton.h" +// For the sake of sane code completion. +#include "leaky_singleton.h" +#endif + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +TLeakyStorage<T>::TLeakyStorage() +{ + new (Get()) T(); +} + +template <class T> +T* TLeakyStorage<T>::Get() +{ + return reinterpret_cast<T*>(Buffer_); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +T* LeakySingleton() +{ + static TLeakyStorage<T> Storage; + return Storage.Get(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/memory/leaky_singleton.h b/library/cpp/yt/memory/leaky_singleton.h new file mode 100644 index 0000000000..03b5e51d78 --- /dev/null +++ b/library/cpp/yt/memory/leaky_singleton.h @@ -0,0 +1,34 @@ +#pragma once + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +class TLeakyStorage +{ +public: + TLeakyStorage(); + + T* Get(); + +private: + alignas(T) char Buffer_[sizeof(T)]; +}; + +//////////////////////////////////////////////////////////////////////////////// + +#define DECLARE_LEAKY_SINGLETON_FRIEND() \ + template <class T> \ + friend class ::NYT::TLeakyStorage; + +template <class T> +T* LeakySingleton(); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define LEAKY_SINGLETON_INL_H_ +#include "leaky_singleton-inl.h" +#undef LEAKY_SINGLETON_INL_H_ diff --git a/library/cpp/yt/memory/new-inl.h b/library/cpp/yt/memory/new-inl.h new file mode 100644 index 0000000000..0a84818516 --- /dev/null +++ b/library/cpp/yt/memory/new-inl.h @@ -0,0 +1,310 @@ +#ifndef NEW_INL_H_ +#error "Direct inclusion of this file is not allowed, include new.h" +// For the sake of sane code completion. +#include "new.h" +#endif + +#include <library/cpp/ytalloc/api/ytalloc.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +struct TRefCountedCookieHolder +{ +#ifdef YT_ENABLE_REF_COUNTED_TRACKING + TRefCountedTypeCookie Cookie = NullRefCountedTypeCookie; + + void InitializeTracking(TRefCountedTypeCookie cookie) + { + YT_ASSERT(Cookie == NullRefCountedTypeCookie); + Cookie = cookie; + TRefCountedTrackerFacade::AllocateInstance(Cookie); + } + + ~TRefCountedCookieHolder() + { + if (Cookie != NullRefCountedTypeCookie) { + TRefCountedTrackerFacade::FreeInstance(Cookie); + } + } +#endif +}; + +template <class T> +struct TRefCountedWrapper final + : public T + , public TRefTracked<T> +{ + template <class... TArgs> + explicit TRefCountedWrapper(TArgs&&... args) + : T(std::forward<TArgs>(args)...) + { } + + ~TRefCountedWrapper() = default; + + void DestroyRefCounted() override + { + T::DestroyRefCountedImpl(this); + } +}; + +template <class T, class TDeleter> +class TRefCountedWrapperWithDeleter final + : public T + , public TRefTracked<T> +{ +public: + template <class... TArgs> + explicit TRefCountedWrapperWithDeleter(const TDeleter& deleter, TArgs&&... args) + : T(std::forward<TArgs>(args)...) + , Deleter_(deleter) + { } + + ~TRefCountedWrapperWithDeleter() = default; + + void DestroyRefCounted() override + { + Deleter_(this); + } + +private: + TDeleter Deleter_; +}; + +template <class T> +struct TRefCountedWrapperWithCookie final + : public T + , public TRefCountedCookieHolder +{ + template <class... TArgs> + explicit TRefCountedWrapperWithCookie(TArgs&&... args) + : T(std::forward<TArgs>(args)...) + { } + + ~TRefCountedWrapperWithCookie() = default; + + void DestroyRefCounted() override + { + T::DestroyRefCountedImpl(this); + } +}; + +namespace NDetail { + +Y_FORCE_INLINE void* AllignedMalloc(size_t size, size_t allignment) +{ +#ifdef _win_ + return ::_aligned_malloc(size, allignment); +#else + void* ptr = nullptr; + ::posix_memalign(&ptr, allignment, size); + return ptr; +#endif +} + +template <class... Args> +Y_FORCE_INLINE void CustomInitialize(Args... args) +{ + Y_UNUSED(args...); +} + +template <class T> +Y_FORCE_INLINE auto CustomInitialize(T* ptr) -> decltype(&T::InitializeRefCounted, void()) +{ + ptr->InitializeRefCounted(); +} + +template <class T, class... As> +Y_FORCE_INLINE T* NewEpilogue(void* ptr, As&& ... args) +{ + try { + auto* instance = static_cast<T*>(ptr); + new (instance) T(std::forward<As>(args)...); + CustomInitialize(instance); + return instance; + } catch (const std::exception& ex) { + // Do not forget to free the memory. + TFreeMemory<T>::Do(ptr); + throw; + } +} + +template <class T, bool = std::is_base_of_v<TRefCountedBase, T>> +struct TConstructHelper +{ + static constexpr size_t RefCounterSpace = (sizeof(TRefCounter) + alignof(T) - 1) & ~(alignof(T) - 1); + static constexpr size_t RefCounterOffset = RefCounterSpace - sizeof(TRefCounter); + static constexpr size_t Size = RefCounterSpace + sizeof(T); + static constexpr size_t Alignment = alignof(T); + + template <class... As> + Y_FORCE_INLINE static T* Construct(void* ptr, As&&... args) + { + auto* refCounter = reinterpret_cast<TRefCounter*>(static_cast<char*>(ptr) + RefCounterOffset); + new (refCounter) TRefCounter(); + auto* object = reinterpret_cast<T*>(refCounter + 1); + if constexpr (std::is_constructible_v<T, As...>) { + new(object) T(std::forward<As>(args)...); + } else { + new(object) T{std::forward<As>(args)...}; + } + CustomInitialize(object); + return object; + } +}; + +template <class T> +struct TConstructHelper<T, true> +{ + static constexpr size_t Size = sizeof(TRefCountedWrapper<T>); + static constexpr size_t Alignment = alignof(TRefCountedWrapper<T>); + + template <class... As> + Y_FORCE_INLINE static TRefCountedWrapper<T>* Construct(void* ptr, As&&... args) + { + using TDerived = TRefCountedWrapper<T>; + auto* object = new(static_cast<TDerived*>(ptr)) TDerived(std::forward<As>(args)...); + CustomInitialize(object); + return object; + } +}; + +template <class T, class... As> +Y_FORCE_INLINE TIntrusivePtr<T> SafeConstruct(void* ptr, As&&... args) +{ + try { + auto* instance = TConstructHelper<T>::Construct(ptr, std::forward<As>(args)...); + return TIntrusivePtr<T>(instance, false); + } catch (const std::exception& ex) { + // Do not forget to free the memory. + TFreeMemory<T>::Do(ptr); + throw; + } +} + +template <size_t Size, size_t Alignment> +void* AllocateConstSizeAligned() +{ + if (Alignment <= 16) { + return NYTAlloc::AllocateConstSize<Size>(); + } else { + return AllignedMalloc(Size, Alignment); + } +} + +} // namespace NDetail + +//////////////////////////////////////////////////////////////////////////////// + +template <class T, class... As, class> +Y_FORCE_INLINE TIntrusivePtr<T> New( + As&&... args) +{ + void* ptr = NDetail::AllocateConstSizeAligned< + NDetail::TConstructHelper<T>::Size, + NDetail::TConstructHelper<T>::Alignment>(); + + return NDetail::SafeConstruct<T>(ptr, std::forward<As>(args)...); +} + +template <class T, class... As, class> +Y_FORCE_INLINE TIntrusivePtr<T> New( + typename T::TAllocator* allocator, + As&&... args) +{ + auto* ptr = allocator->Allocate(NDetail::TConstructHelper<T>::Size); + if (!ptr) { + return nullptr; + } + return NDetail::SafeConstruct<T>(ptr, std::forward<As>(args)...); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class T, class... As, class> +Y_FORCE_INLINE TIntrusivePtr<T> NewWithExtraSpace( + size_t extraSpaceSize, + As&&... args) +{ + auto totalSize = NYT::NDetail::TConstructHelper<T>::Size + extraSpaceSize; + void* ptr = nullptr; + + if (NYT::NDetail::TConstructHelper<T>::Alignment <= 16) { + ptr = NYTAlloc::Allocate(totalSize); + } else { + ptr = NYT::NDetail::AllignedMalloc(totalSize, NYT::NDetail::TConstructHelper<T>::Alignment); + } + + return NYT::NDetail::SafeConstruct<T>(ptr, std::forward<As>(args)...); +} + +template <class T, class... As, class> +Y_FORCE_INLINE TIntrusivePtr<T> NewWithExtraSpace( + typename T::TAllocator* allocator, + size_t extraSpaceSize, + As&&... args) +{ + auto totalSize = NYT::NDetail::TConstructHelper<T>::Size + extraSpaceSize; + auto* ptr = allocator->Allocate(totalSize); + if (!ptr) { + return nullptr; + } + return NYT::NDetail::SafeConstruct<T>(ptr, std::forward<As>(args)...); +} + +//////////////////////////////////////////////////////////////////////////////// + +// Support for polymorphic only +template <class T, class TDeleter, class... As> +Y_FORCE_INLINE TIntrusivePtr<T> NewWithDelete(const TDeleter& deleter, As&&... args) +{ + using TWrapper = TRefCountedWrapperWithDeleter<T, TDeleter>; + void* ptr = NDetail::AllocateConstSizeAligned<sizeof(TWrapper), alignof(TWrapper)>(); + + auto* instance = NDetail::NewEpilogue<TWrapper>( + ptr, + deleter, + std::forward<As>(args)...); + + return TIntrusivePtr<T>(instance, false); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class T, class TTag, int Counter, class... As> +Y_FORCE_INLINE TIntrusivePtr<T> NewWithLocation( + const TSourceLocation& location, + As&&... args) +{ + using TWrapper = TRefCountedWrapperWithCookie<T>; + void* ptr = NDetail::AllocateConstSizeAligned<sizeof(TWrapper), alignof(TWrapper)>(); + + auto* instance = NDetail::NewEpilogue<TWrapper>(ptr, std::forward<As>(args)...); + +#ifdef YT_ENABLE_REF_COUNTED_TRACKING + instance->InitializeTracking(GetRefCountedTypeCookieWithLocation<T, TTag, Counter>(location)); +#else + Y_UNUSED(location); +#endif + + return TIntrusivePtr<T>(instance, false); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +const void* TWithExtraSpace<T>::GetExtraSpacePtr() const +{ + return static_cast<const T*>(this) + 1; +} + +template <class T> +void* TWithExtraSpace<T>::GetExtraSpacePtr() +{ + return static_cast<T*>(this) + 1; +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/memory/new.h b/library/cpp/yt/memory/new.h new file mode 100644 index 0000000000..2db45e0465 --- /dev/null +++ b/library/cpp/yt/memory/new.h @@ -0,0 +1,127 @@ +#pragma once + +#include "intrusive_ptr.h" +#include "ref_tracked.h" + +#include <library/cpp/yt/misc/source_location.h> + +#include <util/system/defaults.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +/*! + * \defgroup yt_new New<T> safe smart pointer constructors + * \ingroup yt_new + * + * This is collection of safe smart pointer constructors. + * + * \page yt_new_rationale Rationale + * New<T> function family was designed to prevent the following problem. + * Consider the following piece of code. + * + * \code + * class TFoo + * : public virtual TRefCounted + * { + * public: + * TFoo(); + * }; + * + * typedef TIntrusivePtr<TFoo> TFooPtr; + * + * void RegisterObject(TFooPtr foo) + * { + * ... + * } + * + * TFoo::TFoo() + * { + * // ... do something before + * RegisterObject(this); + * // ... do something after + * } + * \endcode + * + * What will happen on <tt>new TFoo()</tt> construction? After memory allocation + * the reference counter for newly created instance would be initialized to zero. + * Afterwards, the control goes to TFoo constructor. To invoke + * <tt>RegisterObject</tt> a new temporary smart pointer to the current instance + * have to be created effectively incrementing the reference counter (now one). + * After <tt>RegisterObject</tt> returns the control to the constructor + * the temporary pointer is destroyed effectively decrementing the reference + * counter to zero hence triggering object destruction during its initialization. + * + * To avoid this undefined behavior <tt>New<T></tt> was introduced. + * <tt>New<T></tt> holds a fake + * reference to the object during its construction effectively preventing + * premature destruction. + * + * \note An initialization like <tt>TIntrusivePtr<T> p = new T()</tt> + * would result in a dangling reference due to internals of #New<T> and + * #TRefCountedBase. + */ + +//////////////////////////////////////////////////////////////////////////////// + +template <class T, class = void> +struct THasAllocator +{ + using TFalse = void; +}; + +template <class T> +struct THasAllocator<T, std::void_t<typename T::TAllocator>> +{ + using TTrue = void; +}; + +//////////////////////////////////////////////////////////////////////////////// + +//! Allocates a new instance of |T|. +template <class T, class... As, class = typename THasAllocator<T>::TFalse> +TIntrusivePtr<T> New(As&&... args); + +template <class T, class... As, class = typename THasAllocator<T>::TTrue> +TIntrusivePtr<T> New(typename T::TAllocator* allocator, As&&... args); + +//! Allocates an instance of |T| with additional storage of #extraSpaceSize bytes. +template <class T, class... As, class = typename THasAllocator<T>::TFalse> +TIntrusivePtr<T> NewWithExtraSpace(size_t extraSpaceSize, As&&... args); + +template <class T, class... As, class = typename THasAllocator<T>::TTrue> +TIntrusivePtr<T> NewWithExtraSpace(typename T::TAllocator* allocator, size_t extraSpaceSize, As&&... args); + +//! Allocates a new instance of |T| with user deleter. +template <class T, class TDeleter, class... As> +TIntrusivePtr<T> NewWithDelete(const TDeleter& deleter, As&&... args); + +//! Allocates a new instance of |T|. +//! The allocation is additionally marked with #location. +template <class T, class TTag, int Counter, class... As> +TIntrusivePtr<T> NewWithLocation(const TSourceLocation& location, As&&... args); + +//! Enables calling #New and co for types with private ctors. +#define DECLARE_NEW_FRIEND() \ + template <class DECLARE_NEW_FRIEND_T> \ + friend struct NYT::TRefCountedWrapper; + +//////////////////////////////////////////////////////////////////////////////// + +//! CRTP mixin enabling access to instance's extra space. +template <class T> +class TWithExtraSpace +{ +protected: + const void* GetExtraSpacePtr() const; + void* GetExtraSpacePtr(); +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define NEW_INL_H_ +#include "new-inl.h" +#undef NEW_INL_H_ diff --git a/library/cpp/yt/memory/range.h b/library/cpp/yt/memory/range.h new file mode 100644 index 0000000000..6c71aa9496 --- /dev/null +++ b/library/cpp/yt/memory/range.h @@ -0,0 +1,556 @@ +#pragma once + +#include <library/cpp/yt/assert/assert.h> + +#include <library/cpp/yt/misc/hash.h> + +#include <vector> +#include <array> +#include <optional> +#include <initializer_list> + +// For size_t. +#include <stddef.h> + +namespace google::protobuf { + +//////////////////////////////////////////////////////////////////////////////// +// Forward declarations + +template <class T> +class RepeatedField; + +template <class T> +class RepeatedPtrField; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace google::protobuf + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// +// Forward declarations + +template <class T, size_t N> +class TCompactVector; + +//////////////////////////////////////////////////////////////////////////////// + +//! TRange (inspired by TArrayRef from LLVM) +/*! + * Represents a constant reference to an array (zero or more elements + * consecutively in memory), i. e. a start pointer and a length. It allows + * various APIs to take consecutive elements easily and conveniently. + * + * This class does not own the underlying data, it is expected to be used in + * situations where the data resides in some other buffer, whose lifetime + * extends past that of the TRange. For this reason, it is not in general + * safe to store an TRange. + * + * This is intended to be trivially copyable, so it should be passed by + * value. + */ +template <class T> +class TRange +{ +public: + typedef const T* iterator; + typedef const T* const_iterator; + typedef size_t size_type; + + //! Constructs a null TRange. + TRange() + : Data_(nullptr) + , Length_(0) + { } + + //! Constructs a TRange from a pointer and length. + TRange(const T* data, size_t length) + : Data_(data) + , Length_(length) + { } + + //! Constructs a TRange from a range. + TRange(const T* begin, const T* end) + : Data_(begin) + , Length_(end - begin) + { } + + //! Constructs a TRange from a TCompactVector. + template <size_t N> + TRange(const TCompactVector<T, N>& elements) + : Data_(elements.data()) + , Length_(elements.size()) + { } + + //! Constructs a TRange from an std::vector. + template <class A> + TRange(const std::vector<T, A>& elements) + : Data_(elements.empty() ? nullptr : elements.data()) + , Length_(elements.size()) + { } + + //! Constructs a TRange from a C array. + template <size_t N> + TRange(const T (&elements)[N]) + : Data_(elements) + , Length_(N) + { } + + //! Constructs a TRange from std::initializer_list. + TRange(std::initializer_list<T> elements) + : Data_(elements.begin()) + , Length_(elements.size()) + { } + + //! Constructs a TRange from std::array. + template <size_t N> + TRange(const std::array<T, N>& elements) + : Data_(elements.data()) + , Length_(N) + { } + + //! Constructs a TRange from std::optional. + //! Range will contain 0-1 elements. + explicit TRange(const std::optional<T>& element) + : Data_(element ? &*element : nullptr) + , Length_(element ? 1 : 0) + { } + + const_iterator Begin() const + { + return Data_; + } + + // STL interop, for gcc. + const_iterator begin() const + { + return Begin(); + } + + const_iterator End() const + { + return Data_ + Length_; + } + + // STL interop, for gcc. + const_iterator end() const + { + return End(); + } + + bool Empty() const + { + return Length_ == 0; + } + + bool empty() const + { + return Empty(); + } + + explicit operator bool() const + { + return Data_ != nullptr; + } + + size_t Size() const + { + return Length_; + } + + size_t size() const + { + return Size(); + } + + const T& operator[](size_t index) const + { + YT_ASSERT(index < Size()); + return Data_[index]; + } + + + const T& Front() const + { + YT_ASSERT(Length_ > 0); + return Data_[0]; + } + + const T& Back() const + { + YT_ASSERT(Length_ > 0); + return Data_[Length_ - 1]; + } + + + TRange<T> Slice(size_t startOffset, size_t endOffset) const + { + YT_ASSERT(startOffset <= endOffset && endOffset <= Size()); + return TRange<T>(Begin() + startOffset, endOffset - startOffset); + } + + std::vector<T> ToVector() const + { + return std::vector<T>(Data_, Data_ + Length_); + } + +protected: + //! The start of the array, in an external buffer. + const T* Data_; + + //! The number of elements. + size_t Length_; + +}; + +// STL interop. +template <class T> +typename TRange<T>::const_iterator begin(TRange<T> ref) +{ + return ref.Begin(); +} + +template <class T> +typename TRange<T>::const_iterator end(TRange<T> ref) +{ + return ref.End(); +} + +//////////////////////////////////////////////////////////////////////////////// + +//! Constructs a TRange from a pointer and length. +template <class T> +TRange<T> MakeRange(const T* data, size_t length) +{ + return TRange<T>(data, length); +} + +//! Constructs a TRange from a native range. +template <class T> +TRange<T> MakeRange(const T* begin, const T* end) +{ + return TRange<T>(begin, end); +} + +//! Constructs a TRange from a TCompactVector. +template <class T, size_t N> +TRange<T> MakeRange(const TCompactVector<T, N>& elements) +{ + return elements; +} + +//! "Copy-constructor". +template <class T> +TRange<T> MakeRange(TRange<T> range) +{ + return range; +} + +//! Constructs a TRange from an std::vector. +template <class T> +TRange<T> MakeRange(const std::vector<T>& elements) +{ + return elements; +} + +//! Constructs a TRange from an std::array. +template <class T, size_t N> +TRange<T> MakeRange(const std::array<T, N>& elements) +{ + return elements; +} + +//! Constructs a TRange from a C array. +template <class T, size_t N> +TRange<T> MakeRange(const T (& elements)[N]) +{ + return TRange<T>(elements); +} + +//! Constructs a TRange from RepeatedField. +template <class T> +TRange<T> MakeRange(const google::protobuf::RepeatedField<T>& elements) +{ + return TRange<T>(elements.data(), elements.size()); +} + +//! Constructs a TRange from RepeatedPtrField. +template <class T> +TRange<const T*> MakeRange(const google::protobuf::RepeatedPtrField<T>& elements) +{ + return TRange<const T*>(elements.data(), elements.size()); +} + +template <class U, class T> +TRange<U> ReinterpretCastRange(TRange<T> range) +{ + static_assert(sizeof(T) == sizeof(U), "T and U must have equal sizes."); + return TRange<U>(reinterpret_cast<const U*>(range.Begin()), range.Size()); +}; + +//////////////////////////////////////////////////////////////////////////////// + +// TMutableRange (inspired by TMutableArrayRef from LLVM) +/* + * Represents a mutable reference to an array (zero or more elements + * consecutively in memory), i. e. a start pointer and a length. + * It allows various APIs to take and modify consecutive elements easily and + * conveniently. + * + * This class does not own the underlying data, it is expected to be used in + * situations where the data resides in some other buffer, whose lifetime + * extends past that of the TMutableRange. For this reason, it is not in + * general safe to store a TMutableRange. + * + * This is intended to be trivially copyable, so it should be passed by value. + */ +template <class T> +class TMutableRange + : public TRange<T> +{ +public: + typedef T* iterator; + + //! Constructs a null TMutableRange. + TMutableRange() + { } + + //! Constructs a TMutableRange from a pointer and length. + TMutableRange(T* data, size_t length) + : TRange<T>(data, length) + { } + + //! Constructs a TMutableRange from a range. + TMutableRange(T* begin, T* end) + : TRange<T>(begin, end) + { } + + //! Constructs a TMutableRange from a TCompactVector. + template <size_t N> + TMutableRange(TCompactVector<T, N>& elements) + : TRange<T>(elements) + { } + + //! Constructs a TMutableRange from an std::vector. + TMutableRange(std::vector<T>& elements) + : TRange<T>(elements) + { } + + //! Constructs a TMutableRange from std::array. + template <size_t N> + TMutableRange(std::array<T, N>& elements) + : TRange<T>(elements.data(), N) + { } + + //! Construct a TMutableRange from an std::optional + //! Range will contain 0-1 elements. + explicit TMutableRange(std::optional<T>& optional) + : TRange<T>(optional) + { } + + //! Constructs a TMutableRange from a C array. + template <size_t N> + TMutableRange(T (& elements)[N]) + : TRange<T>(elements) + { } + + using TRange<T>::Begin; + using TRange<T>::End; + using TRange<T>::Front; + using TRange<T>::Back; + using TRange<T>::operator[]; + + iterator Begin() const + { + return const_cast<T*>(this->Data_); + } + + // STL interop, for gcc. + iterator begin() const + { + return Begin(); + } + + iterator End() const + { + return this->Begin() + this->Size(); + } + + // STL interop, for gcc. + iterator end() const + { + return End(); + } + + T& operator[](size_t index) + { + YT_ASSERT(index <= this->Size()); + return Begin()[index]; + } + + T& Front() + { + YT_ASSERT(this->Length_ > 0); + return Begin()[0]; + } + + T& Back() + { + YT_ASSERT(this->Length_ > 0); + return Begin()[this->Length_ - 1]; + } + + TMutableRange<T> Slice(size_t startOffset, size_t endOffset) const + { + YT_ASSERT(startOffset <= endOffset && endOffset <= this->Size()); + return TMutableRange<T>(Begin() + startOffset, endOffset - startOffset); + } + + TMutableRange<T> Slice(T* begin, T* end) const + { + YT_ASSERT(begin >= Begin()); + YT_ASSERT(end <= End()); + return TMutableRange<T>(begin, end); + } +}; + +// STL interop. +template <class T> +typename TMutableRange<T>::iterator begin(TMutableRange<T> ref) +{ + return ref.Begin(); +} + +template <class T> +typename TMutableRange<T>::iterator end(TMutableRange<T> ref) +{ + return ref.End(); +} + +//////////////////////////////////////////////////////////////////////////////// + +//! Constructs a TMutableRange from a pointer and length. +template <class T> +TMutableRange<T> MakeMutableRange(T* data, size_t length) +{ + return TMutableRange<T>(data, length); +} + +//! Constructs a TMutableRange from a native range. +template <class T> +TMutableRange<T> MakeMutableRange(T* begin, T* end) +{ + return TMutableRange<T>(begin, end); +} + +//! Constructs a TMutableRange from a TCompactVector. +template <class T, size_t N> +TMutableRange<T> MakeMutableRange(TCompactVector<T, N>& elements) +{ + return elements; +} + +//! "Copy-constructor". +template <class T> +TMutableRange<T> MakeMutableRange(TMutableRange<T> range) +{ + return range; +} + +//! Constructs a TMutableRange from an std::vector. +template <class T> +TMutableRange<T> MakeMutableRange(std::vector<T>& elements) +{ + return elements; +} + +//! Constructs a TMutableRange from an std::array. +template <class T, size_t N> +TMutableRange<T> MakeMutableRange(std::array<T, N>& elements) +{ + return elements; +} + +//! Constructs a TMutableRange from a C array. +template <class T, size_t N> +TMutableRange<T> MakeMutableRange(T (& elements)[N]) +{ + return TMutableRange<T>(elements); +} + +//! Constructs a TMutableRange from RepeatedField. +template <class T> +TMutableRange<T> MakeMutableRange(google::protobuf::RepeatedField<T>& elements) +{ + return TMutableRange<T>(elements.data(), elements.size()); +} + +//! Constructs a TMutableRange from RepeatedPtrField. +template <class T> +TMutableRange<T*> MakeMutableRange(google::protobuf::RepeatedPtrField<T>& elements) +{ + return TMutableRange<const T*>(elements.data(), elements.size()); +} + +template <class U, class T> +TMutableRange<U> ReinterpretCastMutableRange(TMutableRange<T> range) +{ + static_assert(sizeof(T) == sizeof(U), "T and U must have equal sizes."); + return TMutableRange<U>(reinterpret_cast<U*>(range.Begin()), range.Size()); +} + +//////////////////////////////////////////////////////////////////////////////// + +// Mark TMutableRange and TMutableRange as PODs. +namespace NMpl { + +template <class T> +struct TIsPod; + +template <class T> +struct TIsPod<TRange<T>> +{ + static const bool Value = true; +}; + +template <class T> +struct TIsPod<TMutableRange<T>> +{ + static const bool Value = true; +}; + +} // namespace NMpl + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +template <class T> +struct hash<NYT::TRange<T>> +{ + size_t operator()(const NYT::TRange<T>& range) const + { + size_t result = 0; + for (const auto& element : range) { + NYT::HashCombine(result, element); + } + return result; + } +}; + +template <class T> +struct hash<NYT::TMutableRange<T>> +{ + size_t operator()(const NYT::TMutableRange<T>& range) const + { + size_t result = 0; + for (const auto& element : range) { + NYT::HashCombine(result, element); + } + return result; + } +}; + + diff --git a/library/cpp/yt/memory/ref-inl.h b/library/cpp/yt/memory/ref-inl.h new file mode 100644 index 0000000000..79be8356c5 --- /dev/null +++ b/library/cpp/yt/memory/ref-inl.h @@ -0,0 +1,517 @@ +#ifndef REF_INL_H_ +#error "Direct inclusion of this file is not allowed, include ref.h" +// For the sake of sane code completion. +#include "ref.h" +#endif + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +namespace NDetail { + +extern const char EmptyRefData[]; +extern char MutableEmptyRefData[]; + +} // namespace NDetail + +//////////////////////////////////////////////////////////////////////////////// + +Y_FORCE_INLINE TRef::TRef(const void* data, size_t size) + : TRange<char>(static_cast<const char*>(data), size) +{ } + +Y_FORCE_INLINE TRef::TRef(const void* begin, const void* end) + : TRange<char>(static_cast<const char*>(begin), static_cast<const char*>(end)) +{ } + +Y_FORCE_INLINE TRef TRef::MakeEmpty() +{ + return TRef(NDetail::EmptyRefData, NDetail::EmptyRefData); +} + +Y_FORCE_INLINE TRef TRef::FromString(const TString& str) +{ + return FromStringBuf(str); +} + +Y_FORCE_INLINE TRef TRef::FromStringBuf(TStringBuf strBuf) +{ + return TRef(strBuf.data(), strBuf.length()); +} + +template <class T> +Y_FORCE_INLINE TRef TRef::FromPod(const T& data) +{ + static_assert(TTypeTraits<T>::IsPod || std::is_pod<T>::value, "T must be a pod-type."); + return TRef(&data, sizeof (data)); +} + +Y_FORCE_INLINE TRef TRef::Slice(size_t startOffset, size_t endOffset) const +{ + YT_ASSERT(endOffset >= startOffset && endOffset <= Size()); + return TRef(Begin() + startOffset, endOffset - startOffset); +} + +//////////////////////////////////////////////////////////////////////////////// + +Y_FORCE_INLINE TMutableRef::TMutableRef(void* data, size_t size) + : TMutableRange<char>(static_cast<char*>(data), size) +{ } + +Y_FORCE_INLINE TMutableRef::TMutableRef(void* begin, void* end) + : TMutableRange<char>(static_cast<char*>(begin), static_cast<char*>(end)) +{ } + +Y_FORCE_INLINE TMutableRef TMutableRef::MakeEmpty() +{ + return TMutableRef(NDetail::MutableEmptyRefData, NDetail::MutableEmptyRefData); +} + +Y_FORCE_INLINE TMutableRef::operator TRef() const +{ + return TRef(Begin(), Size()); +} + +template <class T> +Y_FORCE_INLINE TMutableRef TMutableRef::FromPod(T& data) +{ + static_assert(TTypeTraits<T>::IsPod || std::is_pod<T>::value, "T must be a pod-type."); + return TMutableRef(&data, sizeof (data)); +} + +Y_FORCE_INLINE TMutableRef TMutableRef::FromString(TString& str) +{ + // NB: begin() invokes CloneIfShared(). + return TMutableRef(str.begin(), str.length()); +} + +Y_FORCE_INLINE TMutableRef TMutableRef::Slice(size_t startOffset, size_t endOffset) const +{ + YT_ASSERT(endOffset >= startOffset && endOffset <= Size()); + return TMutableRef(Begin() + startOffset, endOffset - startOffset); +} + +//////////////////////////////////////////////////////////////////////////////// + +Y_FORCE_INLINE TSharedRef::TSharedRef(TRef ref, TSharedRange<char>::THolderPtr holder) + : TSharedRange<char>(ref, std::move(holder)) +{ } + +Y_FORCE_INLINE TSharedRef::TSharedRef(const void* data, size_t length, TSharedRange<char>::THolderPtr holder) + : TSharedRange<char>(static_cast<const char*>(data), length, std::move(holder)) +{ } + +Y_FORCE_INLINE TSharedRef::TSharedRef(const void* begin, const void* end, TSharedRange<char>::THolderPtr holder) + : TSharedRange<char>(static_cast<const char*>(begin), static_cast<const char*>(end), std::move(holder)) +{ } + +Y_FORCE_INLINE TSharedRef TSharedRef::MakeEmpty() +{ + return TSharedRef(TRef::MakeEmpty(), nullptr); +} + +Y_FORCE_INLINE TSharedRef::operator TRef() const +{ + return TRef(Begin(), Size()); +} + +template <class TTag> +Y_FORCE_INLINE TSharedRef TSharedRef::FromString(TString str) +{ + return FromString(std::move(str), GetRefCountedTypeCookie<TTag>()); +} + +Y_FORCE_INLINE TSharedRef TSharedRef::FromString(TString str) +{ + return FromString<TDefaultSharedBlobTag>(std::move(str)); +} + +template <class TTag> +Y_FORCE_INLINE TSharedRef TSharedRef::MakeCopy(TRef ref) +{ + return MakeCopy(ref, GetRefCountedTypeCookie<TTag>()); +} + +Y_FORCE_INLINE TSharedRef TSharedRef::Slice(size_t startOffset, size_t endOffset) const +{ + YT_ASSERT(endOffset >= startOffset && endOffset <= Size()); + return TSharedRef(Begin() + startOffset, endOffset - startOffset, Holder_); +} + +Y_FORCE_INLINE TSharedRef TSharedRef::Slice(const void* begin, const void* end) const +{ + YT_ASSERT(begin >= Begin()); + YT_ASSERT(end <= End()); + return TSharedRef(begin, end, Holder_); +} + +//////////////////////////////////////////////////////////////////////////////// + +Y_FORCE_INLINE TSharedMutableRef::TSharedMutableRef(const TMutableRef& ref, TSharedMutableRange<char>::THolderPtr holder) + : TSharedMutableRange<char>(ref, std::move(holder)) +{ } + +Y_FORCE_INLINE TSharedMutableRef::TSharedMutableRef(void* data, size_t length, TSharedMutableRange<char>::THolderPtr holder) + : TSharedMutableRange<char>(static_cast<char*>(data), length, std::move(holder)) +{ } + +Y_FORCE_INLINE TSharedMutableRef::TSharedMutableRef(void* begin, void* end, TSharedMutableRange<char>::THolderPtr holder) + : TSharedMutableRange<char>(static_cast<char*>(begin), static_cast<char*>(end), std::move(holder)) +{ } + +Y_FORCE_INLINE TSharedMutableRef TSharedMutableRef::MakeEmpty() +{ + return TSharedMutableRef(TMutableRef::MakeEmpty(), nullptr); +} + +Y_FORCE_INLINE TSharedMutableRef::operator TMutableRef() const +{ + return TMutableRef(Begin(), Size()); +} + +Y_FORCE_INLINE TSharedMutableRef::operator TSharedRef() const +{ + return TSharedRef(Begin(), Size(), Holder_); +} + +Y_FORCE_INLINE TSharedMutableRef::operator TRef() const +{ + return TRef(Begin(), Size()); +} + +Y_FORCE_INLINE TSharedMutableRef TSharedMutableRef::Allocate(size_t size, bool initializeStorage) +{ + return Allocate<TDefaultSharedBlobTag>(size, initializeStorage); +} + +Y_FORCE_INLINE TSharedMutableRef TSharedMutableRef::AllocatePageAligned(size_t size, bool initializeStorage) +{ + return AllocatePageAligned<TDefaultSharedBlobTag>(size, initializeStorage); +} + +template <class TTag> +Y_FORCE_INLINE TSharedMutableRef TSharedMutableRef::MakeCopy(TRef ref) +{ + return MakeCopy(ref, GetRefCountedTypeCookie<TTag>()); +} + +Y_FORCE_INLINE TSharedMutableRef TSharedMutableRef::Slice(size_t startOffset, size_t endOffset) const +{ + YT_ASSERT(endOffset >= startOffset && endOffset <= Size()); + return TSharedMutableRef(Begin() + startOffset, endOffset - startOffset, Holder_); +} + +Y_FORCE_INLINE TSharedMutableRef TSharedMutableRef::Slice(void* begin, void* end) const +{ + YT_ASSERT(begin >= Begin()); + YT_ASSERT(end <= End()); + return TSharedMutableRef(begin, end, Holder_); +} + +template <class TTag> +Y_FORCE_INLINE TSharedMutableRef TSharedMutableRef::Allocate(size_t size, bool initializeStorage) +{ + return Allocate(size, initializeStorage, GetRefCountedTypeCookie<TTag>()); +} + +template <class TTag> +Y_FORCE_INLINE TSharedMutableRef TSharedMutableRef::AllocatePageAligned(size_t size, bool initializeStorage) +{ + return AllocatePageAligned(size, initializeStorage, GetRefCountedTypeCookie<TTag>()); +} + +//////////////////////////////////////////////////////////////////////////////// + +Y_FORCE_INLINE size_t GetByteSize(TRef ref) +{ + return ref ? ref.Size() : 0; +} + +template <class T> +size_t GetByteSize(TRange<T> parts) +{ + size_t size = 0; + for (const auto& part : parts) { + size += part.Size(); + } + return size; +} + +template <class T> +size_t GetByteSize(const std::vector<T>& parts) +{ + return GetByteSize(MakeRange(parts)); +} + +//////////////////////////////////////////////////////////////////////////////// + +class TSharedRefArrayImpl + : public TRefCounted + , public TWithExtraSpace<TSharedRefArrayImpl> +{ +public: + TSharedRefArrayImpl( + size_t extraSpaceSize, + TRefCountedTypeCookie tagCookie, + size_t size) + : Size_(size) + , ExtraSpaceSize_(extraSpaceSize) + , TagCookie_(tagCookie) + { + for (size_t index = 0; index < Size_; ++index) { + new (MutableBegin() + index) TSharedRef(); + } + RegisterWithRefCountedTracker(); + } + + TSharedRefArrayImpl( + size_t extraSpaceSize, + TRefCountedTypeCookie tagCookie, + const TSharedRef& part) + : Size_(1) + , ExtraSpaceSize_(extraSpaceSize) + , TagCookie_(tagCookie) + { + new (MutableBegin()) TSharedRef(part); + RegisterWithRefCountedTracker(); + } + + TSharedRefArrayImpl( + size_t extraSpaceSize, + TRefCountedTypeCookie tagCookie, + TSharedRef&& part) + : Size_(1) + , ExtraSpaceSize_(extraSpaceSize) + , TagCookie_(tagCookie) + { + new (MutableBegin()) TSharedRef(std::move(part)); + RegisterWithRefCountedTracker(); + } + + template <class TParts> + TSharedRefArrayImpl( + size_t extraSpaceSize, + TRefCountedTypeCookie tagCookie, + const TParts& parts, + TSharedRefArray::TCopyParts) + : Size_(parts.size()) + , ExtraSpaceSize_(extraSpaceSize) + , TagCookie_(tagCookie) + { + for (size_t index = 0; index < Size_; ++index) { + new (MutableBegin() + index) TSharedRef(parts[index]); + } + RegisterWithRefCountedTracker(); + } + + template <class TParts> + TSharedRefArrayImpl( + size_t extraSpaceSize, + TRefCountedTypeCookie tagCookie, + TParts&& parts, + TSharedRefArray::TMoveParts) + : Size_(parts.size()) + , ExtraSpaceSize_(extraSpaceSize) + , TagCookie_(tagCookie) + { + for (size_t index = 0; index < Size_; ++index) { + new (MutableBegin() + index) TSharedRef(std::move(parts[index])); + } + RegisterWithRefCountedTracker(); + } + + ~TSharedRefArrayImpl() + { + for (size_t index = 0; index < Size_; ++index) { + auto& part = MutableBegin()[index]; + if (part.GetHolder() == this) { + part.Holder_.Release(); + } + part.TSharedRef::~TSharedRef(); + } + UnregisterFromRefCountedTracker(); + } + + + size_t Size() const + { + return Size_; + } + + bool Empty() const + { + return Size_ == 0; + } + + const TSharedRef& operator [] (size_t index) const + { + YT_ASSERT(index < Size()); + return Begin()[index]; + } + + + const TSharedRef* Begin() const + { + return static_cast<const TSharedRef*>(GetExtraSpacePtr()); + } + + const TSharedRef* End() const + { + return Begin() + Size_; + } + +private: + friend class TSharedRefArrayBuilder; + + const size_t Size_; + const size_t ExtraSpaceSize_; + const TRefCountedTypeCookie TagCookie_; + + + void RegisterWithRefCountedTracker() + { + TRefCountedTrackerFacade::AllocateTagInstance(TagCookie_); + TRefCountedTrackerFacade::AllocateSpace(TagCookie_, ExtraSpaceSize_); + } + + void UnregisterFromRefCountedTracker() + { + TRefCountedTrackerFacade::FreeTagInstance(TagCookie_); + TRefCountedTrackerFacade::FreeSpace(TagCookie_, ExtraSpaceSize_); + } + + + TSharedRef* MutableBegin() + { + return static_cast<TSharedRef*>(GetExtraSpacePtr()); + } + + TSharedRef* MutableEnd() + { + return MutableBegin() + Size_; + } + + char* GetBeginAllocationPtr() + { + return static_cast<char*>(static_cast<void*>(MutableEnd())); + } +}; + +DEFINE_REFCOUNTED_TYPE(TSharedRefArrayImpl) + +//////////////////////////////////////////////////////////////////////////////// + +struct TSharedRefArrayTag { }; + +Y_FORCE_INLINE TSharedRefArray::TSharedRefArray(TIntrusivePtr<TSharedRefArrayImpl> impl) + : Impl_(std::move(impl)) +{ } + +Y_FORCE_INLINE TSharedRefArray::TSharedRefArray(const TSharedRefArray& other) + : Impl_(other.Impl_) +{ } + +Y_FORCE_INLINE TSharedRefArray::TSharedRefArray(TSharedRefArray&& other) noexcept + : Impl_(std::move(other.Impl_)) +{ } + +Y_FORCE_INLINE TSharedRefArray::TSharedRefArray(const TSharedRef& part) + : Impl_(NewImpl(1, 0, GetRefCountedTypeCookie<TSharedRefArrayTag>(), part)) +{ } + +Y_FORCE_INLINE TSharedRefArray::TSharedRefArray(TSharedRef&& part) + : Impl_(NewImpl(1, 0, GetRefCountedTypeCookie<TSharedRefArrayTag>(), std::move(part))) +{ } + +template <class TParts> +Y_FORCE_INLINE TSharedRefArray::TSharedRefArray(const TParts& parts, TSharedRefArray::TCopyParts) + : Impl_(NewImpl(parts.size(), 0, GetRefCountedTypeCookie<TSharedRefArrayTag>(), parts, TSharedRefArray::TCopyParts{})) +{ } + +template <class TParts> +Y_FORCE_INLINE TSharedRefArray::TSharedRefArray(TParts&& parts, TSharedRefArray::TMoveParts) + : Impl_(NewImpl(parts.size(), 0, GetRefCountedTypeCookie<TSharedRefArrayTag>(), std::move(parts), TSharedRefArray::TMoveParts{})) +{ } + +Y_FORCE_INLINE TSharedRefArray& TSharedRefArray::operator=(const TSharedRefArray& other) +{ + Impl_ = other.Impl_; + return *this; +} + +Y_FORCE_INLINE TSharedRefArray& TSharedRefArray::operator=(TSharedRefArray&& other) +{ + Impl_ = std::move(other.Impl_); + return *this; +} + +Y_FORCE_INLINE void TSharedRefArray::Reset() +{ + Impl_.Reset(); +} + +Y_FORCE_INLINE TSharedRefArray::operator bool() const +{ + return Impl_.operator bool(); +} + +Y_FORCE_INLINE size_t TSharedRefArray::Size() const +{ + return Impl_ ? Impl_->Size() : 0; +} + +Y_FORCE_INLINE size_t TSharedRefArray::size() const +{ + return Impl_ ? Impl_->Size() : 0; +} + +Y_FORCE_INLINE bool TSharedRefArray::Empty() const +{ + return Impl_ ? Impl_->Empty() : true; +} + +Y_FORCE_INLINE const TSharedRef& TSharedRefArray::operator[](size_t index) const +{ + YT_ASSERT(Impl_); + return (*Impl_)[index]; +} + +Y_FORCE_INLINE const TSharedRef* TSharedRefArray::Begin() const +{ + return Impl_ ? Impl_->Begin() : nullptr; +} + +Y_FORCE_INLINE const TSharedRef* TSharedRefArray::End() const +{ + return Impl_ ? Impl_->End() : nullptr; +} + +template <class... As> +TSharedRefArrayImplPtr TSharedRefArray::NewImpl( + size_t size, + size_t poolCapacity, + TRefCountedTypeCookie tagCookie, + As&&... args) +{ + auto extraSpaceSize = sizeof (TSharedRef) * size + poolCapacity; + return NewWithExtraSpace<TSharedRefArrayImpl>( + extraSpaceSize, + extraSpaceSize, + tagCookie, + std::forward<As>(args)...); +} + +Y_FORCE_INLINE const TSharedRef* begin(const TSharedRefArray& array) +{ + return array.Begin(); +} + +Y_FORCE_INLINE const TSharedRef* end(const TSharedRefArray& array) +{ + return array.End(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/memory/ref.cpp b/library/cpp/yt/memory/ref.cpp new file mode 100644 index 0000000000..e8ff42e976 --- /dev/null +++ b/library/cpp/yt/memory/ref.cpp @@ -0,0 +1,378 @@ +#include "ref.h" +#include "blob.h" + +#include <library/cpp/ytalloc/api/ytalloc.h> + +#include <util/system/info.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +namespace NDetail { + +// N.B. We would prefer these arrays to be zero sized +// but zero sized arrays are not supported in MSVC. +const char EmptyRefData[1] = {0}; +char MutableEmptyRefData[1] = {0}; + +} // namespace NDetail + +//////////////////////////////////////////////////////////////////////////////// + +class TBlobHolder + : public TRefCounted +{ +public: + explicit TBlobHolder(TBlob&& blob) + : Blob_(std::move(blob)) + { } + +private: + const TBlob Blob_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class TStringHolder + : public TRefCounted +{ +public: + TStringHolder(TString&& string, TRefCountedTypeCookie cookie) + : String_(std::move(string)) +#ifdef YT_ENABLE_REF_COUNTED_TRACKING + , Cookie_(cookie) +#endif + { +#ifdef YT_ENABLE_REF_COUNTED_TRACKING + TRefCountedTrackerFacade::AllocateTagInstance(Cookie_); + TRefCountedTrackerFacade::AllocateSpace(Cookie_, String_.length()); +#endif + } + ~TStringHolder() + { +#ifdef YT_ENABLE_REF_COUNTED_TRACKING + TRefCountedTrackerFacade::FreeTagInstance(Cookie_); + TRefCountedTrackerFacade::FreeSpace(Cookie_, String_.length()); +#endif + } + + const TString& String() const + { + return String_; + } + +private: + const TString String_; +#ifdef YT_ENABLE_REF_COUNTED_TRACKING + const TRefCountedTypeCookie Cookie_; +#endif +}; + +//////////////////////////////////////////////////////////////////////////////// + +template <class TDerived> +class TAllocationHolderBase + : public TRefCounted +{ +public: + TAllocationHolderBase(size_t size, TRefCountedTypeCookie cookie) + : Size_(size) +#ifdef YT_ENABLE_REF_COUNTED_TRACKING + , Cookie_(cookie) +#endif + { } + + ~TAllocationHolderBase() + { +#ifdef YT_ENABLE_REF_COUNTED_TRACKING + TRefCountedTrackerFacade::FreeTagInstance(Cookie_); + TRefCountedTrackerFacade::FreeSpace(Cookie_, Size_); +#endif + } + + TMutableRef GetRef() + { + return TMutableRef(static_cast<TDerived*>(this)->GetBegin(), Size_); + } + +protected: + const size_t Size_; +#ifdef YT_ENABLE_REF_COUNTED_TRACKING + const TRefCountedTypeCookie Cookie_; +#endif + + void Initialize(bool initializeStorage) + { + if (initializeStorage) { + ::memset(static_cast<TDerived*>(this)->GetBegin(), 0, Size_); + } +#ifdef YT_ENABLE_REF_COUNTED_TRACKING + TRefCountedTrackerFacade::AllocateTagInstance(Cookie_); + TRefCountedTrackerFacade::AllocateSpace(Cookie_, Size_); +#endif + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +class TDefaultAllocationHolder + : public TAllocationHolderBase<TDefaultAllocationHolder> + , public TWithExtraSpace<TDefaultAllocationHolder> +{ +public: + TDefaultAllocationHolder(size_t size, bool initializeStorage, TRefCountedTypeCookie cookie) + : TAllocationHolderBase(size, cookie) + { + Initialize(initializeStorage); + } + + char* GetBegin() + { + return static_cast<char*>(GetExtraSpacePtr()); + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +class TPageAlignedAllocationHolder + : public TAllocationHolderBase<TPageAlignedAllocationHolder> +{ +public: + TPageAlignedAllocationHolder(size_t size, bool initializeStorage, TRefCountedTypeCookie cookie) + : TAllocationHolderBase(size, cookie) + , Begin_(static_cast<char*>(NYTAlloc::AllocatePageAligned(size))) + { + Initialize(initializeStorage); + } + + ~TPageAlignedAllocationHolder() + { + NYTAlloc::Free(Begin_); + } + + char* GetBegin() + { + return Begin_; + } + +private: + char* const Begin_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +TRef TRef::FromBlob(const TBlob& blob) +{ + return TRef(blob.Begin(), blob.Size()); +} + +bool TRef::AreBitwiseEqual(TRef lhs, TRef rhs) +{ + if (lhs.Size() != rhs.Size()) { + return false; + } + if (lhs.Size() == 0) { + return true; + } + return ::memcmp(lhs.Begin(), rhs.Begin(), lhs.Size()) == 0; +} + +//////////////////////////////////////////////////////////////////////////////// + +TMutableRef TMutableRef::FromBlob(TBlob& blob) +{ + return TMutableRef(blob.Begin(), blob.Size()); +} + +//////////////////////////////////////////////////////////////////////////////// + +TSharedRef TSharedRef::FromString(TString str, TRefCountedTypeCookie tagCookie) +{ + auto holder = New<TStringHolder>(std::move(str), tagCookie); + auto ref = TRef::FromString(holder->String()); + return TSharedRef(ref, std::move(holder)); +} + +TSharedRef TSharedRef::FromBlob(TBlob&& blob) +{ + auto ref = TRef::FromBlob(blob); + auto holder = New<TBlobHolder>(std::move(blob)); + return TSharedRef(ref, std::move(holder)); +} + +TSharedRef TSharedRef::MakeCopy(TRef ref, TRefCountedTypeCookie tagCookie) +{ + if (!ref) { + return {}; + } + if (ref.Empty()) { + return TSharedRef::MakeEmpty(); + } + auto result = TSharedMutableRef::Allocate(ref.Size(), false, tagCookie); + ::memcpy(result.Begin(), ref.Begin(), ref.Size()); + return result; +} + +std::vector<TSharedRef> TSharedRef::Split(size_t partSize) const +{ + YT_VERIFY(partSize > 0); + std::vector<TSharedRef> result; + result.reserve(Size() / partSize + 1); + auto sliceBegin = Begin(); + while (sliceBegin < End()) { + auto sliceEnd = sliceBegin + partSize; + if (sliceEnd < sliceBegin || sliceEnd > End()) { + sliceEnd = End(); + } + result.push_back(Slice(sliceBegin, sliceEnd)); + sliceBegin = sliceEnd; + } + return result; +} + +//////////////////////////////////////////////////////////////////////////////// + +TSharedMutableRef TSharedMutableRef::Allocate(size_t size, bool initializeStorage, TRefCountedTypeCookie tagCookie) +{ + auto holder = NewWithExtraSpace<TDefaultAllocationHolder>(size, size, initializeStorage, tagCookie); + auto ref = holder->GetRef(); + return TSharedMutableRef(ref, std::move(holder)); +} + +TSharedMutableRef TSharedMutableRef::AllocatePageAligned(size_t size, bool initializeStorage, TRefCountedTypeCookie tagCookie) +{ + auto holder = New<TPageAlignedAllocationHolder>(size, initializeStorage, tagCookie); + auto ref = holder->GetRef(); + return TSharedMutableRef(ref, std::move(holder)); +} + +TSharedMutableRef TSharedMutableRef::FromBlob(TBlob&& blob) +{ + auto ref = TMutableRef::FromBlob(blob); + auto holder = New<TBlobHolder>(std::move(blob)); + return TSharedMutableRef(ref, std::move(holder)); +} + +TSharedMutableRef TSharedMutableRef::MakeCopy(TRef ref, TRefCountedTypeCookie tagCookie) +{ + if (!ref) { + return {}; + } + if (ref.Empty()) { + return TSharedMutableRef::MakeEmpty(); + } + auto result = Allocate(ref.Size(), false, tagCookie); + ::memcpy(result.Begin(), ref.Begin(), ref.Size()); + return result; +} + +//////////////////////////////////////////////////////////////////////////////// + +TString ToString(TRef ref) +{ + return TString(ref.Begin(), ref.End()); +} + +TString ToString(const TMutableRef& ref) +{ + return ToString(TRef(ref)); +} + +TString ToString(const TSharedRef& ref) +{ + return ToString(TRef(ref)); +} + +TString ToString(const TSharedMutableRef& ref) +{ + return ToString(TRef(ref)); +} + +size_t GetPageSize() +{ + static const size_t PageSize = NSystemInfo::GetPageSize(); + return PageSize; +} + +size_t RoundUpToPage(size_t bytes) +{ + static const size_t PageSize = NSystemInfo::GetPageSize(); + YT_ASSERT((PageSize & (PageSize - 1)) == 0); + return (bytes + PageSize - 1) & (~(PageSize - 1)); +} + +size_t GetByteSize(const TSharedRefArray& array) +{ + size_t size = 0; + if (array) { + for (const auto& part : array) { + size += part.Size(); + } + } + return size; +} + +//////////////////////////////////////////////////////////////////////////////// + +i64 TSharedRefArray::ByteSize() const +{ + i64 result = 0; + if (*this) { + for (const auto& part : *this) { + result += part.Size(); + } + } + return result; +} + +std::vector<TSharedRef> TSharedRefArray::ToVector() const +{ + if (!Impl_) { + return {}; + } + + return std::vector<TSharedRef>(Begin(), End()); +} + +//////////////////////////////////////////////////////////////////////////////// + +TSharedRefArrayBuilder::TSharedRefArrayBuilder( + size_t size, + size_t poolCapacity, + TRefCountedTypeCookie tagCookie) + : AllocationCapacity_(poolCapacity) + , Impl_(TSharedRefArray::NewImpl( + size, + poolCapacity, + tagCookie, + size)) + , CurrentAllocationPtr_(Impl_->GetBeginAllocationPtr()) +{ } + +void TSharedRefArrayBuilder::Add(TSharedRef part) +{ + YT_ASSERT(CurrentPartIndex_ < Impl_->Size()); + Impl_->MutableBegin()[CurrentPartIndex_++] = std::move(part); +} + +TMutableRef TSharedRefArrayBuilder::AllocateAndAdd(size_t size) +{ + YT_ASSERT(CurrentPartIndex_ < Impl_->Size()); + YT_ASSERT(CurrentAllocationPtr_ + size <= Impl_->GetBeginAllocationPtr() + AllocationCapacity_); + TMutableRef ref(CurrentAllocationPtr_, size); + CurrentAllocationPtr_ += size; + TRefCountedPtr holder(Impl_.Get(), false); + TSharedRef sharedRef(ref, std::move(holder)); + Add(std::move(sharedRef)); + return ref; +} + +TSharedRefArray TSharedRefArrayBuilder::Finish() +{ + return TSharedRefArray(std::move(Impl_)); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/memory/ref.h b/library/cpp/yt/memory/ref.h new file mode 100644 index 0000000000..73d19d9013 --- /dev/null +++ b/library/cpp/yt/memory/ref.h @@ -0,0 +1,384 @@ +#pragma once + +#include "new.h" +#include "range.h" +#include "shared_range.h" + +#include <type_traits> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +// Forward declaration. +class TBlob; + +//! A non-owning reference to a range of memory. +class TRef + : public TRange<char> +{ +public: + //! Creates a null TRef. + TRef() = default; + + //! Creates a TRef for a given block of memory. + TRef(const void* data, size_t size); + + //! Creates a TRef for a given range of memory. + TRef(const void* begin, const void* end); + + //! Creates an empty TRef. + static TRef MakeEmpty(); + + //! Creates a non-owning TRef for a given blob. + static TRef FromBlob(const TBlob& blob); + + //! Creates a non-owning TRef for a given string. + static TRef FromString(const TString& str); + + //! Creates a non-owning TRef for a given stringbuf. + static TRef FromStringBuf(TStringBuf strBuf); + + //! Creates a non-owning TRef for a given pod structure. + template <class T> + static TRef FromPod(const T& data); + + //! Creates a TRef for a part of existing range. + TRef Slice(size_t startOffset, size_t endOffset) const; + + //! Compares the content for bitwise equality. + static bool AreBitwiseEqual(TRef lhs, TRef rhs); +}; + +//////////////////////////////////////////////////////////////////////////////// + +//! A non-owning reference to a mutable range of memory. +//! Use with caution :) +class TMutableRef + : public TMutableRange<char> +{ +public: + //! Creates a null TMutableRef. + //! Note empty TMutableRef is not the same as null TMutableRef. + //! `operator bool` can be used to check if ref is nonnull. + TMutableRef() = default; + + //! Creates a TMutableRef for a given block of memory. + TMutableRef(void* data, size_t size); + + //! Creates a TMutableRef for a given range of memory. + TMutableRef(void* begin, void* end); + + //! Creates an empty TMutableRef. + static TMutableRef MakeEmpty(); + + //! Converts a TMutableRef to TRef. + operator TRef() const; + + //! Creates a non-owning TMutableRef for a given blob. + static TMutableRef FromBlob(TBlob& blob); + + //! Creates a non-owning TMutableRef for a given pod structure. + template <class T> + static TMutableRef FromPod(T& data); + + //! Creates a non-owning TMutableRef for a given string. + //! Ensures that the string is not shared. + static TMutableRef FromString(TString& str); + + //! Creates a TMutableRef for a part of existing range. + TMutableRef Slice(size_t startOffset, size_t endOffset) const; +}; + +//////////////////////////////////////////////////////////////////////////////// + +//! Default tag type for memory blocks allocated via TSharedRef. +/*! + * Each newly allocated TSharedRef blob is associated with a tag type + * that appears in ref-counted statistics. + */ +struct TDefaultSharedBlobTag { }; + +//! A reference to a range of memory with shared ownership. +class TSharedRef + : public TSharedRange<char> +{ +public: + //! Creates a null TSharedRef. + TSharedRef() = default; + + //! Creates a TSharedRef with a given holder. + TSharedRef(TRef ref, THolderPtr holder); + + //! Creates a TSharedRef from a pointer and length. + TSharedRef(const void* data, size_t length, THolderPtr holder); + + //! Creates a TSharedRef from a range. + TSharedRef(const void* begin, const void* end, THolderPtr holder); + + //! Creates an empty TSharedRef. + static TSharedRef MakeEmpty(); + + //! Converts a TSharedRef to TRef. + operator TRef() const; + + + //! Creates a TSharedRef from a string. + //! Since strings are ref-counted, no data is copied. + //! The memory is marked with a given tag. + template <class TTag> + static TSharedRef FromString(TString str); + + //! Creates a TSharedRef from a string. + //! Since strings are ref-counted, no data is copied. + //! The memory is marked with TDefaultSharedBlobTag. + static TSharedRef FromString(TString str); + + //! Creates a TSharedRef reference from a string. + //! Since strings are ref-counted, no data is copied. + //! The memory is marked with a given tag. + static TSharedRef FromString(TString str, TRefCountedTypeCookie tagCookie); + + //! Creates a TSharedRef for a given blob taking ownership of its content. + static TSharedRef FromBlob(TBlob&& blob); + + //! Creates a copy of a given TRef. + //! The memory is marked with a given tag. + static TSharedRef MakeCopy(TRef ref, TRefCountedTypeCookie tagCookie); + + //! Creates a copy of a given TRef. + //! The memory is marked with a given tag. + template <class TTag> + static TSharedRef MakeCopy(TRef ref); + + //! Creates a TSharedRef for a part of existing range. + TSharedRef Slice(size_t startOffset, size_t endOffset) const; + + //! Creates a TSharedRef for a part of existing range. + TSharedRef Slice(const void* begin, const void* end) const; + + //! Creates a vector of slices with specified size. + std::vector<TSharedRef> Split(size_t partSize) const; + +private: + friend class TSharedRefArrayImpl; +}; + +//////////////////////////////////////////////////////////////////////////////// + +//! A reference to a mutable range of memory with shared ownership. +//! Use with caution :) +class TSharedMutableRef + : public TSharedMutableRange<char> +{ +public: + //! Creates a null TSharedMutableRef. + TSharedMutableRef() = default; + + //! Creates a TSharedMutableRef with a given holder. + TSharedMutableRef(const TMutableRef& ref, THolderPtr holder); + + //! Creates a TSharedMutableRef from a pointer and length. + TSharedMutableRef(void* data, size_t length, THolderPtr holder); + + //! Creates a TSharedMutableRef from a range. + TSharedMutableRef(void* begin, void* end, THolderPtr holder); + + //! Creates an empty TSharedMutableRef. + static TSharedMutableRef MakeEmpty(); + + //! Converts a TSharedMutableRef to TMutableRef. + operator TMutableRef() const; + + //! Converts a TSharedMutableRef to TSharedRef. + operator TSharedRef() const; + + //! Converts a TSharedMutableRef to TRef. + operator TRef() const; + + + //! Allocates a new shared block of memory. + //! The memory is marked with a given tag. + template <class TTag> + static TSharedMutableRef Allocate(size_t size, bool initializeStorage = true); + + //! Allocates a new shared block of memory. + //! The memory is marked with TDefaultSharedBlobTag. + static TSharedMutableRef Allocate(size_t size, bool initializeStorage = true); + + //! Allocates a new shared block of memory. + //! The memory is marked with a given tag. + static TSharedMutableRef Allocate(size_t size, bool initializeStorage, TRefCountedTypeCookie tagCookie); + + //! Allocates a new page aligned shared block of memory. + //! #size must be divisible by page size. + //! The memory is marked with a given tag. + template <class TTag> + static TSharedMutableRef AllocatePageAligned(size_t size, bool initializeStorage = true); + + //! Allocates a new page aligned shared block of memory. + //! #size must be divisible by page size. + //! The memory is marked with TDefaultSharedBlobTag. + static TSharedMutableRef AllocatePageAligned(size_t size, bool initializeStorage = true); + + //! Allocates a new page aligned shared block of memory. + //! #size must be divisible by page size. + //! The memory is marked with a given tag. + static TSharedMutableRef AllocatePageAligned(size_t size, bool initializeStorage, TRefCountedTypeCookie tagCookie); + + //! Creates a TSharedMutableRef for the whole blob taking ownership of its content. + static TSharedMutableRef FromBlob(TBlob&& blob); + + //! Creates a copy of a given TRef. + //! The memory is marked with a given tag. + static TSharedMutableRef MakeCopy(TRef ref, TRefCountedTypeCookie tagCookie); + + //! Creates a copy of a given TRef. + //! The memory is marked with a given tag. + template <class TTag> + static TSharedMutableRef MakeCopy(TRef ref); + + //! Creates a reference for a part of existing range. + TSharedMutableRef Slice(size_t startOffset, size_t endOffset) const; + + //! Creates a reference for a part of existing range. + TSharedMutableRef Slice(void* begin, void* end) const; +}; + +//////////////////////////////////////////////////////////////////////////////// + +DECLARE_REFCOUNTED_CLASS(TSharedRefArrayImpl) + +//! A smart-pointer to a ref-counted immutable sequence of TSharedRef-s. +class TSharedRefArray +{ +public: + TSharedRefArray() = default; + TSharedRefArray(const TSharedRefArray& other); + TSharedRefArray(TSharedRefArray&& other) noexcept; + + explicit TSharedRefArray(const TSharedRef& part); + explicit TSharedRefArray(TSharedRef&& part); + + struct TCopyParts + { }; + struct TMoveParts + { }; + + template <class TParts> + TSharedRefArray(const TParts& parts, TCopyParts); + template <class TParts> + TSharedRefArray(TParts&& parts, TMoveParts); + + TSharedRefArray& operator = (const TSharedRefArray& other); + TSharedRefArray& operator = (TSharedRefArray&& other); + + explicit operator bool() const; + + void Reset(); + + size_t Size() const; + size_t size() const; + i64 ByteSize() const; + bool Empty() const; + const TSharedRef& operator [] (size_t index) const; + + const TSharedRef* Begin() const; + const TSharedRef* End() const; + + std::vector<TSharedRef> ToVector() const; + +private: + friend class TSharedRefArrayBuilder; + + TSharedRefArrayImplPtr Impl_; + + explicit TSharedRefArray(TSharedRefArrayImplPtr impl); + + template <class... As> + static TSharedRefArrayImplPtr NewImpl( + size_t size, + size_t poolCapacity, + TRefCountedTypeCookie cookie, + As&&... args); +}; + +// STL interop. +const TSharedRef* begin(const TSharedRefArray& array); +const TSharedRef* end(const TSharedRefArray& array); + +//////////////////////////////////////////////////////////////////////////////// + +struct TDefaultSharedRefArrayBuilderTag { }; + +//! A helper for creating TSharedRefArray. +class TSharedRefArrayBuilder +{ +public: + //! Creates a builder instance. + /* + * The user must provide the total (resulting) part count in #size. + * + * Additionally, the user may request a certain memory pool of size #poolCapacity + * to be created. Parts occupiying space in the above pool are created with #AllocateAndAdd + * calls. + * + * The pool (if any) and the array are created within a single memory allocation tagged with + * #tagCookie. + * + * If less than #size parts are added, the trailing ones are null. + */ + explicit TSharedRefArrayBuilder( + size_t size, + size_t poolCapacity = 0, + TRefCountedTypeCookie tagCookie = GetRefCountedTypeCookie<TDefaultSharedRefArrayBuilderTag>()); + + //! Adds an existing TSharedRef part to the constructed array. + void Add(TSharedRef part); + + //! Allocates #size memory from the pool and adds a part to the constuctured array. + /*! + * The resulting TMutableRef enables the user to fill the just-created part appropriately. + * The total sum of #size during all #AllocateAndAll calls must now exceed #allocationCapacity + * passed to the ctor. + * + * The memory is being claimed from the pool contiguously; the user must + * take care of the alignment issues on its own. + */ + TMutableRef AllocateAndAdd(size_t size); + + //! Finalizes the construction; returns the constructed TSharedRefArray. + TSharedRefArray Finish(); + +private: + const size_t AllocationCapacity_; + TSharedRefArrayImplPtr Impl_; + char* CurrentAllocationPtr_; + size_t CurrentPartIndex_ = 0; +}; + + +//////////////////////////////////////////////////////////////////////////////// + +TString ToString(TRef ref); +TString ToString(const TMutableRef& ref); +TString ToString(const TSharedRef& ref); +TString ToString(const TSharedMutableRef& ref); + +size_t GetPageSize(); +size_t RoundUpToPage(size_t bytes); + +size_t GetByteSize(TRef ref); +size_t GetByteSize(const TSharedRefArray& array); +template <class T> +size_t GetByteSize(TRange<T> parts); +template <class T> +size_t GetByteSize(const std::vector<T>& parts); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define REF_INL_H_ +#include "ref-inl.h" +#undef REF_INL_H_ diff --git a/library/cpp/yt/memory/ref_counted-inl.h b/library/cpp/yt/memory/ref_counted-inl.h new file mode 100644 index 0000000000..e6d64fec18 --- /dev/null +++ b/library/cpp/yt/memory/ref_counted-inl.h @@ -0,0 +1,278 @@ +#ifndef REF_COUNTED_INL_H_ +#error "Direct inclusion of this file is not allowed, include ref_counted.h" +// For the sake of sane code completion. +#include "ref_counted.h" +#endif + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +constexpr uint16_t PtrBits = 48; +constexpr uintptr_t PtrMask = (1ULL << PtrBits) - 1; + +template <class T> +Y_FORCE_INLINE char* PackPointer(T* ptr, uint16_t data) +{ + return reinterpret_cast<char*>((static_cast<uintptr_t>(data) << PtrBits) | reinterpret_cast<uintptr_t>(ptr)); +} + +template <class T> +struct TPackedPointer +{ + uint16_t Data; + T* Ptr; +}; + +template <class T> +Y_FORCE_INLINE TPackedPointer<T> UnpackPointer(void* packedPtr) +{ + auto castedPtr = reinterpret_cast<uintptr_t>(packedPtr); + return {static_cast<uint16_t>(castedPtr >> PtrBits), reinterpret_cast<T*>(castedPtr & PtrMask)}; +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class T, class = void> +struct TMemoryReleaser +{ + static void Do(void* ptr, uint16_t /*offset*/) + { + TFreeMemory<T>::Do(ptr); + } +}; + +using TDeleter = void (*)(void*); + +void ScheduleObjectDeletion(void* ptr, TDeleter deleter); + +template <class T> +struct TMemoryReleaser<T, std::enable_if_t<T::EnableHazard>> +{ + static void Do(void* ptr, uint16_t offset) + { + // Base pointer is used in HazardPtr as the identity of object. + auto* basePtr = PackPointer(static_cast<char*>(ptr) + offset, offset); + + ScheduleObjectDeletion(basePtr, [] (void* ptr) { + // Base ptr and the beginning of allocated memory region may differ. + auto [offset, basePtr] = UnpackPointer<char>(ptr); + TFreeMemory<T>::Do(basePtr - offset); + }); + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +Y_FORCE_INLINE int TRefCounter::GetRefCount() const noexcept +{ + return StrongCount_.load(std::memory_order_relaxed); +} + +Y_FORCE_INLINE void TRefCounter::Ref() const noexcept +{ + // It is safe to use relaxed here, since new reference is always created from another live reference. + StrongCount_.fetch_add(1, std::memory_order_relaxed); + + YT_ASSERT(WeakCount_.load(std::memory_order_relaxed) > 0); +} + +Y_FORCE_INLINE bool TRefCounter::TryRef() const noexcept +{ + auto value = StrongCount_.load(std::memory_order_relaxed); + YT_ASSERT(WeakCount_.load(std::memory_order_relaxed) > 0); + + while (value != 0 && !StrongCount_.compare_exchange_weak(value, value + 1)); + return value != 0; +} + +Y_FORCE_INLINE bool TRefCounter::Unref() const +{ + // We must properly synchronize last access to object with it destruction. + // Otherwise compiler might reorder access to object past this decrement. + // + // See http://www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html#boost_atomic.usage_examples.example_reference_counters + // + auto oldStrongCount = StrongCount_.fetch_sub(1, std::memory_order_release); + YT_ASSERT(oldStrongCount > 0); + if (oldStrongCount == 1) { + StrongCount_.load(std::memory_order_acquire); + return true; + } else { + return false; + } +} + +Y_FORCE_INLINE int TRefCounter::GetWeakRefCount() const noexcept +{ + return WeakCount_.load(std::memory_order_acquire); +} + +Y_FORCE_INLINE void TRefCounter::WeakRef() const noexcept +{ + auto oldWeakCount = WeakCount_.fetch_add(1, std::memory_order_relaxed); + YT_ASSERT(oldWeakCount > 0); +} + +Y_FORCE_INLINE bool TRefCounter::WeakUnref() const +{ + auto oldWeakCount = WeakCount_.fetch_sub(1, std::memory_order_release); + YT_ASSERT(oldWeakCount > 0); + if (oldWeakCount == 1) { + WeakCount_.load(std::memory_order_acquire); + return true; + } else { + return false; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class T, bool = std::is_base_of_v<TRefCountedBase, T>> +struct TRefCountedHelper +{ + static_assert( + std::is_final_v<T>, + "Ref-counted objects must be derived from TRefCountedBase or to be final"); + + static constexpr size_t RefCounterSpace = (sizeof(TRefCounter) + alignof(T) - 1) & ~(alignof(T) - 1); + static constexpr size_t RefCounterOffset = RefCounterSpace - sizeof(TRefCounter); + + Y_FORCE_INLINE static const TRefCounter* GetRefCounter(const T* obj) + { + return reinterpret_cast<const TRefCounter*>(obj) - 1; + } + + Y_FORCE_INLINE static void Destroy(const T* obj) + { + auto* refCounter = GetRefCounter(obj); + + // No virtual call when T is final. + obj->~T(); + + char* ptr = reinterpret_cast<char*>(const_cast<TRefCounter*>(refCounter)); + + // Fast path. Weak refs cannot appear if there are neither strong nor weak refs. + if (refCounter->GetWeakRefCount() == 1) { + TMemoryReleaser<T>::Do(ptr - RefCounterOffset, RefCounterSpace); + return; + } + + if (refCounter->WeakUnref()) { + TMemoryReleaser<T>::Do(ptr - RefCounterOffset, RefCounterSpace); + } + } + + Y_FORCE_INLINE static void Deallocate(const T* obj) + { + char* ptr = reinterpret_cast<char*>(const_cast<TRefCounter*>(GetRefCounter(obj))); + TMemoryReleaser<T>::Do(ptr - RefCounterOffset, RefCounterSpace); + } +}; + +template <class T> +struct TRefCountedHelper<T, true> +{ + Y_FORCE_INLINE static const TRefCounter* GetRefCounter(const T* obj) + { + return obj; + } + + Y_FORCE_INLINE static void Destroy(const TRefCountedBase* obj) + { + const_cast<TRefCountedBase*>(obj)->DestroyRefCounted(); + } + + Y_FORCE_INLINE static void Deallocate(const TRefCountedBase* obj) + { + auto* ptr = reinterpret_cast<void**>(const_cast<TRefCountedBase*>(obj)); + auto [offset, ptrToDeleter] = UnpackPointer<void(void*, uint16_t)>(*ptr); + + // The most derived type is erased here. So we cannot call TMemoryReleaser with derived type. + ptrToDeleter(reinterpret_cast<char*>(ptr) - offset, offset); + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +Y_FORCE_INLINE const TRefCounter* GetRefCounter(const T* obj) +{ + return TRefCountedHelper<T>::GetRefCounter(obj); +} + +template <class T> +Y_FORCE_INLINE void DestroyRefCounted(const T* obj) +{ + TRefCountedHelper<T>::Destroy(obj); +} + +template <class T> +Y_FORCE_INLINE void DeallocateRefCounted(const T* obj) +{ + TRefCountedHelper<T>::Deallocate(obj); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +Y_FORCE_INLINE void Ref(T* obj) +{ + GetRefCounter(obj)->Ref(); +} + +template <class T> +Y_FORCE_INLINE void Unref(T* obj) +{ + if (GetRefCounter(obj)->Unref()) { + DestroyRefCounted(obj); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +Y_FORCE_INLINE void TRefCounted::Unref() const +{ + ::NYT::Unref(this); +} + +Y_FORCE_INLINE void TRefCounted::WeakUnref() const +{ + if (TRefCounter::WeakUnref()) { + DeallocateRefCounted(this); + } +} + + +template <class T> +void TRefCounted::DestroyRefCountedImpl(T* ptr) +{ + // No standard way to statically calculate the base offset even if T is final. + // static_cast<TFinalDerived*>(virtualBasePtr) does not work. + + auto* basePtr = static_cast<TRefCountedBase*>(ptr); + auto offset = reinterpret_cast<uintptr_t>(basePtr) - reinterpret_cast<uintptr_t>(ptr); + auto* refCounter = GetRefCounter(ptr); + + // No virtual call when T is final. + ptr->~T(); + + // Fast path. Weak refs cannot appear if there are neither strong nor weak refs. + if (refCounter->GetWeakRefCount() == 1) { + TMemoryReleaser<T>::Do(ptr, offset); + return; + } + + YT_ASSERT(offset < std::numeric_limits<uint16_t>::max()); + + auto* vTablePtr = reinterpret_cast<char**>(basePtr); + *vTablePtr = PackPointer(&TMemoryReleaser<T>::Do, offset); + + if (refCounter->WeakUnref()) { + TMemoryReleaser<T>::Do(ptr, offset); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/memory/ref_counted.h b/library/cpp/yt/memory/ref_counted.h new file mode 100644 index 0000000000..b683615b83 --- /dev/null +++ b/library/cpp/yt/memory/ref_counted.h @@ -0,0 +1,190 @@ +#pragma once + +#include <library/cpp/yt/misc/port.h> + +#include <library/cpp/yt/assert/assert.h> + +#include <library/cpp/ytalloc/api/ytalloc.h> + +#include <atomic> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +//! A technical base class for ref-counted objects and promise states. +class TRefCountedBase +{ +public: + TRefCountedBase() = default; + + // Make destructor protected + virtual ~TRefCountedBase() noexcept = default; + + virtual void DestroyRefCounted() = 0; + +private: + TRefCountedBase(const TRefCountedBase&) = delete; + TRefCountedBase(TRefCountedBase&&) = delete; + + TRefCountedBase& operator=(const TRefCountedBase&) = delete; + TRefCountedBase& operator=(TRefCountedBase&&) = delete; +}; + +//////////////////////////////////////////////////////////////////////////////// + +template <class T, class = void> +struct TFreeMemory +{ + static void Do(void* ptr) + { + NYTAlloc::FreeNonNull(ptr); + } +}; + +template <class T> +struct TFreeMemory<T, std::void_t<typename T::TAllocator>> +{ + static void Do(void* ptr) + { + using TAllocator = typename T::TAllocator; + TAllocator::Free(ptr); + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +class TRefCounter +{ +public: + //! Returns current number of strong references to the object. + /*! + * Note that you should never ever use this method in production code. + * This method is mainly for debugging purposes. + */ + int GetRefCount() const noexcept; + + //! Increments the strong reference counter. + void Ref() const noexcept; + + //! Increments the strong reference counter if it is not null. + bool TryRef() const noexcept; + + //! Decrements the strong reference counter. + bool Unref() const; + + //! Returns current number of weak references to the object. + int GetWeakRefCount() const noexcept; + + //! Increments the weak reference counter. + void WeakRef() const noexcept; + + //! Decrements the weak reference counter. + bool WeakUnref() const; + +private: + mutable std::atomic<int> StrongCount_ = 1; + mutable std::atomic<int> WeakCount_ = 1; +}; + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +const TRefCounter* GetRefCounter(const T* obj); + +template <class T> +void DestroyRefCounted(const T* obj); + +template <class T> +void DeallocateRefCounted(const T* obj); + +//////////////////////////////////////////////////////////////////////////////// + +// API + +template <class T> +void Ref(T* obj); + +template <class T> +void Unref(T* obj); + +//////////////////////////////////////////////////////////////////////////////// + +struct TRefCounted + : public TRefCountedBase + , public TRefCounter +{ + void Unref() const; + + void WeakUnref() const; + + template <class T> + static void DestroyRefCountedImpl(T* ptr); +}; + +//////////////////////////////////////////////////////////////////////////////// + +// Forward declaration. +template <class T> +class TIntrusivePtr; + +using TRefCountedPtr = TIntrusivePtr<TRefCounted>; + +// A bunch of helpful macros that enable working with intrusive pointers to incomplete types. +/* + * Typically when you have a forward-declared type |T| and an instance + * of |TIntrusivePtr<T>| you need the complete definition of |T| to work with + * the pointer even if you're not actually using the members of |T|. + * E.g. the dtor of |TIntrusivePtr<T>|, should you ever need it, must be able + * to unref an instance of |T| and eventually destroy it. + * This may force #inclusion of way more headers than really seems necessary. + * + * |DECLARE_REFCOUNTED_STRUCT|, |DECLARE_REFCOUNTED_CLASS|, and |DEFINE_REFCOUNTED_TYPE| + * alleviate this issue by forcing TIntrusivePtr to work with the free-standing overloads + * of |Ref| and |Unref| instead of their template version. + * These overloads are declared together with the forward declaration of |T| and + * are subsequently defined afterwards. + */ + +#define DECLARE_REFCOUNTED_TYPE(type) \ + using type ## Ptr = ::NYT::TIntrusivePtr<type>; \ + \ + [[maybe_unused]] ATTRIBUTE_USED const ::NYT::TRefCounter* GetRefCounter(const type* obj); \ + [[maybe_unused]] ATTRIBUTE_USED void DestroyRefCounted(const type* obj); \ + [[maybe_unused]] ATTRIBUTE_USED void DeallocateRefCounted(const type* obj); + +//! Forward-declares a class type, defines an intrusive pointer for it, and finally +//! declares Ref/Unref overloads. Use this macro in |public.h|-like files. +#define DECLARE_REFCOUNTED_CLASS(type) \ + class type; \ + DECLARE_REFCOUNTED_TYPE(type) + +//! Forward-declares a struct type, defines an intrusive pointer for it, and finally +//! declares Ref/Unref overloads. Use this macro in |public.h|-like files. +#define DECLARE_REFCOUNTED_STRUCT(type) \ + struct type; \ + DECLARE_REFCOUNTED_TYPE(type) + +//! Provides implementations for Ref/Unref overloads. Use this macro right +//! after the type's full definition. +#define DEFINE_REFCOUNTED_TYPE(type) \ + [[maybe_unused]] ATTRIBUTE_USED Y_FORCE_INLINE const ::NYT::TRefCounter* GetRefCounter(const type* obj) \ + { \ + return ::NYT::TRefCountedHelper<type>::GetRefCounter(obj); \ + } \ + [[maybe_unused]] ATTRIBUTE_USED Y_FORCE_INLINE void DestroyRefCounted(const type* obj) \ + { \ + ::NYT::TRefCountedHelper<type>::Destroy(obj); \ + } \ + [[maybe_unused]] ATTRIBUTE_USED Y_FORCE_INLINE void DeallocateRefCounted(const type* obj) \ + { \ + ::NYT::TRefCountedHelper<type>::Deallocate(obj); \ + } + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define REF_COUNTED_INL_H_ +#include "ref_counted-inl.h" +#undef REF_COUNTED_INL_H_ diff --git a/library/cpp/yt/memory/ref_tracked-inl.h b/library/cpp/yt/memory/ref_tracked-inl.h new file mode 100644 index 0000000000..4bde72881d --- /dev/null +++ b/library/cpp/yt/memory/ref_tracked-inl.h @@ -0,0 +1,49 @@ +#ifndef REF_TRACKED_INL_H_ +#error "Direct inclusion of this file is not allowed, include ref_tracked.h" +// For the sake of sane code completion. +#include "ref_tracked.h" +#endif + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +TRefCountedTypeKey GetRefCountedTypeKey() +{ + return &typeid(T); +} + +template <class T> +Y_FORCE_INLINE TRefCountedTypeCookie GetRefCountedTypeCookie() +{ + static std::atomic<TRefCountedTypeCookie> cookie{NullRefCountedTypeCookie}; + auto cookieValue = cookie.load(std::memory_order_relaxed); + if (Y_UNLIKELY(cookieValue == NullRefCountedTypeCookie)) { + cookieValue = TRefCountedTrackerFacade::GetCookie( + GetRefCountedTypeKey<T>(), + sizeof(T), + NYT::TSourceLocation()); + cookie.store(cookieValue, std::memory_order_relaxed); + } + return cookieValue; +} + +template <class T, class TTag, int Counter> +Y_FORCE_INLINE TRefCountedTypeCookie GetRefCountedTypeCookieWithLocation(const TSourceLocation& location) +{ + static std::atomic<TRefCountedTypeCookie> cookie{NullRefCountedTypeCookie}; + auto cookieValue = cookie.load(std::memory_order_relaxed); + if (Y_UNLIKELY(cookieValue == NullRefCountedTypeCookie)) { + cookieValue = TRefCountedTrackerFacade::GetCookie( + GetRefCountedTypeKey<T>(), + sizeof(T), + location); + cookie.store(cookieValue, std::memory_order_relaxed); + } + return cookieValue; +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/memory/ref_tracked.cpp b/library/cpp/yt/memory/ref_tracked.cpp new file mode 100644 index 0000000000..4dafbc0849 --- /dev/null +++ b/library/cpp/yt/memory/ref_tracked.cpp @@ -0,0 +1,38 @@ +#include "ref_tracked.h" + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +Y_WEAK TRefCountedTypeCookie TRefCountedTrackerFacade::GetCookie( + TRefCountedTypeKey /*typeKey*/, + size_t /*instanceSize*/, + const TSourceLocation& /*location*/) +{ + return NullRefCountedTypeCookie; +} + +Y_WEAK void TRefCountedTrackerFacade::AllocateInstance(TRefCountedTypeCookie /*cookie*/) +{ } + +Y_WEAK void TRefCountedTrackerFacade::FreeInstance(TRefCountedTypeCookie /*cookie*/) +{ } + +Y_WEAK void TRefCountedTrackerFacade::AllocateTagInstance(TRefCountedTypeCookie /*cookie*/) +{ } + +Y_WEAK void TRefCountedTrackerFacade::FreeTagInstance(TRefCountedTypeCookie /*cookie*/) +{ } + +Y_WEAK void TRefCountedTrackerFacade::AllocateSpace(TRefCountedTypeCookie /*cookie*/, size_t /*size*/) +{ } + +Y_WEAK void TRefCountedTrackerFacade::FreeSpace(TRefCountedTypeCookie /*cookie*/, size_t /*size*/) +{ } + +Y_WEAK void TRefCountedTrackerFacade::Dump() +{ } + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/memory/ref_tracked.h b/library/cpp/yt/memory/ref_tracked.h new file mode 100644 index 0000000000..75c1eb5985 --- /dev/null +++ b/library/cpp/yt/memory/ref_tracked.h @@ -0,0 +1,111 @@ +#pragma once + +#include <library/cpp/yt/misc/port.h> +#include <library/cpp/yt/misc/source_location.h> + +#include <util/system/defaults.h> + +#include <atomic> +#include <typeinfo> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +using TRefCountedTypeCookie = int; +const int NullRefCountedTypeCookie = -1; + +using TRefCountedTypeKey = const void*; + +//////////////////////////////////////////////////////////////////////////////// + +// Used to avoid including heavy ref_counted_tracker.h +class TRefCountedTrackerFacade +{ +public: + static TRefCountedTypeCookie GetCookie( + TRefCountedTypeKey typeKey, + size_t instanceSize, + const NYT::TSourceLocation& location); + + static void AllocateInstance(TRefCountedTypeCookie cookie); + static void FreeInstance(TRefCountedTypeCookie cookie); + + static void AllocateTagInstance(TRefCountedTypeCookie cookie); + static void FreeTagInstance(TRefCountedTypeCookie cookie); + + static void AllocateSpace(TRefCountedTypeCookie cookie, size_t size); + static void FreeSpace(TRefCountedTypeCookie cookie, size_t size); + + // Typically invoked from GDB console. + // Dumps the ref-counted statistics sorted by "bytes alive". + static void Dump(); +}; + +//////////////////////////////////////////////////////////////////////////////// + +namespace { + +//! A per-translation unit tag type. +struct TCurrentTranslationUnitTag +{ }; + +} // namespace + +template <class T> +TRefCountedTypeKey GetRefCountedTypeKey(); + +template <class T> +TRefCountedTypeCookie GetRefCountedTypeCookie(); + +template <class T, class TTag, int Counter> +TRefCountedTypeCookie GetRefCountedTypeCookieWithLocation( + const TSourceLocation& location); + +//////////////////////////////////////////////////////////////////////////////// + +//! A lightweight mix-in that integrates any class into TRefCountedTracker statistics. +/*! + * |T| must be the actual derived type. + * + * This mix-in provides statistical tracking only, |T| is responsible for implementing + * lifetime management on its own. + */ +template <class T> +class TRefTracked +{ +public: +#ifdef YT_ENABLE_REF_COUNTED_TRACKING + TRefTracked() + { + auto cookie = GetRefCountedTypeCookie<T>(); + TRefCountedTrackerFacade::AllocateInstance(cookie); + } + + TRefTracked(const TRefTracked&) + { + auto cookie = GetRefCountedTypeCookie<T>(); + TRefCountedTrackerFacade::AllocateInstance(cookie); + } + + TRefTracked(TRefTracked&&) + { + auto cookie = GetRefCountedTypeCookie<T>(); + TRefCountedTrackerFacade::AllocateInstance(cookie); + } + + ~TRefTracked() + { + auto cookie = GetRefCountedTypeCookie<T>(); + TRefCountedTrackerFacade::FreeInstance(cookie); + } +#endif +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define REF_TRACKED_INL_H_ +#include "ref_tracked-inl.h" +#undef REF_TRACKED_INL_H_ diff --git a/library/cpp/yt/memory/shared_range.h b/library/cpp/yt/memory/shared_range.h new file mode 100644 index 0000000000..9841d7a0df --- /dev/null +++ b/library/cpp/yt/memory/shared_range.h @@ -0,0 +1,297 @@ +#pragma once + +#include "intrusive_ptr.h" +#include "range.h" +#include "ref_counted.h" + +#include <library/cpp/yt/assert/assert.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T, size_t N> +class TCompactVector; + +//! TRange with ownership semantics. +template <class T> +class TSharedRange + : public TRange<T> +{ +public: + using THolderPtr = TRefCountedPtr; + + //! Constructs a null TSharedRange. + TSharedRange() + { } + + //! Constructs a TSharedRange from TRange. + TSharedRange(TRange<T> range, THolderPtr holder) + : TRange<T>(range) + , Holder_(std::move(holder)) + { } + + //! Constructs a TSharedRange from a pointer and length. + TSharedRange(const T* data, size_t length, THolderPtr holder) + : TRange<T>(data, length) + , Holder_(std::move(holder)) + { } + + //! Constructs a TSharedRange from a range. + TSharedRange(const T* begin, const T* end, THolderPtr holder) + : TRange<T>(begin, end) + , Holder_(std::move(holder)) + { } + + //! Constructs a TSharedRange from a TCompactVector. + template <size_t N> + TSharedRange(const TCompactVector<T, N>& elements, THolderPtr holder) + : TRange<T>(elements) + , Holder_(std::move(holder)) + { } + + //! Constructs a TSharedRange from an std::vector. + TSharedRange(const std::vector<T>& elements, THolderPtr holder) + : TRange<T>(elements) + , Holder_(std::move(holder)) + { } + + //! Constructs a TSharedRange from a C array. + template <size_t N> + TSharedRange(const T (& elements)[N], THolderPtr holder) + : TRange<T>(elements) + , Holder_(std::move(holder)) + { } + + + void Reset() + { + TRange<T>::Data_ = nullptr; + TRange<T>::Length_ = 0; + Holder_.Reset(); + } + + TSharedRange<T> Slice(size_t startOffset, size_t endOffset) const + { + YT_ASSERT(startOffset <= this->Size()); + YT_ASSERT(endOffset >= startOffset && endOffset <= this->Size()); + return TSharedRange<T>(this->Begin() + startOffset, endOffset - startOffset, Holder_); + } + + TSharedRange<T> Slice(const T* begin, const T* end) const + { + YT_ASSERT(begin >= this->Begin()); + YT_ASSERT(end <= this->End()); + return TSharedRange<T>(begin, end, Holder_); + } + + const THolderPtr& GetHolder() const + { + return Holder_; + } + + THolderPtr&& ReleaseHolder() + { + return std::move(Holder_); + } + +protected: + THolderPtr Holder_; + +}; + +//////////////////////////////////////////////////////////////////////////////// + +//! Constructs a combined holder instance by taking ownership of a given list of holders. +template <class... THolders> +TRefCountedPtr MakeCompositeHolder(THolders&&... holders) +{ + struct THolder + : public TRefCounted + { + std::tuple<typename std::decay<THolders>::type...> Holders; + }; + + auto holder = New<THolder>(); + holder->Holders = std::tuple<THolders...>(std::forward<THolders>(holders)...); + return holder; +} + +template <class T, class TContainer, class... THolders> +TSharedRange<T> DoMakeSharedRange(TContainer&& elements, THolders&&... holders) +{ + struct THolder + : public TRefCounted + { + typename std::decay<TContainer>::type Elements; + std::tuple<typename std::decay<THolders>::type...> Holders; + }; + + auto holder = New<THolder>(); + holder->Holders = std::tuple<THolders...>(std::forward<THolders>(holders)...); + holder->Elements = std::forward<TContainer>(elements); + + auto range = MakeRange<T>(holder->Elements); + + return TSharedRange<T>(range, std::move(holder)); +} + +//! Constructs a TSharedRange by taking ownership of an std::vector. +template <class T, class... THolders> +TSharedRange<T> MakeSharedRange(std::vector<T>&& elements, THolders&&... holders) +{ + return DoMakeSharedRange<T>(std::move(elements), std::forward<THolders>(holders)...); +} + +//! Constructs a TSharedRange by taking ownership of an TCompactVector. +template <class T, size_t N, class... THolders> +TSharedRange<T> MakeSharedRange(TCompactVector<T, N>&& elements, THolders&&... holders) +{ + return DoMakeSharedRange<T>(std::move(elements), std::forward<THolders>(holders)...); +} + +//! Constructs a TSharedRange by copying an std::vector. +template <class T, class... THolders> +TSharedRange<T> MakeSharedRange(const std::vector<T>& elements, THolders&&... holders) +{ + return DoMakeSharedRange<T>(elements, std::forward<THolders>(holders)...); +} + +template <class T, class... THolders> +TSharedRange<T> MakeSharedRange(TRange<T> range, THolders&&... holders) +{ + return TSharedRange<T>(range, MakeCompositeHolder(std::forward<THolders>(holders)...)); +} + +template <class T, class THolder> +TSharedRange<T> MakeSharedRange(TRange<T> range, TIntrusivePtr<THolder> holder) +{ + return TSharedRange<T>(range, std::move(holder)); +} + +template <class U, class T> +TSharedRange<U> ReinterpretCastRange(const TSharedRange<T>& range) +{ + static_assert(sizeof(T) == sizeof(U), "T and U must have equal sizes."); + return TSharedRange<U>(reinterpret_cast<const U*>(range.Begin()), range.Size(), range.GetHolder()); +}; + +//////////////////////////////////////////////////////////////////////////////// + +//! TMutableRange with ownership semantics. +//! Use with caution :) +template <class T> +class TSharedMutableRange + : public TMutableRange<T> +{ +public: + using THolderPtr = TRefCountedPtr; + + //! Constructs a null TSharedMutableRange. + TSharedMutableRange() + { } + + //! Constructs a TSharedMutableRange from TMutableRange. + TSharedMutableRange(TMutableRange<T> range, THolderPtr holder) + : TMutableRange<T>(range) + , Holder_(std::move(holder)) + { } + + //! Constructs a TSharedMutableRange from a pointer and length. + TSharedMutableRange(T* data, size_t length, THolderPtr holder) + : TMutableRange<T>(data, length) + , Holder_(std::move(holder)) + { } + + //! Constructs a TSharedMutableRange from a range. + TSharedMutableRange(T* begin, T* end, THolderPtr holder) + : TMutableRange<T>(begin, end) + , Holder_(std::move(holder)) + { } + + //! Constructs a TSharedMutableRange from a TCompactVector. + template <size_t N> + TSharedMutableRange(TCompactVector<T, N>& elements, THolderPtr holder) + : TMutableRange<T>(elements) + , Holder_(std::move(holder)) + { } + + //! Constructs a TSharedMutableRange from an std::vector. + TSharedMutableRange(std::vector<T>& elements, THolderPtr holder) + : TMutableRange<T>(elements) + , Holder_(std::move(holder)) + { } + + //! Constructs a TSharedMutableRange from a C array. + template <size_t N> + TSharedMutableRange(T (& elements)[N], THolderPtr holder) + : TMutableRange<T>(elements) + , Holder_(std::move(holder)) + { } + + + void Reset() + { + TRange<T>::Data_ = nullptr; + TRange<T>::Length_ = 0; + Holder_.Reset(); + } + + TSharedMutableRange<T> Slice(size_t startOffset, size_t endOffset) const + { + YT_ASSERT(startOffset <= this->Size()); + YT_ASSERT(endOffset >= startOffset && endOffset <= this->Size()); + return TSharedMutableRange<T>(this->Begin() + startOffset, endOffset - startOffset, Holder_); + } + + TSharedMutableRange<T> Slice(T* begin, T* end) const + { + YT_ASSERT(begin >= this->Begin()); + YT_ASSERT(end <= this->End()); + return TSharedMutableRange<T>(begin, end, Holder_); + } + + THolderPtr GetHolder() const + { + return Holder_; + } + + THolderPtr&& ReleaseHolder() + { + return std::move(Holder_); + } + +protected: + THolderPtr Holder_; + +}; + +template <class T, class TContainer, class... THolders> +TSharedMutableRange<T> DoMakeSharedMutableRange(TContainer&& elements, THolders&&... holders) +{ + struct THolder + : public TRefCounted + { + typename std::decay<TContainer>::type Elements; + std::tuple<typename std::decay<THolders>::type...> Holders; + }; + + auto holder = New<THolder>(); + holder->Holders = std::tuple<THolders...>(std::forward<THolders>(holders)...); + holder->Elements = std::forward<TContainer>(elements); + + auto range = TMutableRange<T>(holder->Elements); + + return TSharedMutableRange<T>(range, holder); +} + +//! Constructs a TSharedMutableRange by taking ownership of an std::vector. +template <class T, class... THolders> +TSharedMutableRange<T> MakeSharedMutableRange(std::vector<T>&& elements, THolders&&... holders) +{ + return DoMakeSharedMutableRange<T>(std::move(elements), std::forward<THolders>(holders)...); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/memory/unittests/intrusive_ptr_ut.cpp b/library/cpp/yt/memory/unittests/intrusive_ptr_ut.cpp new file mode 100644 index 0000000000..622bed0eb0 --- /dev/null +++ b/library/cpp/yt/memory/unittests/intrusive_ptr_ut.cpp @@ -0,0 +1,562 @@ +#include <library/cpp/testing/gtest/gtest.h> + +#include <library/cpp/yt/memory/new.h> +#include <library/cpp/yt/memory/ref_counted.h> + +namespace NYT { +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +using ::testing::IsNull; +using ::testing::NotNull; +using ::testing::InSequence; +using ::testing::MockFunction; +using ::testing::StrictMock; + +//////////////////////////////////////////////////////////////////////////////// +// Auxiliary types and functions. +//////////////////////////////////////////////////////////////////////////////// + +// This object tracks number of increments and decrements +// to the reference counter (see traits specialization below). +struct TIntricateObject + : private TNonCopyable +{ + int Increments = 0; + int Decrements = 0; + int Zeros = 0; + + void Ref() + { + ++Increments; + } + + void Unref() + { + ++Decrements; + if (Increments == Decrements) { + ++Zeros; + } + } +}; + +typedef TIntrusivePtr<TIntricateObject> TIntricateObjectPtr; + +void Ref(TIntricateObject* obj) +{ + obj->Ref(); +} + +void Unref(TIntricateObject* obj) +{ + obj->Unref(); +} + +MATCHER_P3(HasRefCounts, increments, decrements, zeros, + "Reference counter " \ + "was incremented " + ::testing::PrintToString(increments) + " times, " + + "was decremented " + ::testing::PrintToString(decrements) + " times, " + + "vanished to zero " + ::testing::PrintToString(zeros) + " times") +{ + Y_UNUSED(result_listener); + return + arg.Increments == increments && + arg.Decrements == decrements && + arg.Zeros == zeros; +} + +void PrintTo(const TIntricateObject& arg, ::std::ostream* os) +{ + *os << arg.Increments << " increments, " + << arg.Decrements << " decrements and " + << arg.Zeros << " times vanished"; +} + +// This is an object which creates intrusive pointers to the self +// during its construction. +class TObjectWithSelfPointers + : public TRefCounted +{ +public: + explicit TObjectWithSelfPointers(IOutputStream* output) + : Output_(output) + { + *Output_ << "Cb"; + + for (int i = 0; i < 3; ++i) { + *Output_ << '!'; + TIntrusivePtr<TObjectWithSelfPointers> ptr(this); + } + + *Output_ << "Ca"; + } + + virtual ~TObjectWithSelfPointers() + { + *Output_ << 'D'; + } + +private: + IOutputStream* const Output_; + +}; + +// This is a simple object with simple reference counting. +class TObjectWithSimpleRC + : public TRefCounted +{ +public: + explicit TObjectWithSimpleRC(IOutputStream* output) + : Output_(output) + { + *Output_ << 'C'; + } + + virtual ~TObjectWithSimpleRC() + { + *Output_ << 'D'; + } + + void DoSomething() + { + *Output_ << '!'; + } + +private: + IOutputStream* const Output_; + +}; + +// This is a simple object with full-fledged reference counting. +class TObjectWithFullRC + : public TRefCounted +{ +public: + explicit TObjectWithFullRC(IOutputStream* output) + : Output_(output) + { + *Output_ << 'C'; + } + + virtual ~TObjectWithFullRC() + { + *Output_ << 'D'; + } + + void DoSomething() + { + *Output_ << '!'; + } + +private: + IOutputStream* const Output_; + +}; + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TIntrusivePtrTest, Empty) +{ + TIntricateObjectPtr emptyPointer; + EXPECT_EQ(nullptr, emptyPointer.Get()); +} + +TEST(TIntrusivePtrTest, Basic) +{ + TIntricateObject object; + + EXPECT_THAT(object, HasRefCounts(0, 0, 0)); + + { + TIntricateObjectPtr owningPointer(&object); + EXPECT_THAT(object, HasRefCounts(1, 0, 0)); + EXPECT_EQ(&object, owningPointer.Get()); + } + + EXPECT_THAT(object, HasRefCounts(1, 1, 1)); + + { + TIntricateObjectPtr nonOwningPointer(&object, false); + EXPECT_THAT(object, HasRefCounts(1, 1, 1)); + EXPECT_EQ(&object, nonOwningPointer.Get()); + } + + EXPECT_THAT(object, HasRefCounts(1, 2, 1)); +} + +TEST(TIntrusivePtrTest, ResetToNull) +{ + TIntricateObject object; + TIntricateObjectPtr ptr(&object); + + EXPECT_THAT(object, HasRefCounts(1, 0, 0)); + EXPECT_EQ(&object, ptr.Get()); + + ptr.Reset(); + + EXPECT_THAT(object, HasRefCounts(1, 1, 1)); + EXPECT_EQ(nullptr, ptr.Get()); +} + +TEST(TIntrusivePtrTest, ResetToOtherObject) +{ + TIntricateObject firstObject; + TIntricateObject secondObject; + + TIntricateObjectPtr ptr(&firstObject); + + EXPECT_THAT(firstObject, HasRefCounts(1, 0, 0)); + EXPECT_THAT(secondObject, HasRefCounts(0, 0, 0)); + EXPECT_EQ(&firstObject, ptr.Get()); + + ptr.Reset(&secondObject); + + EXPECT_THAT(firstObject, HasRefCounts(1, 1, 1)); + EXPECT_THAT(secondObject, HasRefCounts(1, 0, 0)); + EXPECT_EQ(&secondObject, ptr.Get()); +} + +TEST(TIntrusivePtrTest, CopySemantics) +{ + TIntricateObject object; + + TIntricateObjectPtr foo(&object); + EXPECT_THAT(object, HasRefCounts(1, 0, 0)); + + { + TIntricateObjectPtr bar(foo); + EXPECT_THAT(object, HasRefCounts(2, 0, 0)); + EXPECT_EQ(&object, foo.Get()); + EXPECT_EQ(&object, bar.Get()); + } + + EXPECT_THAT(object, HasRefCounts(2, 1, 0)); + + { + TIntricateObjectPtr bar; + bar = foo; + + EXPECT_THAT(object, HasRefCounts(3, 1, 0)); + EXPECT_EQ(&object, foo.Get()); + EXPECT_EQ(&object, bar.Get()); + } + + EXPECT_THAT(object, HasRefCounts(3, 2, 0)); +} + +TEST(TIntrusivePtrTest, MoveSemantics) +{ + TIntricateObject object; + + TIntricateObjectPtr foo(&object); + EXPECT_THAT(object, HasRefCounts(1, 0, 0)); + + { + TIntricateObjectPtr bar(std::move(foo)); + EXPECT_THAT(object, HasRefCounts(1, 0, 0)); + EXPECT_THAT(foo.Get(), IsNull()); + EXPECT_EQ(&object, bar.Get()); + } + + EXPECT_THAT(object, HasRefCounts(1, 1, 1)); + foo.Reset(&object); + EXPECT_THAT(object, HasRefCounts(2, 1, 1)); + + { + TIntricateObjectPtr bar; + bar = std::move(foo); + EXPECT_THAT(object, HasRefCounts(2, 1, 1)); + EXPECT_THAT(foo.Get(), IsNull()); + EXPECT_EQ(&object, bar.Get()); + } +} + +TEST(TIntrusivePtrTest, Swap) +{ + TIntricateObject object; + + TIntricateObjectPtr foo(&object); + TIntricateObjectPtr bar; + + EXPECT_THAT(object, HasRefCounts(1, 0, 0)); + EXPECT_THAT(foo.Get(), NotNull()); + EXPECT_THAT(bar.Get(), IsNull()); + + foo.Swap(bar); + + EXPECT_THAT(object, HasRefCounts(1, 0, 0)); + EXPECT_THAT(foo.Get(), IsNull()); + EXPECT_THAT(bar.Get(), NotNull()); + + foo.Swap(bar); + + EXPECT_THAT(object, HasRefCounts(1, 0, 0)); + EXPECT_THAT(foo.Get(), NotNull()); + EXPECT_THAT(bar.Get(), IsNull()); +} + +TEST(TIntrusivePtrTest, UpCast) +{ + //! This is a simple typical reference-counted object. + class TSimpleObject + : public TRefCounted + { }; + + //! This is a simple inherited reference-counted object. + class TAnotherObject + : public TSimpleObject + { }; + + auto foo = New<TSimpleObject>(); + auto bar = New<TAnotherObject>(); + auto baz = New<TAnotherObject>(); + + foo = baz; + + EXPECT_TRUE(foo == baz); +} + +TEST(TIntrusivePtrTest, DownCast) +{ + class TBaseObject + : public TRefCounted + { }; + + class TDerivedObject + : public TBaseObject + { }; + + //! This is a simple inherited reference-counted object. + class TAnotherObject + : public TBaseObject + { }; + + TIntrusivePtr<TBaseObject> foo = New<TDerivedObject>(); + TIntrusivePtr<TBaseObject> bar = New<TAnotherObject>(); + { + auto baz = StaticPointerCast<TDerivedObject>(foo); + EXPECT_TRUE(foo == baz); + } + { + auto baz = StaticPointerCast<TDerivedObject>(TIntrusivePtr<TBaseObject>{foo}); + EXPECT_TRUE(foo == baz); + } + { + auto baz = DynamicPointerCast<TDerivedObject>(foo); + EXPECT_TRUE(foo == baz); + } + { + auto baz = DynamicPointerCast<TDerivedObject>(bar); + EXPECT_TRUE(nullptr == baz); + } + { + auto baz = ConstPointerCast<const TBaseObject>(foo); + EXPECT_TRUE(foo.Get() == baz.Get()); + } + { + auto baz = ConstPointerCast<const TBaseObject>(TIntrusivePtr<TBaseObject>{foo}); + EXPECT_TRUE(foo.Get() == baz.Get()); + } +} + +TEST(TIntrusivePtrTest, UnspecifiedBoolType) +{ + TIntricateObject object; + + TIntricateObjectPtr foo; + TIntricateObjectPtr bar(&object); + + EXPECT_FALSE(foo); + EXPECT_TRUE(bar); +} + +TEST(TIntrusivePtrTest, ObjectIsNotDestroyedPrematurely) +{ + TStringStream output; + New<TObjectWithSelfPointers>(&output); + + // TObject... appends symbols to the output; see definitions. + EXPECT_STREQ("Cb!!!CaD", output.Str().c_str()); +} + +TEST(TIntrusivePtrTest, EqualityOperator) +{ + TIntricateObject object, anotherObject; + + TIntricateObjectPtr emptyPointer; + TIntricateObjectPtr somePointer(&object); + TIntricateObjectPtr samePointer(&object); + TIntricateObjectPtr anotherPointer(&anotherObject); + + EXPECT_FALSE(somePointer == emptyPointer); + EXPECT_FALSE(samePointer == emptyPointer); + + EXPECT_TRUE(somePointer != emptyPointer); + EXPECT_TRUE(samePointer != emptyPointer); + + EXPECT_TRUE(somePointer == samePointer); + + EXPECT_TRUE(&object == somePointer); + EXPECT_TRUE(&object == samePointer); + + EXPECT_FALSE(somePointer == anotherPointer); + EXPECT_TRUE(somePointer != anotherPointer); + + EXPECT_TRUE(&anotherObject == anotherPointer); +} + +TEST(TIntrusivePtrTest, Reset) +{ + TIntricateObject object; + TIntricateObjectPtr pointer(&object); + EXPECT_THAT(object, HasRefCounts(1, 0, 0)); + EXPECT_EQ(&object, pointer.Release()); + EXPECT_THAT(object, HasRefCounts(1, 0, 0)); +} + +TEST(TIntrusivePtrTest, CompareWithNullptr) +{ + TIntricateObjectPtr pointer1; + EXPECT_TRUE(nullptr == pointer1); + EXPECT_FALSE(nullptr != pointer1); + TIntricateObject object; + TIntricateObjectPtr pointer2(&object); + EXPECT_TRUE(pointer2 != nullptr); + EXPECT_FALSE(pointer2 == nullptr); +} + + +template <class T> +void TestIntrusivePtrBehavior() +{ + typedef TIntrusivePtr<T> TMyPtr; + + TStringStream output; + { + TMyPtr ptr(New<T>(&output)); + { + TMyPtr anotherPtr(ptr); + anotherPtr->DoSomething(); + } + { + TMyPtr anotherPtr(ptr); + anotherPtr->DoSomething(); + } + ptr->DoSomething(); + } + + // TObject... appends symbols to the output; see definitions. + EXPECT_STREQ("C!!!D", output.Str().c_str()); +} + +TEST(TIntrusivePtrTest, SimpleRCBehaviour) +{ + TestIntrusivePtrBehavior<TObjectWithSimpleRC>(); +} + +TEST(TIntrusivePtrTest, FullRCBehaviour) +{ + TestIntrusivePtrBehavior<TObjectWithFullRC>(); +} + +TEST(TIntrusivePtrTest, ObjectAlignment) +{ + struct TObject + : public TRefCounted + { + alignas(64) ui64 Data; + }; + + struct TPODObject final + { + alignas(64) ui64 Data; + }; + + auto foo = New<TObject>(); + auto bar = New<TPODObject>(); + + EXPECT_TRUE(reinterpret_cast<uintptr_t>(foo.Get()) % 64 == 0); + EXPECT_TRUE(reinterpret_cast<uintptr_t>(bar.Get()) % 64 == 0); +} + +TEST(TIntrusivePtrTest, InitStruct) +{ + struct TObj1 final + { + const int A; + const int B; + }; + + New<TObj1>(1, 2); + + struct TExplicitObj final + { + explicit TExplicitObj(int a = 0) + : A(a) + { } + + const int A; + }; + + New<TExplicitObj>(); + New<TExplicitObj>(1); + + struct TObj2 final + { + TObj2(i64 a = 0) + : A(a) + { } + + const i64 A; + }; + + New<TObj2>(123); + + struct TObj3 final + { + TObj3(ui64 a = 0) + : A(a) + { } + + const ui64 A; + }; + + New<TObj3>(123); + + struct TObj4 final + { + TObj4(int a, ui64 b = 0) + : A(a) + , B(b) + { } + + int A; + const ui64 B; + }; + + New<TObj4>(123); + New<TObj4>(123, 123); + + struct TObj5 final + { + TExplicitObj E; + int B; + }; + + New<TObj5>(); + + struct TObj6 final + { + TObj2 O; + int B; + }; + + New<TObj6>(); + New<TObj6>(1, 2); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace +} // namespace NYT diff --git a/library/cpp/yt/memory/unittests/weak_ptr_ut.cpp b/library/cpp/yt/memory/unittests/weak_ptr_ut.cpp new file mode 100644 index 0000000000..180c16b5ca --- /dev/null +++ b/library/cpp/yt/memory/unittests/weak_ptr_ut.cpp @@ -0,0 +1,433 @@ +#include <library/cpp/testing/gtest/gtest.h> + +#include <library/cpp/yt/memory/new.h> +#include <library/cpp/yt/memory/weak_ptr.h> + +#include <array> + +namespace NYT { +namespace { + +using ::testing::IsNull; +using ::testing::NotNull; +using ::testing::InSequence; +using ::testing::MockFunction; +using ::testing::StrictMock; + +//////////////////////////////////////////////////////////////////////////////// +// Auxiliary types and functions. +//////////////////////////////////////////////////////////////////////////////// + +static int ConstructorShadowState = 0; +static int DestructorShadowState = 0; + +void ResetShadowState() +{ + ConstructorShadowState = 0; + DestructorShadowState = 0; +} + +class TIntricateObject + : public TRefCounted +{ +public: + TIntricateObject() + { + ++ConstructorShadowState; + } + + virtual ~TIntricateObject() + { + ++DestructorShadowState; + } + + // Prevent the counter from destruction by holding an additional + // reference to the counter. + void LockCounter() + { + WeakRef(); + } + + // Release an additional reference to the reference counter acquired by + // #LockCounter(). + void UnlockCounter() + { + WeakUnref(); + } + +private: + // Explicitly non-copyable. + TIntricateObject(const TIntricateObject&); + TIntricateObject(TIntricateObject&&); + TIntricateObject& operator=(const TIntricateObject&); + TIntricateObject& operator=(TIntricateObject&&); +}; + +typedef TIntrusivePtr<TIntricateObject> TIntricateObjectPtr; +typedef TWeakPtr<TIntricateObject> TIntricateObjectWkPtr; + +class TDerivedIntricateObject + : public TIntricateObject +{ +private: + // Payload. + [[maybe_unused]] std::array<char, 32> Payload; +}; + +typedef TIntrusivePtr<TDerivedIntricateObject> TDerivedIntricateObjectPtr; +typedef TWeakPtr<TDerivedIntricateObject> TDerivedIntricateObjectWkPtr; + +MATCHER_P2(HasRefCounts, strongRefs, weakRefs, + "The object has " + + ::testing::PrintToString(strongRefs) + " strong and " + + ::testing::PrintToString(weakRefs) + " weak references") +{ + Y_UNUSED(result_listener); + return + arg.GetRefCount() == strongRefs && + arg.GetWeakRefCount() == weakRefs; +} + +template <class T> +void PrintExtrinsicRefCounted(const T& arg, ::std::ostream* os) +{ + *os << arg.GetRefCount() << " strong and " + << arg.GetWeakRefCount() << " weak references"; +} + +void PrintTo(const TIntricateObject& arg, ::std::ostream* os) +{ + PrintExtrinsicRefCounted(arg, os); +} + +//////////////////////////////////////////////////////////////////////////////// + +class TWeakPtrTest + : public ::testing::Test +{ +public: + virtual void SetUp() + { + ResetShadowState(); + } +}; + +TEST_F(TWeakPtrTest, Empty) +{ + TIntricateObjectWkPtr emptyPointer; + EXPECT_EQ(TIntricateObjectPtr(), emptyPointer.Lock()); +} + +TEST_F(TWeakPtrTest, Basic) +{ + TIntricateObjectPtr object = New<TIntricateObject>(); + TIntricateObject* objectPtr = object.Get(); + + EXPECT_THAT(*object, HasRefCounts(1, 1)); + + { + TIntricateObjectWkPtr ptr(objectPtr); + EXPECT_THAT(*object, HasRefCounts(1, 2)); + EXPECT_EQ(object, ptr.Lock()); + } + + EXPECT_THAT(*object, HasRefCounts(1, 1)); + + { + TIntricateObjectWkPtr ptr(object); + EXPECT_THAT(*object, HasRefCounts(1, 2)); + EXPECT_EQ(object, ptr.Lock()); + } + + EXPECT_THAT(*object, HasRefCounts(1, 1)); + + object.Reset(); + + EXPECT_EQ(1, ConstructorShadowState); + EXPECT_EQ(1, DestructorShadowState); +} + +TEST_F(TWeakPtrTest, ResetToNull) +{ + TIntricateObjectPtr object = New<TIntricateObject>(); + TIntricateObjectWkPtr ptr(object); + + EXPECT_THAT(*object, HasRefCounts(1, 2)); + EXPECT_EQ(object, ptr.Lock()); + + ptr.Reset(); + + EXPECT_THAT(*object, HasRefCounts(1, 1)); + EXPECT_EQ(TIntricateObjectPtr(), ptr.Lock()); +} + +TEST_F(TWeakPtrTest, ResetToOtherObject) +{ + TIntricateObjectPtr firstObject = New<TIntricateObject>(); + TIntricateObjectPtr secondObject = New<TIntricateObject>(); + + { + TIntricateObjectWkPtr ptr(firstObject); + + EXPECT_THAT(*firstObject, HasRefCounts(1, 2)); + EXPECT_THAT(*secondObject, HasRefCounts(1, 1)); + EXPECT_EQ(firstObject, ptr.Lock()); + + ptr.Reset(secondObject); + + EXPECT_THAT(*firstObject, HasRefCounts(1, 1)); + EXPECT_THAT(*secondObject, HasRefCounts(1, 2)); + EXPECT_EQ(secondObject, ptr.Lock()); + } + + TIntricateObject* firstObjectPtr = firstObject.Get(); + TIntricateObject* secondObjectPtr = secondObject.Get(); + + { + TIntricateObjectWkPtr ptr(firstObjectPtr); + + EXPECT_THAT(*firstObject, HasRefCounts(1, 2)); + EXPECT_THAT(*secondObject, HasRefCounts(1, 1)); + EXPECT_EQ(firstObject, ptr.Lock()); + + ptr.Reset(secondObjectPtr); + + EXPECT_THAT(*firstObject, HasRefCounts(1, 1)); + EXPECT_THAT(*secondObject, HasRefCounts(1, 2)); + EXPECT_EQ(secondObject, ptr.Lock()); + } +} + +TEST_F(TWeakPtrTest, CopySemantics) +{ + TIntricateObjectPtr object = New<TIntricateObject>(); + TIntricateObjectWkPtr foo(object); + + { + EXPECT_THAT(*object, HasRefCounts(1, 2)); + TIntricateObjectWkPtr bar(foo); + EXPECT_THAT(*object, HasRefCounts(1, 3)); + + EXPECT_EQ(object, foo.Lock()); + EXPECT_EQ(object, bar.Lock()); + } + + { + EXPECT_THAT(*object, HasRefCounts(1, 2)); + TIntricateObjectWkPtr bar; + bar = foo; + EXPECT_THAT(*object, HasRefCounts(1, 3)); + + EXPECT_EQ(object, foo.Lock()); + EXPECT_EQ(object, bar.Lock()); + } +} + +TEST_F(TWeakPtrTest, MoveSemantics) +{ + TIntricateObjectPtr object = New<TIntricateObject>(); + TIntricateObjectWkPtr foo(object); + + { + EXPECT_THAT(*object, HasRefCounts(1, 2)); + TIntricateObjectWkPtr bar(std::move(foo)); + EXPECT_THAT(*object, HasRefCounts(1, 2)); + + EXPECT_EQ(TIntricateObjectPtr(), foo.Lock()); + EXPECT_EQ(object, bar.Lock()); + } + + foo.Reset(object); + + { + EXPECT_THAT(*object, HasRefCounts(1, 2)); + TIntricateObjectWkPtr bar; + bar = std::move(foo); + EXPECT_THAT(*object, HasRefCounts(1, 2)); + + EXPECT_EQ(TIntricateObjectPtr(), foo.Lock()); + EXPECT_EQ(object, bar.Lock()); + } +} + +TEST_F(TWeakPtrTest, OutOfScope) +{ + TIntricateObjectWkPtr ptr; + + EXPECT_EQ(TIntricateObjectPtr(), ptr.Lock()); + { + TIntricateObjectPtr object = New<TIntricateObject>(); + ptr = object; + EXPECT_EQ(object, ptr.Lock()); + } + EXPECT_EQ(TIntricateObjectPtr(), ptr.Lock()); +} + +TEST_F(TWeakPtrTest, OutOfNestedScope) +{ + TIntricateObjectWkPtr foo; + + EXPECT_EQ(TIntricateObjectPtr(), foo.Lock()); + { + TIntricateObjectPtr object = New<TIntricateObject>(); + foo = object; + + EXPECT_EQ(object, foo.Lock()); + { + TIntricateObjectWkPtr bar; + bar = object; + + EXPECT_EQ(object, bar.Lock()); + } + EXPECT_EQ(object, foo.Lock()); + } + EXPECT_EQ(TIntricateObjectPtr(), foo.Lock()); + + EXPECT_EQ(1, ConstructorShadowState); + EXPECT_EQ(1, DestructorShadowState); +} + +TEST_F(TWeakPtrTest, IsExpired) +{ + TIntricateObjectWkPtr ptr; + + EXPECT_TRUE(ptr.IsExpired()); + { + TIntricateObjectPtr object = New<TIntricateObject>(); + ptr = object; + EXPECT_FALSE(ptr.IsExpired()); + } + EXPECT_TRUE(ptr.IsExpired()); +} + +TEST_F(TWeakPtrTest, UpCast) +{ + TDerivedIntricateObjectPtr object = New<TDerivedIntricateObject>(); + TIntricateObjectWkPtr ptr = object; + + EXPECT_EQ(object.Get(), ptr.Lock().Get()); +} + +class TIntricateObjectVirtual + : public virtual TRefCounted +{ +public: + TIntricateObjectVirtual() + { + ++ConstructorShadowState; + } + + virtual ~TIntricateObjectVirtual() + { + ++DestructorShadowState; + } + + // Prevent the counter from destruction by holding an additional + // reference to the counter. + void LockCounter() + { + WeakRef(); + } + + // Release an additional reference to the reference counter acquired by + // #LockCounter(). + void UnlockCounter() + { + WeakUnref(); + } + +private: + // Explicitly non-copyable. + TIntricateObjectVirtual(const TIntricateObjectVirtual&); + TIntricateObjectVirtual(TIntricateObjectVirtual&&); + TIntricateObjectVirtual& operator=(const TIntricateObjectVirtual&); + TIntricateObjectVirtual& operator=(TIntricateObjectVirtual&&); +}; + +TEST_F(TWeakPtrTest, VirtualBase) +{ + auto object = New<TIntricateObjectVirtual>(); + TWeakPtr<TIntricateObjectVirtual> ptr = object; + + object.Reset(); + ptr.Reset(); +} + +#if 0 +class TSlowlyDyingObject + : public TRefCounted +{ +public: + TSlowlyDyingObject() + { + ++ConstructorShadowState; + } + + virtual ~TSlowlyDyingObject() + { + ++DestructorShadowState; + DeathEvent->Wait(); + ++DestructorShadowState; + } +}; + +void PrintTo(const TSlowlyDyingObject& arg, ::std::ostream* os) +{ + PrintExtrinsicRefCounted(arg, os); +} + +typedef TIntrusivePtr<TSlowlyDyingObject> TSlowlyDyingObjectPtr; +typedef TWeakPtr<TSlowlyDyingObject> TSlowlyDyingObjectWkPtr; + +static void* AsynchronousDeleter(void* param) +{ + TSlowlyDyingObjectPtr* indirectObject = + reinterpret_cast<TSlowlyDyingObjectPtr*>(param); + indirectObject->Reset(); + return nullptr; +} + +std::unique_ptr<NThreading::TEvent> DeathEvent; + +TEST_F(TWeakPtrTest, DISABLED_AcquisionOfSlowlyDyingObject) +{ + DeathEvent.reset(new NThreading::TEvent()); + + TSlowlyDyingObjectPtr object = New<TSlowlyDyingObject>(); + TSlowlyDyingObjectWkPtr ptr(object); + + TSlowlyDyingObject* objectPtr = object.Get(); + + EXPECT_EQ(object, ptr.Lock()); + EXPECT_THAT(*objectPtr, HasRefCounts(1, 2)); + + ASSERT_EQ(1, ConstructorShadowState); + ASSERT_EQ(0, DestructorShadowState); + + // Kick off object deletion in the background. + TThread thread(&AsynchronousDeleter, &object); + thread.Start(); + Sleep(TDuration::Seconds(0.100)); + + ASSERT_EQ(1, ConstructorShadowState); + ASSERT_EQ(1, DestructorShadowState); + + EXPECT_EQ(TSlowlyDyingObjectPtr(), ptr.Lock()); + EXPECT_THAT(*objectPtr, HasRefCounts(0, 2)); + + // Finalize object destruction. + DeathEvent->NotifyAll(); + thread.Join(); + + ASSERT_EQ(1, ConstructorShadowState); + ASSERT_EQ(2, DestructorShadowState); + + EXPECT_EQ(TSlowlyDyingObjectPtr(), ptr.Lock()); +} + +#endif + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace +} // namespace NYT diff --git a/library/cpp/yt/memory/unittests/ya.make b/library/cpp/yt/memory/unittests/ya.make new file mode 100644 index 0000000000..f09ad7d0c9 --- /dev/null +++ b/library/cpp/yt/memory/unittests/ya.make @@ -0,0 +1,19 @@ +GTEST(unittester-library-memory) + +OWNER(g:yt) + +IF (NOT OS_WINDOWS) + ALLOCATOR(YT) +ENDIF() + +SRCS( + intrusive_ptr_ut.cpp + weak_ptr_ut.cpp +) + +PEERDIR( + library/cpp/testing/gtest + library/cpp/yt/memory +) + +END() diff --git a/library/cpp/yt/memory/weak_ptr.h b/library/cpp/yt/memory/weak_ptr.h new file mode 100644 index 0000000000..25a242bb8a --- /dev/null +++ b/library/cpp/yt/memory/weak_ptr.h @@ -0,0 +1,314 @@ +#pragma once + +#include "ref_counted.h" + +#include <util/generic/hash.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +class TWeakPtr +{ +public: + typedef T TUnderlying; + + //! Empty constructor. + TWeakPtr() = default; + + TWeakPtr(std::nullptr_t) + { } + + //! Constructor from an unqualified reference. + /*! + * Note that this constructor could be racy due to unsynchronized operations + * on the object and on the counter. + */ + explicit TWeakPtr(T* p) noexcept + : T_(p) + { + +#if defined(_tsan_enabled_) + if (T_) { + RefCounter_ = GetRefCounter(T_); + } +#endif + AcquireRef(); + } + + //! Constructor from a strong reference. + TWeakPtr(const TIntrusivePtr<T>& ptr) noexcept + : TWeakPtr(ptr.Get()) + { } + + //! Constructor from a strong reference with an upcast. + template <class U, class = typename std::enable_if_t<std::is_convertible_v<U*, T*>>> + TWeakPtr(const TIntrusivePtr<U>& ptr) noexcept + : TWeakPtr(ptr.Get()) + { + static_assert( + std::is_base_of_v<TRefCountedBase, T>, + "Cast allowed only for types derived from TRefCountedBase"); + } + + //! Copy constructor. + TWeakPtr(const TWeakPtr& other) noexcept + : TWeakPtr(other.T_) + { } + + //! Copy constructor with an upcast. + template <class U, class = typename std::enable_if_t<std::is_convertible_v<U*, T*>>> + TWeakPtr(const TWeakPtr<U>& other) noexcept + : TWeakPtr(other.Lock()) + { + static_assert( + std::is_base_of_v<TRefCountedBase, T>, + "Cast allowed only for types derived from TRefCountedBase"); + } + + //! Move constructor. + TWeakPtr(TWeakPtr&& other) noexcept + { + other.Swap(*this); + } + + //! Move constructor with an upcast. + template <class U, class = typename std::enable_if_t<std::is_convertible_v<U*, T*>>> + TWeakPtr(TWeakPtr<U>&& other) noexcept + { + static_assert( + std::is_base_of_v<TRefCountedBase, T>, + "Cast allowed only for types derived from TRefCountedBase"); + TIntrusivePtr<U> strongOther = other.Lock(); + if (strongOther) { + T_ = other.T_; + other.T_ = nullptr; + +#if defined(_tsan_enabled_) + RefCounter_ = other.RefCounter_; + other.RefCounter_ = nullptr; +#endif + } + } + + //! Destructor. + ~TWeakPtr() + { + ReleaseRef(); + } + + //! Assignment operator from a strong reference. + template <class U> + TWeakPtr& operator=(const TIntrusivePtr<U>& ptr) noexcept + { + static_assert( + std::is_convertible_v<U*, T*>, + "U* must be convertible to T*"); + TWeakPtr(ptr).Swap(*this); + return *this; + } + + //! Copy assignment operator. + TWeakPtr& operator=(const TWeakPtr& other) noexcept + { + TWeakPtr(other).Swap(*this); + return *this; + } + + //! Copy assignment operator with an upcast. + template <class U> + TWeakPtr& operator=(const TWeakPtr<U>& other) noexcept + { + static_assert( + std::is_convertible_v<U*, T*>, + "U* must be convertible to T*"); + TWeakPtr(other).Swap(*this); + return *this; + } + + //! Move assignment operator. + TWeakPtr& operator=(TWeakPtr&& other) noexcept + { + other.Swap(*this); + return *this; + } + + //! Move assignment operator with an upcast. + template <class U> + TWeakPtr& operator=(TWeakPtr<U>&& other) noexcept + { + static_assert( + std::is_convertible_v<U*, T*>, + "U* must be convertible to T*"); + TWeakPtr(std::move(other)).Swap(*this); + return *this; + } + + //! Drop the pointer. + void Reset() // noexcept + { + TWeakPtr().Swap(*this); + } + + //! Replace the pointer with a specified one. + void Reset(T* p) // noexcept + { + TWeakPtr(p).Swap(*this); + } + + //! Replace the pointer with a specified one. + template <class U> + void Reset(const TIntrusivePtr<U>& ptr) // noexcept + { + static_assert( + std::is_convertible_v<U*, T*>, + "U* must be convertible to T*"); + TWeakPtr(ptr).Swap(*this); + } + + //! Swap the pointer with the other one. + void Swap(TWeakPtr& other) noexcept + { + DoSwap(T_, other.T_); +#if defined(_tsan_enabled_) + DoSwap(RefCounter_, other.RefCounter_); +#endif + } + + //! Acquire a strong reference to the pointee and return a strong pointer. + TIntrusivePtr<T> Lock() const noexcept + { + return T_ && RefCounter()->TryRef() + ? TIntrusivePtr<T>(T_, false) + : TIntrusivePtr<T>(); + } + + bool IsExpired() const noexcept + { + return !T_ || (RefCounter()->GetRefCount() == 0); + } + +private: + void AcquireRef() + { + if (T_) { + RefCounter()->WeakRef(); + } + } + + void ReleaseRef() + { + if (T_) { + // Support incomplete type. + if (RefCounter()->WeakUnref()) { + DeallocateRefCounted(T_); + } + } + } + + template <class U> + friend class TWeakPtr; + template <class U> + friend struct ::THash; + + T* T_ = nullptr; +#if defined(_tsan_enabled_) + const TRefCounter* RefCounter_ = nullptr; + + const TRefCounter* RefCounter() const + { + return RefCounter_; + } +#else + const TRefCounter* RefCounter() const + { + return GetRefCounter(T_); + } +#endif +}; + +//////////////////////////////////////////////////////////////////////////////// + +//! Creates a weak pointer wrapper for a given raw pointer. +//! Compared to |TWeakPtr<T>::ctor|, type inference enables omitting |T|. +template <class T> +TWeakPtr<T> MakeWeak(T* p) +{ + return TWeakPtr<T>(p); +} + +//! Creates a weak pointer wrapper for a given intrusive pointer. +//! Compared to |TWeakPtr<T>::ctor|, type inference enables omitting |T|. +template <class T> +TWeakPtr<T> MakeWeak(const TIntrusivePtr<T>& p) +{ + return TWeakPtr<T>(p); +} + +//! A helper for acquiring weak pointer for pointee, resetting intrusive pointer and then +//! returning the pointee reference count using the acquired weak pointer. +//! This helper is designed for best effort in checking that the object is not leaked after +//! destructing (what seems to be) the last pointer to it. +//! NB: it is possible to rewrite this helper making it working event with intrinsic refcounted objects, +//! but it requires much nastier integration with the intrusive pointer destruction routines. +template <typename T> +int ResetAndGetResidualRefCount(TIntrusivePtr<T>& pointer) +{ + auto weakPointer = MakeWeak(pointer); + pointer.Reset(); + if (pointer = weakPointer.Lock()) { + // This _may_ return 0 if we are again the only holder of the pointee. + return pointer->GetRefCount() - 1; + } else { + return 0; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +// TODO(sandello): Kill comparsions. +template <class T> +bool operator<(const TWeakPtr<T>& lhs, const TWeakPtr<T>& rhs) +{ + return lhs.Lock().Get() < rhs.Lock().Get(); +} + +template <class T> +bool operator>(const TWeakPtr<T>& lhs, const TWeakPtr<T>& rhs) +{ + return lhs.Lock().Get() > rhs.Lock().Get(); +} + +template <class T, class U> +bool operator==(const TWeakPtr<T>& lhs, const TWeakPtr<U>& rhs) +{ + static_assert( + std::is_convertible_v<U*, T*>, + "U* must be convertible to T*"); + return lhs.Lock().Get() == rhs.Lock().Get(); +} + +template <class T, class U> +bool operator!=(const TWeakPtr<T>& lhs, const TWeakPtr<U>& rhs) +{ + static_assert( + std::is_convertible_v<U*, T*>, + "U* must be convertible to T*"); + return lhs.Lock().Get() != rhs.Lock().Get(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + + +//! A hasher for TWeakPtr. +template <class T> +struct THash<NYT::TWeakPtr<T>> +{ + size_t operator () (const NYT::TWeakPtr<T>& ptr) const + { + return THash<const NYT::TRefCountedBase*>()(ptr.T_); + } +}; diff --git a/library/cpp/yt/memory/ya.make b/library/cpp/yt/memory/ya.make new file mode 100644 index 0000000000..a925c714ee --- /dev/null +++ b/library/cpp/yt/memory/ya.make @@ -0,0 +1,31 @@ +LIBRARY() + +OWNER(g:yt) + +SRCS( + blob.cpp + ref.cpp + ref_tracked.cpp +) + +PEERDIR( + library/cpp/yt/assert + library/cpp/yt/misc + library/cpp/ytalloc/api +) + +CHECK_DEPENDENT_DIRS( + ALLOW_ONLY ALL + build + contrib + library + util + library/cpp/yt/assert + library/cpp/yt/misc +) + +END() + +RECURSE_FOR_TESTS( + unittests +) diff --git a/library/cpp/yt/misc/cast-inl.h b/library/cpp/yt/misc/cast-inl.h new file mode 100644 index 0000000000..1920b7c0b7 --- /dev/null +++ b/library/cpp/yt/misc/cast-inl.h @@ -0,0 +1,113 @@ +#ifndef CAST_INL_H_ +#error "Direct inclusion of this file is not allowed, include cast.h" +// For the sake of sane code completion. +#include "cast.h" +#endif + +#include <util/string/cast.h> +#include <util/string/printf.h> + +#include <type_traits> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +namespace NDetail { + +template <class T, class S> +typename std::enable_if<std::is_signed<T>::value && std::is_signed<S>::value, bool>::type IsInIntegralRange(S value) +{ + return value >= std::numeric_limits<T>::min() && value <= std::numeric_limits<T>::max(); +} + +template <class T, class S> +static typename std::enable_if<std::is_signed<T>::value && std::is_unsigned<S>::value, bool>::type IsInIntegralRange(S value) +{ + return value <= static_cast<typename std::make_unsigned<T>::type>(std::numeric_limits<T>::max()); +} + +template <class T, class S> +static typename std::enable_if<std::is_unsigned<T>::value && std::is_signed<S>::value, bool>::type IsInIntegralRange(S value) +{ + return value >= 0 && static_cast<typename std::make_unsigned<S>::type>(value) <= std::numeric_limits<T>::max(); +} + +template <class T, class S> +typename std::enable_if<std::is_unsigned<T>::value && std::is_unsigned<S>::value, bool>::type IsInIntegralRange(S value) +{ + return value <= std::numeric_limits<T>::max(); +} + +template <class T> +TString FormatInvalidCastValue(T value) +{ + return ::ToString(value); +} + +inline TString FormatInvalidCastValue(signed char value) +{ + return TString("'") + value + TString("'"); +} + +inline TString FormatInvalidCastValue(unsigned char value) +{ + return TString("'") + value + TString("'"); +} + +#ifdef __cpp_char8_t +inline TString FormatInvalidCastValue(char8_t value) +{ + return FormatInvalidCastValue(static_cast<unsigned char>(value)); +} +#endif + +} // namespace NDetail + +template <class T, class S> +bool TryIntegralCast(S value, T* result) +{ + if (!NDetail::IsInIntegralRange<T>(value)) { + return false; + } + *result = static_cast<T>(value); + return true; +} + +template <class T, class S> +T CheckedIntegralCast(S value) +{ + T result; + if (!TryIntegralCast<T>(value, &result)) { + throw TSimpleException(Sprintf("Argument value %s is out of expected range", + NDetail::FormatInvalidCastValue(value).c_str())); + } + return result; +} + +template <class T, class S> +bool TryEnumCast(S value, T* result) +{ + auto candidate = static_cast<T>(value); + if (!TEnumTraits<T>::FindLiteralByValue(candidate)) { + return false; + } + *result = candidate; + return true; +} + +template <class T, class S> +T CheckedEnumCast(S value) +{ + T result; + if (!TryEnumCast<T>(value, &result)) { + throw TSimpleException(Sprintf("Invalid value %d of enum type %s", + static_cast<int>(value), + TEnumTraits<T>::GetTypeName().data())); + } + return result; +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/misc/cast.h b/library/cpp/yt/misc/cast.h new file mode 100644 index 0000000000..c7565c9e6d --- /dev/null +++ b/library/cpp/yt/misc/cast.h @@ -0,0 +1,29 @@ +#pragma once + +#include <library/cpp/yt/exception/exception.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T, class S> +bool TryIntegralCast(S value, T* result); + +template <class T, class S> +T CheckedIntegralCast(S value); + +//////////////////////////////////////////////////////////////////////////////// + +template <class T, class S> +bool TryEnumCast(S value, T* result); + +template <class T, class S> +T CheckedEnumCast(S value); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define CAST_INL_H_ +#include "cast-inl.h" +#undef CAST_INL_H_ diff --git a/library/cpp/yt/misc/enum-inl.h b/library/cpp/yt/misc/enum-inl.h new file mode 100644 index 0000000000..59ef704775 --- /dev/null +++ b/library/cpp/yt/misc/enum-inl.h @@ -0,0 +1,385 @@ +#pragma once +#ifndef ENUM_INL_H_ +#error "Direct inclusion of this file is not allowed, include enum.h" +// For the sake of sane code completion. +#include "enum.h" +#endif + +#include <util/string/printf.h> +#include <util/string/cast.h> + +#include <algorithm> +#include <stdexcept> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +#define ENUM__CLASS(name, underlyingType, seq) \ + enum class name : underlyingType \ + { \ + PP_FOR_EACH(ENUM__DOMAIN_ITEM, seq) \ + }; + +#define ENUM__DOMAIN_ITEM(item) \ + PP_IF( \ + PP_IS_SEQUENCE(item), \ + ENUM__DOMAIN_ITEM_SEQ, \ + ENUM__DOMAIN_ITEM_ATOMIC \ + )(item)() + +#define ENUM__DOMAIN_ITEM_ATOMIC(item) \ + item PP_COMMA + +#define ENUM__DOMAIN_ITEM_SEQ(seq) \ + PP_ELEMENT(seq, 0) = PP_ELEMENT(seq, 1) PP_COMMA + +//////////////////////////////////////////////////////////////////////////////// + +namespace NDetail { + +template <typename TValues> +static constexpr bool AreValuesDistinct(const TValues& values) +{ + for (int i = 0; i < static_cast<int>(values.size()); ++i) { + for (int j = i + 1; j < static_cast<int>(values.size()); ++j) { + if (values[i] == values[j]) { + return false; + } + } + } + return true; +} + +} // namespace NDetail + +//////////////////////////////////////////////////////////////////////////////// + +#define ENUM__BEGIN_TRAITS(name, underlyingType, isBit, isStringSerializable, seq) \ + struct TEnumTraitsImpl_##name \ + { \ + using TType = name; \ + using TUnderlying = underlyingType; \ + [[maybe_unused]] static constexpr bool IsBitEnum = isBit; \ + [[maybe_unused]] static constexpr bool IsStringSerializableEnum = isStringSerializable; \ + [[maybe_unused]] static constexpr int DomainSize = PP_COUNT(seq); \ + \ + static constexpr std::array<TStringBuf, DomainSize> Names{{ \ + PP_FOR_EACH(ENUM__GET_DOMAIN_NAMES_ITEM, seq) \ + }}; \ + static constexpr std::array<TType, DomainSize> Values{{ \ + PP_FOR_EACH(ENUM__GET_DOMAIN_VALUES_ITEM, seq) \ + }}; \ + \ + static TStringBuf GetTypeName() \ + { \ + static constexpr TStringBuf typeName = PP_STRINGIZE(name); \ + return typeName; \ + } \ + \ + static const TStringBuf* FindLiteralByValue(TType value) \ + { \ + for (int i = 0; i < DomainSize; ++i) { \ + if (Values[i] == value) { \ + return &Names[i]; \ + } \ + } \ + return nullptr; \ + } \ + \ + static bool FindValueByLiteral(TStringBuf literal, TType* result) \ + { \ + for (int i = 0; i < DomainSize; ++i) { \ + if (Names[i] == literal) { \ + *result = Values[i]; \ + return true; \ + } \ + } \ + return false; \ + } \ + \ + static const std::array<TStringBuf, DomainSize>& GetDomainNames() \ + { \ + return Names; \ + } \ + \ + static const std::array<TType, DomainSize>& GetDomainValues() \ + { \ + return Values; \ + } \ + \ + static TType FromString(TStringBuf str) \ + { \ + TType value; \ + if (!FindValueByLiteral(str, &value)) { \ + throw ::NYT::TSimpleException(Sprintf("Error parsing %s value %s", \ + PP_STRINGIZE(name), \ + TString(str).Quote().c_str()).c_str()); \ + } \ + return value; \ + } + +#define ENUM__GET_DOMAIN_VALUES_ITEM(item) \ + PP_IF( \ + PP_IS_SEQUENCE(item), \ + ENUM__GET_DOMAIN_VALUES_ITEM_SEQ, \ + ENUM__GET_DOMAIN_VALUES_ITEM_ATOMIC \ + )(item) + +#define ENUM__GET_DOMAIN_VALUES_ITEM_SEQ(seq) \ + ENUM__GET_DOMAIN_VALUES_ITEM_ATOMIC(PP_ELEMENT(seq, 0)) + +#define ENUM__GET_DOMAIN_VALUES_ITEM_ATOMIC(item) \ + TType::item, + +#define ENUM__GET_DOMAIN_NAMES_ITEM(item) \ + PP_IF( \ + PP_IS_SEQUENCE(item), \ + ENUM__GET_DOMAIN_NAMES_ITEM_SEQ, \ + ENUM__GET_DOMAIN_NAMES_ITEM_ATOMIC \ + )(item) + +#define ENUM__GET_DOMAIN_NAMES_ITEM_SEQ(seq) \ + ENUM__GET_DOMAIN_NAMES_ITEM_ATOMIC(PP_ELEMENT(seq, 0)) + +#define ENUM__GET_DOMAIN_NAMES_ITEM_ATOMIC(item) \ + TStringBuf(PP_STRINGIZE(item)), + +#define ENUM__DECOMPOSE \ + static std::vector<TType> Decompose(TType value) \ + { \ + std::vector<TType> result; \ + for (int i = 0; i < DomainSize; ++i) { \ + if (static_cast<TUnderlying>(value) & static_cast<TUnderlying>(Values[i])) { \ + result.push_back(Values[i]); \ + } \ + } \ + return result; \ + } + +#define ENUM__MINMAX \ + static constexpr TType GetMinValue() \ + { \ + static_assert(!Values.empty()); \ + return *std::min_element(std::begin(Values), std::end(Values)); \ + } \ + \ + static constexpr TType GetMaxValue() \ + { \ + static_assert(!Values.empty()); \ + return *std::max_element(std::begin(Values), std::end(Values)); \ + } + +#define ENUM__VALIDATE_UNIQUE(name) \ + static_assert(::NYT::NDetail::AreValuesDistinct(Values), \ + "Enumeration " #name " contains duplicate values"); + +#define ENUM__END_TRAITS(name) \ + }; \ + \ + [[maybe_unused]] inline TEnumTraitsImpl_##name GetEnumTraitsImpl(name) \ + { \ + return TEnumTraitsImpl_##name(); \ + } \ + \ + using ::ToString; \ + [[maybe_unused]] inline TString ToString(name value) \ + { \ + return ::NYT::TEnumTraits<name>::ToString(value); \ + } + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +std::vector<T> TEnumTraits<T, true>::Decompose(T value) +{ + return TImpl::Decompose(value); +} + +template <class T> +T TEnumTraits<T, true>::FromString(TStringBuf str) +{ + return TImpl::FromString(str); +} + +template <class T> +TString TEnumTraits<T, true>::ToString(TType value) +{ + TString result; + const auto* literal = FindLiteralByValue(value); + if (literal) { + result = *literal; + } else { + result = GetTypeName(); + result += "("; + result += ::ToString(static_cast<TUnderlying>(value)); + result += ")"; + } + return result; +} + +template <class T> +auto TEnumTraits<T, true>::GetDomainValues() -> const std::array<T, DomainSize>& +{ + return TImpl::GetDomainValues(); +} + +template <class T> +auto TEnumTraits<T, true>::GetDomainNames() -> const std::array<TStringBuf, DomainSize>& +{ + return TImpl::GetDomainNames(); +} + +template <class T> +constexpr T TEnumTraits<T, true>::GetMaxValue() +{ + return TImpl::GetMaxValue(); +} + +template <class T> +constexpr T TEnumTraits<T, true>::GetMinValue() +{ + return TImpl::GetMinValue(); +} + +template <class T> +bool TEnumTraits<T, true>::FindValueByLiteral(TStringBuf literal, TType* result) +{ + return TImpl::FindValueByLiteral(literal, result); +} + +template <class T> +const TStringBuf* TEnumTraits<T, true>::FindLiteralByValue(TType value) +{ + return TImpl::FindLiteralByValue(value); +} + +template <class T> +TStringBuf TEnumTraits<T, true>::GetTypeName() +{ + return TImpl::GetTypeName(); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class E, class T, E Min, E Max> +TEnumIndexedVector<E, T, Min, Max>::TEnumIndexedVector() + : Items_{} +{ } + +template <class E, class T, E Min, E Max> +TEnumIndexedVector<E, T, Min, Max>::TEnumIndexedVector(std::initializer_list<T> elements) + : Items_{} +{ + Y_ASSERT(std::distance(elements.begin(), elements.end()) <= N); + size_t index = 0; + for (const auto& element : elements) { + Items_[index++] = element; + } +} + +template <class E, class T, E Min, E Max> +T& TEnumIndexedVector<E, T, Min, Max>::operator[] (E index) +{ + Y_ASSERT(index >= Min && index <= Max); + return Items_[static_cast<TUnderlying>(index) - static_cast<TUnderlying>(Min)]; +} + +template <class E, class T, E Min, E Max> +const T& TEnumIndexedVector<E, T, Min, Max>::operator[] (E index) const +{ + return const_cast<TEnumIndexedVector&>(*this)[index]; +} + +template <class E, class T, E Min, E Max> +T* TEnumIndexedVector<E, T, Min, Max>::begin() +{ + return Items_.data(); +} + +template <class E, class T, E Min, E Max> +const T* TEnumIndexedVector<E, T, Min, Max>::begin() const +{ + return Items_.data(); +} + +template <class E, class T, E Min, E Max> +T* TEnumIndexedVector<E, T, Min, Max>::end() +{ + return begin() + N; +} + +template <class E, class T, E Min, E Max> +const T* TEnumIndexedVector<E, T, Min, Max>::end() const +{ + return begin() + N; +} + +template <class E, class T, E Min, E Max> +bool TEnumIndexedVector<E, T, Min, Max>::IsDomainValue(E value) +{ + return value >= Min && value <= Max; +} + +//////////////////////////////////////////////////////////////////////////////// + +#define ENUM__BINARY_BITWISE_OPERATOR(T, assignOp, op) \ + [[maybe_unused]] inline constexpr T operator op (T lhs, T rhs) \ + { \ + using TUnderlying = typename TEnumTraits<T>::TUnderlying; \ + return T(static_cast<TUnderlying>(lhs) op static_cast<TUnderlying>(rhs)); \ + } \ + \ + [[maybe_unused]] inline T& operator assignOp (T& lhs, T rhs) \ + { \ + using TUnderlying = typename TEnumTraits<T>::TUnderlying; \ + lhs = T(static_cast<TUnderlying>(lhs) op static_cast<TUnderlying>(rhs)); \ + return lhs; \ + } + +#define ENUM__UNARY_BITWISE_OPERATOR(T, op) \ + [[maybe_unused]] inline constexpr T operator op (T value) \ + { \ + using TUnderlying = typename TEnumTraits<T>::TUnderlying; \ + return T(op static_cast<TUnderlying>(value)); \ + } + +#define ENUM__BIT_SHIFT_OPERATOR(T, assignOp, op) \ + [[maybe_unused]] inline constexpr T operator op (T lhs, size_t rhs) \ + { \ + using TUnderlying = typename TEnumTraits<T>::TUnderlying; \ + return T(static_cast<TUnderlying>(lhs) op rhs); \ + } \ + \ + [[maybe_unused]] inline T& operator assignOp (T& lhs, size_t rhs) \ + { \ + using TUnderlying = typename TEnumTraits<T>::TUnderlying; \ + lhs = T(static_cast<TUnderlying>(lhs) op rhs); \ + return lhs; \ + } + +#define ENUM__BITWISE_OPS(name) \ + ENUM__BINARY_BITWISE_OPERATOR(name, &=, &) \ + ENUM__BINARY_BITWISE_OPERATOR(name, |=, | ) \ + ENUM__BINARY_BITWISE_OPERATOR(name, ^=, ^) \ + ENUM__UNARY_BITWISE_OPERATOR(name, ~) \ + ENUM__BIT_SHIFT_OPERATOR(name, <<=, << ) \ + ENUM__BIT_SHIFT_OPERATOR(name, >>=, >> ) + +//////////////////////////////////////////////////////////////////////////////// + +template <typename E, typename> +bool Any(E value) +{ + return static_cast<typename TEnumTraits<E>::TUnderlying>(value) != 0; +} + +template <class E, typename> +bool None(E value) +{ + return static_cast<typename TEnumTraits<E>::TUnderlying>(value) == 0; +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/misc/enum.h b/library/cpp/yt/misc/enum.h new file mode 100644 index 0000000000..894364aa43 --- /dev/null +++ b/library/cpp/yt/misc/enum.h @@ -0,0 +1,243 @@ +#pragma once + +#include "preprocessor.h" + +#include <util/generic/strbuf.h> + +#include <stdexcept> +#include <type_traits> +#include <array> +#include <vector> + +#include <library/cpp/yt/exception/exception.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// +/* + * Smart enumerations augment C++ enum classes with a bunch of reflection + * capabilities accessible via TEnumTraits class specialization. + * + * Please refer to the unit test for an actual example of usage + * (unittests/enum_ut.cpp). + */ + +// Actual overload must be provided with defines DEFINE_ENUM_XXX (see below). +template <class T> +void GetEnumTraitsImpl(T); + +template < + class T, + bool = std::is_enum<T>::value && + !std::is_convertible<T, int>::value && + !std::is_same<void, decltype(GetEnumTraitsImpl(T()))>::value +> +struct TEnumTraits +{ + static constexpr bool IsEnum = false; + static constexpr bool IsBitEnum = false; + static constexpr bool IsStringSerializableEnum = false; +}; + +template <class T> +struct TEnumTraits<T, true> +{ + using TImpl = decltype(GetEnumTraitsImpl(T())); + using TType = T; + using TUnderlying = typename TImpl::TUnderlying; + + static constexpr bool IsEnum = true; + static constexpr bool IsBitEnum = TImpl::IsBitEnum; + static constexpr bool IsStringSerializableEnum = TImpl::IsStringSerializableEnum; + + static constexpr int DomainSize = TImpl::DomainSize; + + static TStringBuf GetTypeName(); + + static const TStringBuf* FindLiteralByValue(TType value); + static bool FindValueByLiteral(TStringBuf literal, TType* result); + + static const std::array<TStringBuf, DomainSize>& GetDomainNames(); + static const std::array<TType, DomainSize>& GetDomainValues(); + + static TType FromString(TStringBuf str); + static TString ToString(TType value); + + // For non-bit enums only. + static constexpr TType GetMinValue(); + static constexpr TType GetMaxValue(); + + // For bit enums only. + static std::vector<TType> Decompose(TType value); + + // LLVM SmallDenseMap interop. + // This should only be used for enums whose underlying type has big enough range + // (see getEmptyKey and getTombstoneKey functions). + struct TDenseMapInfo + { + static inline TType getEmptyKey() + { + return static_cast<TType>(-1); + } + + static inline TType getTombstoneKey() + { + return static_cast<TType>(-2); + } + + static unsigned getHashValue(const TType& key) + { + return static_cast<unsigned>(key) * 37U; + } + + static bool isEqual(const TType& lhs, const TType& rhs) + { + return lhs == rhs; + } + }; +}; + +//////////////////////////////////////////////////////////////////////////////// + +//! Defines a smart enumeration with a specific underlying type. +/*! + * \param name Enumeration name. + * \param seq Enumeration domain encoded as a <em>sequence</em>. + * \param underlyingType Underlying type. + */ +#define DEFINE_ENUM_WITH_UNDERLYING_TYPE(name, underlyingType, seq) \ + ENUM__CLASS(name, underlyingType, seq) \ + ENUM__BEGIN_TRAITS(name, underlyingType, false, false, seq) \ + ENUM__MINMAX \ + ENUM__VALIDATE_UNIQUE(name) \ + ENUM__END_TRAITS(name) + +//! Defines a smart enumeration with a specific underlying type. +//! Duplicate enumeration values are allowed. +#define DEFINE_AMBIGUOUS_ENUM_WITH_UNDERLYING_TYPE(name, underlyingType, seq) \ + ENUM__CLASS(name, underlyingType, seq) \ + ENUM__BEGIN_TRAITS(name, underlyingType, false, false, seq) \ + ENUM__MINMAX \ + ENUM__END_TRAITS(name) + +//! Defines a smart enumeration with the default |int| underlying type. +#define DEFINE_ENUM(name, seq) \ + DEFINE_ENUM_WITH_UNDERLYING_TYPE(name, int, seq) + +//! Defines a smart enumeration with a specific underlying type. +/*! + * \param name Enumeration name. + * \param seq Enumeration domain encoded as a <em>sequence</em>. + * \param underlyingType Underlying type. + */ +#define DEFINE_BIT_ENUM_WITH_UNDERLYING_TYPE(name, underlyingType, seq) \ + ENUM__CLASS(name, underlyingType, seq) \ + ENUM__BEGIN_TRAITS(name, underlyingType, true, false, seq) \ + ENUM__DECOMPOSE \ + ENUM__VALIDATE_UNIQUE(name) \ + ENUM__END_TRAITS(name) \ + ENUM__BITWISE_OPS(name) + +//! Defines a smart enumeration with a specific underlying type. +//! Duplicate enumeration values are allowed. +/*! + * \param name Enumeration name. + * \param seq Enumeration domain encoded as a <em>sequence</em>. + * \param underlyingType Underlying type. + */ +#define DEFINE_AMBIGUOUS_BIT_ENUM_WITH_UNDERLYING_TYPE(name, underlyingType, seq) \ + ENUM__CLASS(name, underlyingType, seq) \ + ENUM__BEGIN_TRAITS(name, underlyingType, true, false, seq) \ + ENUM__DECOMPOSE \ + ENUM__END_TRAITS(name) \ + ENUM__BITWISE_OPS(name) + +//! Defines a smart enumeration with the default |unsigned| underlying type. +/*! + * \param name Enumeration name. + * \param seq Enumeration domain encoded as a <em>sequence</em>. + */ +#define DEFINE_BIT_ENUM(name, seq) \ + DEFINE_BIT_ENUM_WITH_UNDERLYING_TYPE(name, unsigned, seq) + +//! Defines a smart enumeration with a specific underlying type and IsStringSerializable attribute. +/*! + * \param name Enumeration name. + * \param seq Enumeration domain encoded as a <em>sequence</em>. + * \param underlyingType Underlying type. + */ +#define DEFINE_STRING_SERIALIZABLE_ENUM_WITH_UNDERLYING_TYPE(name, underlyingType, seq) \ + ENUM__CLASS(name, underlyingType, seq) \ + ENUM__BEGIN_TRAITS(name, underlyingType, false, true, seq) \ + ENUM__MINMAX \ + ENUM__VALIDATE_UNIQUE(name) \ + ENUM__END_TRAITS(name) \ + +//! Defines a smart enumeration with a specific underlying type and IsStringSerializable attribute. +//! Duplicate enumeration values are allowed. +#define DEFINE_AMBIGUOUS_STRING_SERIALIZABLE_ENUM_WITH_UNDERLYING_TYPE(name, underlyingType, seq) \ + ENUM__CLASS(name, underlyingType, seq) \ + ENUM__BEGIN_TRAITS(name, underlyingType, false, true, seq) \ + ENUM__MINMAX \ + ENUM__END_TRAITS(name) + +//! Defines a smart enumeration with the default |int| underlying type and IsStringSerializable attribute. +#define DEFINE_STRING_SERIALIZABLE_ENUM(name, seq) \ + DEFINE_STRING_SERIALIZABLE_ENUM_WITH_UNDERLYING_TYPE(name, int, seq) + +//////////////////////////////////////////////////////////////////////////////// + +//! A statically sized vector with elements of type |T| indexed by +//! the items of enumeration type |E|. +/*! + * Items are value-initialized on construction. + */ +template < + class E, + class T, + E Min = TEnumTraits<E>::GetMinValue(), + E Max = TEnumTraits<E>::GetMaxValue() +> +class TEnumIndexedVector +{ +public: + using TIndex = E; + using TValue = T; + + TEnumIndexedVector(); + TEnumIndexedVector(std::initializer_list<T> elements); + + T& operator[] (E index); + const T& operator[] (E index) const; + + // STL interop. + T* begin(); + const T* begin() const; + T* end(); + const T* end() const; + + static bool IsDomainValue(E value); + +private: + using TUnderlying = typename TEnumTraits<E>::TUnderlying; + static constexpr int N = static_cast<TUnderlying>(Max) - static_cast<TUnderlying>(Min) + 1; + std::array<T, N> Items_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +//! Returns |true| iff the enumeration value is not bitwise zero. +template <typename E, typename = std::enable_if_t<NYT::TEnumTraits<E>::IsBitEnum, E>> +bool Any(E value); + +//! Returns |true| iff the enumeration value is bitwise zero. +template <typename E, typename = std::enable_if_t<NYT::TEnumTraits<E>::IsBitEnum, E>> +bool None(E value); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define ENUM_INL_H_ +#include "enum-inl.h" +#undef ENUM_INL_H_ diff --git a/library/cpp/yt/misc/guid-inl.h b/library/cpp/yt/misc/guid-inl.h new file mode 100644 index 0000000000..2d94b5701b --- /dev/null +++ b/library/cpp/yt/misc/guid-inl.h @@ -0,0 +1,76 @@ +#ifndef GUID_INL_H_ +#error "Direct inclusion of this file is not allowed, include guid.h" +// For the sake of sane code completion. +#include "guid.h" +#endif + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +Y_FORCE_INLINE constexpr TGuid::TGuid() + : Parts32{} +{ } + +Y_FORCE_INLINE constexpr TGuid::TGuid(ui32 part0, ui32 part1, ui32 part2, ui32 part3) + : Parts32{part0, part1, part2, part3} +{ } + +Y_FORCE_INLINE constexpr TGuid::TGuid(ui64 part0, ui64 part1) + : Parts64{part0, part1} +{ } + +Y_FORCE_INLINE bool TGuid::IsEmpty() const +{ + return Parts64[0] == 0 && Parts64[1] == 0; +} + +Y_FORCE_INLINE TGuid::operator bool() const +{ + return !IsEmpty(); +} + +//////////////////////////////////////////////////////////////////////////////// + +Y_FORCE_INLINE bool operator == (TGuid lhs, TGuid rhs) +{ + return lhs.Parts64[0] == rhs.Parts64[0] && + lhs.Parts64[1] == rhs.Parts64[1]; +} + +Y_FORCE_INLINE bool operator != (TGuid lhs, TGuid rhs) +{ + return !(lhs == rhs); +} + +Y_FORCE_INLINE bool operator < (TGuid lhs, TGuid rhs) +{ +#ifdef __GNUC__ + ui64 lhs0 = __builtin_bswap64(lhs.Parts64[0]); + ui64 rhs0 = __builtin_bswap64(rhs.Parts64[0]); + if (lhs0 < rhs0) { + return true; + } + if (lhs0 > rhs0) { + return false; + } + ui64 lhs1 = __builtin_bswap64(lhs.Parts64[1]); + ui64 rhs1 = __builtin_bswap64(rhs.Parts64[1]); + return lhs1 < rhs1; +#else + return memcmp(&lhs, &rhs, sizeof(TGuid)) < 0; +#endif +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +Y_FORCE_INLINE size_t THash<NYT::TGuid>::operator()(const NYT::TGuid& guid) const +{ + const size_t p = 1000000009; // prime number + return guid.Parts32[0] + + guid.Parts32[1] * p + + guid.Parts32[2] * p * p + + guid.Parts32[3] * p * p * p; +} diff --git a/library/cpp/yt/misc/guid.cpp b/library/cpp/yt/misc/guid.cpp new file mode 100644 index 0000000000..882787d7a2 --- /dev/null +++ b/library/cpp/yt/misc/guid.cpp @@ -0,0 +1,203 @@ +#include "guid.h" + +#include <util/random/random.h> + +#include <util/string/printf.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +namespace { + +const ui8 HexDigits1[16] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66 +}; + +const ui16 HexDigits2[256] = { + 0x3030, 0x3130, 0x3230, 0x3330, 0x3430, 0x3530, 0x3630, 0x3730, 0x3830, 0x3930, 0x6130, 0x6230, 0x6330, 0x6430, 0x6530, 0x6630, + 0x3031, 0x3131, 0x3231, 0x3331, 0x3431, 0x3531, 0x3631, 0x3731, 0x3831, 0x3931, 0x6131, 0x6231, 0x6331, 0x6431, 0x6531, 0x6631, + 0x3032, 0x3132, 0x3232, 0x3332, 0x3432, 0x3532, 0x3632, 0x3732, 0x3832, 0x3932, 0x6132, 0x6232, 0x6332, 0x6432, 0x6532, 0x6632, + 0x3033, 0x3133, 0x3233, 0x3333, 0x3433, 0x3533, 0x3633, 0x3733, 0x3833, 0x3933, 0x6133, 0x6233, 0x6333, 0x6433, 0x6533, 0x6633, + 0x3034, 0x3134, 0x3234, 0x3334, 0x3434, 0x3534, 0x3634, 0x3734, 0x3834, 0x3934, 0x6134, 0x6234, 0x6334, 0x6434, 0x6534, 0x6634, + 0x3035, 0x3135, 0x3235, 0x3335, 0x3435, 0x3535, 0x3635, 0x3735, 0x3835, 0x3935, 0x6135, 0x6235, 0x6335, 0x6435, 0x6535, 0x6635, + 0x3036, 0x3136, 0x3236, 0x3336, 0x3436, 0x3536, 0x3636, 0x3736, 0x3836, 0x3936, 0x6136, 0x6236, 0x6336, 0x6436, 0x6536, 0x6636, + 0x3037, 0x3137, 0x3237, 0x3337, 0x3437, 0x3537, 0x3637, 0x3737, 0x3837, 0x3937, 0x6137, 0x6237, 0x6337, 0x6437, 0x6537, 0x6637, + 0x3038, 0x3138, 0x3238, 0x3338, 0x3438, 0x3538, 0x3638, 0x3738, 0x3838, 0x3938, 0x6138, 0x6238, 0x6338, 0x6438, 0x6538, 0x6638, + 0x3039, 0x3139, 0x3239, 0x3339, 0x3439, 0x3539, 0x3639, 0x3739, 0x3839, 0x3939, 0x6139, 0x6239, 0x6339, 0x6439, 0x6539, 0x6639, + 0x3061, 0x3161, 0x3261, 0x3361, 0x3461, 0x3561, 0x3661, 0x3761, 0x3861, 0x3961, 0x6161, 0x6261, 0x6361, 0x6461, 0x6561, 0x6661, + 0x3062, 0x3162, 0x3262, 0x3362, 0x3462, 0x3562, 0x3662, 0x3762, 0x3862, 0x3962, 0x6162, 0x6262, 0x6362, 0x6462, 0x6562, 0x6662, + 0x3063, 0x3163, 0x3263, 0x3363, 0x3463, 0x3563, 0x3663, 0x3763, 0x3863, 0x3963, 0x6163, 0x6263, 0x6363, 0x6463, 0x6563, 0x6663, + 0x3064, 0x3164, 0x3264, 0x3364, 0x3464, 0x3564, 0x3664, 0x3764, 0x3864, 0x3964, 0x6164, 0x6264, 0x6364, 0x6464, 0x6564, 0x6664, + 0x3065, 0x3165, 0x3265, 0x3365, 0x3465, 0x3565, 0x3665, 0x3765, 0x3865, 0x3965, 0x6165, 0x6265, 0x6365, 0x6465, 0x6565, 0x6665, + 0x3066, 0x3166, 0x3266, 0x3366, 0x3466, 0x3566, 0x3666, 0x3766, 0x3866, 0x3966, 0x6166, 0x6266, 0x6366, 0x6466, 0x6566, 0x6666 +}; + +} // anonymous namespace + +//////////////////////////////////////////////////////////////////////////////// + +TGuid TGuid::Create() +{ + return TGuid(RandomNumber<ui64>(), RandomNumber<ui64>()); +} + +TGuid TGuid::FromString(TStringBuf str) +{ + TGuid guid; + if (!FromString(str, &guid)) { + throw TSimpleException(Sprintf("Error parsing GUID \"%s\"", + TString(str).c_str())); + } + return guid; +} + +bool TGuid::FromString(TStringBuf str, TGuid* result) +{ + size_t partId = 3; + ui64 partValue = 0; + bool isEmptyPart = true; + + for (size_t i = 0; i != str.size(); ++i) { + const char c = str[i]; + + if (c == '-') { + if (isEmptyPart || partId == 0) { // x-y--z, -x-y-z or x-y-z-m-... + return false; + } + result->Parts32[partId] = static_cast<ui32>(partValue); + --partId; + partValue = 0; + isEmptyPart = true; + continue; + } + + ui32 digit = 0; + if ('0' <= c && c <= '9') { + digit = c - '0'; + } else if ('a' <= c && c <= 'f') { + digit = c - 'a' + 10; + } else if ('A' <= c && c <= 'F') { + digit = c - 'A' + 10; + } else { + return false; // non-hex character + } + + partValue = partValue * 16 + digit; + isEmptyPart = false; + + // overflow check + if (partValue > Max<ui32>()) { + return false; + } + } + + if (partId != 0 || isEmptyPart) { // x-y or x-y-z- + return false; + } + result->Parts32[partId] = static_cast<ui32>(partValue); + return true; +} + +TGuid TGuid::FromStringHex32(TStringBuf str) +{ + TGuid guid; + if (!FromStringHex32(str, &guid)) { + throw TSimpleException(Sprintf("Error parsing Hex32 GUID \"%s\"", + TString(str).c_str())); + } + return guid; +} + +bool TGuid::FromStringHex32(TStringBuf str, TGuid* result) +{ + if (str.size() != 32) { + return false; + } + + bool ok = true; + int i = 0; + auto parseChar = [&] { + const char c = str[i++]; + ui32 digit = 0; + if ('0' <= c && c <= '9') { + digit = c - '0'; + } else if ('a' <= c && c <= 'f') { + digit = c - 'a' + 10; + } else if ('A' <= c && c <= 'F') { + digit = c - 'A' + 10; + } else { + ok = false; + } + return digit; + }; + + for (size_t j = 0; j < 16; ++j) { + result->ReversedParts8[15 - j] = parseChar() * 16 + parseChar(); + } + + return ok; +} + +char* WriteGuidToBuffer(char* ptr, TGuid value) +{ + auto writeHex1 = [&] (ui8 x) { + *ptr = HexDigits1[x]; + ptr += 1; + }; + + auto writeHex2 = [&] (ui8 x) { + ::memcpy(ptr, &HexDigits2[x], 2); + ptr += 2; + }; + + auto writeComponent = [&] (ui32 x) { + /* */ if (x >= 0x10000000) { + writeHex2((x >> 24) & 0xff); + writeHex2((x >> 16) & 0xff); + writeHex2((x >> 8) & 0xff); + writeHex2( x & 0xff); + } else if (x >= 0x1000000) { + writeHex1( x >> 24); + writeHex2((x >> 16) & 0xff); + writeHex2((x >> 8) & 0xff); + writeHex2( x & 0xff); + } else if (x >= 0x100000) { + writeHex2((x >> 16) & 0xff); + writeHex2((x >> 8) & 0xff); + writeHex2( x & 0xff); + } else if (x >= 0x10000) { + writeHex1( x >> 16); + writeHex2((x >> 8) & 0xff); + writeHex2( x & 0xff); + } else if (x >= 0x1000) { + writeHex2( x >> 8); + writeHex2( x & 0xff); + } else if (x >= 0x100) { + writeHex1( x >> 8); + writeHex2( x & 0xff); + } else if (x >= 0x10) { + writeHex2( x); + } else { + writeHex1( x); + } + }; + + auto writeDash = [&] () { + *ptr++ = '-'; + }; + + writeComponent(value.Parts32[3]); + writeDash(); + writeComponent(value.Parts32[2]); + writeDash(); + writeComponent(value.Parts32[1]); + writeDash(); + writeComponent(value.Parts32[0]); + + return ptr; +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/misc/guid.h b/library/cpp/yt/misc/guid.h new file mode 100644 index 0000000000..ec4ba3526a --- /dev/null +++ b/library/cpp/yt/misc/guid.h @@ -0,0 +1,109 @@ +#pragma once + +#include <util/generic/string.h> +#include <util/generic/typetraits.h> + +#include <library/cpp/yt/exception/exception.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +//! TGuid is 16-byte value that might be interpreted as four little-endian 32-bit integers or two 64-bit little-endian integers. +/*! + * *-------------------------*-------------------------* + * | Parts64[0] | Parts64[1] | + * *------------*------------*------------*------------* + * | Parts32[0] | Parts32[1] | Parts32[2] | Parts32[3] | + * *------------*------------*------------*------------* + * | 15..............................................0 | + * *---------------------------------------------------* + * + * Note, that bytes are kept in memory in reverse order. + * + * Canonical text representation of guid consists of four base-16 numbers. + * In text form, Parts32[3] comes first, and Parts32[0] comes last. + * + * For example: + * + * xxyyzzaa-0-1234-ff + * + * xx is byte [0] + * yy is byte [1] + * zz is byte [2] + * 12 is byte [8] + * 34 is byte [9] + * ff is byte [15] + */ +struct TGuid +{ + union + { + ui32 Parts32[4]; + ui64 Parts64[2]; + ui8 ReversedParts8[16]; + }; + + //! Constructs a null (zero) guid. + constexpr TGuid(); + + //! Constructs guid from parts. + constexpr TGuid(ui32 part0, ui32 part1, ui32 part2, ui32 part3); + + //! Constructs guid from parts. + constexpr TGuid(ui64 part0, ui64 part1); + + //! Copies an existing guid. + TGuid(const TGuid& other) = default; + + //! Checks if TGuid is zero. + bool IsEmpty() const; + + //! Converts TGuid to bool, returns |false| iff TGuid is zero. + explicit operator bool() const; + + //! Creates a new instance. + static TGuid Create(); + + //! Parses guid from TStringBuf, throws an exception if something went wrong. + static TGuid FromString(TStringBuf str); + + //! Parses guid from TStringBuf, returns |true| if everything was ok. + static bool FromString(TStringBuf str, TGuid* guid); + + //! Same as FromString, but expects exactly 32 hex digits without dashes. + static TGuid FromStringHex32(TStringBuf str); + + //! Same as TryFromString, but expects exactly 32 hex digits without dashes. + static bool FromStringHex32(TStringBuf str, TGuid* guid); +}; + +bool operator == (TGuid lhs, TGuid rhs); +bool operator != (TGuid lhs, TGuid rhs); +bool operator < (TGuid lhs, TGuid rhs); + +//////////////////////////////////////////////////////////////////////////////// + +constexpr int MaxGuidStringSize = 4 * 8 + 3; +char* WriteGuidToBuffer(char* ptr, TGuid value); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +//////////////////////////////////////////////////////////////////////////////// + +Y_DECLARE_PODTYPE(NYT::TGuid); + +//! A hasher for TGuid. +template <> +struct THash<NYT::TGuid> +{ + size_t operator()(const NYT::TGuid& guid) const; +}; + +//////////////////////////////////////////////////////////////////////////////// + +#define GUID_INL_H_ +#include "guid-inl.h" +#undef GUID_INL_H_ diff --git a/library/cpp/yt/misc/hash-inl.h b/library/cpp/yt/misc/hash-inl.h new file mode 100644 index 0000000000..46eeefe620 --- /dev/null +++ b/library/cpp/yt/misc/hash-inl.h @@ -0,0 +1,47 @@ +#ifndef HASH_INL_H_ +#error "Direct inclusion of this file is not allowed, include hash.h" +// For the sake of sane code completion. +#include "hash.h" +#endif + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +inline void HashCombine(size_t& h, size_t k) +{ + static_assert(sizeof(size_t) == 8, "size_t must be 64 bit."); + + const size_t m = 0xc6a4a7935bd1e995ULL; + const int r = 47; + + k *= m; + k ^= k >> r; + k *= m; + + h ^= k; + h *= m; +} + +template <class T> +void HashCombine(size_t& h, const T& k) +{ + HashCombine(h, THash<T>()(k)); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class TElement, class TUnderlying> +TRandomizedHash<TElement, TUnderlying>::TRandomizedHash() + : Seed_(RandomNumber<size_t>()) +{ } + +template <class TElement, class TUnderlying> +size_t TRandomizedHash<TElement, TUnderlying>::operator ()(const TElement& element) const +{ + return Underlying_(element) + Seed_; +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/misc/hash.h b/library/cpp/yt/misc/hash.h new file mode 100644 index 0000000000..2fecf89506 --- /dev/null +++ b/library/cpp/yt/misc/hash.h @@ -0,0 +1,42 @@ +#pragma once + +#include <util/generic/hash.h> + +#include <util/random/random.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +//! Updates #h with #k. +//! Cf. |boost::hash_combine|. +void HashCombine(size_t& h, size_t k); + +//! Updates #h with the hash of #k. +//! Cf. |boost::hash_combine|. +template <class T> +void HashCombine(size_t& h, const T& k); + +//////////////////////////////////////////////////////////////////////////////// + +//! Provides a hasher that randomizes the results of another one. +template <class TElement, class TUnderlying = ::THash<TElement>> +class TRandomizedHash +{ +public: + TRandomizedHash(); + size_t operator () (const TElement& element) const; + +private: + size_t Seed_; + TUnderlying Underlying_; + +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define HASH_INL_H_ +#include "hash-inl.h" +#undef HASH_INL_H_ diff --git a/library/cpp/yt/misc/port.h b/library/cpp/yt/misc/port.h new file mode 100644 index 0000000000..b24ac50995 --- /dev/null +++ b/library/cpp/yt/misc/port.h @@ -0,0 +1,70 @@ +#pragma once + +#include <util/system/platform.h> + +// Check platform bitness. +#if !defined(_64_) + #error YT requires 64-bit platform +#endif + +// This define enables tracking of reference-counted objects to provide +// various insightful information on memory usage and object creation patterns. +#define YT_ENABLE_REF_COUNTED_TRACKING + +// This define enables logging with TRACE level. You can still disable trace logging +// for particular TU by discarding this macro identifier. +#define YT_ENABLE_TRACE_LOGGING + +#ifndef NDEBUG + // This define enables thread affinity check -- a user-defined verification ensuring + // that some functions are called from particular threads. + #define YT_ENABLE_THREAD_AFFINITY_CHECK + + // This define enables tracking of BIND callbacks location. + #define YT_ENABLE_BIND_LOCATION_TRACKING + + // This define enables checking that all required protobuf fields are present + // during serialization. + #define YT_VALIDATE_REQUIRED_PROTO_FIELDS + + // Detects deadlocks caused by recursive acquisitions of (non-recursive) spin locks. + #define YT_ENABLE_SPIN_LOCK_OWNERSHIP_TRACKING +#endif + +// Configure SSE usage. +#ifdef SSE42_ENABLED + #define YT_USE_SSE42 +#endif + +#ifdef _win_ + // Someone above has defined this by including one of Windows headers. + #undef GetMessage + #undef Yield + + // For protobuf-generated files: + // C4125: decimal digit terminates octal escape sequence + #pragma warning (disable: 4125) + // C4505: unreferenced local function has been removed + #pragma warning (disable : 4505) + // C4121: alignment of a member was sensitive to packing + #pragma warning (disable: 4121) + // C4503: decorated name length exceeded, name was truncated + #pragma warning (disable : 4503) + // C4714: function marked as __forceinline not inlined + #pragma warning (disable: 4714) + // C4250: inherits via dominance + #pragma warning (disable: 4250) +#endif + +#if defined(__GNUC__) || defined(__clang__) + #define PER_THREAD __thread + #define ATTRIBUTE_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address)) + // Prevent GCC from throwing out functions in release builds. + #define ATTRIBUTE_USED __attribute__((used)) +#elif defined(_MSC_VER) + #define PER_THREAD __declspec(thread) + #define ATTRIBUTE_NO_SANITIZE_ADDRESS + #define ATTRIBUTE_USED +#else + #error Unsupported compiler +#endif diff --git a/library/cpp/yt/misc/preprocessor-gen.h b/library/cpp/yt/misc/preprocessor-gen.h new file mode 100644 index 0000000000..b809941bcd --- /dev/null +++ b/library/cpp/yt/misc/preprocessor-gen.h @@ -0,0 +1,1249 @@ +#pragma once + +// WARNING: This file was auto-generated. +// Please, consider incorporating any changes into the generator. + +// Generated on Wed Dec 9 14:20:20 2015. + + +/*! + \internal +*/ + +#ifndef PREPROCESSOR_GEN_H_ +#error "Direct inclusion of this file is not allowed, include preprocessor.h" +// For the sake of sane code completion. +#include "preprocessor.h" +#endif +#undef PREPROCESSOR_GEN_H_ + +//////////////////////////////////////////////////////////////////////////////// +#define PP_COUNT_IMPL(...) PP_CONCAT(PP_COUNT_CONST_, \ + PP_COUNT_IMPL_0 __VA_ARGS__) +#define PP_COUNT_CONST_PP_COUNT_IMPL_0 0 +#define PP_COUNT_CONST_PP_COUNT_IMPL_1 1 +#define PP_COUNT_CONST_PP_COUNT_IMPL_2 2 +#define PP_COUNT_CONST_PP_COUNT_IMPL_3 3 +#define PP_COUNT_CONST_PP_COUNT_IMPL_4 4 +#define PP_COUNT_CONST_PP_COUNT_IMPL_5 5 +#define PP_COUNT_CONST_PP_COUNT_IMPL_6 6 +#define PP_COUNT_CONST_PP_COUNT_IMPL_7 7 +#define PP_COUNT_CONST_PP_COUNT_IMPL_8 8 +#define PP_COUNT_CONST_PP_COUNT_IMPL_9 9 +#define PP_COUNT_CONST_PP_COUNT_IMPL_10 10 +#define PP_COUNT_CONST_PP_COUNT_IMPL_11 11 +#define PP_COUNT_CONST_PP_COUNT_IMPL_12 12 +#define PP_COUNT_CONST_PP_COUNT_IMPL_13 13 +#define PP_COUNT_CONST_PP_COUNT_IMPL_14 14 +#define PP_COUNT_CONST_PP_COUNT_IMPL_15 15 +#define PP_COUNT_CONST_PP_COUNT_IMPL_16 16 +#define PP_COUNT_CONST_PP_COUNT_IMPL_17 17 +#define PP_COUNT_CONST_PP_COUNT_IMPL_18 18 +#define PP_COUNT_CONST_PP_COUNT_IMPL_19 19 +#define PP_COUNT_CONST_PP_COUNT_IMPL_20 20 +#define PP_COUNT_CONST_PP_COUNT_IMPL_21 21 +#define PP_COUNT_CONST_PP_COUNT_IMPL_22 22 +#define PP_COUNT_CONST_PP_COUNT_IMPL_23 23 +#define PP_COUNT_CONST_PP_COUNT_IMPL_24 24 +#define PP_COUNT_CONST_PP_COUNT_IMPL_25 25 +#define PP_COUNT_CONST_PP_COUNT_IMPL_26 26 +#define PP_COUNT_CONST_PP_COUNT_IMPL_27 27 +#define PP_COUNT_CONST_PP_COUNT_IMPL_28 28 +#define PP_COUNT_CONST_PP_COUNT_IMPL_29 29 +#define PP_COUNT_CONST_PP_COUNT_IMPL_30 30 +#define PP_COUNT_CONST_PP_COUNT_IMPL_31 31 +#define PP_COUNT_CONST_PP_COUNT_IMPL_32 32 +#define PP_COUNT_CONST_PP_COUNT_IMPL_33 33 +#define PP_COUNT_CONST_PP_COUNT_IMPL_34 34 +#define PP_COUNT_CONST_PP_COUNT_IMPL_35 35 +#define PP_COUNT_CONST_PP_COUNT_IMPL_36 36 +#define PP_COUNT_CONST_PP_COUNT_IMPL_37 37 +#define PP_COUNT_CONST_PP_COUNT_IMPL_38 38 +#define PP_COUNT_CONST_PP_COUNT_IMPL_39 39 +#define PP_COUNT_CONST_PP_COUNT_IMPL_40 40 +#define PP_COUNT_CONST_PP_COUNT_IMPL_41 41 +#define PP_COUNT_CONST_PP_COUNT_IMPL_42 42 +#define PP_COUNT_CONST_PP_COUNT_IMPL_43 43 +#define PP_COUNT_CONST_PP_COUNT_IMPL_44 44 +#define PP_COUNT_CONST_PP_COUNT_IMPL_45 45 +#define PP_COUNT_CONST_PP_COUNT_IMPL_46 46 +#define PP_COUNT_CONST_PP_COUNT_IMPL_47 47 +#define PP_COUNT_CONST_PP_COUNT_IMPL_48 48 +#define PP_COUNT_CONST_PP_COUNT_IMPL_49 49 +#define PP_COUNT_CONST_PP_COUNT_IMPL_50 50 +#define PP_COUNT_CONST_PP_COUNT_IMPL_51 51 +#define PP_COUNT_CONST_PP_COUNT_IMPL_52 52 +#define PP_COUNT_CONST_PP_COUNT_IMPL_53 53 +#define PP_COUNT_CONST_PP_COUNT_IMPL_54 54 +#define PP_COUNT_CONST_PP_COUNT_IMPL_55 55 +#define PP_COUNT_CONST_PP_COUNT_IMPL_56 56 +#define PP_COUNT_CONST_PP_COUNT_IMPL_57 57 +#define PP_COUNT_CONST_PP_COUNT_IMPL_58 58 +#define PP_COUNT_CONST_PP_COUNT_IMPL_59 59 +#define PP_COUNT_CONST_PP_COUNT_IMPL_60 60 +#define PP_COUNT_CONST_PP_COUNT_IMPL_61 61 +#define PP_COUNT_CONST_PP_COUNT_IMPL_62 62 +#define PP_COUNT_CONST_PP_COUNT_IMPL_63 63 +#define PP_COUNT_CONST_PP_COUNT_IMPL_64 64 +#define PP_COUNT_CONST_PP_COUNT_IMPL_65 65 +#define PP_COUNT_CONST_PP_COUNT_IMPL_66 66 +#define PP_COUNT_CONST_PP_COUNT_IMPL_67 67 +#define PP_COUNT_CONST_PP_COUNT_IMPL_68 68 +#define PP_COUNT_CONST_PP_COUNT_IMPL_69 69 +#define PP_COUNT_CONST_PP_COUNT_IMPL_70 70 +#define PP_COUNT_CONST_PP_COUNT_IMPL_71 71 +#define PP_COUNT_CONST_PP_COUNT_IMPL_72 72 +#define PP_COUNT_CONST_PP_COUNT_IMPL_73 73 +#define PP_COUNT_CONST_PP_COUNT_IMPL_74 74 +#define PP_COUNT_CONST_PP_COUNT_IMPL_75 75 +#define PP_COUNT_CONST_PP_COUNT_IMPL_76 76 +#define PP_COUNT_CONST_PP_COUNT_IMPL_77 77 +#define PP_COUNT_CONST_PP_COUNT_IMPL_78 78 +#define PP_COUNT_CONST_PP_COUNT_IMPL_79 79 +#define PP_COUNT_CONST_PP_COUNT_IMPL_80 80 +#define PP_COUNT_CONST_PP_COUNT_IMPL_81 81 +#define PP_COUNT_CONST_PP_COUNT_IMPL_82 82 +#define PP_COUNT_CONST_PP_COUNT_IMPL_83 83 +#define PP_COUNT_CONST_PP_COUNT_IMPL_84 84 +#define PP_COUNT_CONST_PP_COUNT_IMPL_85 85 +#define PP_COUNT_CONST_PP_COUNT_IMPL_86 86 +#define PP_COUNT_CONST_PP_COUNT_IMPL_87 87 +#define PP_COUNT_CONST_PP_COUNT_IMPL_88 88 +#define PP_COUNT_CONST_PP_COUNT_IMPL_89 89 +#define PP_COUNT_CONST_PP_COUNT_IMPL_90 90 +#define PP_COUNT_CONST_PP_COUNT_IMPL_91 91 +#define PP_COUNT_CONST_PP_COUNT_IMPL_92 92 +#define PP_COUNT_CONST_PP_COUNT_IMPL_93 93 +#define PP_COUNT_CONST_PP_COUNT_IMPL_94 94 +#define PP_COUNT_CONST_PP_COUNT_IMPL_95 95 +#define PP_COUNT_CONST_PP_COUNT_IMPL_96 96 +#define PP_COUNT_CONST_PP_COUNT_IMPL_97 97 +#define PP_COUNT_CONST_PP_COUNT_IMPL_98 98 +#define PP_COUNT_CONST_PP_COUNT_IMPL_99 99 +#define PP_COUNT_CONST_PP_COUNT_IMPL_100 100 +#define PP_COUNT_CONST_PP_COUNT_IMPL_101 101 +#define PP_COUNT_CONST_PP_COUNT_IMPL_102 102 +#define PP_COUNT_CONST_PP_COUNT_IMPL_103 103 +#define PP_COUNT_CONST_PP_COUNT_IMPL_104 104 +#define PP_COUNT_CONST_PP_COUNT_IMPL_105 105 +#define PP_COUNT_CONST_PP_COUNT_IMPL_106 106 +#define PP_COUNT_CONST_PP_COUNT_IMPL_107 107 +#define PP_COUNT_CONST_PP_COUNT_IMPL_108 108 +#define PP_COUNT_CONST_PP_COUNT_IMPL_109 109 +#define PP_COUNT_CONST_PP_COUNT_IMPL_110 110 +#define PP_COUNT_CONST_PP_COUNT_IMPL_111 111 +#define PP_COUNT_CONST_PP_COUNT_IMPL_112 112 +#define PP_COUNT_CONST_PP_COUNT_IMPL_113 113 +#define PP_COUNT_CONST_PP_COUNT_IMPL_114 114 +#define PP_COUNT_CONST_PP_COUNT_IMPL_115 115 +#define PP_COUNT_CONST_PP_COUNT_IMPL_116 116 +#define PP_COUNT_CONST_PP_COUNT_IMPL_117 117 +#define PP_COUNT_CONST_PP_COUNT_IMPL_118 118 +#define PP_COUNT_CONST_PP_COUNT_IMPL_119 119 +#define PP_COUNT_CONST_PP_COUNT_IMPL_120 120 +#define PP_COUNT_CONST_PP_COUNT_IMPL_121 121 +#define PP_COUNT_CONST_PP_COUNT_IMPL_122 122 +#define PP_COUNT_CONST_PP_COUNT_IMPL_123 123 +#define PP_COUNT_CONST_PP_COUNT_IMPL_124 124 +#define PP_COUNT_CONST_PP_COUNT_IMPL_125 125 +#define PP_COUNT_CONST_PP_COUNT_IMPL_126 126 +#define PP_COUNT_CONST_PP_COUNT_IMPL_127 127 +#define PP_COUNT_CONST_PP_COUNT_IMPL_128 128 +#define PP_COUNT_CONST_PP_COUNT_IMPL_129 129 +#define PP_COUNT_CONST_PP_COUNT_IMPL_130 130 +#define PP_COUNT_CONST_PP_COUNT_IMPL_131 131 +#define PP_COUNT_CONST_PP_COUNT_IMPL_132 132 +#define PP_COUNT_CONST_PP_COUNT_IMPL_133 133 +#define PP_COUNT_CONST_PP_COUNT_IMPL_134 134 +#define PP_COUNT_CONST_PP_COUNT_IMPL_135 135 +#define PP_COUNT_CONST_PP_COUNT_IMPL_136 136 +#define PP_COUNT_CONST_PP_COUNT_IMPL_137 137 +#define PP_COUNT_CONST_PP_COUNT_IMPL_138 138 +#define PP_COUNT_CONST_PP_COUNT_IMPL_139 139 +#define PP_COUNT_CONST_PP_COUNT_IMPL_140 140 +#define PP_COUNT_CONST_PP_COUNT_IMPL_141 141 +#define PP_COUNT_CONST_PP_COUNT_IMPL_142 142 +#define PP_COUNT_CONST_PP_COUNT_IMPL_143 143 +#define PP_COUNT_CONST_PP_COUNT_IMPL_144 144 +#define PP_COUNT_CONST_PP_COUNT_IMPL_145 145 +#define PP_COUNT_CONST_PP_COUNT_IMPL_146 146 +#define PP_COUNT_CONST_PP_COUNT_IMPL_147 147 +#define PP_COUNT_CONST_PP_COUNT_IMPL_148 148 +#define PP_COUNT_CONST_PP_COUNT_IMPL_149 149 +#define PP_COUNT_CONST_PP_COUNT_IMPL_150 150 +#define PP_COUNT_CONST_PP_COUNT_IMPL_151 151 +#define PP_COUNT_CONST_PP_COUNT_IMPL_152 152 +#define PP_COUNT_CONST_PP_COUNT_IMPL_153 153 +#define PP_COUNT_CONST_PP_COUNT_IMPL_154 154 +#define PP_COUNT_CONST_PP_COUNT_IMPL_155 155 +#define PP_COUNT_CONST_PP_COUNT_IMPL_156 156 +#define PP_COUNT_CONST_PP_COUNT_IMPL_157 157 +#define PP_COUNT_CONST_PP_COUNT_IMPL_158 158 +#define PP_COUNT_CONST_PP_COUNT_IMPL_159 159 +#define PP_COUNT_CONST_PP_COUNT_IMPL_160 160 +#define PP_COUNT_CONST_PP_COUNT_IMPL_161 161 +#define PP_COUNT_CONST_PP_COUNT_IMPL_162 162 +#define PP_COUNT_CONST_PP_COUNT_IMPL_163 163 +#define PP_COUNT_CONST_PP_COUNT_IMPL_164 164 +#define PP_COUNT_CONST_PP_COUNT_IMPL_165 165 +#define PP_COUNT_CONST_PP_COUNT_IMPL_166 166 +#define PP_COUNT_CONST_PP_COUNT_IMPL_167 167 +#define PP_COUNT_CONST_PP_COUNT_IMPL_168 168 +#define PP_COUNT_CONST_PP_COUNT_IMPL_169 169 +#define PP_COUNT_CONST_PP_COUNT_IMPL_170 170 +#define PP_COUNT_CONST_PP_COUNT_IMPL_171 171 +#define PP_COUNT_CONST_PP_COUNT_IMPL_172 172 +#define PP_COUNT_CONST_PP_COUNT_IMPL_173 173 +#define PP_COUNT_CONST_PP_COUNT_IMPL_174 174 +#define PP_COUNT_CONST_PP_COUNT_IMPL_175 175 +#define PP_COUNT_CONST_PP_COUNT_IMPL_176 176 +#define PP_COUNT_CONST_PP_COUNT_IMPL_177 177 +#define PP_COUNT_CONST_PP_COUNT_IMPL_178 178 +#define PP_COUNT_CONST_PP_COUNT_IMPL_179 179 +#define PP_COUNT_CONST_PP_COUNT_IMPL_180 180 +#define PP_COUNT_CONST_PP_COUNT_IMPL_181 181 +#define PP_COUNT_CONST_PP_COUNT_IMPL_182 182 +#define PP_COUNT_CONST_PP_COUNT_IMPL_183 183 +#define PP_COUNT_CONST_PP_COUNT_IMPL_184 184 +#define PP_COUNT_CONST_PP_COUNT_IMPL_185 185 +#define PP_COUNT_CONST_PP_COUNT_IMPL_186 186 +#define PP_COUNT_CONST_PP_COUNT_IMPL_187 187 +#define PP_COUNT_CONST_PP_COUNT_IMPL_188 188 +#define PP_COUNT_CONST_PP_COUNT_IMPL_189 189 +#define PP_COUNT_CONST_PP_COUNT_IMPL_190 190 +#define PP_COUNT_CONST_PP_COUNT_IMPL_191 191 +#define PP_COUNT_CONST_PP_COUNT_IMPL_192 192 +#define PP_COUNT_CONST_PP_COUNT_IMPL_193 193 +#define PP_COUNT_CONST_PP_COUNT_IMPL_194 194 +#define PP_COUNT_CONST_PP_COUNT_IMPL_195 195 +#define PP_COUNT_CONST_PP_COUNT_IMPL_196 196 +#define PP_COUNT_CONST_PP_COUNT_IMPL_197 197 +#define PP_COUNT_CONST_PP_COUNT_IMPL_198 198 +#define PP_COUNT_CONST_PP_COUNT_IMPL_199 199 +#define PP_COUNT_IMPL_0(_) PP_COUNT_IMPL_1 +#define PP_COUNT_IMPL_1(_) PP_COUNT_IMPL_2 +#define PP_COUNT_IMPL_2(_) PP_COUNT_IMPL_3 +#define PP_COUNT_IMPL_3(_) PP_COUNT_IMPL_4 +#define PP_COUNT_IMPL_4(_) PP_COUNT_IMPL_5 +#define PP_COUNT_IMPL_5(_) PP_COUNT_IMPL_6 +#define PP_COUNT_IMPL_6(_) PP_COUNT_IMPL_7 +#define PP_COUNT_IMPL_7(_) PP_COUNT_IMPL_8 +#define PP_COUNT_IMPL_8(_) PP_COUNT_IMPL_9 +#define PP_COUNT_IMPL_9(_) PP_COUNT_IMPL_10 +#define PP_COUNT_IMPL_10(_) PP_COUNT_IMPL_11 +#define PP_COUNT_IMPL_11(_) PP_COUNT_IMPL_12 +#define PP_COUNT_IMPL_12(_) PP_COUNT_IMPL_13 +#define PP_COUNT_IMPL_13(_) PP_COUNT_IMPL_14 +#define PP_COUNT_IMPL_14(_) PP_COUNT_IMPL_15 +#define PP_COUNT_IMPL_15(_) PP_COUNT_IMPL_16 +#define PP_COUNT_IMPL_16(_) PP_COUNT_IMPL_17 +#define PP_COUNT_IMPL_17(_) PP_COUNT_IMPL_18 +#define PP_COUNT_IMPL_18(_) PP_COUNT_IMPL_19 +#define PP_COUNT_IMPL_19(_) PP_COUNT_IMPL_20 +#define PP_COUNT_IMPL_20(_) PP_COUNT_IMPL_21 +#define PP_COUNT_IMPL_21(_) PP_COUNT_IMPL_22 +#define PP_COUNT_IMPL_22(_) PP_COUNT_IMPL_23 +#define PP_COUNT_IMPL_23(_) PP_COUNT_IMPL_24 +#define PP_COUNT_IMPL_24(_) PP_COUNT_IMPL_25 +#define PP_COUNT_IMPL_25(_) PP_COUNT_IMPL_26 +#define PP_COUNT_IMPL_26(_) PP_COUNT_IMPL_27 +#define PP_COUNT_IMPL_27(_) PP_COUNT_IMPL_28 +#define PP_COUNT_IMPL_28(_) PP_COUNT_IMPL_29 +#define PP_COUNT_IMPL_29(_) PP_COUNT_IMPL_30 +#define PP_COUNT_IMPL_30(_) PP_COUNT_IMPL_31 +#define PP_COUNT_IMPL_31(_) PP_COUNT_IMPL_32 +#define PP_COUNT_IMPL_32(_) PP_COUNT_IMPL_33 +#define PP_COUNT_IMPL_33(_) PP_COUNT_IMPL_34 +#define PP_COUNT_IMPL_34(_) PP_COUNT_IMPL_35 +#define PP_COUNT_IMPL_35(_) PP_COUNT_IMPL_36 +#define PP_COUNT_IMPL_36(_) PP_COUNT_IMPL_37 +#define PP_COUNT_IMPL_37(_) PP_COUNT_IMPL_38 +#define PP_COUNT_IMPL_38(_) PP_COUNT_IMPL_39 +#define PP_COUNT_IMPL_39(_) PP_COUNT_IMPL_40 +#define PP_COUNT_IMPL_40(_) PP_COUNT_IMPL_41 +#define PP_COUNT_IMPL_41(_) PP_COUNT_IMPL_42 +#define PP_COUNT_IMPL_42(_) PP_COUNT_IMPL_43 +#define PP_COUNT_IMPL_43(_) PP_COUNT_IMPL_44 +#define PP_COUNT_IMPL_44(_) PP_COUNT_IMPL_45 +#define PP_COUNT_IMPL_45(_) PP_COUNT_IMPL_46 +#define PP_COUNT_IMPL_46(_) PP_COUNT_IMPL_47 +#define PP_COUNT_IMPL_47(_) PP_COUNT_IMPL_48 +#define PP_COUNT_IMPL_48(_) PP_COUNT_IMPL_49 +#define PP_COUNT_IMPL_49(_) PP_COUNT_IMPL_50 +#define PP_COUNT_IMPL_50(_) PP_COUNT_IMPL_51 +#define PP_COUNT_IMPL_51(_) PP_COUNT_IMPL_52 +#define PP_COUNT_IMPL_52(_) PP_COUNT_IMPL_53 +#define PP_COUNT_IMPL_53(_) PP_COUNT_IMPL_54 +#define PP_COUNT_IMPL_54(_) PP_COUNT_IMPL_55 +#define PP_COUNT_IMPL_55(_) PP_COUNT_IMPL_56 +#define PP_COUNT_IMPL_56(_) PP_COUNT_IMPL_57 +#define PP_COUNT_IMPL_57(_) PP_COUNT_IMPL_58 +#define PP_COUNT_IMPL_58(_) PP_COUNT_IMPL_59 +#define PP_COUNT_IMPL_59(_) PP_COUNT_IMPL_60 +#define PP_COUNT_IMPL_60(_) PP_COUNT_IMPL_61 +#define PP_COUNT_IMPL_61(_) PP_COUNT_IMPL_62 +#define PP_COUNT_IMPL_62(_) PP_COUNT_IMPL_63 +#define PP_COUNT_IMPL_63(_) PP_COUNT_IMPL_64 +#define PP_COUNT_IMPL_64(_) PP_COUNT_IMPL_65 +#define PP_COUNT_IMPL_65(_) PP_COUNT_IMPL_66 +#define PP_COUNT_IMPL_66(_) PP_COUNT_IMPL_67 +#define PP_COUNT_IMPL_67(_) PP_COUNT_IMPL_68 +#define PP_COUNT_IMPL_68(_) PP_COUNT_IMPL_69 +#define PP_COUNT_IMPL_69(_) PP_COUNT_IMPL_70 +#define PP_COUNT_IMPL_70(_) PP_COUNT_IMPL_71 +#define PP_COUNT_IMPL_71(_) PP_COUNT_IMPL_72 +#define PP_COUNT_IMPL_72(_) PP_COUNT_IMPL_73 +#define PP_COUNT_IMPL_73(_) PP_COUNT_IMPL_74 +#define PP_COUNT_IMPL_74(_) PP_COUNT_IMPL_75 +#define PP_COUNT_IMPL_75(_) PP_COUNT_IMPL_76 +#define PP_COUNT_IMPL_76(_) PP_COUNT_IMPL_77 +#define PP_COUNT_IMPL_77(_) PP_COUNT_IMPL_78 +#define PP_COUNT_IMPL_78(_) PP_COUNT_IMPL_79 +#define PP_COUNT_IMPL_79(_) PP_COUNT_IMPL_80 +#define PP_COUNT_IMPL_80(_) PP_COUNT_IMPL_81 +#define PP_COUNT_IMPL_81(_) PP_COUNT_IMPL_82 +#define PP_COUNT_IMPL_82(_) PP_COUNT_IMPL_83 +#define PP_COUNT_IMPL_83(_) PP_COUNT_IMPL_84 +#define PP_COUNT_IMPL_84(_) PP_COUNT_IMPL_85 +#define PP_COUNT_IMPL_85(_) PP_COUNT_IMPL_86 +#define PP_COUNT_IMPL_86(_) PP_COUNT_IMPL_87 +#define PP_COUNT_IMPL_87(_) PP_COUNT_IMPL_88 +#define PP_COUNT_IMPL_88(_) PP_COUNT_IMPL_89 +#define PP_COUNT_IMPL_89(_) PP_COUNT_IMPL_90 +#define PP_COUNT_IMPL_90(_) PP_COUNT_IMPL_91 +#define PP_COUNT_IMPL_91(_) PP_COUNT_IMPL_92 +#define PP_COUNT_IMPL_92(_) PP_COUNT_IMPL_93 +#define PP_COUNT_IMPL_93(_) PP_COUNT_IMPL_94 +#define PP_COUNT_IMPL_94(_) PP_COUNT_IMPL_95 +#define PP_COUNT_IMPL_95(_) PP_COUNT_IMPL_96 +#define PP_COUNT_IMPL_96(_) PP_COUNT_IMPL_97 +#define PP_COUNT_IMPL_97(_) PP_COUNT_IMPL_98 +#define PP_COUNT_IMPL_98(_) PP_COUNT_IMPL_99 +#define PP_COUNT_IMPL_99(_) PP_COUNT_IMPL_100 +#define PP_COUNT_IMPL_100(_) PP_COUNT_IMPL_101 +#define PP_COUNT_IMPL_101(_) PP_COUNT_IMPL_102 +#define PP_COUNT_IMPL_102(_) PP_COUNT_IMPL_103 +#define PP_COUNT_IMPL_103(_) PP_COUNT_IMPL_104 +#define PP_COUNT_IMPL_104(_) PP_COUNT_IMPL_105 +#define PP_COUNT_IMPL_105(_) PP_COUNT_IMPL_106 +#define PP_COUNT_IMPL_106(_) PP_COUNT_IMPL_107 +#define PP_COUNT_IMPL_107(_) PP_COUNT_IMPL_108 +#define PP_COUNT_IMPL_108(_) PP_COUNT_IMPL_109 +#define PP_COUNT_IMPL_109(_) PP_COUNT_IMPL_110 +#define PP_COUNT_IMPL_110(_) PP_COUNT_IMPL_111 +#define PP_COUNT_IMPL_111(_) PP_COUNT_IMPL_112 +#define PP_COUNT_IMPL_112(_) PP_COUNT_IMPL_113 +#define PP_COUNT_IMPL_113(_) PP_COUNT_IMPL_114 +#define PP_COUNT_IMPL_114(_) PP_COUNT_IMPL_115 +#define PP_COUNT_IMPL_115(_) PP_COUNT_IMPL_116 +#define PP_COUNT_IMPL_116(_) PP_COUNT_IMPL_117 +#define PP_COUNT_IMPL_117(_) PP_COUNT_IMPL_118 +#define PP_COUNT_IMPL_118(_) PP_COUNT_IMPL_119 +#define PP_COUNT_IMPL_119(_) PP_COUNT_IMPL_120 +#define PP_COUNT_IMPL_120(_) PP_COUNT_IMPL_121 +#define PP_COUNT_IMPL_121(_) PP_COUNT_IMPL_122 +#define PP_COUNT_IMPL_122(_) PP_COUNT_IMPL_123 +#define PP_COUNT_IMPL_123(_) PP_COUNT_IMPL_124 +#define PP_COUNT_IMPL_124(_) PP_COUNT_IMPL_125 +#define PP_COUNT_IMPL_125(_) PP_COUNT_IMPL_126 +#define PP_COUNT_IMPL_126(_) PP_COUNT_IMPL_127 +#define PP_COUNT_IMPL_127(_) PP_COUNT_IMPL_128 +#define PP_COUNT_IMPL_128(_) PP_COUNT_IMPL_129 +#define PP_COUNT_IMPL_129(_) PP_COUNT_IMPL_130 +#define PP_COUNT_IMPL_130(_) PP_COUNT_IMPL_131 +#define PP_COUNT_IMPL_131(_) PP_COUNT_IMPL_132 +#define PP_COUNT_IMPL_132(_) PP_COUNT_IMPL_133 +#define PP_COUNT_IMPL_133(_) PP_COUNT_IMPL_134 +#define PP_COUNT_IMPL_134(_) PP_COUNT_IMPL_135 +#define PP_COUNT_IMPL_135(_) PP_COUNT_IMPL_136 +#define PP_COUNT_IMPL_136(_) PP_COUNT_IMPL_137 +#define PP_COUNT_IMPL_137(_) PP_COUNT_IMPL_138 +#define PP_COUNT_IMPL_138(_) PP_COUNT_IMPL_139 +#define PP_COUNT_IMPL_139(_) PP_COUNT_IMPL_140 +#define PP_COUNT_IMPL_140(_) PP_COUNT_IMPL_141 +#define PP_COUNT_IMPL_141(_) PP_COUNT_IMPL_142 +#define PP_COUNT_IMPL_142(_) PP_COUNT_IMPL_143 +#define PP_COUNT_IMPL_143(_) PP_COUNT_IMPL_144 +#define PP_COUNT_IMPL_144(_) PP_COUNT_IMPL_145 +#define PP_COUNT_IMPL_145(_) PP_COUNT_IMPL_146 +#define PP_COUNT_IMPL_146(_) PP_COUNT_IMPL_147 +#define PP_COUNT_IMPL_147(_) PP_COUNT_IMPL_148 +#define PP_COUNT_IMPL_148(_) PP_COUNT_IMPL_149 +#define PP_COUNT_IMPL_149(_) PP_COUNT_IMPL_150 +#define PP_COUNT_IMPL_150(_) PP_COUNT_IMPL_151 +#define PP_COUNT_IMPL_151(_) PP_COUNT_IMPL_152 +#define PP_COUNT_IMPL_152(_) PP_COUNT_IMPL_153 +#define PP_COUNT_IMPL_153(_) PP_COUNT_IMPL_154 +#define PP_COUNT_IMPL_154(_) PP_COUNT_IMPL_155 +#define PP_COUNT_IMPL_155(_) PP_COUNT_IMPL_156 +#define PP_COUNT_IMPL_156(_) PP_COUNT_IMPL_157 +#define PP_COUNT_IMPL_157(_) PP_COUNT_IMPL_158 +#define PP_COUNT_IMPL_158(_) PP_COUNT_IMPL_159 +#define PP_COUNT_IMPL_159(_) PP_COUNT_IMPL_160 +#define PP_COUNT_IMPL_160(_) PP_COUNT_IMPL_161 +#define PP_COUNT_IMPL_161(_) PP_COUNT_IMPL_162 +#define PP_COUNT_IMPL_162(_) PP_COUNT_IMPL_163 +#define PP_COUNT_IMPL_163(_) PP_COUNT_IMPL_164 +#define PP_COUNT_IMPL_164(_) PP_COUNT_IMPL_165 +#define PP_COUNT_IMPL_165(_) PP_COUNT_IMPL_166 +#define PP_COUNT_IMPL_166(_) PP_COUNT_IMPL_167 +#define PP_COUNT_IMPL_167(_) PP_COUNT_IMPL_168 +#define PP_COUNT_IMPL_168(_) PP_COUNT_IMPL_169 +#define PP_COUNT_IMPL_169(_) PP_COUNT_IMPL_170 +#define PP_COUNT_IMPL_170(_) PP_COUNT_IMPL_171 +#define PP_COUNT_IMPL_171(_) PP_COUNT_IMPL_172 +#define PP_COUNT_IMPL_172(_) PP_COUNT_IMPL_173 +#define PP_COUNT_IMPL_173(_) PP_COUNT_IMPL_174 +#define PP_COUNT_IMPL_174(_) PP_COUNT_IMPL_175 +#define PP_COUNT_IMPL_175(_) PP_COUNT_IMPL_176 +#define PP_COUNT_IMPL_176(_) PP_COUNT_IMPL_177 +#define PP_COUNT_IMPL_177(_) PP_COUNT_IMPL_178 +#define PP_COUNT_IMPL_178(_) PP_COUNT_IMPL_179 +#define PP_COUNT_IMPL_179(_) PP_COUNT_IMPL_180 +#define PP_COUNT_IMPL_180(_) PP_COUNT_IMPL_181 +#define PP_COUNT_IMPL_181(_) PP_COUNT_IMPL_182 +#define PP_COUNT_IMPL_182(_) PP_COUNT_IMPL_183 +#define PP_COUNT_IMPL_183(_) PP_COUNT_IMPL_184 +#define PP_COUNT_IMPL_184(_) PP_COUNT_IMPL_185 +#define PP_COUNT_IMPL_185(_) PP_COUNT_IMPL_186 +#define PP_COUNT_IMPL_186(_) PP_COUNT_IMPL_187 +#define PP_COUNT_IMPL_187(_) PP_COUNT_IMPL_188 +#define PP_COUNT_IMPL_188(_) PP_COUNT_IMPL_189 +#define PP_COUNT_IMPL_189(_) PP_COUNT_IMPL_190 +#define PP_COUNT_IMPL_190(_) PP_COUNT_IMPL_191 +#define PP_COUNT_IMPL_191(_) PP_COUNT_IMPL_192 +#define PP_COUNT_IMPL_192(_) PP_COUNT_IMPL_193 +#define PP_COUNT_IMPL_193(_) PP_COUNT_IMPL_194 +#define PP_COUNT_IMPL_194(_) PP_COUNT_IMPL_195 +#define PP_COUNT_IMPL_195(_) PP_COUNT_IMPL_196 +#define PP_COUNT_IMPL_196(_) PP_COUNT_IMPL_197 +#define PP_COUNT_IMPL_197(_) PP_COUNT_IMPL_198 +#define PP_COUNT_IMPL_198(_) PP_COUNT_IMPL_199 +#define PP_COUNT_IMPL_199(_) PP_COUNT_IMPL_200 + +//////////////////////////////////////////////////////////////////////////////// +#define PP_KILL_IMPL(seq, index) PP_CONCAT(PP_KILL_IMPL_, index) seq +#define PP_KILL_IMPL_0 +#define PP_KILL_IMPL_1(_) PP_KILL_IMPL_0 +#define PP_KILL_IMPL_2(_) PP_KILL_IMPL_1 +#define PP_KILL_IMPL_3(_) PP_KILL_IMPL_2 +#define PP_KILL_IMPL_4(_) PP_KILL_IMPL_3 +#define PP_KILL_IMPL_5(_) PP_KILL_IMPL_4 +#define PP_KILL_IMPL_6(_) PP_KILL_IMPL_5 +#define PP_KILL_IMPL_7(_) PP_KILL_IMPL_6 +#define PP_KILL_IMPL_8(_) PP_KILL_IMPL_7 +#define PP_KILL_IMPL_9(_) PP_KILL_IMPL_8 +#define PP_KILL_IMPL_10(_) PP_KILL_IMPL_9 +#define PP_KILL_IMPL_11(_) PP_KILL_IMPL_10 +#define PP_KILL_IMPL_12(_) PP_KILL_IMPL_11 +#define PP_KILL_IMPL_13(_) PP_KILL_IMPL_12 +#define PP_KILL_IMPL_14(_) PP_KILL_IMPL_13 +#define PP_KILL_IMPL_15(_) PP_KILL_IMPL_14 +#define PP_KILL_IMPL_16(_) PP_KILL_IMPL_15 +#define PP_KILL_IMPL_17(_) PP_KILL_IMPL_16 +#define PP_KILL_IMPL_18(_) PP_KILL_IMPL_17 +#define PP_KILL_IMPL_19(_) PP_KILL_IMPL_18 +#define PP_KILL_IMPL_20(_) PP_KILL_IMPL_19 +#define PP_KILL_IMPL_21(_) PP_KILL_IMPL_20 +#define PP_KILL_IMPL_22(_) PP_KILL_IMPL_21 +#define PP_KILL_IMPL_23(_) PP_KILL_IMPL_22 +#define PP_KILL_IMPL_24(_) PP_KILL_IMPL_23 +#define PP_KILL_IMPL_25(_) PP_KILL_IMPL_24 +#define PP_KILL_IMPL_26(_) PP_KILL_IMPL_25 +#define PP_KILL_IMPL_27(_) PP_KILL_IMPL_26 +#define PP_KILL_IMPL_28(_) PP_KILL_IMPL_27 +#define PP_KILL_IMPL_29(_) PP_KILL_IMPL_28 +#define PP_KILL_IMPL_30(_) PP_KILL_IMPL_29 +#define PP_KILL_IMPL_31(_) PP_KILL_IMPL_30 +#define PP_KILL_IMPL_32(_) PP_KILL_IMPL_31 +#define PP_KILL_IMPL_33(_) PP_KILL_IMPL_32 +#define PP_KILL_IMPL_34(_) PP_KILL_IMPL_33 +#define PP_KILL_IMPL_35(_) PP_KILL_IMPL_34 +#define PP_KILL_IMPL_36(_) PP_KILL_IMPL_35 +#define PP_KILL_IMPL_37(_) PP_KILL_IMPL_36 +#define PP_KILL_IMPL_38(_) PP_KILL_IMPL_37 +#define PP_KILL_IMPL_39(_) PP_KILL_IMPL_38 +#define PP_KILL_IMPL_40(_) PP_KILL_IMPL_39 +#define PP_KILL_IMPL_41(_) PP_KILL_IMPL_40 +#define PP_KILL_IMPL_42(_) PP_KILL_IMPL_41 +#define PP_KILL_IMPL_43(_) PP_KILL_IMPL_42 +#define PP_KILL_IMPL_44(_) PP_KILL_IMPL_43 +#define PP_KILL_IMPL_45(_) PP_KILL_IMPL_44 +#define PP_KILL_IMPL_46(_) PP_KILL_IMPL_45 +#define PP_KILL_IMPL_47(_) PP_KILL_IMPL_46 +#define PP_KILL_IMPL_48(_) PP_KILL_IMPL_47 +#define PP_KILL_IMPL_49(_) PP_KILL_IMPL_48 +#define PP_KILL_IMPL_50(_) PP_KILL_IMPL_49 +#define PP_KILL_IMPL_51(_) PP_KILL_IMPL_50 +#define PP_KILL_IMPL_52(_) PP_KILL_IMPL_51 +#define PP_KILL_IMPL_53(_) PP_KILL_IMPL_52 +#define PP_KILL_IMPL_54(_) PP_KILL_IMPL_53 +#define PP_KILL_IMPL_55(_) PP_KILL_IMPL_54 +#define PP_KILL_IMPL_56(_) PP_KILL_IMPL_55 +#define PP_KILL_IMPL_57(_) PP_KILL_IMPL_56 +#define PP_KILL_IMPL_58(_) PP_KILL_IMPL_57 +#define PP_KILL_IMPL_59(_) PP_KILL_IMPL_58 +#define PP_KILL_IMPL_60(_) PP_KILL_IMPL_59 +#define PP_KILL_IMPL_61(_) PP_KILL_IMPL_60 +#define PP_KILL_IMPL_62(_) PP_KILL_IMPL_61 +#define PP_KILL_IMPL_63(_) PP_KILL_IMPL_62 +#define PP_KILL_IMPL_64(_) PP_KILL_IMPL_63 +#define PP_KILL_IMPL_65(_) PP_KILL_IMPL_64 +#define PP_KILL_IMPL_66(_) PP_KILL_IMPL_65 +#define PP_KILL_IMPL_67(_) PP_KILL_IMPL_66 +#define PP_KILL_IMPL_68(_) PP_KILL_IMPL_67 +#define PP_KILL_IMPL_69(_) PP_KILL_IMPL_68 +#define PP_KILL_IMPL_70(_) PP_KILL_IMPL_69 +#define PP_KILL_IMPL_71(_) PP_KILL_IMPL_70 +#define PP_KILL_IMPL_72(_) PP_KILL_IMPL_71 +#define PP_KILL_IMPL_73(_) PP_KILL_IMPL_72 +#define PP_KILL_IMPL_74(_) PP_KILL_IMPL_73 +#define PP_KILL_IMPL_75(_) PP_KILL_IMPL_74 +#define PP_KILL_IMPL_76(_) PP_KILL_IMPL_75 +#define PP_KILL_IMPL_77(_) PP_KILL_IMPL_76 +#define PP_KILL_IMPL_78(_) PP_KILL_IMPL_77 +#define PP_KILL_IMPL_79(_) PP_KILL_IMPL_78 +#define PP_KILL_IMPL_80(_) PP_KILL_IMPL_79 +#define PP_KILL_IMPL_81(_) PP_KILL_IMPL_80 +#define PP_KILL_IMPL_82(_) PP_KILL_IMPL_81 +#define PP_KILL_IMPL_83(_) PP_KILL_IMPL_82 +#define PP_KILL_IMPL_84(_) PP_KILL_IMPL_83 +#define PP_KILL_IMPL_85(_) PP_KILL_IMPL_84 +#define PP_KILL_IMPL_86(_) PP_KILL_IMPL_85 +#define PP_KILL_IMPL_87(_) PP_KILL_IMPL_86 +#define PP_KILL_IMPL_88(_) PP_KILL_IMPL_87 +#define PP_KILL_IMPL_89(_) PP_KILL_IMPL_88 +#define PP_KILL_IMPL_90(_) PP_KILL_IMPL_89 +#define PP_KILL_IMPL_91(_) PP_KILL_IMPL_90 +#define PP_KILL_IMPL_92(_) PP_KILL_IMPL_91 +#define PP_KILL_IMPL_93(_) PP_KILL_IMPL_92 +#define PP_KILL_IMPL_94(_) PP_KILL_IMPL_93 +#define PP_KILL_IMPL_95(_) PP_KILL_IMPL_94 +#define PP_KILL_IMPL_96(_) PP_KILL_IMPL_95 +#define PP_KILL_IMPL_97(_) PP_KILL_IMPL_96 +#define PP_KILL_IMPL_98(_) PP_KILL_IMPL_97 +#define PP_KILL_IMPL_99(_) PP_KILL_IMPL_98 +#define PP_KILL_IMPL_100(_) PP_KILL_IMPL_99 +#define PP_KILL_IMPL_101(_) PP_KILL_IMPL_100 +#define PP_KILL_IMPL_102(_) PP_KILL_IMPL_101 +#define PP_KILL_IMPL_103(_) PP_KILL_IMPL_102 +#define PP_KILL_IMPL_104(_) PP_KILL_IMPL_103 +#define PP_KILL_IMPL_105(_) PP_KILL_IMPL_104 +#define PP_KILL_IMPL_106(_) PP_KILL_IMPL_105 +#define PP_KILL_IMPL_107(_) PP_KILL_IMPL_106 +#define PP_KILL_IMPL_108(_) PP_KILL_IMPL_107 +#define PP_KILL_IMPL_109(_) PP_KILL_IMPL_108 +#define PP_KILL_IMPL_110(_) PP_KILL_IMPL_109 +#define PP_KILL_IMPL_111(_) PP_KILL_IMPL_110 +#define PP_KILL_IMPL_112(_) PP_KILL_IMPL_111 +#define PP_KILL_IMPL_113(_) PP_KILL_IMPL_112 +#define PP_KILL_IMPL_114(_) PP_KILL_IMPL_113 +#define PP_KILL_IMPL_115(_) PP_KILL_IMPL_114 +#define PP_KILL_IMPL_116(_) PP_KILL_IMPL_115 +#define PP_KILL_IMPL_117(_) PP_KILL_IMPL_116 +#define PP_KILL_IMPL_118(_) PP_KILL_IMPL_117 +#define PP_KILL_IMPL_119(_) PP_KILL_IMPL_118 +#define PP_KILL_IMPL_120(_) PP_KILL_IMPL_119 +#define PP_KILL_IMPL_121(_) PP_KILL_IMPL_120 +#define PP_KILL_IMPL_122(_) PP_KILL_IMPL_121 +#define PP_KILL_IMPL_123(_) PP_KILL_IMPL_122 +#define PP_KILL_IMPL_124(_) PP_KILL_IMPL_123 +#define PP_KILL_IMPL_125(_) PP_KILL_IMPL_124 +#define PP_KILL_IMPL_126(_) PP_KILL_IMPL_125 +#define PP_KILL_IMPL_127(_) PP_KILL_IMPL_126 +#define PP_KILL_IMPL_128(_) PP_KILL_IMPL_127 +#define PP_KILL_IMPL_129(_) PP_KILL_IMPL_128 +#define PP_KILL_IMPL_130(_) PP_KILL_IMPL_129 +#define PP_KILL_IMPL_131(_) PP_KILL_IMPL_130 +#define PP_KILL_IMPL_132(_) PP_KILL_IMPL_131 +#define PP_KILL_IMPL_133(_) PP_KILL_IMPL_132 +#define PP_KILL_IMPL_134(_) PP_KILL_IMPL_133 +#define PP_KILL_IMPL_135(_) PP_KILL_IMPL_134 +#define PP_KILL_IMPL_136(_) PP_KILL_IMPL_135 +#define PP_KILL_IMPL_137(_) PP_KILL_IMPL_136 +#define PP_KILL_IMPL_138(_) PP_KILL_IMPL_137 +#define PP_KILL_IMPL_139(_) PP_KILL_IMPL_138 +#define PP_KILL_IMPL_140(_) PP_KILL_IMPL_139 +#define PP_KILL_IMPL_141(_) PP_KILL_IMPL_140 +#define PP_KILL_IMPL_142(_) PP_KILL_IMPL_141 +#define PP_KILL_IMPL_143(_) PP_KILL_IMPL_142 +#define PP_KILL_IMPL_144(_) PP_KILL_IMPL_143 +#define PP_KILL_IMPL_145(_) PP_KILL_IMPL_144 +#define PP_KILL_IMPL_146(_) PP_KILL_IMPL_145 +#define PP_KILL_IMPL_147(_) PP_KILL_IMPL_146 +#define PP_KILL_IMPL_148(_) PP_KILL_IMPL_147 +#define PP_KILL_IMPL_149(_) PP_KILL_IMPL_148 +#define PP_KILL_IMPL_150(_) PP_KILL_IMPL_149 +#define PP_KILL_IMPL_151(_) PP_KILL_IMPL_150 +#define PP_KILL_IMPL_152(_) PP_KILL_IMPL_151 +#define PP_KILL_IMPL_153(_) PP_KILL_IMPL_152 +#define PP_KILL_IMPL_154(_) PP_KILL_IMPL_153 +#define PP_KILL_IMPL_155(_) PP_KILL_IMPL_154 +#define PP_KILL_IMPL_156(_) PP_KILL_IMPL_155 +#define PP_KILL_IMPL_157(_) PP_KILL_IMPL_156 +#define PP_KILL_IMPL_158(_) PP_KILL_IMPL_157 +#define PP_KILL_IMPL_159(_) PP_KILL_IMPL_158 +#define PP_KILL_IMPL_160(_) PP_KILL_IMPL_159 +#define PP_KILL_IMPL_161(_) PP_KILL_IMPL_160 +#define PP_KILL_IMPL_162(_) PP_KILL_IMPL_161 +#define PP_KILL_IMPL_163(_) PP_KILL_IMPL_162 +#define PP_KILL_IMPL_164(_) PP_KILL_IMPL_163 +#define PP_KILL_IMPL_165(_) PP_KILL_IMPL_164 +#define PP_KILL_IMPL_166(_) PP_KILL_IMPL_165 +#define PP_KILL_IMPL_167(_) PP_KILL_IMPL_166 +#define PP_KILL_IMPL_168(_) PP_KILL_IMPL_167 +#define PP_KILL_IMPL_169(_) PP_KILL_IMPL_168 +#define PP_KILL_IMPL_170(_) PP_KILL_IMPL_169 +#define PP_KILL_IMPL_171(_) PP_KILL_IMPL_170 +#define PP_KILL_IMPL_172(_) PP_KILL_IMPL_171 +#define PP_KILL_IMPL_173(_) PP_KILL_IMPL_172 +#define PP_KILL_IMPL_174(_) PP_KILL_IMPL_173 +#define PP_KILL_IMPL_175(_) PP_KILL_IMPL_174 +#define PP_KILL_IMPL_176(_) PP_KILL_IMPL_175 +#define PP_KILL_IMPL_177(_) PP_KILL_IMPL_176 +#define PP_KILL_IMPL_178(_) PP_KILL_IMPL_177 +#define PP_KILL_IMPL_179(_) PP_KILL_IMPL_178 +#define PP_KILL_IMPL_180(_) PP_KILL_IMPL_179 +#define PP_KILL_IMPL_181(_) PP_KILL_IMPL_180 +#define PP_KILL_IMPL_182(_) PP_KILL_IMPL_181 +#define PP_KILL_IMPL_183(_) PP_KILL_IMPL_182 +#define PP_KILL_IMPL_184(_) PP_KILL_IMPL_183 +#define PP_KILL_IMPL_185(_) PP_KILL_IMPL_184 +#define PP_KILL_IMPL_186(_) PP_KILL_IMPL_185 +#define PP_KILL_IMPL_187(_) PP_KILL_IMPL_186 +#define PP_KILL_IMPL_188(_) PP_KILL_IMPL_187 +#define PP_KILL_IMPL_189(_) PP_KILL_IMPL_188 +#define PP_KILL_IMPL_190(_) PP_KILL_IMPL_189 +#define PP_KILL_IMPL_191(_) PP_KILL_IMPL_190 +#define PP_KILL_IMPL_192(_) PP_KILL_IMPL_191 +#define PP_KILL_IMPL_193(_) PP_KILL_IMPL_192 +#define PP_KILL_IMPL_194(_) PP_KILL_IMPL_193 +#define PP_KILL_IMPL_195(_) PP_KILL_IMPL_194 +#define PP_KILL_IMPL_196(_) PP_KILL_IMPL_195 +#define PP_KILL_IMPL_197(_) PP_KILL_IMPL_196 +#define PP_KILL_IMPL_198(_) PP_KILL_IMPL_197 +#define PP_KILL_IMPL_199(_) PP_KILL_IMPL_198 +#define PP_KILL_IMPL_200(_) PP_KILL_IMPL_199 + +//////////////////////////////////////////////////////////////////////////////// +#define PP_ELEMENT_IMPL(seq, \ + index) PP_ELEMENT_IMPL_A((PP_CONCAT(PP_ELEMENT_IMPL_, index) seq)) +#define PP_ELEMENT_IMPL_A(x) PP_ELEMENT_IMPL_C(PP_ELEMENT_IMPL_B x) +#define PP_ELEMENT_IMPL_B(x, _) x +#define PP_ELEMENT_IMPL_C(x) x +#define PP_ELEMENT_IMPL_0(x) x, PP_NIL +#define PP_ELEMENT_IMPL_1(_) PP_ELEMENT_IMPL_0 +#define PP_ELEMENT_IMPL_2(_) PP_ELEMENT_IMPL_1 +#define PP_ELEMENT_IMPL_3(_) PP_ELEMENT_IMPL_2 +#define PP_ELEMENT_IMPL_4(_) PP_ELEMENT_IMPL_3 +#define PP_ELEMENT_IMPL_5(_) PP_ELEMENT_IMPL_4 +#define PP_ELEMENT_IMPL_6(_) PP_ELEMENT_IMPL_5 +#define PP_ELEMENT_IMPL_7(_) PP_ELEMENT_IMPL_6 +#define PP_ELEMENT_IMPL_8(_) PP_ELEMENT_IMPL_7 +#define PP_ELEMENT_IMPL_9(_) PP_ELEMENT_IMPL_8 +#define PP_ELEMENT_IMPL_10(_) PP_ELEMENT_IMPL_9 +#define PP_ELEMENT_IMPL_11(_) PP_ELEMENT_IMPL_10 +#define PP_ELEMENT_IMPL_12(_) PP_ELEMENT_IMPL_11 +#define PP_ELEMENT_IMPL_13(_) PP_ELEMENT_IMPL_12 +#define PP_ELEMENT_IMPL_14(_) PP_ELEMENT_IMPL_13 +#define PP_ELEMENT_IMPL_15(_) PP_ELEMENT_IMPL_14 +#define PP_ELEMENT_IMPL_16(_) PP_ELEMENT_IMPL_15 +#define PP_ELEMENT_IMPL_17(_) PP_ELEMENT_IMPL_16 +#define PP_ELEMENT_IMPL_18(_) PP_ELEMENT_IMPL_17 +#define PP_ELEMENT_IMPL_19(_) PP_ELEMENT_IMPL_18 +#define PP_ELEMENT_IMPL_20(_) PP_ELEMENT_IMPL_19 +#define PP_ELEMENT_IMPL_21(_) PP_ELEMENT_IMPL_20 +#define PP_ELEMENT_IMPL_22(_) PP_ELEMENT_IMPL_21 +#define PP_ELEMENT_IMPL_23(_) PP_ELEMENT_IMPL_22 +#define PP_ELEMENT_IMPL_24(_) PP_ELEMENT_IMPL_23 +#define PP_ELEMENT_IMPL_25(_) PP_ELEMENT_IMPL_24 +#define PP_ELEMENT_IMPL_26(_) PP_ELEMENT_IMPL_25 +#define PP_ELEMENT_IMPL_27(_) PP_ELEMENT_IMPL_26 +#define PP_ELEMENT_IMPL_28(_) PP_ELEMENT_IMPL_27 +#define PP_ELEMENT_IMPL_29(_) PP_ELEMENT_IMPL_28 +#define PP_ELEMENT_IMPL_30(_) PP_ELEMENT_IMPL_29 +#define PP_ELEMENT_IMPL_31(_) PP_ELEMENT_IMPL_30 +#define PP_ELEMENT_IMPL_32(_) PP_ELEMENT_IMPL_31 +#define PP_ELEMENT_IMPL_33(_) PP_ELEMENT_IMPL_32 +#define PP_ELEMENT_IMPL_34(_) PP_ELEMENT_IMPL_33 +#define PP_ELEMENT_IMPL_35(_) PP_ELEMENT_IMPL_34 +#define PP_ELEMENT_IMPL_36(_) PP_ELEMENT_IMPL_35 +#define PP_ELEMENT_IMPL_37(_) PP_ELEMENT_IMPL_36 +#define PP_ELEMENT_IMPL_38(_) PP_ELEMENT_IMPL_37 +#define PP_ELEMENT_IMPL_39(_) PP_ELEMENT_IMPL_38 +#define PP_ELEMENT_IMPL_40(_) PP_ELEMENT_IMPL_39 +#define PP_ELEMENT_IMPL_41(_) PP_ELEMENT_IMPL_40 +#define PP_ELEMENT_IMPL_42(_) PP_ELEMENT_IMPL_41 +#define PP_ELEMENT_IMPL_43(_) PP_ELEMENT_IMPL_42 +#define PP_ELEMENT_IMPL_44(_) PP_ELEMENT_IMPL_43 +#define PP_ELEMENT_IMPL_45(_) PP_ELEMENT_IMPL_44 +#define PP_ELEMENT_IMPL_46(_) PP_ELEMENT_IMPL_45 +#define PP_ELEMENT_IMPL_47(_) PP_ELEMENT_IMPL_46 +#define PP_ELEMENT_IMPL_48(_) PP_ELEMENT_IMPL_47 +#define PP_ELEMENT_IMPL_49(_) PP_ELEMENT_IMPL_48 +#define PP_ELEMENT_IMPL_50(_) PP_ELEMENT_IMPL_49 +#define PP_ELEMENT_IMPL_51(_) PP_ELEMENT_IMPL_50 +#define PP_ELEMENT_IMPL_52(_) PP_ELEMENT_IMPL_51 +#define PP_ELEMENT_IMPL_53(_) PP_ELEMENT_IMPL_52 +#define PP_ELEMENT_IMPL_54(_) PP_ELEMENT_IMPL_53 +#define PP_ELEMENT_IMPL_55(_) PP_ELEMENT_IMPL_54 +#define PP_ELEMENT_IMPL_56(_) PP_ELEMENT_IMPL_55 +#define PP_ELEMENT_IMPL_57(_) PP_ELEMENT_IMPL_56 +#define PP_ELEMENT_IMPL_58(_) PP_ELEMENT_IMPL_57 +#define PP_ELEMENT_IMPL_59(_) PP_ELEMENT_IMPL_58 +#define PP_ELEMENT_IMPL_60(_) PP_ELEMENT_IMPL_59 +#define PP_ELEMENT_IMPL_61(_) PP_ELEMENT_IMPL_60 +#define PP_ELEMENT_IMPL_62(_) PP_ELEMENT_IMPL_61 +#define PP_ELEMENT_IMPL_63(_) PP_ELEMENT_IMPL_62 +#define PP_ELEMENT_IMPL_64(_) PP_ELEMENT_IMPL_63 +#define PP_ELEMENT_IMPL_65(_) PP_ELEMENT_IMPL_64 +#define PP_ELEMENT_IMPL_66(_) PP_ELEMENT_IMPL_65 +#define PP_ELEMENT_IMPL_67(_) PP_ELEMENT_IMPL_66 +#define PP_ELEMENT_IMPL_68(_) PP_ELEMENT_IMPL_67 +#define PP_ELEMENT_IMPL_69(_) PP_ELEMENT_IMPL_68 +#define PP_ELEMENT_IMPL_70(_) PP_ELEMENT_IMPL_69 +#define PP_ELEMENT_IMPL_71(_) PP_ELEMENT_IMPL_70 +#define PP_ELEMENT_IMPL_72(_) PP_ELEMENT_IMPL_71 +#define PP_ELEMENT_IMPL_73(_) PP_ELEMENT_IMPL_72 +#define PP_ELEMENT_IMPL_74(_) PP_ELEMENT_IMPL_73 +#define PP_ELEMENT_IMPL_75(_) PP_ELEMENT_IMPL_74 +#define PP_ELEMENT_IMPL_76(_) PP_ELEMENT_IMPL_75 +#define PP_ELEMENT_IMPL_77(_) PP_ELEMENT_IMPL_76 +#define PP_ELEMENT_IMPL_78(_) PP_ELEMENT_IMPL_77 +#define PP_ELEMENT_IMPL_79(_) PP_ELEMENT_IMPL_78 +#define PP_ELEMENT_IMPL_80(_) PP_ELEMENT_IMPL_79 +#define PP_ELEMENT_IMPL_81(_) PP_ELEMENT_IMPL_80 +#define PP_ELEMENT_IMPL_82(_) PP_ELEMENT_IMPL_81 +#define PP_ELEMENT_IMPL_83(_) PP_ELEMENT_IMPL_82 +#define PP_ELEMENT_IMPL_84(_) PP_ELEMENT_IMPL_83 +#define PP_ELEMENT_IMPL_85(_) PP_ELEMENT_IMPL_84 +#define PP_ELEMENT_IMPL_86(_) PP_ELEMENT_IMPL_85 +#define PP_ELEMENT_IMPL_87(_) PP_ELEMENT_IMPL_86 +#define PP_ELEMENT_IMPL_88(_) PP_ELEMENT_IMPL_87 +#define PP_ELEMENT_IMPL_89(_) PP_ELEMENT_IMPL_88 +#define PP_ELEMENT_IMPL_90(_) PP_ELEMENT_IMPL_89 +#define PP_ELEMENT_IMPL_91(_) PP_ELEMENT_IMPL_90 +#define PP_ELEMENT_IMPL_92(_) PP_ELEMENT_IMPL_91 +#define PP_ELEMENT_IMPL_93(_) PP_ELEMENT_IMPL_92 +#define PP_ELEMENT_IMPL_94(_) PP_ELEMENT_IMPL_93 +#define PP_ELEMENT_IMPL_95(_) PP_ELEMENT_IMPL_94 +#define PP_ELEMENT_IMPL_96(_) PP_ELEMENT_IMPL_95 +#define PP_ELEMENT_IMPL_97(_) PP_ELEMENT_IMPL_96 +#define PP_ELEMENT_IMPL_98(_) PP_ELEMENT_IMPL_97 +#define PP_ELEMENT_IMPL_99(_) PP_ELEMENT_IMPL_98 +#define PP_ELEMENT_IMPL_100(_) PP_ELEMENT_IMPL_99 +#define PP_ELEMENT_IMPL_101(_) PP_ELEMENT_IMPL_100 +#define PP_ELEMENT_IMPL_102(_) PP_ELEMENT_IMPL_101 +#define PP_ELEMENT_IMPL_103(_) PP_ELEMENT_IMPL_102 +#define PP_ELEMENT_IMPL_104(_) PP_ELEMENT_IMPL_103 +#define PP_ELEMENT_IMPL_105(_) PP_ELEMENT_IMPL_104 +#define PP_ELEMENT_IMPL_106(_) PP_ELEMENT_IMPL_105 +#define PP_ELEMENT_IMPL_107(_) PP_ELEMENT_IMPL_106 +#define PP_ELEMENT_IMPL_108(_) PP_ELEMENT_IMPL_107 +#define PP_ELEMENT_IMPL_109(_) PP_ELEMENT_IMPL_108 +#define PP_ELEMENT_IMPL_110(_) PP_ELEMENT_IMPL_109 +#define PP_ELEMENT_IMPL_111(_) PP_ELEMENT_IMPL_110 +#define PP_ELEMENT_IMPL_112(_) PP_ELEMENT_IMPL_111 +#define PP_ELEMENT_IMPL_113(_) PP_ELEMENT_IMPL_112 +#define PP_ELEMENT_IMPL_114(_) PP_ELEMENT_IMPL_113 +#define PP_ELEMENT_IMPL_115(_) PP_ELEMENT_IMPL_114 +#define PP_ELEMENT_IMPL_116(_) PP_ELEMENT_IMPL_115 +#define PP_ELEMENT_IMPL_117(_) PP_ELEMENT_IMPL_116 +#define PP_ELEMENT_IMPL_118(_) PP_ELEMENT_IMPL_117 +#define PP_ELEMENT_IMPL_119(_) PP_ELEMENT_IMPL_118 +#define PP_ELEMENT_IMPL_120(_) PP_ELEMENT_IMPL_119 +#define PP_ELEMENT_IMPL_121(_) PP_ELEMENT_IMPL_120 +#define PP_ELEMENT_IMPL_122(_) PP_ELEMENT_IMPL_121 +#define PP_ELEMENT_IMPL_123(_) PP_ELEMENT_IMPL_122 +#define PP_ELEMENT_IMPL_124(_) PP_ELEMENT_IMPL_123 +#define PP_ELEMENT_IMPL_125(_) PP_ELEMENT_IMPL_124 +#define PP_ELEMENT_IMPL_126(_) PP_ELEMENT_IMPL_125 +#define PP_ELEMENT_IMPL_127(_) PP_ELEMENT_IMPL_126 +#define PP_ELEMENT_IMPL_128(_) PP_ELEMENT_IMPL_127 +#define PP_ELEMENT_IMPL_129(_) PP_ELEMENT_IMPL_128 +#define PP_ELEMENT_IMPL_130(_) PP_ELEMENT_IMPL_129 +#define PP_ELEMENT_IMPL_131(_) PP_ELEMENT_IMPL_130 +#define PP_ELEMENT_IMPL_132(_) PP_ELEMENT_IMPL_131 +#define PP_ELEMENT_IMPL_133(_) PP_ELEMENT_IMPL_132 +#define PP_ELEMENT_IMPL_134(_) PP_ELEMENT_IMPL_133 +#define PP_ELEMENT_IMPL_135(_) PP_ELEMENT_IMPL_134 +#define PP_ELEMENT_IMPL_136(_) PP_ELEMENT_IMPL_135 +#define PP_ELEMENT_IMPL_137(_) PP_ELEMENT_IMPL_136 +#define PP_ELEMENT_IMPL_138(_) PP_ELEMENT_IMPL_137 +#define PP_ELEMENT_IMPL_139(_) PP_ELEMENT_IMPL_138 +#define PP_ELEMENT_IMPL_140(_) PP_ELEMENT_IMPL_139 +#define PP_ELEMENT_IMPL_141(_) PP_ELEMENT_IMPL_140 +#define PP_ELEMENT_IMPL_142(_) PP_ELEMENT_IMPL_141 +#define PP_ELEMENT_IMPL_143(_) PP_ELEMENT_IMPL_142 +#define PP_ELEMENT_IMPL_144(_) PP_ELEMENT_IMPL_143 +#define PP_ELEMENT_IMPL_145(_) PP_ELEMENT_IMPL_144 +#define PP_ELEMENT_IMPL_146(_) PP_ELEMENT_IMPL_145 +#define PP_ELEMENT_IMPL_147(_) PP_ELEMENT_IMPL_146 +#define PP_ELEMENT_IMPL_148(_) PP_ELEMENT_IMPL_147 +#define PP_ELEMENT_IMPL_149(_) PP_ELEMENT_IMPL_148 +#define PP_ELEMENT_IMPL_150(_) PP_ELEMENT_IMPL_149 +#define PP_ELEMENT_IMPL_151(_) PP_ELEMENT_IMPL_150 +#define PP_ELEMENT_IMPL_152(_) PP_ELEMENT_IMPL_151 +#define PP_ELEMENT_IMPL_153(_) PP_ELEMENT_IMPL_152 +#define PP_ELEMENT_IMPL_154(_) PP_ELEMENT_IMPL_153 +#define PP_ELEMENT_IMPL_155(_) PP_ELEMENT_IMPL_154 +#define PP_ELEMENT_IMPL_156(_) PP_ELEMENT_IMPL_155 +#define PP_ELEMENT_IMPL_157(_) PP_ELEMENT_IMPL_156 +#define PP_ELEMENT_IMPL_158(_) PP_ELEMENT_IMPL_157 +#define PP_ELEMENT_IMPL_159(_) PP_ELEMENT_IMPL_158 +#define PP_ELEMENT_IMPL_160(_) PP_ELEMENT_IMPL_159 +#define PP_ELEMENT_IMPL_161(_) PP_ELEMENT_IMPL_160 +#define PP_ELEMENT_IMPL_162(_) PP_ELEMENT_IMPL_161 +#define PP_ELEMENT_IMPL_163(_) PP_ELEMENT_IMPL_162 +#define PP_ELEMENT_IMPL_164(_) PP_ELEMENT_IMPL_163 +#define PP_ELEMENT_IMPL_165(_) PP_ELEMENT_IMPL_164 +#define PP_ELEMENT_IMPL_166(_) PP_ELEMENT_IMPL_165 +#define PP_ELEMENT_IMPL_167(_) PP_ELEMENT_IMPL_166 +#define PP_ELEMENT_IMPL_168(_) PP_ELEMENT_IMPL_167 +#define PP_ELEMENT_IMPL_169(_) PP_ELEMENT_IMPL_168 +#define PP_ELEMENT_IMPL_170(_) PP_ELEMENT_IMPL_169 +#define PP_ELEMENT_IMPL_171(_) PP_ELEMENT_IMPL_170 +#define PP_ELEMENT_IMPL_172(_) PP_ELEMENT_IMPL_171 +#define PP_ELEMENT_IMPL_173(_) PP_ELEMENT_IMPL_172 +#define PP_ELEMENT_IMPL_174(_) PP_ELEMENT_IMPL_173 +#define PP_ELEMENT_IMPL_175(_) PP_ELEMENT_IMPL_174 +#define PP_ELEMENT_IMPL_176(_) PP_ELEMENT_IMPL_175 +#define PP_ELEMENT_IMPL_177(_) PP_ELEMENT_IMPL_176 +#define PP_ELEMENT_IMPL_178(_) PP_ELEMENT_IMPL_177 +#define PP_ELEMENT_IMPL_179(_) PP_ELEMENT_IMPL_178 +#define PP_ELEMENT_IMPL_180(_) PP_ELEMENT_IMPL_179 +#define PP_ELEMENT_IMPL_181(_) PP_ELEMENT_IMPL_180 +#define PP_ELEMENT_IMPL_182(_) PP_ELEMENT_IMPL_181 +#define PP_ELEMENT_IMPL_183(_) PP_ELEMENT_IMPL_182 +#define PP_ELEMENT_IMPL_184(_) PP_ELEMENT_IMPL_183 +#define PP_ELEMENT_IMPL_185(_) PP_ELEMENT_IMPL_184 +#define PP_ELEMENT_IMPL_186(_) PP_ELEMENT_IMPL_185 +#define PP_ELEMENT_IMPL_187(_) PP_ELEMENT_IMPL_186 +#define PP_ELEMENT_IMPL_188(_) PP_ELEMENT_IMPL_187 +#define PP_ELEMENT_IMPL_189(_) PP_ELEMENT_IMPL_188 +#define PP_ELEMENT_IMPL_190(_) PP_ELEMENT_IMPL_189 +#define PP_ELEMENT_IMPL_191(_) PP_ELEMENT_IMPL_190 +#define PP_ELEMENT_IMPL_192(_) PP_ELEMENT_IMPL_191 +#define PP_ELEMENT_IMPL_193(_) PP_ELEMENT_IMPL_192 +#define PP_ELEMENT_IMPL_194(_) PP_ELEMENT_IMPL_193 +#define PP_ELEMENT_IMPL_195(_) PP_ELEMENT_IMPL_194 +#define PP_ELEMENT_IMPL_196(_) PP_ELEMENT_IMPL_195 +#define PP_ELEMENT_IMPL_197(_) PP_ELEMENT_IMPL_196 +#define PP_ELEMENT_IMPL_198(_) PP_ELEMENT_IMPL_197 +#define PP_ELEMENT_IMPL_199(_) PP_ELEMENT_IMPL_198 +#define PP_ELEMENT_IMPL_200(_) PP_ELEMENT_IMPL_199 + +//////////////////////////////////////////////////////////////////////////////// +#define PP_HEAD_IMPL(seq) PP_ELEMENT_IMPL(seq, 0) + +//////////////////////////////////////////////////////////////////////////////// +#define PP_TAIL_IMPL(seq) PP_KILL_IMPL(seq, 1) + +//////////////////////////////////////////////////////////////////////////////// +#define PP_FOR_EACH_IMPL(what, seq) PP_CONCAT(PP_FOR_EACH_IMPL_, \ + PP_COUNT(seq))(what, seq) +#define PP_FOR_EACH_IMPL_0(what, seq) +#define PP_FOR_EACH_IMPL_1(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_0(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_2(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_1(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_3(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_2(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_4(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_3(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_5(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_4(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_6(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_5(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_7(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_6(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_8(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_7(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_9(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_8(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_10(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_9(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_11(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_10(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_12(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_11(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_13(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_12(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_14(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_13(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_15(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_14(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_16(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_15(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_17(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_16(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_18(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_17(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_19(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_18(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_20(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_19(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_21(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_20(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_22(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_21(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_23(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_22(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_24(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_23(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_25(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_24(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_26(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_25(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_27(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_26(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_28(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_27(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_29(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_28(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_30(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_29(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_31(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_30(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_32(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_31(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_33(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_32(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_34(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_33(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_35(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_34(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_36(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_35(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_37(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_36(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_38(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_37(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_39(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_38(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_40(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_39(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_41(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_40(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_42(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_41(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_43(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_42(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_44(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_43(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_45(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_44(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_46(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_45(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_47(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_46(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_48(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_47(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_49(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_48(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_50(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_49(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_51(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_50(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_52(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_51(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_53(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_52(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_54(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_53(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_55(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_54(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_56(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_55(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_57(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_56(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_58(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_57(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_59(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_58(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_60(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_59(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_61(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_60(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_62(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_61(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_63(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_62(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_64(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_63(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_65(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_64(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_66(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_65(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_67(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_66(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_68(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_67(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_69(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_68(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_70(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_69(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_71(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_70(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_72(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_71(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_73(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_72(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_74(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_73(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_75(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_74(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_76(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_75(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_77(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_76(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_78(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_77(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_79(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_78(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_80(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_79(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_81(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_80(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_82(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_81(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_83(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_82(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_84(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_83(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_85(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_84(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_86(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_85(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_87(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_86(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_88(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_87(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_89(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_88(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_90(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_89(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_91(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_90(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_92(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_91(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_93(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_92(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_94(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_93(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_95(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_94(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_96(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_95(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_97(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_96(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_98(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_97(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_99(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_98(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_100(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_99(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_101(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_100(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_102(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_101(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_103(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_102(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_104(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_103(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_105(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_104(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_106(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_105(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_107(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_106(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_108(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_107(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_109(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_108(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_110(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_109(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_111(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_110(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_112(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_111(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_113(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_112(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_114(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_113(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_115(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_114(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_116(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_115(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_117(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_116(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_118(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_117(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_119(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_118(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_120(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_119(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_121(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_120(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_122(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_121(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_123(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_122(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_124(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_123(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_125(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_124(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_126(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_125(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_127(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_126(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_128(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_127(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_129(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_128(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_130(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_129(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_131(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_130(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_132(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_131(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_133(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_132(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_134(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_133(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_135(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_134(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_136(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_135(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_137(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_136(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_138(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_137(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_139(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_138(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_140(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_139(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_141(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_140(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_142(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_141(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_143(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_142(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_144(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_143(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_145(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_144(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_146(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_145(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_147(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_146(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_148(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_147(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_149(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_148(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_150(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_149(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_151(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_150(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_152(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_151(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_153(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_152(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_154(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_153(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_155(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_154(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_156(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_155(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_157(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_156(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_158(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_157(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_159(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_158(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_160(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_159(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_161(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_160(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_162(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_161(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_163(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_162(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_164(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_163(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_165(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_164(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_166(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_165(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_167(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_166(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_168(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_167(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_169(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_168(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_170(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_169(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_171(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_170(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_172(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_171(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_173(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_172(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_174(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_173(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_175(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_174(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_176(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_175(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_177(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_176(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_178(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_177(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_179(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_178(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_180(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_179(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_181(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_180(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_182(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_181(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_183(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_182(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_184(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_183(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_185(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_184(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_186(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_185(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_187(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_186(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_188(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_187(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_189(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_188(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_190(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_189(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_191(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_190(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_192(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_191(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_193(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_192(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_194(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_193(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_195(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_194(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_196(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_195(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_197(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_196(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_198(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_197(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_199(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_198(what, PP_TAIL(seq)) +#define PP_FOR_EACH_IMPL_200(what, \ + seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_199(what, PP_TAIL(seq)) +//////////////////////////////////////////////////////////////////////////////// +/*! + \endinternal +*/ diff --git a/library/cpp/yt/misc/preprocessor-gen.h.pump b/library/cpp/yt/misc/preprocessor-gen.h.pump new file mode 100644 index 0000000000..0f178ae37e --- /dev/null +++ b/library/cpp/yt/misc/preprocessor-gen.h.pump @@ -0,0 +1,70 @@ +#pragma once + +$$ Please, use Pump to convert this source file to valid C++ header. +$$ Note that lines in this file could be longer than 80 symbols. +$var n = 199 +$range i 0..n + +/*! + \internal +*/ + +#ifndef PREPROCESSOR_GEN_H_ +#error "Direct inclusion of this file is not allowed, include preprocessor.h" +// For the sake of sane code completion. +#include "preprocessor.h" +#endif +#undef PREPROCESSOR_GEN_H_ + +//////////////////////////////////////////////////////////////////////////////// +#define PP_COUNT_IMPL(...) PP_CONCAT(PP_COUNT_CONST_, PP_COUNT_IMPL_0 __VA_ARGS__) + +$for i [[ +#define PP_COUNT_CONST_PP_COUNT_IMPL_$i $i + +]] + +$for i [[ +#define PP_COUNT_IMPL_$i(_) PP_COUNT_IMPL_$(i+1) + +]] + +//////////////////////////////////////////////////////////////////////////////// +#define PP_KILL_IMPL(seq, index) PP_CONCAT(PP_KILL_IMPL_, index) seq +#define PP_KILL_IMPL_0 + +$for i [[ +#define PP_KILL_IMPL_$(i+1)(_) PP_KILL_IMPL_$i + +]] + +//////////////////////////////////////////////////////////////////////////////// +#define PP_ELEMENT_IMPL(seq, index) PP_ELEMENT_IMPL_A((PP_CONCAT(PP_ELEMENT_IMPL_, index) seq)) +#define PP_ELEMENT_IMPL_A(x) PP_ELEMENT_IMPL_C(PP_ELEMENT_IMPL_B x) +#define PP_ELEMENT_IMPL_B(x, _) x +#define PP_ELEMENT_IMPL_C(x) x +#define PP_ELEMENT_IMPL_0(x) x, PP_NIL + +$for i [[ +#define PP_ELEMENT_IMPL_$(i+1)(_) PP_ELEMENT_IMPL_$i + +]] + +//////////////////////////////////////////////////////////////////////////////// +#define PP_HEAD_IMPL(seq) PP_ELEMENT_IMPL(seq, 0) + +//////////////////////////////////////////////////////////////////////////////// +#define PP_TAIL_IMPL(seq) PP_KILL_IMPL(seq, 1) + +//////////////////////////////////////////////////////////////////////////////// +#define PP_FOR_EACH_IMPL(what, seq) PP_CONCAT(PP_FOR_EACH_IMPL_, PP_COUNT(seq))(what, seq) +#define PP_FOR_EACH_IMPL_0(what, seq) + +$for i [[ +#define PP_FOR_EACH_IMPL_$(i+1)(what, seq) what(PP_HEAD(seq)) PP_FOR_EACH_IMPL_$i(what, PP_TAIL(seq)) + +]] +//////////////////////////////////////////////////////////////////////////////// +/*! + \endinternal +*/ diff --git a/library/cpp/yt/misc/preprocessor.h b/library/cpp/yt/misc/preprocessor.h new file mode 100644 index 0000000000..9afd3ae902 --- /dev/null +++ b/library/cpp/yt/misc/preprocessor.h @@ -0,0 +1,124 @@ +#pragma once + +/*! + * \file preprocesor.h + * \brief Preprocessor metaprogramming macroses + */ + +#if !defined(_MSC_VER) && !defined(__GNUC__) +# error Your compiler is not currently supported. +#endif + +/*! + * \defgroup yt_pp Preprocessor metaprogramming macroses + * \ingroup yt_commons + * + * This is collection of macro definitions for various metaprogramming tasks + * with the preprocessor. + * + * \{ + * + * \page yt_pp_sequences Sequences + * Everything revolves around the concept of a \em sequence. A typical + * sequence is encoded like <tt>(1)(2)(3)...</tt>. Internally this allows + * to apply some macro to the every element in the sequence (see #PP_FOR_EACH). + * + * Note that sequences can be nested, i. e. <tt>((1)(2)(3))(a)(b)(c)</tt> + * + * \page yt_pp_examples Examples + * Please refer to the unit test for an actual example of usage + * (unittests/preprocessor_ut.cpp). + * + */ + +//! Concatenates two tokens. +#define PP_CONCAT(x, y) PP_CONCAT_A(x, y) +//! \cond Implementation +#define PP_CONCAT_A(x, y) PP_CONCAT_B(x, y) +#define PP_CONCAT_B(x, y) x ## y +//! \endcond + +//! Transforms token into the string forcing argument expansion. +#define PP_STRINGIZE(x) PP_STRINGIZE_A(x) +//! \cond Implementation +#define PP_STRINGIZE_A(x) PP_STRINGIZE_B(x) +#define PP_STRINGIZE_B(x) #x +//! \endcond + +//! \cond Implementation +#define PP_LEFT_PARENTHESIS ( +#define PP_RIGHT_PARENTHESIS ) +#define PP_COMMA() , +#define PP_EMPTY() +//! \endcond + +//! Performs (non-lazy) conditional expansion. +/*! + * \param cond Condition; should expands to either \c PP_TRUE or \c PP_FALSE. + * \param _then Expansion result in case when \c cond holds. + * \param _else Expansion result in case when \c cond does not hold. + */ +#define PP_IF(cond, _then, _else) PP_CONCAT(PP_IF_, cond)(_then, _else) +//! \cond Implementation +#define PP_IF_PP_TRUE(x, y) x +#define PP_IF_PP_FALSE(x, y) y +//! \endcond + +//! Tests whether supplied argument can be treated as a sequence +//! (i. e. <tt>()()()...</tt>) +#define PP_IS_SEQUENCE(arg) PP_CONCAT(PP_IS_SEQUENCE_B_, PP_COUNT((PP_NIL PP_IS_SEQUENCE_A arg PP_NIL))) +//! \cond Implementation +#define PP_IS_SEQUENCE_A(_) PP_RIGHT_PARENTHESIS PP_LEFT_PARENTHESIS +#define PP_IS_SEQUENCE_B_1 PP_FALSE +#define PP_IS_SEQUENCE_B_2 PP_TRUE +//! \endcond + +//! Computes the number of elements in the sequence. +#define PP_COUNT(...) PP_COUNT_IMPL(__VA_ARGS__) + +//! Removes first \c n elements from the sequence. +#define PP_KILL(seq, n) PP_KILL_IMPL(seq, n) + +//! Extracts the head of the sequence. +/*! For example, \code PP_HEAD((0)(1)(2)(3)) == 0 \endcode + */ +#define PP_HEAD(...) PP_HEAD_IMPL(__VA_ARGS__) + +//! Extracts the tail of the sequence. +/*! For example, \code PP_TAIL((0)(1)(2)(3)) == (1)(2)(3) \endcode + */ +#define PP_TAIL(...) PP_TAIL_IMPL(__VA_ARGS__) + +//! Extracts the element with the specified index from the sequence. +/*! For example, \code PP_ELEMENT((0)(1)(2)(3), 1) == 1 \endcode + */ +#define PP_ELEMENT(seq, index) PP_ELEMENT_IMPL(seq, index) + +//! Applies the macro to every member of the sequence. +/*! For example, + * \code + * #define MyFunctor(x) +x+ + * PP_FOR_EACH(MyFunctor, (0)(1)(2)(3)) == +0+ +1+ +2+ +3+ + * \encode + */ +#define PP_FOR_EACH(what, seq) PP_FOR_EACH_IMPL(what, seq) + +//! Declares an anonymous variable. +#ifdef __COUNTER__ +#define PP_ANONYMOUS_VARIABLE(str) PP_CONCAT(str, __COUNTER__) +#else +#define PP_ANONYMOUS_VARIABLE(str) PP_CONCAT(str, __LINE__) +#endif + +//! Insert prefix based on presence of additional arguments. +#define PP_ONE_OR_NONE(a, ...) PP_THIRD(a, ## __VA_ARGS__, a) +#define PP_THIRD(a, b, ...) __VA_ARGS__ + +//! \cond Implementation +#define PREPROCESSOR_GEN_H_ +#include "preprocessor-gen.h" +#undef PREPROCESSOR_GEN_H_ +//! \endcond + +/*! \} */ + diff --git a/library/cpp/yt/misc/property.h b/library/cpp/yt/misc/property.h new file mode 100644 index 0000000000..bef8024ae1 --- /dev/null +++ b/library/cpp/yt/misc/property.h @@ -0,0 +1,289 @@ +#pragma once + +//////////////////////////////////////////////////////////////////////////////// + +//! Declares a trivial public read-write property that is passed by reference. +#define DECLARE_BYREF_RW_PROPERTY(type, name) \ +public: \ + type& name(); \ + const type& name() const; + +//! Defines a trivial public read-write property that is passed by reference. +//! All arguments after name are used as default value (via braced-init-list). +#define DEFINE_BYREF_RW_PROPERTY(type, name, ...) \ +protected: \ + type name##_ { __VA_ARGS__ }; \ + \ +public: \ + Y_FORCE_INLINE type& name() \ + { \ + return name##_; \ + } \ + \ + Y_FORCE_INLINE const type& name() const \ + { \ + return name##_; \ + } + +//! Defines a trivial public read-write property that is passed by reference +//! and is not inline-initialized. +#define DEFINE_BYREF_RW_PROPERTY_NO_INIT(type, name) \ +protected: \ + type name##_; \ + \ +public: \ + Y_FORCE_INLINE type& name() \ + { \ + return name##_; \ + } \ + \ + Y_FORCE_INLINE const type& name() const \ + { \ + return name##_; \ + } + +//! Forwards a trivial public read-write property that is passed by reference. +#define DELEGATE_BYREF_RW_PROPERTY(declaringType, type, name, delegateTo) \ + type& declaringType::name() \ + { \ + return (delegateTo).name(); \ + } \ + \ + const type& declaringType::name() const \ + { \ + return (delegateTo).name(); \ + } + +//////////////////////////////////////////////////////////////////////////////// + +//! Declares a trivial public read-only property that is passed by reference. +#define DECLARE_BYREF_RO_PROPERTY(type, name) \ +public: \ + const type& name() const; + +//! Defines a trivial public read-only property that is passed by reference. +//! All arguments after name are used as default value (via braced-init-list). +#define DEFINE_BYREF_RO_PROPERTY(type, name, ...) \ +protected: \ + type name##_ { __VA_ARGS__ }; \ + \ +public: \ + Y_FORCE_INLINE const type& name() const \ + { \ + return name##_; \ + } + +//! Defines a trivial public read-only property that is passed by reference +//! and is not inline-initialized. +#define DEFINE_BYREF_RO_PROPERTY_NO_INIT(type, name) \ +protected: \ + type name##_; \ + \ +public: \ + Y_FORCE_INLINE const type& name() const \ + { \ + return name##_; \ + } + +//! Forwards a trivial public read-only property that is passed by reference. +#define DELEGATE_BYREF_RO_PROPERTY(declaringType, type, name, delegateTo) \ + const type& declaringType::name() const \ + { \ + return (delegateTo).name(); \ + } + +//////////////////////////////////////////////////////////////////////////////// + +//! Declares a trivial public read-write property that is passed by value. +#define DECLARE_BYVAL_RW_PROPERTY(type, name) \ +public: \ + type Get##name() const; \ + void Set##name(type value); + +//! Defines a trivial public read-write property that is passed by value. +//! All arguments after name are used as default value (via braced-init-list). +#define DEFINE_BYVAL_RW_PROPERTY(type, name, ...) \ +protected: \ + type name##_ { __VA_ARGS__ }; \ + \ +public: \ + Y_FORCE_INLINE type Get##name() const \ + { \ + return name##_; \ + } \ + \ + Y_FORCE_INLINE void Set##name(type value) \ + { \ + name##_ = value; \ + } \ + +//! Defines a trivial public read-write property that is passed by value. +//! All arguments after name are used as default value (via braced-init-list). +#define DEFINE_BYVAL_RW_PROPERTY_WITH_FLUENT_SETTER(declaringType, type, name, ...) \ +protected: \ + type name##_ { __VA_ARGS__ }; \ + \ +public: \ + Y_FORCE_INLINE type Get##name() const \ + { \ + return name##_; \ + } \ + \ + Y_FORCE_INLINE void Set##name(type value) &\ + { \ + name##_ = value; \ + } \ + \ + Y_FORCE_INLINE declaringType&& Set##name(type value) &&\ + { \ + name##_ = value; \ + return std::move(*this); \ + } \ + +//! Defines a trivial public read-write property that is passed by value +//! and is not inline-initialized. +#define DEFINE_BYVAL_RW_PROPERTY_NO_INIT(type, name, ...) \ +protected: \ + type name##_; \ + \ +public: \ + Y_FORCE_INLINE type Get##name() const \ + { \ + return name##_; \ + } \ + \ + Y_FORCE_INLINE void Set##name(type value) \ + { \ + name##_ = value; \ + } \ + +//! Forwards a trivial public read-write property that is passed by value. +#define DELEGATE_BYVAL_RW_PROPERTY(declaringType, type, name, delegateTo) \ + type declaringType::Get##name() const \ + { \ + return (delegateTo).Get##name(); \ + } \ + \ + void declaringType::Set##name(type value) \ + { \ + (delegateTo).Set##name(value); \ + } + +//////////////////////////////////////////////////////////////////////////////// + +//! Declares a trivial public read-only property that is passed by value. +#define DECLARE_BYVAL_RO_PROPERTY(type, name) \ +public: \ + type Get##name() const; + +//! Defines a trivial public read-only property that is passed by value. +//! All arguments after name are used as default value (via braced-init-list). +#define DEFINE_BYVAL_RO_PROPERTY(type, name, ...) \ +protected: \ + type name##_ { __VA_ARGS__ }; \ + \ +public: \ + Y_FORCE_INLINE type Get##name() const \ + { \ + return name##_; \ + } + + +//! Defines a trivial public read-only property that is passed by value +//! and is not inline-initialized. +#define DEFINE_BYVAL_RO_PROPERTY_NO_INIT(type, name) \ +protected: \ + type name##_; \ + \ +public: \ + Y_FORCE_INLINE type Get##name() const \ + { \ + return name##_; \ + } + +//! Forwards a trivial public read-only property that is passed by value. +#define DELEGATE_BYVAL_RO_PROPERTY(declaringType, type, name, delegateTo) \ + type declaringType::Get##name() \ + { \ + return (delegateTo).Get##name(); \ + } + +//////////////////////////////////////////////////////////////////////////////// + +//! Below are macro helpers for extra properties. +//! Extra properties should be used for lazy memory allocation for properties that +//! hold default values for the majority of objects. + +//! Initializes extra property holder if it is not initialized. +#define INITIALIZE_EXTRA_PROPERTY_HOLDER(holder) \ + if (!holder##_) { \ + holder##_.reset(new decltype(holder##_)::element_type()); \ + } + +//! Declares an extra property holder. Holder contains extra properties values. +//! Holder is not created until some property is set with a non-default value. +//! If there is no holder property getter returns default value. +#define DECLARE_EXTRA_PROPERTY_HOLDER(type, holder) \ +public: \ + Y_FORCE_INLINE bool HasCustom##holder() const \ + { \ + return static_cast<bool>(holder##_); \ + } \ + Y_FORCE_INLINE const type* GetCustom##holder() const \ + { \ + return holder##_.get(); \ + } \ + Y_FORCE_INLINE type* GetCustom##holder() \ + { \ + return holder##_.get(); \ + } \ + Y_FORCE_INLINE void InitializeCustom##holder() \ + { \ + INITIALIZE_EXTRA_PROPERTY_HOLDER(holder) \ + } \ +private: \ + std::unique_ptr<type> holder##_; \ + static const type Default##holder##_; + +//! Defines a storage for extra properties default values. +#define DEFINE_EXTRA_PROPERTY_HOLDER(class, type, holder) \ + const type class::Default##holder##_; + +//! Defines a public read-write extra property that is passed by value. +#define DEFINE_BYVAL_RW_EXTRA_PROPERTY(holder, name) \ +public: \ + Y_FORCE_INLINE decltype(holder##_->name) Get##name() const \ + { \ + if (!holder##_) { \ + return Default##holder##_.name; \ + } \ + return holder##_->name; \ + } \ + Y_FORCE_INLINE void Set##name(decltype(holder##_->name) val) \ + { \ + if (!holder##_) { \ + if (val == Default##holder##_.name) { \ + return; \ + } \ + INITIALIZE_EXTRA_PROPERTY_HOLDER(holder); \ + } \ + holder##_->name = val; \ + } + +//! Defines a public read-write extra property that is passed by reference. +#define DEFINE_BYREF_RW_EXTRA_PROPERTY(holder, name) \ +public: \ + Y_FORCE_INLINE const decltype(holder##_->name)& name() const \ + { \ + if (!holder##_) { \ + return Default##holder##_.name; \ + } \ + return holder##_->name; \ + } \ + Y_FORCE_INLINE decltype(holder##_->name)& Mutable##name() \ + { \ + INITIALIZE_EXTRA_PROPERTY_HOLDER(holder); \ + return holder##_->name; \ + } + +//////////////////////////////////////////////////////////////////////////////// diff --git a/library/cpp/yt/misc/source_location.cpp b/library/cpp/yt/misc/source_location.cpp new file mode 100644 index 0000000000..8d22d43636 --- /dev/null +++ b/library/cpp/yt/misc/source_location.cpp @@ -0,0 +1,54 @@ +#include "source_location.h" + +#include <string.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +const char* TSourceLocation::GetFileName() const +{ + return FileName_; +} + +int TSourceLocation::GetLine() const +{ + return Line_; +} + +bool TSourceLocation::IsValid() const +{ + return FileName_ != nullptr; +} + +bool TSourceLocation::operator<(const TSourceLocation& other) const +{ + const char* fileName = FileName_ ? FileName_ : ""; + const char* otherFileName = other.FileName_ ? other.FileName_ : ""; + int fileNameResult = strcmp(fileName, otherFileName); + if (fileNameResult != 0) { + return fileNameResult < 0; + } + + if (Line_ < other.Line_) { + return true; + } + if (Line_ > other.Line_) { + return false; + } + + return false; +} + +bool TSourceLocation::operator==(const TSourceLocation& other) const +{ + const char* fileName = FileName_ ? FileName_ : ""; + const char* otherFileName = other.FileName_ ? other.FileName_ : ""; + return + strcmp(fileName, otherFileName) == 0 && + Line_ == other.Line_; +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/misc/source_location.h b/library/cpp/yt/misc/source_location.h new file mode 100644 index 0000000000..84213eea70 --- /dev/null +++ b/library/cpp/yt/misc/source_location.h @@ -0,0 +1,38 @@ +#pragma once + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +class TSourceLocation +{ +public: + TSourceLocation() + : FileName_(nullptr) + , Line_(-1) + { } + + TSourceLocation(const char* fileName, int line) + : FileName_(fileName) + , Line_(line) + { } + + const char* GetFileName() const; + int GetLine() const; + bool IsValid() const; + + bool operator<(const TSourceLocation& other) const; + bool operator==(const TSourceLocation& other) const; + +private: + const char* FileName_; + int Line_; + +}; + +//! Defines a macro to record the current source location. +#define FROM_HERE ::NYT::TSourceLocation(__FILE__, __LINE__) + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/misc/unittests/enum_ut.cpp b/library/cpp/yt/misc/unittests/enum_ut.cpp new file mode 100644 index 0000000000..6af11b04dc --- /dev/null +++ b/library/cpp/yt/misc/unittests/enum_ut.cpp @@ -0,0 +1,250 @@ +#include <library/cpp/testing/gtest/gtest.h> + +#include <library/cpp/yt/misc/enum.h> + +namespace NYT { +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +DEFINE_ENUM(ESimple, + (X) + (Y) + (Z) +); + +DEFINE_ENUM(EColor, + ((Red) (10)) + ((Green)(20)) + ((Blue) (30)) + (Black) + (White) +); + +DEFINE_BIT_ENUM(EFlag, + ((_1)(0x0001)) + ((_2)(0x0002)) + ((_3)(0x0004)) + ((_4)(0x0008)) +); + +DEFINE_AMBIGUOUS_ENUM_WITH_UNDERLYING_TYPE(EMultipleNames, int, + (A1) + ((A2)(0)) + (B) + (C) + ((D1)(100)) + ((D2)(100)) +); + +//////////////////////////////////////////////////////////////////////////////// + +template <class T, size_t N> +std::vector<T> ToVector(std::array<T, N> array) +{ + return std::vector<T>(array.begin(), array.end()); +} + +TEST(TEnumTest, Domain) +{ + EXPECT_EQ(3, TEnumTraits<ESimple>::DomainSize); + std::vector<ESimple> v { + ESimple::X, + ESimple::Y, + ESimple::Z + }; + EXPECT_EQ(v, ToVector(TEnumTraits<ESimple>::GetDomainValues())); + EXPECT_EQ(ESimple::X, TEnumTraits<ESimple>::GetMinValue()); + EXPECT_EQ(ESimple::Z, TEnumTraits<ESimple>::GetMaxValue()); +} + +TEST(TEnumTest, Basic) +{ + EXPECT_EQ(0, static_cast<int>(ESimple::X)); + EXPECT_EQ(1, static_cast<int>(ESimple::Y)); + EXPECT_EQ(2, static_cast<int>(ESimple::Z)); + + EXPECT_EQ(0, static_cast<int>(EColor( ))); + EXPECT_EQ(5, static_cast<int>(EColor(5))); + + EXPECT_EQ(10, static_cast<int>(EColor::Red )); + EXPECT_EQ(20, static_cast<int>(EColor::Green)); + EXPECT_EQ(30, static_cast<int>(EColor::Blue )); + EXPECT_EQ(31, static_cast<int>(EColor::Black)); + EXPECT_EQ(32, static_cast<int>(EColor::White)); +} + +TEST(TEnumTest, ToString) +{ + EXPECT_EQ("EColor(0)", ToString(EColor( ))); + EXPECT_EQ("EColor(5)", ToString(EColor(5))); + + EXPECT_EQ("Red", ToString(EColor(EColor::Red ))); + EXPECT_EQ("Green", ToString(EColor::Green)); + EXPECT_EQ("Blue", ToString(EColor(EColor::Blue ))); + EXPECT_EQ("Black", ToString(EColor::Black)); + EXPECT_EQ("White", ToString(EColor::White)); +} + +TEST(TEnumTest, FromString) +{ + EXPECT_EQ(EColor::Red , TEnumTraits<EColor>::FromString("Red" )); + EXPECT_EQ(EColor::Green, TEnumTraits<EColor>::FromString("Green")); + EXPECT_EQ(EColor::Blue , TEnumTraits<EColor>::FromString("Blue" )); + EXPECT_EQ(EColor::Black, TEnumTraits<EColor>::FromString("Black")); + EXPECT_EQ(EColor::White, TEnumTraits<EColor>::FromString("White")); + + EXPECT_THROW(TEnumTraits<EColor>::FromString("Pink"), std::exception); + + EColor color; + bool returnValue; + + returnValue = TEnumTraits<EColor>::FindValueByLiteral("Red", &color); + EXPECT_EQ(EColor::Red, color); + EXPECT_TRUE(returnValue); + + returnValue = TEnumTraits<EColor>::FindValueByLiteral("Pink", &color); + EXPECT_EQ(EColor::Red, color); + EXPECT_FALSE(returnValue); +} + +TEST(TEnumTest, Ordering) +{ + ESimple a(ESimple::X); + ESimple b(ESimple::Y); + ESimple c(ESimple::Y); + ESimple d(ESimple::Z); + + EXPECT_FALSE(a < a); EXPECT_FALSE(a > a); + EXPECT_TRUE (a < b); EXPECT_TRUE (b > a); + EXPECT_TRUE (a < c); EXPECT_TRUE (c > a); + EXPECT_TRUE (a < d); EXPECT_TRUE (d > a); + + EXPECT_FALSE(b < a); EXPECT_FALSE(a > b); + EXPECT_FALSE(b < b); EXPECT_FALSE(b > b); + EXPECT_FALSE(b < c); EXPECT_FALSE(c > b); + EXPECT_TRUE (b < d); EXPECT_TRUE (d > b); + + EXPECT_FALSE(c < a); EXPECT_FALSE(a > c); + EXPECT_FALSE(c < b); EXPECT_FALSE(b > c); + EXPECT_FALSE(c < c); EXPECT_FALSE(c > c); + EXPECT_TRUE (c < d); EXPECT_TRUE (d > c); + + EXPECT_FALSE(d < a); EXPECT_FALSE(a > d); + EXPECT_FALSE(d < b); EXPECT_FALSE(b > d); + EXPECT_FALSE(d < c); EXPECT_FALSE(c > d); + EXPECT_FALSE(d < d); EXPECT_FALSE(d > d); + + EXPECT_TRUE (a <= b); + EXPECT_TRUE (b <= c); + EXPECT_TRUE (c <= d); + + EXPECT_TRUE (a == a); + EXPECT_FALSE(a == b); + EXPECT_TRUE (b == c); + EXPECT_FALSE(c == d); + EXPECT_FALSE(d == a); + + EXPECT_FALSE(a != a); + EXPECT_TRUE (a != b); + EXPECT_FALSE(b != c); + EXPECT_TRUE (c != d); + EXPECT_TRUE (d != a); +} + +TEST(TEnumTest, OrderingWithDomainValues) +{ + EColor color(EColor::Black); + + EXPECT_LT(EColor::Red, color); + EXPECT_LT(color, EColor::White); + + EXPECT_GT(color, EColor::Red); + EXPECT_GT(EColor::White, color); + + EXPECT_LE(EColor::Red, color); + EXPECT_LE(color, EColor::White); + + EXPECT_GE(EColor::White, color); + EXPECT_GE(color, EColor::Red); + + EXPECT_EQ(color, EColor::Black); + EXPECT_EQ(EColor::Black, color); + + EXPECT_NE(color, EColor::Blue); + EXPECT_NE(EColor::Blue, color); +} + +TEST(TEnumTest, DomainSize) +{ + EXPECT_EQ(3, TEnumTraits<ESimple>::DomainSize); + EXPECT_EQ(5, TEnumTraits<EColor>::DomainSize); +} + +TEST(TEnumTest, DomainValues) +{ + std::vector<ESimple> simpleValues; + simpleValues.push_back(ESimple::X); + simpleValues.push_back(ESimple::Y); + simpleValues.push_back(ESimple::Z); + EXPECT_EQ(simpleValues, ToVector(TEnumTraits<ESimple>::GetDomainValues())); + + std::vector<EColor> colorValues; + colorValues.push_back(EColor::Red); + colorValues.push_back(EColor::Green); + colorValues.push_back(EColor::Blue); + colorValues.push_back(EColor::Black); + colorValues.push_back(EColor::White); + EXPECT_EQ(colorValues, ToVector(TEnumTraits<EColor>::GetDomainValues())); +} + +TEST(TEnumTest, Decompose1) +{ + auto f = EFlag(0); + std::vector<EFlag> ff { }; + EXPECT_EQ(TEnumTraits<EFlag>::Decompose(f), ff); +} + +TEST(TEnumTest, Decompose2) +{ + auto f = EFlag::_1; + std::vector<EFlag> ff {EFlag::_1}; + EXPECT_EQ(TEnumTraits<EFlag>::Decompose(f), ff); +} + +TEST(TEnumTest, Decompose3) +{ + auto f = EFlag(EFlag::_1|EFlag::_2); + std::vector<EFlag> ff{EFlag::_1, EFlag::_2}; + EXPECT_EQ(TEnumTraits<EFlag>::Decompose(f), ff); +} + +TEST(TEnumTest, Decompose4) +{ + auto f = EFlag(EFlag::_2|EFlag::_4); + std::vector<EFlag> ff{EFlag::_2, EFlag::_4}; + EXPECT_EQ(TEnumTraits<EFlag>::Decompose(f), ff); +} + +TEST(TEnumTest, MultipleNames) +{ + EXPECT_EQ(EMultipleNames::A1, TEnumTraits<EMultipleNames>::FromString("A1")); + EXPECT_EQ(EMultipleNames::A1, TEnumTraits<EMultipleNames>::FromString("A2")); + EXPECT_EQ(EMultipleNames::B, TEnumTraits<EMultipleNames>::FromString("B")); + EXPECT_EQ(EMultipleNames::C, TEnumTraits<EMultipleNames>::FromString("C")); + EXPECT_EQ(EMultipleNames::D1, TEnumTraits<EMultipleNames>::FromString("D1")); + EXPECT_EQ(EMultipleNames::D1, TEnumTraits<EMultipleNames>::FromString("D2")); + + EXPECT_EQ("A1", ToString(EMultipleNames::A1)); + EXPECT_EQ("A1", ToString(EMultipleNames::A2)); + EXPECT_EQ("B", ToString(EMultipleNames::B)); + EXPECT_EQ("C", ToString(EMultipleNames::C)); + EXPECT_EQ("D1", ToString(EMultipleNames::D1)); + EXPECT_EQ("D1", ToString(EMultipleNames::D2)); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace +} // namespace NYT + diff --git a/library/cpp/yt/misc/unittests/guid_ut.cpp b/library/cpp/yt/misc/unittests/guid_ut.cpp new file mode 100644 index 0000000000..ce9ee52109 --- /dev/null +++ b/library/cpp/yt/misc/unittests/guid_ut.cpp @@ -0,0 +1,20 @@ +#include <library/cpp/testing/gtest/gtest.h> + +#include <library/cpp/yt/misc/guid.h> + +namespace NYT { +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TGuidTest, RandomGuids) +{ + auto guid = TGuid::Create(); + auto otherGuid = TGuid::Create(); + EXPECT_FALSE(guid == otherGuid); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace +} // namespace NYT diff --git a/library/cpp/yt/misc/unittests/preprocessor_ut.cpp b/library/cpp/yt/misc/unittests/preprocessor_ut.cpp new file mode 100644 index 0000000000..397e2a6a61 --- /dev/null +++ b/library/cpp/yt/misc/unittests/preprocessor_ut.cpp @@ -0,0 +1,102 @@ +#include <library/cpp/testing/gtest/gtest.h> + +#include <library/cpp/yt/misc/preprocessor.h> + +namespace NYT { +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TPreprocessorTest, Concatenation) +{ + EXPECT_EQ(12, PP_CONCAT(1, 2)); + EXPECT_STREQ("FooBar", PP_STRINGIZE(PP_CONCAT(Foo, Bar))); +} + +TEST(TPreprocessorTest, Stringize) +{ + EXPECT_STREQ(PP_STRINGIZE(123456), "123456"); + EXPECT_STREQ(PP_STRINGIZE(FooBar), "FooBar"); + EXPECT_STREQ(PP_STRINGIZE(T::XYZ), "T::XYZ"); +} + +TEST(TPreprocessorTest, Count) +{ + EXPECT_EQ(0, PP_COUNT()); + EXPECT_EQ(1, PP_COUNT((0))); + EXPECT_EQ(2, PP_COUNT((0)(0))); + EXPECT_EQ(3, PP_COUNT((0)(0)(0))); + EXPECT_EQ(4, PP_COUNT((0)(0)(0)(0))); + EXPECT_EQ(5, PP_COUNT((0)(0)(0)(0)(0))); +} + +TEST(TPreprocessorTest, Kill) +{ + EXPECT_STREQ("PP_NIL (0)", PP_STRINGIZE(PP_NIL PP_KILL((0), 0))); + EXPECT_STREQ("PP_NIL", PP_STRINGIZE(PP_NIL PP_KILL((0), 1))); + EXPECT_STREQ("PP_NIL (0)(1)(2)", PP_STRINGIZE(PP_NIL PP_KILL((0)(1)(2), 0))); + EXPECT_STREQ("PP_NIL (1)(2)", PP_STRINGIZE(PP_NIL PP_KILL((0)(1)(2), 1))); + EXPECT_STREQ("PP_NIL (2)", PP_STRINGIZE(PP_NIL PP_KILL((0)(1)(2), 2))); + EXPECT_STREQ("PP_NIL", PP_STRINGIZE(PP_NIL PP_KILL((0)(1)(2), 3))); +} + +TEST(TPreprocessorTest, Head) +{ + EXPECT_STREQ("0", PP_STRINGIZE(PP_HEAD((0)))); + EXPECT_STREQ("0", PP_STRINGIZE(PP_HEAD((0)(1)))); + EXPECT_STREQ("0", PP_STRINGIZE(PP_HEAD((0)(1)(2)))); +} + +TEST(TPreprocessorTest, Tail) +{ + EXPECT_STREQ("PP_NIL", PP_STRINGIZE(PP_NIL PP_TAIL((0)))); + EXPECT_STREQ("PP_NIL (1)", PP_STRINGIZE(PP_NIL PP_TAIL((0)(1)))); + EXPECT_STREQ("PP_NIL (1)(2)", PP_STRINGIZE(PP_NIL PP_TAIL((0)(1)(2)))); +} + +TEST(TPreprocessorTest, ForEach) +{ + EXPECT_STREQ( + "\"Foo\" \"Bar\" \"Spam\" \"Ham\"", + PP_STRINGIZE(PP_FOR_EACH(PP_STRINGIZE, (Foo)(Bar)(Spam)(Ham))) + ); +#define my_functor(x) +x+ + EXPECT_STREQ( + "+1+ +2+ +3+", + PP_STRINGIZE(PP_FOR_EACH(my_functor, (1)(2)(3))) + ); +#undef my_functor +} + +TEST(TPreprocessorTest, MakeSingleton) +{ + EXPECT_EQ(1, PP_ELEMENT((1), 0)); + EXPECT_EQ(1, PP_ELEMENT((1)(2), 0)); + EXPECT_EQ(2, PP_ELEMENT((1)(2), 1)); + EXPECT_EQ(1, PP_ELEMENT((1)(2)(3), 0)); + EXPECT_EQ(2, PP_ELEMENT((1)(2)(3), 1)); + EXPECT_EQ(3, PP_ELEMENT((1)(2)(3), 2)); + EXPECT_EQ(1, PP_ELEMENT((1)(2)(3)(4), 0)); + EXPECT_EQ(2, PP_ELEMENT((1)(2)(3)(4), 1)); + EXPECT_EQ(3, PP_ELEMENT((1)(2)(3)(4), 2)); + EXPECT_EQ(4, PP_ELEMENT((1)(2)(3)(4), 3)); +} + +TEST(TPreprocessorTest, Conditional) +{ + EXPECT_EQ(1, PP_IF(PP_TRUE, 1, 2)); + EXPECT_EQ(2, PP_IF(PP_FALSE, 1, 2)); +} + +TEST(TPreprocessorTest, IsSequence) +{ + EXPECT_STREQ("PP_FALSE", PP_STRINGIZE(PP_IS_SEQUENCE( 0 ))); + EXPECT_STREQ("PP_TRUE", PP_STRINGIZE(PP_IS_SEQUENCE((0) ))); + EXPECT_STREQ("PP_TRUE", PP_STRINGIZE(PP_IS_SEQUENCE((0)(0)))); + EXPECT_STREQ("PP_FALSE", PP_STRINGIZE(PP_IS_SEQUENCE(PP_NIL))); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace +} // namespace NYT diff --git a/library/cpp/yt/misc/unittests/ya.make b/library/cpp/yt/misc/unittests/ya.make new file mode 100644 index 0000000000..690082ca59 --- /dev/null +++ b/library/cpp/yt/misc/unittests/ya.make @@ -0,0 +1,15 @@ +GTEST(unittester-library-misc) + +OWNER(g:yt) + +SRCS( + enum_ut.cpp + guid_ut.cpp + preprocessor_ut.cpp +) + +PEERDIR( + library/cpp/yt/misc +) + +END() diff --git a/library/cpp/yt/misc/variant-inl.h b/library/cpp/yt/misc/variant-inl.h new file mode 100644 index 0000000000..fb7d98d4be --- /dev/null +++ b/library/cpp/yt/misc/variant-inl.h @@ -0,0 +1,70 @@ +#ifndef VARIANT_INL_H_ +#error "Direct inclusion of this file is not allowed, include variant.h" +// For the sake of sane code completion. +#include "variant.h" +#endif + +#include <type_traits> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +namespace NDetail { + +template <class T> +struct TIndexOf<T> +{ + static constexpr size_t Value = std::numeric_limits<size_t>::max(); +}; + +template <class T, class T0, class... Ts> +struct TIndexOf<T, T0, Ts...> +{ + static constexpr size_t Value = std::is_same_v<T, T0> + ? 0 + : 1 + TIndexOf<T, Ts...>::Value; +}; + +template <size_t Index, class... Ts> +struct TVariantFormatter; + +template <size_t Index> +struct TVariantFormatter<Index> +{ + template <class TVariant> + static void Do(TStringBuilderBase* /*builder*/, const TVariant& /*variant*/, TStringBuf /*spec*/) + { } +}; + +template <size_t Index, class T, class... Ts> +struct TVariantFormatter<Index, T, Ts...> +{ + template <class TVariant> + static void Do(TStringBuilderBase* builder, const TVariant& variant, TStringBuf spec) + { + if (variant.index() == Index) { + FormatValue(builder, std::get<Index>(variant), spec); + } else { + TVariantFormatter<Index + 1, Ts...>::Do(builder, variant, spec); + } + } +}; + +} // namespace NDetail + +template <class... Ts> +void FormatValue(TStringBuilderBase* builder, const std::variant<Ts...>& variant, TStringBuf spec) +{ + NDetail::TVariantFormatter<0, Ts...>::Do(builder, variant, spec); +} + +template <class... Ts> +TString ToString(const std::variant<Ts...>& variant) +{ + return ToStringViaBuilder(variant); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/misc/variant.h b/library/cpp/yt/misc/variant.h new file mode 100644 index 0000000000..27c0a2bc08 --- /dev/null +++ b/library/cpp/yt/misc/variant.h @@ -0,0 +1,76 @@ +#pragma once + +#include <variant> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +namespace NDetail { + +template <class T, class... Ts> +struct TIndexOf; + +} // namespace NDetail + +template <class T, class V> +struct TVariantIndex; + +template <class T, class... Ts> +struct TVariantIndex<T, std::variant<Ts...>> + : std::integral_constant<size_t, NDetail::TIndexOf<T, Ts...>::Value> +{ }; + +template <class T, class V> +constexpr size_t VariantIndexV = TVariantIndex<T, V>::value; + +//////////////////////////////////////////////////////////////////////////////// + +class TStringBuilderBase; + +template <class... Ts> +void FormatValue(TStringBuilderBase* builder, const std::variant<Ts...>& variant, TStringBuf spec); + +template <class... Ts> +TString ToString(const std::variant<Ts...>& variant); + +//////////////////////////////////////////////////////////////////////////////// + +//! A concise way of creating a functor with an overloaded operator(). +/*! + * Very useful for std::visit-ing variants. For example: + * + * std::visit(TOverloaded{ + * [] (int i) { printf("The variant holds an int: %d!", i); }, + * [] (const std::string& s) { printf("The variant holds a string: '%s'!", s); } + * }, variantVariable); + */ +template<class... Ts> struct TOverloaded : Ts... { using Ts::operator()...; }; +template<class... Ts> TOverloaded(Ts...) -> TOverloaded<Ts...>; + +//////////////////////////////////////////////////////////////////////////////// + +//! An alternative to std::visit that takes its variant argument first. +/*! + * This deprives it of being able to visit a Cartesian product of variants but + * in exchange allows to receive multiple visitor functors. All of operator()s + * these functors have are used to visit the variant after a single unified + * overload resolution. For example: + * + * Visit(variantVariable, + * [] (int i) { printf("The variant holds an int: %d!", i); }, + * [] (const std::string& s) { printf("The variant holds a string: '%s'!", s); }); + */ +template <class T, class... U> +auto Visit(T&& variant, U&&... visitorOverloads) +{ + return std::visit(TOverloaded{std::forward<U>(visitorOverloads)...}, std::forward<T>(variant)); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define VARIANT_INL_H_ +#include "variant-inl.h" +#undef VARIANT_INL_H_ diff --git a/library/cpp/yt/misc/ya.make b/library/cpp/yt/misc/ya.make new file mode 100644 index 0000000000..bb76711ddd --- /dev/null +++ b/library/cpp/yt/misc/ya.make @@ -0,0 +1,27 @@ +LIBRARY() + +OWNER(g:yt) + +SRCS( + guid.cpp + source_location.cpp +) + +PEERDIR( + library/cpp/yt/exception +) + +CHECK_DEPENDENT_DIRS( + ALLOW_ONLY ALL + build + contrib + library + util + yt/yt/library/small_containers +) + +END() + +RECURSE_FOR_TESTS( + unittests +) diff --git a/library/cpp/yt/small_containers/compact_flat_map-inl.h b/library/cpp/yt/small_containers/compact_flat_map-inl.h new file mode 100644 index 0000000000..45a4dd1de3 --- /dev/null +++ b/library/cpp/yt/small_containers/compact_flat_map-inl.h @@ -0,0 +1,184 @@ +#ifndef COMPACT_FLAT_MAP_INL_H_ +#error "Direct inclusion of this file is not allowed, include compact_flat_map.h" +// For the sake of sane code completion. +#include "compact_flat_map.h" +#endif + +namespace NYT { + +/////////////////////////////////////////////////////////////////////////////// + +template <class K, class V, unsigned N> +template <class TInputIterator> +TCompactFlatMap<K, V, N>::TCompactFlatMap(TInputIterator begin, TInputIterator end) +{ + insert(begin, end); +} + +template <class K, class V, unsigned N> +bool TCompactFlatMap<K, V, N>::operator==(const TCompactFlatMap& rhs) const +{ + return Storage_ == rhs.Storage_; +} + +template <class K, class V, unsigned N> +bool TCompactFlatMap<K, V, N>::operator!=(const TCompactFlatMap& rhs) const +{ + return !(*this == rhs); +} + +template <class K, class V, unsigned N> +typename TCompactFlatMap<K, V, N>::iterator TCompactFlatMap<K, V, N>::begin() +{ + return Storage_.begin(); +} + +template <class K, class V, unsigned N> +typename TCompactFlatMap<K, V, N>::const_iterator TCompactFlatMap<K, V, N>::begin() const +{ + return Storage_.begin(); +} + +template <class K, class V, unsigned N> +typename TCompactFlatMap<K, V, N>::iterator TCompactFlatMap<K, V, N>::end() +{ + return Storage_.end(); +} + +template <class K, class V, unsigned N> +typename TCompactFlatMap<K, V, N>::const_iterator TCompactFlatMap<K, V, N>::end() const +{ + return Storage_.end(); +} + +template <class K, class V, unsigned N> +void TCompactFlatMap<K, V, N>::reserve(size_type n) +{ + Storage_.reserve(n); +} + +template <class K, class V, unsigned N> +typename TCompactFlatMap<K, V, N>::size_type TCompactFlatMap<K, V, N>::size() const +{ + return Storage_.size(); +} + +template <class K, class V, unsigned N> +int TCompactFlatMap<K, V, N>::ssize() const +{ + return static_cast<int>(Storage_.size()); +} + +template <class K, class V, unsigned N> +bool TCompactFlatMap<K, V, N>::empty() const +{ + return Storage_.empty(); +} + +template <class K, class V, unsigned N> +void TCompactFlatMap<K, V, N>::clear() +{ + Storage_.clear(); +} + +template <class K, class V, unsigned N> +typename TCompactFlatMap<K, V, N>::iterator TCompactFlatMap<K, V, N>::find(const K& k) +{ + auto [rangeBegin, rangeEnd] = EqualRange(k); + return rangeBegin == rangeEnd ? end() : rangeBegin; +} + +template <class K, class V, unsigned N> +typename TCompactFlatMap<K, V, N>::const_iterator TCompactFlatMap<K, V, N>::find(const K& k) const +{ + auto [rangeBegin, rangeEnd] = EqualRange(k); + return rangeBegin == rangeEnd ? end() : rangeBegin; +} + +template <class K, class V, unsigned N> +bool TCompactFlatMap<K, V, N>::contains(const K& k) const +{ + return find(k) != end(); +} + +template <class K, class V, unsigned N> +auto TCompactFlatMap<K, V, N>::insert(const value_type& value) -> std::pair<iterator, bool> +{ + auto [rangeBegin, rangeEnd] = EqualRange(value.first); + if (rangeBegin != rangeEnd) { + return {rangeBegin, false}; + } else { + auto it = Storage_.insert(rangeBegin, value); + return {it, true}; + } +} + +template <class K, class V, unsigned N> +template <class TInputIterator> +void TCompactFlatMap<K, V, N>::insert(TInputIterator begin, TInputIterator end) +{ + for (auto it = begin; it != end; ++it) { + insert(*it); + } +} + +template <class K, class V, unsigned N> +template <class... TArgs> +auto TCompactFlatMap<K, V, N>::emplace(TArgs&&... args) -> std::pair<iterator, bool> +{ + return insert(value_type(std::forward<TArgs>(args)...)); +} + +template <class K, class V, unsigned N> +V& TCompactFlatMap<K, V, N>::operator[](const K& k) +{ + auto [it, inserted] = insert({k, V()}); + return it->second; +} + +template <class K, class V, unsigned N> +void TCompactFlatMap<K, V, N>::erase(const K& k) +{ + auto [rangeBegin, rangeEnd] = EqualRange(k); + erase(rangeBegin, rangeEnd); +} + +template <class K, class V, unsigned N> +void TCompactFlatMap<K, V, N>::erase(iterator pos) +{ + Storage_.erase(pos); + + // Try to keep the storage inline. This is why erase doesn't return an iterator. + Storage_.shrink_to_small(); +} + +template <class K, class V, unsigned N> +void TCompactFlatMap<K, V, N>::erase(iterator b, iterator e) +{ + Storage_.erase(b, e); + + // Try to keep the storage inline. This is why erase doesn't return an iterator. + Storage_.shrink_to_small(); +} + +template <class K, class V, unsigned N> +std::pair<typename TCompactFlatMap<K, V, N>::iterator, typename TCompactFlatMap<K, V, N>::iterator> +TCompactFlatMap<K, V, N>::EqualRange(const K& k) +{ + auto result = std::equal_range(Storage_.begin(), Storage_.end(), k, TKeyComparer()); + YT_ASSERT(std::distance(result.first, result.second) <= 1); + return result; +} + +template <class K, class V, unsigned N> +std::pair<typename TCompactFlatMap<K, V, N>::const_iterator, typename TCompactFlatMap<K, V, N>::const_iterator> +TCompactFlatMap<K, V, N>::EqualRange(const K& k) const +{ + auto result = std::equal_range(Storage_.begin(), Storage_.end(), k, TKeyComparer()); + YT_ASSERT(std::distance(result.first, result.second) <= 1); + return result; +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/small_containers/compact_flat_map.h b/library/cpp/yt/small_containers/compact_flat_map.h new file mode 100644 index 0000000000..13bdc0e9da --- /dev/null +++ b/library/cpp/yt/small_containers/compact_flat_map.h @@ -0,0 +1,109 @@ +#pragma once + +#include "compact_vector.h" + +namespace NYT { + +/////////////////////////////////////////////////////////////////////////////// + +//! A flat map implementation over TCompactVector that tries to keep data inline. +/*! + * Similarly to SmallSet, this is implemented via binary search over a sorted + * vector. Unlike SmallSet, however, this one never falls back to std::map (or + * set) for larger sizes. This means that the flat map is only useful + * - at small sizes, when there's absolutely no chance of it getting big, or + * - when it's filled once and is then only read from. + * + * In return, the flat map provides + * - a smaller size overhead and + * - a guarantee that if data fits into inline storage, it goes there. + * + * Because of the latter, one should be very careful with iterators: virtually + * any call to insert or erase may potentially invalidate all iterators. + */ +template <class K, class V, unsigned N> +class TCompactFlatMap +{ +public: + // NB: can't make this pair<const K, V> as TCompactVector requires its type + // parameter to be copy-assignable. + using value_type = std::pair<K, V>; + using key_type = K; + using mapped_type = V; + +private: + using TStorage = TCompactVector<value_type, N>; + + struct TKeyComparer + { + bool operator()(const K& lhs, const value_type& rhs) + { + return lhs < rhs.first; + } + + bool operator()(const value_type& lhs, const K& rhs) + { + return lhs.first < rhs; + } + }; + +public: + using iterator = typename TStorage::iterator; + using const_iterator = typename TStorage::const_iterator; + using size_type = size_t; + + TCompactFlatMap() = default; + + template <class TInputIterator> + TCompactFlatMap(TInputIterator begin, TInputIterator end); + + bool operator==(const TCompactFlatMap& rhs) const; + bool operator!=(const TCompactFlatMap& rhs) const; + + iterator begin(); + const_iterator begin() const; + + iterator end(); + const_iterator end() const; + + void reserve(size_type n); + + size_type size() const; + int ssize() const; + + bool empty() const; + void clear(); + + iterator find(const K& k); + const_iterator find(const K& k) const; + + bool contains(const K& k) const; + + std::pair<iterator, bool> insert(const value_type& value); + + template <class TInputIterator> + void insert(TInputIterator begin, TInputIterator end); + + template <class... TArgs> + std::pair<iterator, bool> emplace(TArgs&&... args); + + V& operator[](const K& k); + + void erase(const K& k); + void erase(iterator pos); + void erase(iterator b, iterator e); + +private: + std::pair<iterator, iterator> EqualRange(const K& k); + std::pair<const_iterator, const_iterator> EqualRange(const K& k) const; + + TStorage Storage_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define COMPACT_FLAT_MAP_INL_H_ +#include "compact_flat_map-inl.h" +#undef COMPACT_FLAT_MAP_INL_H_ diff --git a/library/cpp/yt/small_containers/compact_heap-inl.h b/library/cpp/yt/small_containers/compact_heap-inl.h new file mode 100644 index 0000000000..2c6b3507ba --- /dev/null +++ b/library/cpp/yt/small_containers/compact_heap-inl.h @@ -0,0 +1,152 @@ +#ifndef COMPACT_HEAP_INL_H_ +#error "Direct inclusion of this file is not allowed, include compact_heap.h" +// For the sake of sane code completion. +#include "compact_heap.h" +#endif + +#include <library/cpp/yt/assert/assert.h> + +#include <algorithm> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T, size_t N, class C> +TCompactHeap<T, N, C>::TCompactHeap(C comparator) noexcept + : Comparator_(TReverseComparator(std::move(comparator))) +{ } + +template <class T, size_t N, class C> +void TCompactHeap<T, N, C>::push(T value) +{ + bool wasInline = IsInline(); + Heap_.push_back(std::move(value)); + if (Y_UNLIKELY(!IsInline())) { + if (wasInline) { + std::make_heap(Heap_.begin(), Heap_.end(), Comparator_); + } else { + std::push_heap(Heap_.begin(), Heap_.end(), Comparator_); + } + } +} + +template <class T, size_t N, class C> +void TCompactHeap<T, N, C>::pop() +{ + YT_ASSERT(!empty()); + + if (Y_LIKELY(IsInline())) { + auto minIt = std::max_element(Heap_.begin(), Heap_.end(), Comparator_); + std::swap(*minIt, Heap_.back()); + Heap_.pop_back(); + } else { + std::pop_heap(Heap_.begin(), Heap_.end(), Comparator_); + Heap_.pop_back(); + } +} + +template <class T, size_t N, class C> +auto TCompactHeap<T, N, C>::get_min() const -> const_reference +{ + YT_ASSERT(!empty()); + + if (Y_LIKELY(IsInline())) { + return *std::max_element(Heap_.begin(), Heap_.end(), Comparator_); + } else { + return Heap_.front(); + } +} + +template <class T, size_t N, class C> +auto TCompactHeap<T, N, C>::extract_min() -> value_type +{ + YT_ASSERT(!empty()); + + if (Y_LIKELY(IsInline())) { + auto minIt = std::max_element(Heap_.begin(), Heap_.end(), Comparator_); + std::swap(*minIt, Heap_.back()); + auto value = Heap_.back(); + Heap_.pop_back(); + + return value; + } else { + std::pop_heap(Heap_.begin(), Heap_.end(), Comparator_); + auto value = std::move(Heap_.back()); + Heap_.pop_back(); + + return value; + } +} + +template <class T, size_t N, class C> +auto TCompactHeap<T, N, C>::begin() const -> const_iterator +{ + return Heap_.begin(); +} + +template <class T, size_t N, class C> +auto TCompactHeap<T, N, C>::end() const -> const_iterator +{ + return Heap_.end(); +} + +template <class T, size_t N, class C> +void TCompactHeap<T, N, C>::swap(TCompactHeap<T, N, C>& other) +{ + Heap_.swap(other.Heap_); + std::swap(Comparator_, other.Comparator_); +} + +template <class T, size_t N, class C> +size_t TCompactHeap<T, N, C>::size() const +{ + return Heap_.size(); +} + +template <class T, size_t N, class C> +size_t TCompactHeap<T, N, C>::capacity() const +{ + return Heap_.capacity(); +} + +template <class T, size_t N, class C> +size_t TCompactHeap<T, N, C>::max_size() const +{ + return Heap_.max_size(); +} + +template <class T, size_t N, class C> +bool TCompactHeap<T, N, C>::empty() const +{ + return Heap_.empty(); +} + +template <class T, size_t N, class C> +void TCompactHeap<T, N, C>::shrink_to_small() +{ + Heap_.shrink_to_small(); +} + +template <class T, size_t N, class C> +bool TCompactHeap<T, N, C>::IsInline() const +{ + return Heap_.capacity() == N; +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class T, size_t N, class C> +TCompactHeap<T, N, C>::TReverseComparator::TReverseComparator(C underlying) + : Underlying_(std::move(underlying)) +{ } + +template <class T, size_t N, class C> +bool TCompactHeap<T, N, C>::TReverseComparator::operator()(const T& lhs, const T& rhs) const +{ + return Underlying_(rhs, lhs); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/small_containers/compact_heap.h b/library/cpp/yt/small_containers/compact_heap.h new file mode 100644 index 0000000000..951b36962d --- /dev/null +++ b/library/cpp/yt/small_containers/compact_heap.h @@ -0,0 +1,75 @@ +#pragma once + +#include "compact_vector.h" + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +//! A heap structure optimized for storing elements inline +//! and with little memory overhead. See TCompactVector. +/*! + * When inline, uses linear search for selecting minimum + * instead of storing heap. + */ +template <class T, size_t N, class C = std::less<T>> +class TCompactHeap +{ +public: + static_assert(N <= 8, "TCompactHeap is not optimal for large N"); + + explicit TCompactHeap(C comparator = C()) noexcept; + + using value_type = T; + + using const_reference = const T&; + + using const_iterator = const T*; + + using difference_type = ptrdiff_t; + using size_type = size_t; + + void push(T value); + void pop(); + + const_reference get_min() const; + value_type extract_min(); + + const_iterator begin() const; + const_iterator end() const; + + void swap(TCompactHeap<T, N, C>& other); + + size_t size() const; + size_t capacity() const; + size_t max_size() const; + + bool empty() const; + + void shrink_to_small(); + +private: + TCompactVector<T, N> Heap_; + + class TReverseComparator + { + public: + explicit TReverseComparator(C underlying); + + bool operator()(const T& lhs, const T& rhs) const; + + private: + C Underlying_; + }; + TReverseComparator Comparator_; + + bool IsInline() const; +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define COMPACT_HEAP_INL_H_ +#include "compact_heap-inl.h" +#undef COMPACT_HEAP_INL_H_ diff --git a/library/cpp/yt/small_containers/compact_set-inl.h b/library/cpp/yt/small_containers/compact_set-inl.h new file mode 100644 index 0000000000..75b8600175 --- /dev/null +++ b/library/cpp/yt/small_containers/compact_set-inl.h @@ -0,0 +1,322 @@ +#ifndef COMPACT_SET_INL_H_ +#error "Direct inclusion of this file is not allowed, include compact_set.h" +// For the sake of sane code completion. +#include "compact_set.h" +#endif + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template <typename T, unsigned N, typename C> +class TCompactSet<T, N, C>::const_iterator +{ +private: + friend class TCompactSet<T, N, C>; + + union + { + TVectorConstIterator VIter; + TSetConstIterator SIter; + }; + + bool Small; + + const_iterator(TVectorConstIterator it) + : VIter(it) + , Small(true) + { } + + const_iterator(TSetConstIterator it) + : SIter(it) + , Small(false) + { } + + template <class TOther> + void ConstructFrom(TOther&& rhs) + { + Y_VERIFY_DEBUG(Small == rhs.Small); + + if (Small) { + new (&VIter)TVectorConstIterator(std::forward<TOther>(rhs).VIter); + } else { + new (&SIter)TSetConstIterator(std::forward<TOther>(rhs).SIter); + } + } + + template <class TOther> + const_iterator& AssignFrom(TOther&& rhs) + { + if (this == &rhs) { + return *this; + } + + if (Small && rhs.Small) { + VIter = std::forward<TOther>(rhs).VIter; + } else if (!Small && !rhs.Small) { + SIter = std::forward<TOther>(rhs).SIter; + } else { + if (Small) { + VIter.~TVectorConstIterator(); + } else { + SIter.~TSetConstIterator(); + } + + if (rhs.Small) { + new (&VIter)TVectorConstIterator(std::forward<TOther>(rhs).VIter); + } else { + new (&SIter)TSetConstIterator(std::forward<TOther>(rhs).SIter); + } + } + + Small = rhs.Small; + + return *this; + } + +public: + static_assert(std::is_same_v< + typename std::iterator_traits<TVectorConstIterator>::difference_type, + typename std::iterator_traits<TSetConstIterator>::difference_type>); + static_assert(std::is_same_v< + typename std::iterator_traits<TVectorConstIterator>::value_type, + typename std::iterator_traits<TSetConstIterator>::value_type>); + static_assert(std::is_same_v< + typename std::iterator_traits<TVectorConstIterator>::pointer, + typename std::iterator_traits<TSetConstIterator>::pointer>); + static_assert(std::is_same_v< + typename std::iterator_traits<TVectorConstIterator>::reference, + typename std::iterator_traits<TSetConstIterator>::reference>); + + using difference_type = typename std::iterator_traits<TVectorConstIterator>::difference_type; + using value_type = typename std::iterator_traits<TVectorConstIterator>::value_type; + using pointer = typename std::iterator_traits<TVectorConstIterator>::pointer; + using reference = typename std::iterator_traits<TVectorConstIterator>::reference; + using iterator_category = std::bidirectional_iterator_tag; + + const_iterator(const const_iterator& rhs) + : Small(rhs.Small) + { + ConstructFrom(rhs); + } + + const_iterator(const_iterator&& rhs) + : Small(rhs.Small) + { + ConstructFrom(std::move(rhs)); + } + + ~const_iterator() + { + if (Small) { + VIter.~TVectorConstIterator(); + } else { + SIter.~TSetConstIterator(); + } + } + + const_iterator& operator=(const const_iterator& rhs) + { + return AssignFrom(rhs); + } + + const_iterator& operator=(const_iterator&& rhs) + { + return AssignFrom(std::move(rhs)); + } + + const_iterator& operator++() + { + if (Small) { + ++VIter; + } else { + ++SIter; + } + + return *this; + } + + const_iterator operator++(int) + { + auto result = *this; + + if (Small) { + ++VIter; + } else { + ++SIter; + } + + return result; + } + + const_iterator& operator--() + { + if (Small) { + --VIter; + } else { + --SIter; + } + + return *this; + } + + const_iterator operator--(int) + { + auto result = *this; + + if (Small) { + --VIter; + } else { + --SIter; + } + + return result; + } + + bool operator==(const const_iterator& rhs) const + { + if (Small != rhs.Small) { + return false; + } + + return Small ? (VIter == rhs.VIter) : (SIter == rhs.SIter); + } + + bool operator!=(const const_iterator& rhs) const + { + return !(*this == rhs); + } + + const T& operator*() const + { + return Small ? *VIter : *SIter; + } + + const T* operator->() const + { + return &operator*(); + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +template <typename T, unsigned N, typename C> +bool TCompactSet<T, N, C>::empty() const +{ + return Vector.empty() && Set.empty(); +} + +template <typename T, unsigned N, typename C> +typename TCompactSet<T, N, C>::size_type TCompactSet<T, N, C>::size() const +{ + return IsSmall() ? Vector.size() : Set.size(); +} + +template <typename T, unsigned N, typename C> +const T& TCompactSet<T, N, C>::front() const +{ + return IsSmall() ? Vector.front() : *Set.begin(); +} + + +template <typename T, unsigned N, typename C> +typename TCompactSet<T, N, C>::size_type TCompactSet<T, N, C>::count(const T& v) const +{ + if (IsSmall()) { + return std::binary_search(Vector.begin(), Vector.end(), v, C()) ? 1 : 0; + } else { + return Set.count(v); + } +} + +template <typename T, unsigned N, typename C> +std::pair<typename TCompactSet<T, N, C>::const_iterator, bool> TCompactSet<T, N, C>::insert(const T& v) +{ + if (!IsSmall()) { + auto [it, inserted] = Set.insert(v); + return {const_iterator(std::move(it)), inserted}; + } + + auto it = std::lower_bound(Vector.begin(), Vector.end(), v, C()); + if (it != Vector.end() && !C()(v, *it)) { + return {const_iterator(std::move(it)), false}; // Don't reinsert if it already exists. + } + + if (Vector.size() < N) { + auto newIt = Vector.insert(it, v); + return {const_iterator(std::move(newIt)), true}; + } + + Set.insert(Vector.begin(), Vector.end()); + Vector.clear(); + + auto [newIt, inserted] = Set.insert(v); + Y_VERIFY_DEBUG(inserted); + return {const_iterator(std::move(newIt)), true}; +} + +template <typename T, unsigned N, typename C> +template <typename TIter> +void TCompactSet<T, N, C>::insert(TIter i, TIter e) +{ + for (; i != e; ++i) { + insert(*i); + } +} + +template <typename T, unsigned N, typename C> +bool TCompactSet<T, N, C>::erase(const T& v) +{ + if (!IsSmall()) { + return Set.erase(v); + } + + auto [rangeBegin, rangeEnd] = std::equal_range(Vector.begin(), Vector.end(), v, C()); + if (rangeBegin != rangeEnd) { + Vector.erase(rangeBegin, rangeEnd); + return true; + } else { + return false; + } +} + +template <typename T, unsigned N, typename C> +void TCompactSet<T, N, C>::clear() +{ + Vector.clear(); + Set.clear(); +} + +template <typename T, unsigned N, typename C> +typename TCompactSet<T, N, C>::const_iterator TCompactSet<T, N, C>::begin() const +{ + return IsSmall() ? const_iterator(Vector.begin()) : const_iterator(Set.begin()); +} + +template <typename T, unsigned N, typename C> +typename TCompactSet<T, N, C>::const_iterator TCompactSet<T, N, C>::cbegin() const +{ + return begin(); +} + +template <typename T, unsigned N, typename C> +typename TCompactSet<T, N, C>::const_iterator TCompactSet<T, N, C>::end() const +{ + return IsSmall() ? const_iterator(Vector.end()) : const_iterator(Set.end()); +} + +template <typename T, unsigned N, typename C> +typename TCompactSet<T, N, C>::const_iterator TCompactSet<T, N, C>::cend() const +{ + return end(); +} + +template <typename T, unsigned N, typename C> +bool TCompactSet<T, N, C>::IsSmall() const +{ + return Set.empty(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/small_containers/compact_set.h b/library/cpp/yt/small_containers/compact_set.h new file mode 100644 index 0000000000..2ca8713ea7 --- /dev/null +++ b/library/cpp/yt/small_containers/compact_set.h @@ -0,0 +1,86 @@ +//===- llvm/ADT/SmallSet.h - 'Normally small' sets --------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the TCompactSet class. +// +//===----------------------------------------------------------------------===// + +#pragma once + +#include "compact_vector.h" + +#include <util/system/yassert.h> + +#include <cstddef> +#include <iterator> +#include <set> +#include <type_traits> + +namespace NYT { + +/// TCompactSet - This maintains a set of unique values, optimizing for the case +/// when the set is small (less than N). In this case, the set can be +/// maintained with no mallocs. If the set gets large, we expand to using an +/// std::set to maintain reasonable lookup times. +/// +/// Note that any modification of the set may invalidate *all* iterators. +template <typename T, unsigned N, typename C = std::less<T>> +class TCompactSet +{ +private: + /// Use a CompactVector to hold the elements here (even though it will never + /// reach its 'large' stage) to avoid calling the default ctors of elements + /// we will never use. + TCompactVector<T, N> Vector; + std::set<T, C> Set; + + using TSetConstIterator = typename std::set<T, C>::const_iterator; + using TVectorConstIterator = typename TCompactVector<T, N>::const_iterator; + +public: + class const_iterator; + using size_type = std::size_t; + + TCompactSet() {} + + [[nodiscard]] bool empty() const; + + size_type size() const; + + const T& front() const; + + /// count - Return true if the element is in the set. + size_type count(const T& v) const; + + /// insert - Insert an element into the set if it isn't already there. + std::pair<const_iterator, bool> insert(const T& v); + + template <typename TIter> + void insert(TIter i, TIter e); + + bool erase(const T& v); + + void clear(); + + const_iterator begin() const; + const_iterator cbegin() const; + + const_iterator end() const; + const_iterator cend() const; + +private: + bool IsSmall() const; +}; + +} // namespace NYT + +#define COMPACT_SET_INL_H_ +#include "compact_set-inl.h" +#undef COMPACT_SET_INL_H_ + diff --git a/library/cpp/yt/small_containers/compact_vector-inl.h b/library/cpp/yt/small_containers/compact_vector-inl.h new file mode 100644 index 0000000000..52507e4768 --- /dev/null +++ b/library/cpp/yt/small_containers/compact_vector-inl.h @@ -0,0 +1,1011 @@ +#ifndef COMPACT_VECTOR_INL_H_ +#error "Direct inclusion of this file is not allowed, include compact_vector.h" +// For the sake of sane code completion. +#include "compact_vector.h" +#endif +#undef COMPACT_VECTOR_INL_H_ + +#include <library/cpp/yt/assert/assert.h> + +#include <library/cpp/yt/malloc/malloc.h> + +#include <util/system/compiler.h> + +#include <algorithm> +#include <bit> + +#include <string.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +static_assert(sizeof(uintptr_t) == 8); + +// TODO(gritukan, babenko): Uncomment check below after DEVTOOLS-7870. +// static_assert(std::endian::native == std::endian::little); + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +struct TCompactVectorOnHeapStorage +{ + T* End; + T* Capacity; + T Elements[0]; +}; + +//////////////////////////////////////////////////////////////////////////////// + +template <class TVector, class TPtr> +class TCompactVectorReallocationPtrAdjuster +{ +public: + TCompactVectorReallocationPtrAdjuster(TVector* vector, TPtr& ptr) + : Vector_(vector) + , Ptr_(ptr) + , Index_(ptr >= Vector_->begin() && ptr <= Vector_->end() + ? std::distance(Vector_->begin(), const_cast<typename TVector::iterator>(ptr)) + : -1) + { } + + ~TCompactVectorReallocationPtrAdjuster() + { + if (Index_ >= 0) { + Ptr_ = Vector_->begin() + Index_; + } + } + +private: + TVector* const Vector_; + TPtr& Ptr_; + const ptrdiff_t Index_; +}; + +template <class TVector> +class TCompactVectorReallocationPtrAdjuster<TVector, std::nullptr_t> +{ +public: + TCompactVectorReallocationPtrAdjuster(TVector* /*vector*/, std::nullptr_t /*ptr*/) + { } +}; + +//////////////////////////////////////////////////////////////////////////////// + +template <class T, size_t N> +TCompactVector<T, N>::TCompactVector() noexcept +{ + InlineMeta_.SizePlusOne = 1; +} + +template <class T, size_t N> +TCompactVector<T, N>::TCompactVector(const TCompactVector& other) + : TCompactVector() +{ + assign(other.begin(), other.end()); +} + +template <class T, size_t N> +template <size_t OtherN> +TCompactVector<T, N>::TCompactVector(const TCompactVector<T, OtherN>& other) + : TCompactVector() +{ + assign(other.begin(), other.end()); +} + +template <class T, size_t N> +TCompactVector<T, N>::TCompactVector(TCompactVector&& other) noexcept(std::is_nothrow_move_constructible_v<T>) + : TCompactVector() +{ + swap(other); +} + +template <class T, size_t N> +template <size_t OtherN> +TCompactVector<T, N>::TCompactVector(TCompactVector<T, OtherN>&& other) + : TCompactVector() +{ + swap(other); +} + +template <class T, size_t N> +TCompactVector<T, N>::TCompactVector(size_type count) + : TCompactVector() +{ + assign(count, T()); +} + +template <class T, size_t N> +TCompactVector<T, N>::TCompactVector(size_type count, const T& value) + : TCompactVector() +{ + assign(count, value); +} + +template <class T, size_t N> +template <class TIterator> +TCompactVector<T, N>::TCompactVector(TIterator first, TIterator last) + : TCompactVector() +{ + assign(first, last); +} + +template <class T, size_t N> +TCompactVector<T, N>::TCompactVector(std::initializer_list<T> list) + : TCompactVector() +{ + assign(list.begin(), list.end()); +} + +template <class T, size_t N> +TCompactVector<T, N>::~TCompactVector() +{ + if (Y_LIKELY(IsInline())) { + Destroy(&InlineElements_[0], &InlineElements_[InlineMeta_.SizePlusOne - 1]); + } else { + auto* storage = OnHeapMeta_.Storage; + Destroy(storage->Elements, storage->End); + ::free(storage); + } +} + +template <class T, size_t N> +bool TCompactVector<T, N>::empty() const +{ + if (Y_LIKELY(IsInline())) { + return InlineMeta_.SizePlusOne == 1; + } else { + const auto* storage = OnHeapMeta_.Storage; + return storage->Elements == storage->End; + } +} + +template <class T, size_t N> +auto TCompactVector<T, N>::begin() -> iterator +{ + return Y_LIKELY(IsInline()) ? &InlineElements_[0] : OnHeapMeta_.Storage->Elements; +} + +template <class T, size_t N> +auto TCompactVector<T, N>::begin() const -> const_iterator +{ + return const_cast<TCompactVector*>(this)->begin(); +} + +template <class T, size_t N> +auto TCompactVector<T, N>::end() -> iterator +{ + return Y_LIKELY(IsInline()) ? &InlineElements_[InlineMeta_.SizePlusOne - 1] : OnHeapMeta_.Storage->End; +} + +template <class T, size_t N> +auto TCompactVector<T, N>::end() const -> const_iterator +{ + return const_cast<TCompactVector*>(this)->end(); +} + +template <class T, size_t N> +auto TCompactVector<T, N>::rbegin() -> reverse_iterator +{ + return static_cast<reverse_iterator>(end()); +} + +template <class T, size_t N> +auto TCompactVector<T, N>::rbegin() const -> const_reverse_iterator +{ + return static_cast<const_reverse_iterator>(end()); +} + +template <class T, size_t N> +auto TCompactVector<T, N>::rend() -> reverse_iterator +{ + return static_cast<reverse_iterator>(begin()); +} + +template <class T, size_t N> +auto TCompactVector<T, N>::rend() const -> const_reverse_iterator +{ + return static_cast<const_reverse_iterator>(begin()); +} + +template <class T, size_t N> +auto TCompactVector<T, N>::size() const -> size_type +{ + if (Y_LIKELY(IsInline())) { + return InlineMeta_.SizePlusOne - 1; + } else { + const auto* storage = OnHeapMeta_.Storage; + return storage->End - storage->Elements; + } +} + +template <class T, size_t N> +auto TCompactVector<T, N>::capacity() const -> size_type +{ + if (Y_LIKELY(IsInline())) { + return N; + } else { + const auto* storage = OnHeapMeta_.Storage; + return storage->Capacity - storage->Elements; + } +} + +template <class T, size_t N> +auto TCompactVector<T, N>::max_size() const -> size_type +{ + return static_cast<size_type>(-1) / sizeof(T); +} + +template <class T, size_t N> +auto TCompactVector<T, N>::data() -> pointer +{ + return static_cast<pointer>(begin()); +} + +template <class T, size_t N> +auto TCompactVector<T, N>::data() const -> const_pointer +{ + return static_cast<const_pointer>(begin()); +} + +template <class T, size_t N> +auto TCompactVector<T, N>::operator[](size_type index) -> reference +{ + YT_ASSERT(index < size()); + return begin()[index]; +} + +template <class T, size_t N> +auto TCompactVector<T, N>::operator[](size_type index) const -> const_reference +{ + return const_cast<TCompactVector*>(this)->operator[](index); +} + +template <class T, size_t N> +auto TCompactVector<T, N>::front() -> reference +{ + YT_ASSERT(!empty()); + return begin()[0]; +} + +template <class T, size_t N> +auto TCompactVector<T, N>::front() const -> const_reference +{ + return const_cast<TCompactVector*>(this)->front(); +} + +template <class T, size_t N> +auto TCompactVector<T, N>::back() -> reference +{ + YT_ASSERT(!empty()); + return end()[-1]; +} + +template <class T, size_t N> +auto TCompactVector<T, N>::back() const -> const_reference +{ + return const_cast<TCompactVector*>(this)->back(); +} + +template <class T, size_t N> +void TCompactVector<T, N>::push_back(const T& value) +{ + PushBackImpl( + &value, + [&] (T* dst, const T* value) { + ::new(dst) T(*value); + }); +} + +template <class T, size_t N> +void TCompactVector<T, N>::push_back(T&& value) +{ + PushBackImpl( + &value, + [&] (T* dst, T* value) { + ::new(dst) T(std::move(*value)); + }); +} + +template <class T, size_t N> +template <class... TArgs> +auto TCompactVector<T, N>::emplace(const_iterator pos, TArgs&&... args) -> iterator +{ + return InsertOneImpl( + pos, + nullptr, + [&] (auto* dst, std::nullptr_t) { + ::new(dst) T(std::forward<TArgs>(args)...); + }, + [&] (auto* dst, std::nullptr_t) { + *dst = T(std::forward<TArgs>(args)...); + }); +} + +template <class T, size_t N> +template <class... TArgs> +auto TCompactVector<T, N>::emplace_back(TArgs&&... args) -> reference +{ + return PushBackImpl( + nullptr, + [&] (T* dst, std::nullptr_t) { + ::new(dst) T(std::forward<TArgs>(args)...); + }); +} + +template <class T, size_t N> +void TCompactVector<T, N>::pop_back() +{ + YT_ASSERT(!empty()); + + if (Y_LIKELY(IsInline())) { + InlineElements_[InlineMeta_.SizePlusOne - 2].T::~T(); + --InlineMeta_.SizePlusOne; + } else { + auto* storage = OnHeapMeta_.Storage; + storage->End[-1].T::~T(); + --storage->End; + } +} + +template <class T, size_t N> +auto TCompactVector<T, N>::erase(const_iterator pos) -> iterator +{ + YT_ASSERT(pos >= begin()); + YT_ASSERT(pos < end()); + + auto* mutablePos = const_cast<iterator>(pos); + Move(mutablePos + 1, end(), mutablePos); + pop_back(); + + return mutablePos; +} + +template <class T, size_t N> +auto TCompactVector<T, N>::erase(const_iterator first, const_iterator last) -> iterator +{ + YT_ASSERT(first >= begin()); + YT_ASSERT(last <= end()); + + auto* mutableFirst = const_cast<iterator>(first); + auto* mutableLast = const_cast<iterator>(last); + auto count = std::distance(mutableFirst, mutableLast); + + if (Y_LIKELY(IsInline())) { + auto* end = &InlineElements_[0] + InlineMeta_.SizePlusOne - 1; + Move(mutableLast, end, mutableFirst); + Destroy(end - count, end); + InlineMeta_.SizePlusOne -= count; + } else { + auto* storage = OnHeapMeta_.Storage; + auto* end = storage->End; + Move(mutableLast, storage->End, mutableFirst); + Destroy(end - count, end); + storage->End -= count; + } + + return mutableFirst; +} + +template <class T, size_t N> +void TCompactVector<T, N>::clear() +{ + if (Y_LIKELY(IsInline())) { + Destroy(&InlineElements_[0], &InlineElements_[InlineMeta_.SizePlusOne - 1]); + InlineMeta_.SizePlusOne = 1; + } else { + auto* storage = OnHeapMeta_.Storage; + Destroy(storage->Elements, storage->End); + storage->End = storage->Elements; + } +} + +template <class T, size_t N> +void TCompactVector<T, N>::resize(size_type newSize) +{ + ResizeImpl( + newSize, + [] (auto* dst) { + ::new(dst) T(); + }); +} + +template <class T, size_t N> +void TCompactVector<T, N>::resize(size_type newSize, const T& value) +{ + ResizeImpl( + newSize, + [&] (auto* dst) { + ::new(dst) T(value); + }); +} + +template <class T, size_t N> +void TCompactVector<T, N>::reserve(size_t newCapacity) +{ + if (Y_UNLIKELY(newCapacity > N)) { + EnsureOnHeapCapacity(newCapacity, /*incremental*/ false); + } +} + +template <class T, size_t N> +void TCompactVector<T, N>::swap(TCompactVector& other) +{ + if (this == &other) { + return; + } + + if (!IsInline() && !other.IsInline()) { + std::swap(OnHeapMeta_.Storage, other.OnHeapMeta_.Storage); + return; + } + + auto* lhs = this; + auto* rhs = &other; + if (lhs->size() < rhs->size()) { + std::swap(lhs, rhs); + } + + size_t rhsSize = rhs->size(); + size_t lhsSize = lhs->size(); + if (lhsSize > rhs->capacity()) { + rhs->EnsureOnHeapCapacity(lhs->size(), /*incremental*/ false); + } + + for (size_t index = 0; index < rhsSize; ++index) { + std::swap((*lhs)[index], (*rhs)[index]); + } + + UninitializedMove(lhs->begin() + rhsSize, lhs->end(), rhs->end()); + Destroy(lhs->begin() + rhsSize, lhs->end()); + + rhs->SetSize(lhsSize); + lhs->SetSize(rhsSize); +} + +template <class T, size_t N> +void TCompactVector<T, N>::assign(size_type count, const T& value) +{ + clear(); + + if (Y_UNLIKELY(count > capacity())) { + EnsureOnHeapCapacity(count, /*incremental*/ false); + } + + auto* dst = begin(); + std::uninitialized_fill(dst, dst + count, value); + + SetSize(count); +} + +template <class T, size_t N> +template <class TIterator> +void TCompactVector<T, N>::assign(TIterator first, TIterator last) +{ + clear(); + + auto count = std::distance(first, last); + if (Y_UNLIKELY(count > static_cast<ptrdiff_t>(capacity()))) { + EnsureOnHeapCapacity(count, /*incremental*/ false); + } + + std::uninitialized_copy(first, last, begin()); + + SetSize(count); +} + +template <class T, size_t N> +void TCompactVector<T, N>::assign(std::initializer_list<T> list) +{ + assign(list.begin(), list.end()); +} + +template <class T, size_t N> +template <size_t OtherN> +void TCompactVector<T, N>::assign(const TCompactVector<T, OtherN>& other) +{ + if constexpr(N == OtherN) { + if (this == &other) { + return; + } + } + + auto otherSize = other.size(); + auto otherBegin = other.begin(); + + if (capacity() >= otherSize) { + const auto* src = other.begin(); + auto* dst = begin(); + + auto thisSize = size(); + auto copySize = std::min(thisSize, otherSize); + Copy(src, src + copySize, dst); + src += copySize; + dst += copySize; + + auto uninitializedCopySize = otherSize - copySize; + UninitializedCopy(src, src + uninitializedCopySize, dst); + // NB: src += uninitializedCopySize is not needed. + dst += uninitializedCopySize; + + if (thisSize > otherSize) { + Destroy(dst, end()); + } + + SetSize(otherSize); + return; + } + + clear(); + + EnsureOnHeapCapacity(otherSize, /*incremental*/ false); + + YT_ASSERT(!IsInline()); + auto* storage = OnHeapMeta_.Storage; + UninitializedCopy(otherBegin, otherBegin + otherSize, storage->Elements); + storage->End = storage->Elements + otherSize; +} + +template <class T, size_t N> +template <size_t OtherN> +void TCompactVector<T, N>::assign(TCompactVector<T, OtherN>&& other) +{ + if constexpr(N == OtherN) { + if (this == &other) { + return; + } + } + + clear(); + + if (!other.IsInline()) { + if (Y_UNLIKELY(!IsInline())) { + ::free(OnHeapMeta_.Storage); + } + OnHeapMeta_.Storage = other.OnHeapMeta_.Storage; + other.InlineMeta_.SizePlusOne = 1; + return; + } + + auto otherSize = other.size(); + if (Y_UNLIKELY(otherSize > capacity())) { + EnsureOnHeapCapacity(otherSize, /*incremental*/ false); + } + + auto* otherBegin = other.begin(); + UninitializedMove(otherBegin, otherBegin + otherSize, begin()); + SetSize(otherSize); + + other.clear(); +} + +template <class T, size_t N> +auto TCompactVector<T, N>::operator=(const TCompactVector& other) -> TCompactVector& +{ + assign(other); + return *this; +} + +template <class T, size_t N> +template <size_t OtherN> +auto TCompactVector<T, N>::operator=(const TCompactVector<T, OtherN>& other) -> TCompactVector& +{ + assign(other); + return *this; +} + +template <class T, size_t N> +auto TCompactVector<T, N>::operator=(TCompactVector&& other) -> TCompactVector& +{ + assign(std::move(other)); + return *this; +} + +template <class T, size_t N> +template <size_t OtherN> +auto TCompactVector<T, N>::operator=(TCompactVector<T, OtherN>&& other) -> TCompactVector& +{ + assign(std::move(other)); + return *this; +} + +template <class T, size_t N> +auto TCompactVector<T, N>::operator=(std::initializer_list<T> list) -> TCompactVector& +{ + assign(list); + return *this; +} + +template <class T, size_t N> +auto TCompactVector<T, N>::insert(const_iterator pos, const T& value) -> iterator +{ + return InsertOneImpl( + pos, + &value, + [&] (auto* dst, const auto* value) { + ::new(dst) T(*value); + }, + [&] (auto* dst, const auto* value) { + *dst = *value; + }); +} + +template <class T, size_t N> +auto TCompactVector<T, N>::insert(const_iterator pos, T&& value) -> iterator +{ + return InsertOneImpl( + pos, + &value, + [&] (auto* dst, auto* value) { + ::new(dst) T(std::move(*value)); + }, + [&] (auto* dst, auto* value) { + *dst = std::move(*value); + }); +} + +template <class T, size_t N> +auto TCompactVector<T, N>::insert(const_iterator pos, size_type count, const T& value) -> iterator +{ + return InsertManyImpl( + pos, + count, + [&] (auto* dstFirst, auto* dstLast) { + for (auto* dst = dstFirst; dst != dstLast; ++dst) { + ::new(dst) T(value); + } + }, + [&] (auto* dstFirst, auto* dstLast) { + for (auto* dst = dstFirst; dst != dstLast; ++dst) { + *dst = value; + } + }); +} + +template <class T, size_t N> +template <class TIterator> +auto TCompactVector<T, N>::insert(const_iterator pos, TIterator first, TIterator last) -> iterator +{ + auto current = first; + return InsertManyImpl( + pos, + std::distance(first, last), + [&] (auto* dstFirst, auto* dstLast) { + for (auto* dst = dstFirst; dst != dstLast; ++dst) { + ::new(dst) T(*current++); + } + }, + [&] (auto* dstFirst, auto* dstLast) { + for (auto* dst = dstFirst; dst != dstLast; ++dst) { + *dst = *current++; + } + }); +} + +template <class T, size_t N> +auto TCompactVector<T, N>::insert(const_iterator pos, std::initializer_list<T> list) -> iterator +{ + return insert(pos, list.begin(), list.end()); +} + +template <class T, size_t N> +void TCompactVector<T, N>::shrink_to_small() +{ + if (Y_LIKELY(IsInline())) { + return; + } + + auto size = this->size(); + if (size > N) { + return; + } + + auto* storage = OnHeapMeta_.Storage; + UninitializedMove(storage->Elements, storage->End, &InlineElements_[0]); + Destroy(storage->Elements, storage->End); + ::free(storage); + + InlineMeta_.SizePlusOne = size + 1; +} + +template <class T, size_t N> +bool TCompactVector<T, N>::IsInline() const +{ + return InlineMeta_.SizePlusOne != 0; +} + +template <class T, size_t N> +void TCompactVector<T, N>::SetSize(size_t newSize) +{ + if (Y_LIKELY(IsInline())) { + InlineMeta_.SizePlusOne = newSize + 1; + } else { + auto* storage = OnHeapMeta_.Storage; + storage->End = storage->Elements + newSize; + } +} + +template <class T, size_t N> +void TCompactVector<T, N>::EnsureOnHeapCapacity(size_t newCapacity, bool incremental) +{ + newCapacity = std::max(newCapacity, N + 1); + if (incremental) { + newCapacity = std::max(newCapacity, capacity() * 2); + } + + auto byteSize = sizeof(TOnHeapStorage) + newCapacity * sizeof(T); + byteSize = nallocx(byteSize, 0); + + newCapacity = (byteSize - sizeof(TOnHeapStorage)) / sizeof(T); + + auto* newStorage = static_cast<TOnHeapStorage*>(::malloc(byteSize)); + YT_VERIFY((reinterpret_cast<uintptr_t>(newStorage) >> 56) == 0); + + newStorage->Capacity = newStorage->Elements + newCapacity; + + size_t size; + if (IsInline()) { + size = InlineMeta_.SizePlusOne - 1; + UninitializedMove(&InlineElements_[0], &InlineElements_[0] + size, newStorage->Elements); + Destroy(&InlineElements_[0], &InlineElements_[0] + size); + } else { + auto* storage = OnHeapMeta_.Storage; + size = storage->End - storage->Elements; + UninitializedMove(storage->Elements, storage->End, newStorage->Elements); + Destroy(storage->Elements, storage->End); + ::free(storage); + } + + newStorage->End = newStorage->Elements + size; + OnHeapMeta_.Storage = newStorage; +} + +template <class T, size_t N> +template <class TPtr, class F> +auto TCompactVector<T, N>::PushBackImpl(TPtr valuePtr, F&& func) -> reference +{ + auto sizePlusOne = InlineMeta_.SizePlusOne; + if (Y_LIKELY(sizePlusOne != 0 && sizePlusOne != N + 1)) { + auto* dst = &InlineElements_[sizePlusOne - 1]; + func(dst, valuePtr); + ++InlineMeta_.SizePlusOne; + return *dst; + } + + auto hasSpareOnHeapCapacity = [&] { + if (sizePlusOne != 0) { + return false; + } + auto* storage = OnHeapMeta_.Storage; + return storage->End < storage->Capacity; + }; + + if (Y_UNLIKELY(!hasSpareOnHeapCapacity())) { + TCompactVectorReallocationPtrAdjuster<TCompactVector, TPtr> valuePtrAdjuster(this, valuePtr); + EnsureOnHeapCapacity(0, /*incremental*/ true); + } + + YT_ASSERT(!IsInline()); + auto* storage = OnHeapMeta_.Storage; + auto* dst = storage->End++; + func(dst, valuePtr); + + return *dst; +} + +template <class T, size_t N> +template <class F> +void TCompactVector<T, N>::ResizeImpl(size_type newSize, F&& func) +{ + auto size = this->size(); + if (newSize > size) { + if (Y_UNLIKELY(newSize > capacity())) { + EnsureOnHeapCapacity(newSize, /*incremental*/ false); + } + + auto* first = end(); + auto* last = first + newSize - size; + for (auto* current = first; current != last; ++current) { + func(current); + } + } else if (newSize < size) { + Destroy(begin() + newSize, end()); + } + + SetSize(newSize); +} + +template <class T, size_t N> +template <class TPtr, class UninitializedF, class InitializedF> +auto TCompactVector<T, N>::InsertOneImpl(const_iterator pos, TPtr valuePtr, UninitializedF&& uninitializedFunc, InitializedF&& initializedFunc) -> iterator +{ + YT_ASSERT(pos >= begin()); + YT_ASSERT(pos <= end()); + + auto* mutablePos = const_cast<iterator>(pos); + + auto newSize = size() + 1; + if (Y_UNLIKELY(newSize > capacity())) { + TCompactVectorReallocationPtrAdjuster<TCompactVector, iterator> mutablePosAdjuster(this, mutablePos); + TCompactVectorReallocationPtrAdjuster<TCompactVector, TPtr> valuePtrAdjuster(this, valuePtr); + EnsureOnHeapCapacity(newSize, /*incremental*/ true); + } + + auto* end = this->end(); + + if constexpr(!std::is_same_v<TPtr, std::nullptr_t>) { + if (valuePtr >= mutablePos && valuePtr < end) { + ++valuePtr; + } + } + + auto moveCount = std::distance(mutablePos, end); + if (moveCount == 0) { + uninitializedFunc(end, valuePtr); + } else { + if constexpr(std::is_trivially_copyable_v<T>) { + ::memmove(mutablePos + 1, mutablePos, moveCount * sizeof(T)); + } else { + ::new(end) T(std::move(end[-1])); + MoveBackward(mutablePos, end - 1, mutablePos + 1); + } + initializedFunc(mutablePos, valuePtr); + } + + SetSize(newSize); + + return mutablePos; +} + +template <class T, size_t N> +template <class UninitializedF, class InitializedF> +auto TCompactVector<T, N>::InsertManyImpl(const_iterator pos, size_t insertCount, UninitializedF&& uninitializedFunc, InitializedF&& initializedFunc) -> iterator +{ + YT_ASSERT(pos >= begin()); + YT_ASSERT(pos <= end()); + + auto* mutablePos = const_cast<iterator>(pos); + if (insertCount == 0) { + return mutablePos; + } + + auto size = this->size(); + auto newSize = size + insertCount; + if (Y_UNLIKELY(newSize > capacity())) { + auto index = std::distance(begin(), mutablePos); + EnsureOnHeapCapacity(newSize, /*incremental*/ true); + mutablePos = begin() + index; + } + + auto* end = this->end(); + auto moveCount = std::distance(mutablePos, end); + if constexpr(std::is_trivially_copyable_v<T>) { + ::memmove(mutablePos + insertCount, mutablePos, moveCount * sizeof(T)); + initializedFunc(mutablePos, mutablePos + insertCount); + } else { + if (static_cast<ptrdiff_t>(insertCount) >= moveCount) { + UninitializedMove(mutablePos, end, mutablePos + insertCount); + initializedFunc(mutablePos, end); + uninitializedFunc(end, end + insertCount - moveCount); + } else { + auto overlapCount = moveCount - insertCount; + UninitializedMove(mutablePos + overlapCount, end, mutablePos + overlapCount + insertCount); + MoveBackward(mutablePos, mutablePos + overlapCount, mutablePos + insertCount); + initializedFunc(mutablePos, mutablePos + insertCount); + } + } + + SetSize(newSize); + + return mutablePos; +} + +template <class T, size_t N> +void TCompactVector<T, N>::Destroy(T* first, T* last) +{ + if constexpr(!std::is_trivially_destructible_v<T>) { + for (auto* current = first; current != last; ++current) { + current->T::~T(); + } + } +} + +template <class T, size_t N> +template <class T1, class T2> +void TCompactVector<T, N>::Copy(const T1* srcFirst, const T1* srcLast, T2* dst) +{ + if constexpr(std::is_trivially_copyable_v<T1> && std::is_same_v<T1, T2>) { + ::memcpy(dst, srcFirst, (srcLast - srcFirst) * sizeof(T)); + } else { + std::copy(srcFirst, srcLast, dst); + } +} + +template <class T, size_t N> +template <class T1, class T2> +void TCompactVector<T, N>::UninitializedCopy(const T1* srcFirst, const T1* srcLast, T2* dst) +{ + if constexpr(std::is_trivially_copyable_v<T1> && std::is_same_v<T1, T2>) { + ::memcpy(dst, srcFirst, (srcLast - srcFirst) * sizeof(T)); + } else { + std::uninitialized_copy(srcFirst, srcLast, dst); + } +} + +template <class T, size_t N> +void TCompactVector<T, N>::Move(T* srcFirst, T* srcLast, T* dst) +{ + if constexpr(std::is_trivially_copyable_v<T>) { + ::memmove(dst, srcFirst, (srcLast - srcFirst) * sizeof(T)); + } else { + std::move(srcFirst, srcLast, dst); + } +} + +template <class T, size_t N> +void TCompactVector<T, N>::UninitializedMove(T* srcFirst, T* srcLast, T* dst) +{ + if constexpr(std::is_trivially_copyable_v<T>) { + ::memcpy(dst, srcFirst, (srcLast - srcFirst) * sizeof(T)); + } else { + std::uninitialized_move(srcFirst, srcLast, dst); + } +} + +template <class T, size_t N> +void TCompactVector<T, N>::MoveBackward(T* srcFirst, T* srcLast, T* dst) +{ + auto* src = srcLast; + dst += std::distance(srcFirst, srcLast); + while (src > srcFirst) { + *--dst = std::move(*--src); + } +} + +///////////////////////////////////////////////////////////////////////////// + +template <class T, size_t LhsN, size_t RhsN> +bool operator==(const TCompactVector<T, LhsN>& lhs, const TCompactVector<T, RhsN>& rhs) +{ + if constexpr(LhsN == RhsN) { + if (&lhs == &rhs) { + return true; + } + } + + if (lhs.size() != rhs.size()) { + return false; + } + + return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); +} + +template <class T, size_t LhsN, size_t RhsN> +bool operator!=(const TCompactVector<T, LhsN>& lhs, const TCompactVector<T, RhsN>& rhs) +{ + return !(lhs == rhs); +} + +template <class T, size_t LhsN, size_t RhsN> +bool operator<(const TCompactVector<T, LhsN>& lhs, const TCompactVector<T, RhsN>& rhs) +{ + return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); +} + +///////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +namespace std { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T, size_t N> +void swap(NYT::TCompactVector<T, N>& lhs, NYT::TCompactVector<T, N>& rhs) +{ + lhs.swap(rhs); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace std diff --git a/library/cpp/yt/small_containers/compact_vector.h b/library/cpp/yt/small_containers/compact_vector.h new file mode 100644 index 0000000000..6c4a0b0e39 --- /dev/null +++ b/library/cpp/yt/small_containers/compact_vector.h @@ -0,0 +1,219 @@ +#pragma once + +#include <util/system/defaults.h> + +#include <cstdint> +#include <iterator> +#include <limits> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +struct TCompactVectorOnHeapStorage; + +//! A vector-like structure optimized for storing elements inline +//! and with little memory overhead. +/*! + * Stores up to #N (<= 254) elements inline. + * + * When capacity starts exceeding #N, moves all elements to heap; + * \see #TCompactVectorOnHeapStorage. + * + * When linked with YTAlloc, employs its API to adjust the on-heap capacity in accordance + * to the actual size of the allocated region. + * + * Assuming the entropy and the alignment constraints, yields a seemingly optimal memory overhead. + * E.g. TCompactVector<uint8_t, 7> takes 8 bytes and TCompactVector<uint32_t, 3> takes 16 bytes. + * \see #ByteSize. + * + * Assumes (and asserts) the following: + * 1) the platform is 64 bit; + * 2) the highest 8 bits of pointers returned by |malloc| are zeroes; + * 3) the platform is little-endian. + */ +template <class T, size_t N> +class TCompactVector +{ +public: + static_assert(N < std::numeric_limits<uint8_t>::max()); + + using size_type = size_t; + using difference_type = ptrdiff_t; + + using value_type = T; + + using iterator = T*; + using const_iterator = const T*; + + using const_reverse_iterator = std::reverse_iterator<const_iterator>; + using reverse_iterator = std::reverse_iterator<iterator>; + + using reference = T&; + using const_reference = const T&; + + using pointer = T*; + using const_pointer = const T*; + + TCompactVector() noexcept; + TCompactVector(const TCompactVector& other); + template <size_t OtherN> + TCompactVector(const TCompactVector<T, OtherN>& other); + TCompactVector(TCompactVector&& other) noexcept(std::is_nothrow_move_constructible_v<T>); + template <size_t OtherN> + TCompactVector(TCompactVector<T, OtherN>&& other); + explicit TCompactVector(size_type count); + TCompactVector(size_type count, const T& value); + template <class TIterator> + TCompactVector(TIterator first, TIterator last); + TCompactVector(std::initializer_list<T> list); + + ~TCompactVector(); + + [[nodiscard]] bool empty() const; + + iterator begin(); + const_iterator begin() const; + iterator end(); + const_iterator end() const; + + reverse_iterator rbegin(); + const_reverse_iterator rbegin() const; + reverse_iterator rend(); + const_reverse_iterator rend() const; + + size_type size() const; + size_type capacity() const; + size_type max_size() const; + + pointer data(); + const_pointer data() const; + + reference operator[](size_type index); + const_reference operator[](size_type index) const; + + reference front(); + const_reference front() const; + reference back(); + const_reference back() const; + + void push_back(const T& value); + void push_back(T&& value); + + template <class... TArgs> + iterator emplace(const_iterator pos, TArgs&&... args); + template <class... TArgs> + reference emplace_back(TArgs&&... args); + + void pop_back(); + + iterator erase(const_iterator pos); + iterator erase(const_iterator first, const_iterator last); + + void clear(); + + void resize(size_type newSize); + void resize(size_type newSize, const T& value); + + void reserve(size_type newCapacity); + + void swap(TCompactVector& other); + + void assign(size_type count, const T& value); + template <class TIterator> + void assign(TIterator first, TIterator last); + void assign(std::initializer_list<T> list); + template <size_t OtherN> + void assign(const TCompactVector<T, OtherN>& other); + template <size_t OtherN> + void assign(TCompactVector<T, OtherN>&& other); + + TCompactVector& operator=(const TCompactVector& other); + template <size_t OtherN> + TCompactVector& operator=(const TCompactVector<T, OtherN>& other); + TCompactVector& operator=(TCompactVector&& other); + template <size_t OtherN> + TCompactVector& operator=(TCompactVector<T, OtherN>&& other); + TCompactVector& operator=(std::initializer_list<T> list); + + iterator insert(const_iterator pos, const T& value); + iterator insert(const_iterator pos, T&& value); + iterator insert(const_iterator pos, size_type count, const T& value); + template <class TIterator> + iterator insert(const_iterator pos, TIterator first, TIterator last); + iterator insert(const_iterator pos, std::initializer_list<T> list); + + void shrink_to_small(); + +private: + template <class OtherT, size_t OtherN> + friend class TCompactVector; + + using TOnHeapStorage = TCompactVectorOnHeapStorage<T>; + + static constexpr size_t ByteSize = + (sizeof(T) * N + alignof(T) + sizeof(uintptr_t) - 1) & + ~(sizeof(uintptr_t) - 1); + + struct TInlineMeta + { + char Padding[ByteSize - sizeof(uint8_t)]; + // > 0 indicates inline storage + // == 0 indicates on-heap storage + uint8_t SizePlusOne; + } alias_hack; + + struct TOnHeapMeta + { + char Padding[ByteSize - sizeof(uintptr_t)]; + TOnHeapStorage* Storage; + } alias_hack; + + union + { + T InlineElements_[N]; + TInlineMeta InlineMeta_; + TOnHeapMeta OnHeapMeta_; + }; + + bool IsInline() const; + void SetSize(size_t newSize); + void EnsureOnHeapCapacity(size_t newCapacity, bool incremental); + template <class TPtr, class F> + reference PushBackImpl(TPtr valuePtr, F&& func); + template <class F> + void ResizeImpl(size_t newSize, F&& func); + template <class TPtr, class UninitializedF, class InitializedF> + iterator InsertOneImpl(const_iterator pos, TPtr valuePtr, UninitializedF&& uninitializedFunc, InitializedF&& initializedFunc); + template <class UninitializedF, class InitializedF> + iterator InsertManyImpl(const_iterator pos, size_t insertCount, UninitializedF&& uninitializedFunc, InitializedF&& initializedFunc); + + static void Destroy(T* first, T* last); + template <class T1, class T2> + static void Copy(const T1* srcFirst, const T1* srcLast, T2* dst); + template <class T1, class T2> + static void UninitializedCopy(const T1* srcFirst, const T1* srcLast, T2* dst); + static void Move(T* srcFirst, T* srcLast, T* dst); + static void MoveBackward(T* srcFirst, T* srcLast, T* dst); + static void UninitializedMove(T* srcFirst, T* srcLast, T* dst); +}; + +//////////////////////////////////////////////////////////////////////////////// + +template <class T, size_t LhsN, size_t RhsN> +bool operator==(const TCompactVector<T, LhsN>& lhs, const TCompactVector<T, RhsN>& rhs); + +template <class T, size_t LhsN, size_t RhsN> +bool operator!=(const TCompactVector<T, LhsN>& lhs, const TCompactVector<T, RhsN>& rhs); + +template <class T, size_t LhsN, size_t RhsN> +bool operator<(const TCompactVector<T, LhsN>& lhs, const TCompactVector<T, RhsN>& rhs); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define COMPACT_VECTOR_INL_H_ +#include "compact_vector-inl.h" +#undef COMPACT_VECTOR_INL_H_ diff --git a/library/cpp/yt/small_containers/unittests/compact_flat_map_ut.cpp b/library/cpp/yt/small_containers/unittests/compact_flat_map_ut.cpp new file mode 100644 index 0000000000..0b2f290692 --- /dev/null +++ b/library/cpp/yt/small_containers/unittests/compact_flat_map_ut.cpp @@ -0,0 +1,225 @@ +#include <yt/yt/core/test_framework/framework.h> + +#include <yt/yt/core/misc/compact_flat_map.h> + +#include <string> +#include <vector> + +namespace NYT { +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +using TMap = TCompactFlatMap<std::string, std::string, 2>; + +TMap CreateMap() +{ + std::vector<std::pair<std::string, std::string>> data = {{"I", "met"}, {"a", "traveller"}, {"from", "an"}, {"antique", "land"}}; + return {data.begin(), data.end()}; +} + +TEST(CompactFlatMapTest, DefaultEmpty) +{ + TMap m; + EXPECT_TRUE(m.empty()); + EXPECT_EQ(m.begin(), m.end()); +} + +TEST(CompactFlatMapTest, Reserve) +{ + // No real way to test reserve - just use it and wiggle about. + auto m1 = CreateMap(); + TMap m2; + m2.reserve(m1.size()); + m2.insert(m1.begin(), m1.end()); + EXPECT_EQ(m1.size(), m2.size()); +} + +TEST(CompactFlatMapTest, Size) +{ + auto m = CreateMap(); + + EXPECT_EQ(m.size(), 4u); + EXPECT_EQ(m.ssize(), 4); + + m.insert({"Who", "said"}); + + EXPECT_EQ(m.size(), 5u); + EXPECT_EQ(m.ssize(), 5); + + m.erase("antique"); + + EXPECT_EQ(m.size(), 4u); + EXPECT_EQ(m.ssize(), 4); +} + +TEST(CompactFlatMapTest, ClearAndEmpty) +{ + auto m = CreateMap(); + + EXPECT_FALSE(m.empty()); + EXPECT_NE(m.begin(), m.end()); + + m.clear(); + + EXPECT_TRUE(m.empty()); + EXPECT_EQ(m.begin(), m.end()); + + m.insert({"Who", "said"}); + + EXPECT_FALSE(m.empty()); + EXPECT_NE(m.begin(), m.end()); +} + +TEST(CompactFlatMapTest, FindMutable) +{ + auto m = CreateMap(); + { + auto it = m.find("from"); + EXPECT_NE(it, m.end()); + EXPECT_EQ(it->second, "an"); + it->second = "the"; + } + { + auto it = m.find("from"); + EXPECT_NE(it, m.end()); + EXPECT_EQ(it->second, "the"); + } + { + auto it = m.find("Who"); + EXPECT_EQ(it, m.end()); + } +} + +TEST(CompactFlatMapTest, FindConst) +{ + const auto& m = CreateMap(); + { + auto it = m.find("from"); + EXPECT_NE(it, m.end()); + EXPECT_EQ(it->second, "an"); + } + { + auto it = m.find("Who"); + EXPECT_EQ(it, m.end()); + } +} + +TEST(CompactFlatMapTest, Insert) +{ + auto m = CreateMap(); + + auto [it, inserted] = m.insert({"Who", "said"}); + EXPECT_TRUE(inserted); + EXPECT_EQ(m.ssize(), 5); + EXPECT_NE(it, m.end()); + EXPECT_EQ(it, m.find("Who")); + EXPECT_EQ(it->second, "said"); + + auto [it2, inserted2] = m.insert({"Who", "told"}); + EXPECT_FALSE(inserted2); + EXPECT_EQ(m.ssize(), 5); + EXPECT_EQ(it2, it); + EXPECT_EQ(it->second, "said"); + + std::vector<std::pair<std::string, std::string>> data = {{"Two", "vast"}, {"and", "trunkless"}, {"legs", "of"}, {"stone", "Stand"}, {"in", "the"}, {"desert", "..."}}; + m.insert(data.begin(), data.end()); + EXPECT_EQ(m.ssize(), 11); + EXPECT_NE(m.find("and"), m.end()); + EXPECT_EQ(m.find("and")->second, "trunkless"); +} + +TEST(CompactFlatMapTest, Emplace) +{ + auto m = CreateMap(); + + auto [it, inserted] = m.emplace("Far", "place"); + EXPECT_TRUE(inserted); + EXPECT_EQ(m.ssize(), 5); + EXPECT_NE(it, m.end()); + EXPECT_EQ(it, m.find("Far")); + EXPECT_EQ(it->second, "place"); + + auto [it2, inserted2] = m.emplace("Far", "behind"); + EXPECT_FALSE(inserted2); + EXPECT_EQ(m.ssize(), 5); + EXPECT_EQ(it2, it); + EXPECT_EQ(it->second, "place"); +} + +TEST(CompactFlatMapTest, Subscript) +{ + auto m = CreateMap(); + + EXPECT_EQ(m["antique"], "land"); + EXPECT_EQ(m.ssize(), 4); + + EXPECT_EQ(m["Who"], ""); + EXPECT_EQ(m.ssize(), 5); +} + +TEST(CompactFlatMapTest, Erase) +{ + auto m = CreateMap(); + + m.erase("antique"); + EXPECT_EQ(m.ssize(), 3); + + m.erase("Who"); + EXPECT_EQ(m.ssize(), 3); + + m.erase(m.begin(), m.end()); + EXPECT_TRUE(m.empty()); +} + +TEST(CompactFlatMapTest, GrowShrink) +{ + TMap m; + m.insert({"Two", "vast"}); + m.insert({"and", "trunkless"}); + m.insert({"legs", "of"}); + m.insert({"stone", "Stand"}); + m.insert({"in", "the"}); + m.insert({"desert", "..."}); + + m.erase("legs"); + m.erase("stone"); + m.erase("in"); + m.erase("desert"); + + EXPECT_EQ(m.ssize(), 2); + + // Must not crash or trigger asan. +} + +TEST(CompactFlatMapTest, GrowShrinkGrow) +{ + TMap m; + m.insert({"Two", "vast"}); + m.insert({"and", "trunkless"}); + m.insert({"legs", "of"}); + m.insert({"stone", "Stand"}); + m.insert({"in", "the"}); + m.insert({"desert", "..."}); + + m.erase("legs"); + m.erase("stone"); + m.erase("in"); + m.erase("desert"); + + EXPECT_EQ(m.ssize(), 2); + + m.insert({"I", "met"}); + m.insert({"a", "traveller"}); + m.insert({"from", "an"}); + m.insert({"antique", "land"}); + + EXPECT_EQ(m.ssize(), 6); + + // Must not crash or trigger asan. +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace +} // namespace NYT diff --git a/library/cpp/yt/small_containers/unittests/compact_heap_ut.cpp b/library/cpp/yt/small_containers/unittests/compact_heap_ut.cpp new file mode 100644 index 0000000000..259c576e87 --- /dev/null +++ b/library/cpp/yt/small_containers/unittests/compact_heap_ut.cpp @@ -0,0 +1,108 @@ +#include <library/cpp/yt/small_containers/compact_heap.h> + +#include <library/cpp/testing/gtest/gtest.h> + +#include <random> + +namespace NYT { +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +TEST(CompactHeapTest, Simple) +{ + TCompactHeap<int, 2> heap; + heap.push(3); + heap.push(1); + heap.push(2); + + EXPECT_EQ(3u, heap.size()); + EXPECT_EQ(1, heap.extract_min()); + + EXPECT_EQ(2, heap.get_min()); + EXPECT_EQ(2, heap.extract_min()); + + EXPECT_EQ(3, heap.extract_min()); + + EXPECT_TRUE(heap.empty()); +} + +TEST(CompactHeapTest, SimpleComparator) +{ + TCompactHeap<int, 2, std::greater<int>> heap; + heap.push(3); + heap.push(1); + heap.push(2); + + EXPECT_EQ(3u, heap.size()); + EXPECT_EQ(3, heap.extract_min()); + EXPECT_EQ(2, heap.get_min()); + EXPECT_EQ(2, heap.extract_min()); + EXPECT_EQ(1, heap.extract_min()); + EXPECT_TRUE(heap.empty()); +} + +TEST(CompactHeapTest, Capacity) +{ + TCompactHeap<int, 2> heap; + EXPECT_EQ(2u, heap.capacity()); + EXPECT_EQ(0u, heap.size()); + + for (int i = 0; i < 100; ++i) { + heap.push(i); + } + EXPECT_LE(100u, heap.capacity()); + EXPECT_EQ(100u, heap.size()); + + for (int i = 0; i < 99; ++i) { + heap.pop(); + } + EXPECT_LE(100u, heap.capacity()); + EXPECT_EQ(1u, heap.size()); + + heap.shrink_to_small(); + + EXPECT_EQ(2u, heap.capacity()); + EXPECT_EQ(1u, heap.size()); +} + +TEST(CompactHeapTest, Stress) +{ + TCompactHeap<int, 3, std::greater<int>> heap; + std::vector<int> values; + + std::mt19937 rng(42); + for (int iteration = 0; iteration < 1000; ++iteration) { + EXPECT_EQ(values.size(), heap.size()); + EXPECT_EQ(values.empty(), heap.empty()); + + { + std::vector<int> content(heap.begin(), heap.end()); + std::sort(content.rbegin(), content.rend()); + EXPECT_EQ(values, content); + } + + if (!values.empty()) { + EXPECT_EQ(values[0], heap.get_min()); + } + + if (values.empty() || rng() % 2 == 0) { + int x = rng() % 100; + heap.push(x); + values.push_back(x); + std::sort(values.rbegin(), values.rend()); + } else { + if (rng() % 2 == 0) { + EXPECT_EQ(values[0], heap.extract_min()); + } else { + heap.pop(); + } + values.erase(values.begin()); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace +} // namespace NYT diff --git a/library/cpp/yt/small_containers/unittests/compact_set_ut.cpp b/library/cpp/yt/small_containers/unittests/compact_set_ut.cpp new file mode 100644 index 0000000000..ebab5846e1 --- /dev/null +++ b/library/cpp/yt/small_containers/unittests/compact_set_ut.cpp @@ -0,0 +1,204 @@ +//===- llvm/unittest/ADT/SmallSetTest.cpp ---------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// CompactSet unit tests. +// +//===----------------------------------------------------------------------===// + +#include <library/cpp/yt/small_containers/compact_set.h> + +#include <library/cpp/testing/gtest/gtest.h> + +#include <string> + +namespace NYT { +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +TEST(CompactSetTest, Insert) { + + TCompactSet<int, 4> s1; + + for (int i = 0; i < 4; i++) + s1.insert(i); + + for (int i = 0; i < 4; i++) + s1.insert(i); + + EXPECT_EQ(4u, s1.size()); + + for (int i = 0; i < 4; i++) + EXPECT_EQ(1u, s1.count(i)); + + EXPECT_EQ(0u, s1.count(4)); +} + +TEST(CompactSetTest, Grow) { + TCompactSet<int, 4> s1; + + for (int i = 0; i < 8; i++) + s1.insert(i); + + EXPECT_EQ(8u, s1.size()); + + for (int i = 0; i < 8; i++) + EXPECT_EQ(1u, s1.count(i)); + + EXPECT_EQ(0u, s1.count(8)); +} + +TEST(CompactSetTest, Erase) { + TCompactSet<int, 4> s1; + + for (int i = 0; i < 8; i++) + s1.insert(i); + + EXPECT_EQ(8u, s1.size()); + + // Remove elements one by one and check if all other elements are still there. + for (int i = 0; i < 8; i++) { + EXPECT_EQ(1u, s1.count(i)); + EXPECT_TRUE(s1.erase(i)); + EXPECT_EQ(0u, s1.count(i)); + EXPECT_EQ(8u - i - 1, s1.size()); + for (int j = i + 1; j < 8; j++) + EXPECT_EQ(1u, s1.count(j)); + } + + EXPECT_EQ(0u, s1.count(8)); +} + +TEST(CompactSetTest, IteratorInt) { + TCompactSet<int, 4> s1; + + // Test the 'small' case. + for (int i = 0; i < 3; i++) + s1.insert(i); + + std::vector<int> V(s1.begin(), s1.end()); + // Make sure the elements are in the expected order. + std::sort(V.begin(), V.end()); + for (int i = 0; i < 3; i++) + EXPECT_EQ(i, V[i]); + + // Test the 'big' case by adding a few more elements to switch to std::set + // internally. + for (int i = 3; i < 6; i++) + s1.insert(i); + + V.assign(s1.begin(), s1.end()); + // Make sure the elements are in the expected order. + std::sort(V.begin(), V.end()); + for (int i = 0; i < 6; i++) + EXPECT_EQ(i, V[i]); +} + +TEST(CompactSetTest, IteratorString) { + // Test CompactSetIterator for TCompactSet with a type with non-trivial + // ctors/dtors. + TCompactSet<std::string, 2> s1; + + s1.insert("str 1"); + s1.insert("str 2"); + s1.insert("str 1"); + + std::vector<std::string> V(s1.begin(), s1.end()); + std::sort(V.begin(), V.end()); + EXPECT_EQ(2u, s1.size()); + EXPECT_EQ("str 1", V[0]); + EXPECT_EQ("str 2", V[1]); + + s1.insert("str 4"); + s1.insert("str 0"); + s1.insert("str 4"); + + V.assign(s1.begin(), s1.end()); + // Make sure the elements are in the expected order. + std::sort(V.begin(), V.end()); + EXPECT_EQ(4u, s1.size()); + EXPECT_EQ("str 0", V[0]); + EXPECT_EQ("str 1", V[1]); + EXPECT_EQ("str 2", V[2]); + EXPECT_EQ("str 4", V[3]); +} + +TEST(CompactSetTest, IteratorIncMoveCopy) { + // Test CompactSetIterator for TCompactSet with a type with non-trivial + // ctors/dtors. + TCompactSet<std::string, 2> s1; + + s1.insert("str 1"); + s1.insert("str 2"); + + auto Iter = s1.begin(); + EXPECT_EQ("str 1", *Iter); + ++Iter; + EXPECT_EQ("str 2", *Iter); + + s1.insert("str 4"); + s1.insert("str 0"); + auto Iter2 = s1.begin(); + Iter = std::move(Iter2); + EXPECT_EQ("str 0", *Iter); +} + +// These test weren't taken from llvm. + +TEST(CompactSetTest, Empty) { + TCompactSet<int, 10> v; + EXPECT_TRUE(v.empty()); + + auto data = {1, 2, 3, 4, 5}; + + v.insert(data.begin(), data.end()); // not crossing size threshold + v.erase(4); + v.erase(2); + v.erase(3); + v.erase(5); + v.erase(1); + EXPECT_TRUE(v.empty()); + + auto data2 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + v.insert(data2.begin(), data2.end()); // crossing size threshold + v.erase(7); + v.erase(3); + v.erase(1); + v.erase(10); + v.erase(9); + v.erase(0); + v.erase(2); + v.erase(6); + v.erase(4); + v.erase(5); + v.erase(8); + EXPECT_TRUE(v.empty()); +} + +TEST(CompactSetTest, ForEach) { + TCompactSet<int, 10> v; + + auto data = {10, 9, 3, 4, 1, 5, 6, 8}; + + v.insert(data.begin(), data.end()); + + std::vector<int> buf(v.begin(), v.end()); + std::vector<int> expected{1, 3, 4, 5, 6, 8, 9, 10}; + EXPECT_EQ(expected, buf); + + auto data2 = {7, 1, 2, 0}; + v.insert(data2.begin(), data2.end()); + buf.assign(v.begin(), v.end()); + expected = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + EXPECT_EQ(expected, buf); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace +} // namespace NYT diff --git a/library/cpp/yt/small_containers/unittests/compact_vector_ut.cpp b/library/cpp/yt/small_containers/unittests/compact_vector_ut.cpp new file mode 100644 index 0000000000..770d62b9fd --- /dev/null +++ b/library/cpp/yt/small_containers/unittests/compact_vector_ut.cpp @@ -0,0 +1,1084 @@ +// Adapted from the following: +//===- llvm/unittest/ADT/CompactVectorTest.cpp ------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// TCompactVector unit tests. +// +//===----------------------------------------------------------------------===// + +#include <yt/yt/core/test_framework/framework.h> + +#include <yt/yt/core/misc/compact_vector.h> + +#include <algorithm> +#include <list> + +#include <stdarg.h> + +namespace NYT { +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +/// A helper class that counts the total number of constructor and +/// destructor calls. +class Constructable { +private: + static int numConstructorCalls; + static int numMoveConstructorCalls; + static int numCopyConstructorCalls; + static int numDestructorCalls; + static int numAssignmentCalls; + static int numMoveAssignmentCalls; + static int numCopyAssignmentCalls; + + bool constructed; + int value; + +public: + Constructable() : constructed(true), value(0) { + ++numConstructorCalls; + } + + Constructable(int val) : constructed(true), value(val) { + ++numConstructorCalls; + } + + Constructable(const Constructable & src) : constructed(true) { + value = src.value; + ++numConstructorCalls; + ++numCopyConstructorCalls; + } + + Constructable(Constructable && src) : constructed(true) { + value = src.value; + ++numConstructorCalls; + ++numMoveConstructorCalls; + } + + ~Constructable() { + EXPECT_TRUE(constructed); + ++numDestructorCalls; + constructed = false; + } + + Constructable & operator=(const Constructable & src) { + EXPECT_TRUE(constructed); + value = src.value; + ++numAssignmentCalls; + ++numCopyAssignmentCalls; + return *this; + } + + Constructable & operator=(Constructable && src) { + EXPECT_TRUE(constructed); + value = src.value; + ++numAssignmentCalls; + ++numMoveAssignmentCalls; + return *this; + } + + int getValue() const { + return abs(value); + } + + static void reset() { + numConstructorCalls = 0; + numMoveConstructorCalls = 0; + numCopyConstructorCalls = 0; + numDestructorCalls = 0; + numAssignmentCalls = 0; + numMoveAssignmentCalls = 0; + numCopyAssignmentCalls = 0; + } + + static int getNumConstructorCalls() { + return numConstructorCalls; + } + + static int getNumMoveConstructorCalls() { + return numMoveConstructorCalls; + } + + static int getNumCopyConstructorCalls() { + return numCopyConstructorCalls; + } + + static int getNumDestructorCalls() { + return numDestructorCalls; + } + + static int getNumAssignmentCalls() { + return numAssignmentCalls; + } + + static int getNumMoveAssignmentCalls() { + return numMoveAssignmentCalls; + } + + static int getNumCopyAssignmentCalls() { + return numCopyAssignmentCalls; + } + + friend bool operator==(const Constructable & c0, const Constructable & c1) { + return c0.getValue() == c1.getValue(); + } +}; + +int Constructable::numConstructorCalls; +int Constructable::numCopyConstructorCalls; +int Constructable::numMoveConstructorCalls; +int Constructable::numDestructorCalls; +int Constructable::numAssignmentCalls; +int Constructable::numCopyAssignmentCalls; +int Constructable::numMoveAssignmentCalls; + +struct NonCopyable { + NonCopyable() {} + NonCopyable(NonCopyable &&) {} + NonCopyable &operator=(NonCopyable &&) { return *this; } +private: + NonCopyable(const NonCopyable &) = delete; + NonCopyable &operator=(const NonCopyable &) = delete; +}; + +[[maybe_unused]] void CompileTest() { + TCompactVector<NonCopyable, 0> V; + V.resize(42); +} + +class CompactVectorTestBase : public testing::Test { +protected: + + void SetUp() override { + Constructable::reset(); + } + + template <typename VectorT> + void assertEmpty(VectorT & v) { + // Size tests + EXPECT_EQ(0u, v.size()); + EXPECT_TRUE(v.empty()); + + // Iterator tests + EXPECT_TRUE(v.begin() == v.end()); + } + + // Assert that v contains the specified values, in order. + template <typename VectorT> + void assertValuesInOrder(VectorT & v, size_t size, ...) { + EXPECT_EQ(size, v.size()); + + va_list ap; + va_start(ap, size); + for (size_t i = 0; i < size; ++i) { + int value = va_arg(ap, int); + EXPECT_EQ(value, v[i].getValue()); + } + + va_end(ap); + } + + // Generate a sequence of values to initialize the vector. + template <typename VectorT> + void makeSequence(VectorT & v, int start, int end) { + for (int i = start; i <= end; ++i) { + v.push_back(Constructable(i)); + } + } +}; + +// Test fixture class +template <typename VectorT> +class CompactVectorTest : public CompactVectorTestBase { +protected: + VectorT theVector; + VectorT otherVector; +}; + + +typedef ::testing::Types<TCompactVector<Constructable, 0>, + TCompactVector<Constructable, 1>, + TCompactVector<Constructable, 2>, + TCompactVector<Constructable, 4>, + TCompactVector<Constructable, 5> + > CompactVectorTestTypes; +TYPED_TEST_SUITE(CompactVectorTest, CompactVectorTestTypes); + +// New vector test. +TYPED_TEST(CompactVectorTest, EmptyVectorTest) { + SCOPED_TRACE("EmptyVectorTest"); + this->assertEmpty(this->theVector); + EXPECT_TRUE(this->theVector.rbegin() == this->theVector.rend()); + EXPECT_EQ(0, Constructable::getNumConstructorCalls()); + EXPECT_EQ(0, Constructable::getNumDestructorCalls()); +} + +// Simple insertions and deletions. +TYPED_TEST(CompactVectorTest, PushPopTest) { + SCOPED_TRACE("PushPopTest"); + + // Track whether the vector will potentially have to grow. + bool RequiresGrowth = this->theVector.capacity() < 3; + + // Push an element + this->theVector.push_back(Constructable(1)); + + // Size tests + this->assertValuesInOrder(this->theVector, 1u, 1); + EXPECT_FALSE(this->theVector.begin() == this->theVector.end()); + EXPECT_FALSE(this->theVector.empty()); + + // Push another element + this->theVector.push_back(Constructable(2)); + this->assertValuesInOrder(this->theVector, 2u, 1, 2); + + // Insert at beginning + this->theVector.insert(this->theVector.begin(), this->theVector[1]); + this->assertValuesInOrder(this->theVector, 3u, 2, 1, 2); + + // Pop one element + this->theVector.pop_back(); + this->assertValuesInOrder(this->theVector, 2u, 2, 1); + + // Pop remaining elements + this->theVector.pop_back(); + this->theVector.pop_back(); + this->assertEmpty(this->theVector); + + // Check number of constructor calls. Should be 2 for each list element, + // one for the argument to push_back, one for the argument to insert, + // and one for the list element itself. + if (!RequiresGrowth) { + EXPECT_EQ(5, Constructable::getNumConstructorCalls()); + EXPECT_EQ(5, Constructable::getNumDestructorCalls()); + } else { + // If we had to grow the vector, these only have a lower bound, but should + // always be equal. + EXPECT_LE(5, Constructable::getNumConstructorCalls()); + EXPECT_EQ(Constructable::getNumConstructorCalls(), + Constructable::getNumDestructorCalls()); + } +} + +TYPED_TEST(CompactVectorTest, InsertEnd) +{ + SCOPED_TRACE("InsertEnd"); + + TCompactVector<TString, 5> vector; + for (int index = 0; index < 10; ++index) { + vector.insert(vector.end(), ToString(index)); + } + for (int index = 0; index < 10; ++index) { + EXPECT_EQ(vector[index], ToString(index)); + } +} + +TYPED_TEST(CompactVectorTest, ShrinkToSmall) +{ + SCOPED_TRACE("ShrinkToSmall"); + + TCompactVector<TString, 5> vector; + for (int index = 0; index < 10; ++index) { + vector.shrink_to_small(); + vector.push_back(ToString(index)); + } + + for (int index = 0; index < 6; ++index) { + vector.pop_back(); + } + + EXPECT_EQ(std::ssize(vector), 4); + EXPECT_GE(static_cast<int>(vector.capacity()), 10); + vector.shrink_to_small(); + EXPECT_EQ(std::ssize(vector), 4); + EXPECT_EQ(static_cast<int>(vector.capacity()), 5); + for (int index = 0; index < 4; ++index) { + EXPECT_EQ(vector[index], ToString(index)); + } +} + +// Clear test. +TYPED_TEST(CompactVectorTest, ClearTest) { + SCOPED_TRACE("ClearTest"); + + this->theVector.reserve(2); + this->makeSequence(this->theVector, 1, 2); + this->theVector.clear(); + + this->assertEmpty(this->theVector); + EXPECT_EQ(4, Constructable::getNumConstructorCalls()); + EXPECT_EQ(4, Constructable::getNumDestructorCalls()); +} + +// Resize smaller test. +TYPED_TEST(CompactVectorTest, ResizeShrinkTest) { + SCOPED_TRACE("ResizeShrinkTest"); + + this->theVector.reserve(3); + this->makeSequence(this->theVector, 1, 3); + this->theVector.resize(1); + + this->assertValuesInOrder(this->theVector, 1u, 1); + EXPECT_EQ(6, Constructable::getNumConstructorCalls()); + EXPECT_EQ(5, Constructable::getNumDestructorCalls()); +} + +// Resize bigger test. +TYPED_TEST(CompactVectorTest, ResizeGrowTest) { + SCOPED_TRACE("ResizeGrowTest"); + + this->theVector.resize(2); + + EXPECT_EQ(2, Constructable::getNumConstructorCalls()); + EXPECT_EQ(0, Constructable::getNumDestructorCalls()); + EXPECT_EQ(2u, this->theVector.size()); +} + +TYPED_TEST(CompactVectorTest, ResizeWithElementsTest) { + this->theVector.resize(2); + + Constructable::reset(); + + this->theVector.resize(4); + + size_t Ctors = Constructable::getNumConstructorCalls(); + EXPECT_TRUE(Ctors == 2 || Ctors == 4); + size_t MoveCtors = Constructable::getNumMoveConstructorCalls(); + EXPECT_TRUE(MoveCtors == 0 || MoveCtors == 2); + size_t Dtors = Constructable::getNumDestructorCalls(); + EXPECT_TRUE(Dtors == 0 || Dtors == 2); +} + +// Resize with fill value. +TYPED_TEST(CompactVectorTest, ResizeFillTest) { + SCOPED_TRACE("ResizeFillTest"); + + this->theVector.resize(3, Constructable(77)); + this->assertValuesInOrder(this->theVector, 3u, 77, 77, 77); +} + +// Overflow past fixed size. +TYPED_TEST(CompactVectorTest, OverflowTest) { + SCOPED_TRACE("OverflowTest"); + + // Push more elements than the fixed size. + this->makeSequence(this->theVector, 1, 10); + + // Test size and values. + EXPECT_EQ(10u, this->theVector.size()); + for (int i = 0; i < 10; ++i) { + EXPECT_EQ(i+1, this->theVector[i].getValue()); + } + + // Now resize back to fixed size. + this->theVector.resize(1); + + this->assertValuesInOrder(this->theVector, 1u, 1); +} + +// Iteration tests. +TYPED_TEST(CompactVectorTest, IterationTest) { + this->makeSequence(this->theVector, 1, 2); + + // Forward Iteration + typename TypeParam::iterator it = this->theVector.begin(); + EXPECT_TRUE(*it == this->theVector.front()); + EXPECT_TRUE(*it == this->theVector[0]); + EXPECT_EQ(1, it->getValue()); + ++it; + EXPECT_TRUE(*it == this->theVector[1]); + EXPECT_TRUE(*it == this->theVector.back()); + EXPECT_EQ(2, it->getValue()); + ++it; + EXPECT_TRUE(it == this->theVector.end()); + --it; + EXPECT_TRUE(*it == this->theVector[1]); + EXPECT_EQ(2, it->getValue()); + --it; + EXPECT_TRUE(*it == this->theVector[0]); + EXPECT_EQ(1, it->getValue()); + + // Reverse Iteration + typename TypeParam::reverse_iterator rit = this->theVector.rbegin(); + EXPECT_TRUE(*rit == this->theVector[1]); + EXPECT_EQ(2, rit->getValue()); + ++rit; + EXPECT_TRUE(*rit == this->theVector[0]); + EXPECT_EQ(1, rit->getValue()); + ++rit; + EXPECT_TRUE(rit == this->theVector.rend()); + --rit; + EXPECT_TRUE(*rit == this->theVector[0]); + EXPECT_EQ(1, rit->getValue()); + --rit; + EXPECT_TRUE(*rit == this->theVector[1]); + EXPECT_EQ(2, rit->getValue()); +} + +// Swap test. +TYPED_TEST(CompactVectorTest, SwapTest) { + SCOPED_TRACE("SwapTest"); + + this->makeSequence(this->theVector, 1, 2); + std::swap(this->theVector, this->otherVector); + + this->assertEmpty(this->theVector); + this->assertValuesInOrder(this->otherVector, 2u, 1, 2); +} + +// Append test +TYPED_TEST(CompactVectorTest, AppendTest) { + SCOPED_TRACE("AppendTest"); + + this->makeSequence(this->otherVector, 2, 3); + + this->theVector.push_back(Constructable(1)); + this->theVector.insert(this->theVector.end(), this->otherVector.begin(), this->otherVector.end()); + + this->assertValuesInOrder(this->theVector, 3u, 1, 2, 3); +} + +// Append repeated test +TYPED_TEST(CompactVectorTest, AppendRepeatedTest) { + SCOPED_TRACE("AppendRepeatedTest"); + + this->theVector.push_back(Constructable(1)); + this->theVector.insert(this->theVector.end(), 2, Constructable(77)); + this->assertValuesInOrder(this->theVector, 3u, 1, 77, 77); +} + +// Assign test +TYPED_TEST(CompactVectorTest, AssignTest) { + SCOPED_TRACE("AssignTest"); + + this->theVector.push_back(Constructable(1)); + this->theVector.assign(2, Constructable(77)); + this->assertValuesInOrder(this->theVector, 2u, 77, 77); +} + +// Move-assign test +TYPED_TEST(CompactVectorTest, MoveAssignTest) { + SCOPED_TRACE("MoveAssignTest"); + + // Set up our vector with a single element, but enough capacity for 4. + this->theVector.reserve(4); + this->theVector.push_back(Constructable(1)); + + // Set up the other vector with 2 elements. + this->otherVector.push_back(Constructable(2)); + this->otherVector.push_back(Constructable(3)); + + // Move-assign from the other vector. + this->theVector = std::move(this->otherVector); + + // Make sure we have the right result. + this->assertValuesInOrder(this->theVector, 2u, 2, 3); + + // Make sure the # of constructor/destructor calls line up. There + // are two live objects after clearing the other vector. + this->otherVector.clear(); + EXPECT_EQ(Constructable::getNumConstructorCalls()-2, + Constructable::getNumDestructorCalls()); + + // There shouldn't be any live objects any more. + this->theVector.clear(); + EXPECT_EQ(Constructable::getNumConstructorCalls(), + Constructable::getNumDestructorCalls()); +} + +// Erase a single element +TYPED_TEST(CompactVectorTest, EraseTest) { + SCOPED_TRACE("EraseTest"); + + this->makeSequence(this->theVector, 1, 3); + this->theVector.erase(this->theVector.begin()); + this->assertValuesInOrder(this->theVector, 2u, 2, 3); +} + +// Erase a range of elements +TYPED_TEST(CompactVectorTest, EraseRangeTest) { + SCOPED_TRACE("EraseRangeTest"); + + this->makeSequence(this->theVector, 1, 3); + this->theVector.erase(this->theVector.begin(), this->theVector.begin() + 2); + this->assertValuesInOrder(this->theVector, 1u, 3); +} + +// Insert a single element. +TYPED_TEST(CompactVectorTest, InsertTest) { + SCOPED_TRACE("InsertTest"); + + this->makeSequence(this->theVector, 1, 3); + typename TypeParam::iterator I = + this->theVector.insert(this->theVector.begin() + 1, Constructable(77)); + EXPECT_EQ(this->theVector.begin() + 1, I); + this->assertValuesInOrder(this->theVector, 4u, 1, 77, 2, 3); +} + +// Insert a copy of a single element. +TYPED_TEST(CompactVectorTest, InsertCopy) { + SCOPED_TRACE("InsertTest"); + + this->makeSequence(this->theVector, 1, 3); + Constructable C(77); + typename TypeParam::iterator I = + this->theVector.insert(this->theVector.begin() + 1, C); + EXPECT_EQ(this->theVector.begin() + 1, I); + this->assertValuesInOrder(this->theVector, 4u, 1, 77, 2, 3); +} + +// Insert repeated elements. +TYPED_TEST(CompactVectorTest, InsertRepeatedTest) { + SCOPED_TRACE("InsertRepeatedTest"); + + this->makeSequence(this->theVector, 1, 4); + Constructable::reset(); + auto I = + this->theVector.insert(this->theVector.begin() + 1, 2, Constructable(16)); + // Move construct the top element into newly allocated space, and optionally + // reallocate the whole buffer, move constructing into it. + // FIXME: This is inefficient, we shouldn't move things into newly allocated + // space, then move them up/around, there should only be 2 or 4 move + // constructions here. + EXPECT_TRUE(Constructable::getNumMoveConstructorCalls() == 2 || + Constructable::getNumMoveConstructorCalls() == 6); + // Move assign the next two to shift them up and make a gap. + EXPECT_EQ(1, Constructable::getNumMoveAssignmentCalls()); + // Copy construct the two new elements from the parameter. + EXPECT_EQ(2, Constructable::getNumCopyAssignmentCalls()); + // All without any copy construction. + EXPECT_EQ(0, Constructable::getNumCopyConstructorCalls()); + EXPECT_EQ(this->theVector.begin() + 1, I); + this->assertValuesInOrder(this->theVector, 6u, 1, 16, 16, 2, 3, 4); +} + + +TYPED_TEST(CompactVectorTest, InsertRepeatedAtEndTest) { + SCOPED_TRACE("InsertRepeatedTest"); + + this->makeSequence(this->theVector, 1, 4); + Constructable::reset(); + auto I = this->theVector.insert(this->theVector.end(), 2, Constructable(16)); + // Just copy construct them into newly allocated space + EXPECT_EQ(2, Constructable::getNumCopyConstructorCalls()); + // Move everything across if reallocation is needed. + EXPECT_TRUE(Constructable::getNumMoveConstructorCalls() == 0 || + Constructable::getNumMoveConstructorCalls() == 4); + // Without ever moving or copying anything else. + EXPECT_EQ(0, Constructable::getNumCopyAssignmentCalls()); + EXPECT_EQ(0, Constructable::getNumMoveAssignmentCalls()); + + EXPECT_EQ(this->theVector.begin() + 4, I); + this->assertValuesInOrder(this->theVector, 6u, 1, 2, 3, 4, 16, 16); +} + +TYPED_TEST(CompactVectorTest, InsertRepeatedEmptyTest) { + SCOPED_TRACE("InsertRepeatedTest"); + + this->makeSequence(this->theVector, 10, 15); + + // Empty insert. + EXPECT_EQ(this->theVector.end(), + this->theVector.insert(this->theVector.end(), + 0, Constructable(42))); + EXPECT_EQ(this->theVector.begin() + 1, + this->theVector.insert(this->theVector.begin() + 1, + 0, Constructable(42))); +} + +// Insert range. +TYPED_TEST(CompactVectorTest, InsertRangeTest) { + SCOPED_TRACE("InsertRangeTest"); + + Constructable Arr[3] = + { Constructable(77), Constructable(77), Constructable(77) }; + + this->makeSequence(this->theVector, 1, 3); + Constructable::reset(); + auto I = this->theVector.insert(this->theVector.begin() + 1, Arr, Arr + 3); + // Move construct the top 3 elements into newly allocated space. + // Possibly move the whole sequence into new space first. + // FIXME: This is inefficient, we shouldn't move things into newly allocated + // space, then move them up/around, there should only be 2 or 3 move + // constructions here. + EXPECT_TRUE(Constructable::getNumMoveConstructorCalls() == 2 || + Constructable::getNumMoveConstructorCalls() == 5); + // Copy assign the lower 2 new elements into existing space. + EXPECT_EQ(2, Constructable::getNumCopyAssignmentCalls()); + // Copy construct the third element into newly allocated space. + EXPECT_EQ(1, Constructable::getNumCopyConstructorCalls()); + EXPECT_EQ(this->theVector.begin() + 1, I); + this->assertValuesInOrder(this->theVector, 6u, 1, 77, 77, 77, 2, 3); +} + + +TYPED_TEST(CompactVectorTest, InsertRangeAtEndTest) { + SCOPED_TRACE("InsertRangeTest"); + + Constructable Arr[3] = + { Constructable(77), Constructable(77), Constructable(77) }; + + this->makeSequence(this->theVector, 1, 3); + + // Insert at end. + Constructable::reset(); + auto I = this->theVector.insert(this->theVector.end(), Arr, Arr+3); + // Copy construct the 3 elements into new space at the top. + EXPECT_EQ(3, Constructable::getNumCopyConstructorCalls()); + // Don't copy/move anything else. + EXPECT_EQ(0, Constructable::getNumCopyAssignmentCalls()); + // Reallocation might occur, causing all elements to be moved into the new + // buffer. + EXPECT_TRUE(Constructable::getNumMoveConstructorCalls() == 0 || + Constructable::getNumMoveConstructorCalls() == 3); + EXPECT_EQ(0, Constructable::getNumMoveAssignmentCalls()); + EXPECT_EQ(this->theVector.begin() + 3, I); + this->assertValuesInOrder(this->theVector, 6u, + 1, 2, 3, 77, 77, 77); +} + +TYPED_TEST(CompactVectorTest, InsertEmptyRangeTest) { + SCOPED_TRACE("InsertRangeTest"); + + this->makeSequence(this->theVector, 1, 3); + + // Empty insert. + EXPECT_EQ(this->theVector.end(), + this->theVector.insert(this->theVector.end(), + this->theVector.begin(), + this->theVector.begin())); + EXPECT_EQ(this->theVector.begin() + 1, + this->theVector.insert(this->theVector.begin() + 1, + this->theVector.begin(), + this->theVector.begin())); +} + +// Comparison tests. +TYPED_TEST(CompactVectorTest, ComparisonTest) { + SCOPED_TRACE("ComparisonTest"); + + this->makeSequence(this->theVector, 1, 3); + this->makeSequence(this->otherVector, 1, 3); + + EXPECT_TRUE(this->theVector == this->otherVector); + EXPECT_FALSE(this->theVector != this->otherVector); + + this->otherVector.clear(); + this->makeSequence(this->otherVector, 2, 4); + + EXPECT_FALSE(this->theVector == this->otherVector); + EXPECT_TRUE(this->theVector != this->otherVector); +} + +// Constant vector tests. +TYPED_TEST(CompactVectorTest, ConstVectorTest) { + const TypeParam constVector; + + EXPECT_EQ(0u, constVector.size()); + EXPECT_TRUE(constVector.empty()); + EXPECT_TRUE(constVector.begin() == constVector.end()); +} + +// Direct array access. +TYPED_TEST(CompactVectorTest, DirectVectorTest) { + EXPECT_EQ(0u, this->theVector.size()); + this->theVector.reserve(4); + EXPECT_LE(4u, this->theVector.capacity()); + EXPECT_EQ(0, Constructable::getNumConstructorCalls()); + this->theVector.push_back(1); + this->theVector.push_back(2); + this->theVector.push_back(3); + this->theVector.push_back(4); + EXPECT_EQ(4u, this->theVector.size()); + EXPECT_EQ(8, Constructable::getNumConstructorCalls()); + EXPECT_EQ(1, this->theVector[0].getValue()); + EXPECT_EQ(2, this->theVector[1].getValue()); + EXPECT_EQ(3, this->theVector[2].getValue()); + EXPECT_EQ(4, this->theVector[3].getValue()); +} + +TYPED_TEST(CompactVectorTest, IteratorTest) { + std::list<int> L; + this->theVector.insert(this->theVector.end(), L.begin(), L.end()); +} + +template <typename InvalidType> class DualCompactVectorsTest; + +template <typename VectorT1, typename VectorT2> +class DualCompactVectorsTest<std::pair<VectorT1, VectorT2>> : public CompactVectorTestBase { +protected: + VectorT1 theVector; + VectorT2 otherVector; + + template <typename T, size_t N> + static size_t NumBuiltinElts(const TCompactVector<T, N>&) { return N; } +}; + +typedef ::testing::Types< + // Small mode -> Small mode. + std::pair<TCompactVector<Constructable, 4>, TCompactVector<Constructable, 4>>, + // Small mode -> Big mode. + std::pair<TCompactVector<Constructable, 4>, TCompactVector<Constructable, 2>>, + // Big mode -> Small mode. + std::pair<TCompactVector<Constructable, 2>, TCompactVector<Constructable, 4>>, + // Big mode -> Big mode. + std::pair<TCompactVector<Constructable, 2>, TCompactVector<Constructable, 2>> + > DualCompactVectorTestTypes; + +TYPED_TEST_SUITE(DualCompactVectorsTest, DualCompactVectorTestTypes); + +TYPED_TEST(DualCompactVectorsTest, MoveAssignment) { + SCOPED_TRACE("MoveAssignTest-DualVectorTypes"); + + // Set up our vector with four elements. + for (unsigned I = 0; I < 4; ++I) + this->otherVector.push_back(Constructable(I)); + + const Constructable *OrigDataPtr = this->otherVector.data(); + + // Move-assign from the other vector. + this->theVector = + std::move(this->otherVector); + + // Make sure we have the right result. + this->assertValuesInOrder(this->theVector, 4u, 0, 1, 2, 3); + + // Make sure the # of constructor/destructor calls line up. There + // are two live objects after clearing the other vector. + this->otherVector.clear(); + EXPECT_EQ(Constructable::getNumConstructorCalls()-4, + Constructable::getNumDestructorCalls()); + + // If the source vector (otherVector) was in small-mode, assert that we just + // moved the data pointer over. + EXPECT_TRUE(this->NumBuiltinElts(this->otherVector) == 4 || + this->theVector.data() == OrigDataPtr); + + // There shouldn't be any live objects any more. + this->theVector.clear(); + EXPECT_EQ(Constructable::getNumConstructorCalls(), + Constructable::getNumDestructorCalls()); + + // We shouldn't have copied anything in this whole process. + EXPECT_EQ(Constructable::getNumCopyConstructorCalls(), 0); +} + +struct notassignable { + int &x; + notassignable(int &x) : x(x) {} +}; + +TEST(CompactVectorCustomTest, NoAssignTest) { + int x = 0; + TCompactVector<notassignable, 2> vec; + vec.push_back(notassignable(x)); + x = 42; + EXPECT_EQ(42, vec.back().x); +} + +struct MovedFrom { + bool hasValue; + MovedFrom() : hasValue(true) { + } + MovedFrom(MovedFrom&& m) : hasValue(m.hasValue) { + m.hasValue = false; + } + MovedFrom &operator=(MovedFrom&& m) { + hasValue = m.hasValue; + m.hasValue = false; + return *this; + } +}; + +TEST(CompactVectorTest, MidInsert) { + TCompactVector<MovedFrom, 3> v; + v.push_back(MovedFrom()); + v.insert(v.begin(), MovedFrom()); + for (MovedFrom &m : v) + EXPECT_TRUE(m.hasValue); +} + +enum EmplaceableArgState { + EAS_Defaulted, + EAS_Arg, + EAS_LValue, + EAS_RValue, + EAS_Failure +}; +template <int I> struct EmplaceableArg { + EmplaceableArgState State; + EmplaceableArg() : State(EAS_Defaulted) {} + EmplaceableArg(EmplaceableArg &&X) + : State(X.State == EAS_Arg ? EAS_RValue : EAS_Failure) {} + EmplaceableArg(EmplaceableArg &X) + : State(X.State == EAS_Arg ? EAS_LValue : EAS_Failure) {} + + explicit EmplaceableArg(bool) : State(EAS_Arg) {} + +private: + EmplaceableArg &operator=(EmplaceableArg &&) = delete; + EmplaceableArg &operator=(const EmplaceableArg &) = delete; +}; + +enum EmplaceableState { ES_Emplaced, ES_Moved }; +struct Emplaceable { + EmplaceableArg<0> A0; + EmplaceableArg<1> A1; + EmplaceableArg<2> A2; + EmplaceableArg<3> A3; + EmplaceableState State; + + Emplaceable() : State(ES_Emplaced) {} + + template <class A0Ty> + explicit Emplaceable(A0Ty &&A0) + : A0(std::forward<A0Ty>(A0)), State(ES_Emplaced) {} + + template <class A0Ty, class A1Ty> + Emplaceable(A0Ty &&A0, A1Ty &&A1) + : A0(std::forward<A0Ty>(A0)), A1(std::forward<A1Ty>(A1)), + State(ES_Emplaced) {} + + template <class A0Ty, class A1Ty, class A2Ty> + Emplaceable(A0Ty &&A0, A1Ty &&A1, A2Ty &&A2) + : A0(std::forward<A0Ty>(A0)), A1(std::forward<A1Ty>(A1)), + A2(std::forward<A2Ty>(A2)), State(ES_Emplaced) {} + + template <class A0Ty, class A1Ty, class A2Ty, class A3Ty> + Emplaceable(A0Ty &&A0, A1Ty &&A1, A2Ty &&A2, A3Ty &&A3) + : A0(std::forward<A0Ty>(A0)), A1(std::forward<A1Ty>(A1)), + A2(std::forward<A2Ty>(A2)), A3(std::forward<A3Ty>(A3)), + State(ES_Emplaced) {} + + Emplaceable(Emplaceable &&) : State(ES_Moved) {} + Emplaceable &operator=(Emplaceable &&) { + State = ES_Moved; + return *this; + } + +private: + Emplaceable(const Emplaceable &) = delete; + Emplaceable &operator=(const Emplaceable &) = delete; +}; + +TEST(CompactVectorTest, EmplaceBack) { + EmplaceableArg<0> A0(true); + EmplaceableArg<1> A1(true); + EmplaceableArg<2> A2(true); + EmplaceableArg<3> A3(true); + { + TCompactVector<Emplaceable, 3> V; + V.emplace_back(); + EXPECT_TRUE(V.size() == 1); + EXPECT_TRUE(V.back().State == ES_Emplaced); + EXPECT_TRUE(V.back().A0.State == EAS_Defaulted); + EXPECT_TRUE(V.back().A1.State == EAS_Defaulted); + EXPECT_TRUE(V.back().A2.State == EAS_Defaulted); + EXPECT_TRUE(V.back().A3.State == EAS_Defaulted); + } + { + TCompactVector<Emplaceable, 3> V; + V.emplace_back(std::move(A0)); + EXPECT_TRUE(V.size() == 1); + EXPECT_TRUE(V.back().State == ES_Emplaced); + EXPECT_TRUE(V.back().A0.State == EAS_RValue); + EXPECT_TRUE(V.back().A1.State == EAS_Defaulted); + EXPECT_TRUE(V.back().A2.State == EAS_Defaulted); + EXPECT_TRUE(V.back().A3.State == EAS_Defaulted); + } + { + TCompactVector<Emplaceable, 3> V; + V.emplace_back(A0); + EXPECT_TRUE(V.size() == 1); + EXPECT_TRUE(V.back().State == ES_Emplaced); + EXPECT_TRUE(V.back().A0.State == EAS_LValue); + EXPECT_TRUE(V.back().A1.State == EAS_Defaulted); + EXPECT_TRUE(V.back().A2.State == EAS_Defaulted); + EXPECT_TRUE(V.back().A3.State == EAS_Defaulted); + } + { + TCompactVector<Emplaceable, 3> V; + V.emplace_back(A0, A1); + EXPECT_TRUE(V.size() == 1); + EXPECT_TRUE(V.back().State == ES_Emplaced); + EXPECT_TRUE(V.back().A0.State == EAS_LValue); + EXPECT_TRUE(V.back().A1.State == EAS_LValue); + EXPECT_TRUE(V.back().A2.State == EAS_Defaulted); + EXPECT_TRUE(V.back().A3.State == EAS_Defaulted); + } + { + TCompactVector<Emplaceable, 3> V; + V.emplace_back(std::move(A0), std::move(A1)); + EXPECT_TRUE(V.size() == 1); + EXPECT_TRUE(V.back().State == ES_Emplaced); + EXPECT_TRUE(V.back().A0.State == EAS_RValue); + EXPECT_TRUE(V.back().A1.State == EAS_RValue); + EXPECT_TRUE(V.back().A2.State == EAS_Defaulted); + EXPECT_TRUE(V.back().A3.State == EAS_Defaulted); + } + { + TCompactVector<Emplaceable, 3> V; + V.emplace_back(std::move(A0), A1, std::move(A2), A3); + EXPECT_TRUE(V.size() == 1); + EXPECT_TRUE(V.back().State == ES_Emplaced); + EXPECT_TRUE(V.back().A0.State == EAS_RValue); + EXPECT_TRUE(V.back().A1.State == EAS_LValue); + EXPECT_TRUE(V.back().A2.State == EAS_RValue); + EXPECT_TRUE(V.back().A3.State == EAS_LValue); + } + { + TCompactVector<int, 1> V; + V.emplace_back(); + V.emplace_back(42); + EXPECT_EQ(2U, V.size()); + EXPECT_EQ(0, V[0]); + EXPECT_EQ(42, V[1]); + } +} + +TEST(CompactVectorTest, Emplace) { + EmplaceableArg<0> A0(true); + EmplaceableArg<1> A1(true); + EmplaceableArg<2> A2(true); + EmplaceableArg<3> A3(true); + { + TCompactVector<Emplaceable, 3> V; + V.emplace(V.end()); + EXPECT_TRUE(V.size() == 1); + EXPECT_TRUE(V.back().State == ES_Emplaced); + EXPECT_TRUE(V.back().A0.State == EAS_Defaulted); + EXPECT_TRUE(V.back().A1.State == EAS_Defaulted); + EXPECT_TRUE(V.back().A2.State == EAS_Defaulted); + EXPECT_TRUE(V.back().A3.State == EAS_Defaulted); + } + { + TCompactVector<Emplaceable, 3> V; + V.emplace(V.end(), std::move(A0)); + EXPECT_TRUE(V.size() == 1); + EXPECT_TRUE(V.back().State == ES_Emplaced); + EXPECT_TRUE(V.back().A0.State == EAS_RValue); + EXPECT_TRUE(V.back().A1.State == EAS_Defaulted); + EXPECT_TRUE(V.back().A2.State == EAS_Defaulted); + EXPECT_TRUE(V.back().A3.State == EAS_Defaulted); + } + { + TCompactVector<Emplaceable, 3> V; + V.emplace(V.end(), A0); + EXPECT_TRUE(V.size() == 1); + EXPECT_TRUE(V.back().State == ES_Emplaced); + EXPECT_TRUE(V.back().A0.State == EAS_LValue); + EXPECT_TRUE(V.back().A1.State == EAS_Defaulted); + EXPECT_TRUE(V.back().A2.State == EAS_Defaulted); + EXPECT_TRUE(V.back().A3.State == EAS_Defaulted); + } + { + TCompactVector<Emplaceable, 3> V; + V.emplace(V.end(), A0, A1); + EXPECT_TRUE(V.size() == 1); + EXPECT_TRUE(V.back().State == ES_Emplaced); + EXPECT_TRUE(V.back().A0.State == EAS_LValue); + EXPECT_TRUE(V.back().A1.State == EAS_LValue); + EXPECT_TRUE(V.back().A2.State == EAS_Defaulted); + EXPECT_TRUE(V.back().A3.State == EAS_Defaulted); + } + { + TCompactVector<Emplaceable, 3> V; + V.emplace(V.end(), std::move(A0), std::move(A1)); + EXPECT_TRUE(V.size() == 1); + EXPECT_TRUE(V.back().State == ES_Emplaced); + EXPECT_TRUE(V.back().A0.State == EAS_RValue); + EXPECT_TRUE(V.back().A1.State == EAS_RValue); + EXPECT_TRUE(V.back().A2.State == EAS_Defaulted); + EXPECT_TRUE(V.back().A3.State == EAS_Defaulted); + } + { + TCompactVector<Emplaceable, 3> V; + V.emplace(V.end(), std::move(A0), A1, std::move(A2), A3); + EXPECT_TRUE(V.size() == 1); + EXPECT_TRUE(V.back().State == ES_Emplaced); + EXPECT_TRUE(V.back().A0.State == EAS_RValue); + EXPECT_TRUE(V.back().A1.State == EAS_LValue); + EXPECT_TRUE(V.back().A2.State == EAS_RValue); + EXPECT_TRUE(V.back().A3.State == EAS_LValue); + } + { + TCompactVector<int, 1> V; + V.emplace_back(42); + V.emplace(V.begin(), 0); + EXPECT_EQ(2U, V.size()); + EXPECT_EQ(0, V[0]); + EXPECT_EQ(42, V[1]); + } +} + +template <class T, size_t N> +class TStubArray +{ +public: + TStubArray(const TCompactVector<T, N>& vector) + : Vector_(vector) + { } + + bool equals(std::initializer_list<T> list) + { + return std::equal(Vector_.begin(), Vector_.end(), list.begin()); + } + + TCompactVector<T, N> Vector_; +}; + +template <typename T, size_t N> +TStubArray<T, N> makeArrayRef(const TCompactVector<T, N>& vector) +{ + return TStubArray<T, N>(vector); +} + +TEST(CompactVectorTest, InitializerList) { + TCompactVector<int, 2> V1 = {}; + EXPECT_TRUE(V1.empty()); + V1 = {0, 0}; + EXPECT_TRUE(makeArrayRef(V1).equals({0, 0})); + V1 = {-1, -1}; + EXPECT_TRUE(makeArrayRef(V1).equals({-1, -1})); + + TCompactVector<int, 2> V2 = {1, 2, 3, 4}; + EXPECT_TRUE(makeArrayRef(V2).equals({1, 2, 3, 4})); + V2.assign({4}); + EXPECT_TRUE(makeArrayRef(V2).equals({4})); + V2.insert(V2.end(), {3, 2}); + EXPECT_TRUE(makeArrayRef(V2).equals({4, 3, 2})); + V2.insert(V2.begin() + 1, 5); + EXPECT_TRUE(makeArrayRef(V2).equals({4, 5, 3, 2})); +} + +TEST(CompactVectorTest, AssignToShorter) { + TCompactVector<TString, 4> lhs; + TCompactVector<TString, 4> rhs; + rhs.emplace_back("foo"); + lhs = rhs; + EXPECT_EQ(1U, lhs.size()); + EXPECT_EQ("foo", lhs[0]); +} + +TEST(CompactVectorTest, AssignToLonger) { + TCompactVector<TString, 4> lhs; + lhs.emplace_back("bar"); + lhs.emplace_back("baz"); + TCompactVector<TString, 4> rhs; + rhs.emplace_back("foo"); + lhs = rhs; + EXPECT_EQ(1U, lhs.size()); + EXPECT_EQ("foo", lhs[0]); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace +} // namespace NYT diff --git a/library/cpp/yt/small_containers/unittests/ya.make b/library/cpp/yt/small_containers/unittests/ya.make new file mode 100644 index 0000000000..6bd8e1d0ec --- /dev/null +++ b/library/cpp/yt/small_containers/unittests/ya.make @@ -0,0 +1,17 @@ +GTEST(unittester-small-containers) + +OWNER(g:yt) + +SRCS( + compact_flat_map_ut.cpp + compact_heap_ut.cpp + compact_set_ut.cpp + compact_vector_ut.cpp +) + +PEERDIR( + library/cpp/yt/small_containers + library/cpp/testing/gtest +) + +END() diff --git a/library/cpp/yt/small_containers/ya.make b/library/cpp/yt/small_containers/ya.make new file mode 100644 index 0000000000..d886b2ddac --- /dev/null +++ b/library/cpp/yt/small_containers/ya.make @@ -0,0 +1,20 @@ +LIBRARY() + +PEERDIR( + library/cpp/yt/assert + library/cpp/yt/malloc +) + +CHECK_DEPENDENT_DIRS( + ALLOW_ONLY ALL + build + contrib + library + util +) + +END() + +RECURSE_FOR_TESTS( + unittests +) diff --git a/library/cpp/yt/string/enum-inl.h b/library/cpp/yt/string/enum-inl.h new file mode 100644 index 0000000000..ab8acff71b --- /dev/null +++ b/library/cpp/yt/string/enum-inl.h @@ -0,0 +1,118 @@ +#ifndef ENUM_INL_H_ +#error "Direct inclusion of this file is not allowed, include enum.h" +// For the sake of sane code completion. +#include "enum.h" +#endif + +#include <util/string/printf.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +namespace NDetail { + +[[noreturn]] +void ThrowMalformedEnumValueException( + TStringBuf typeName, + TStringBuf value); + +void FormatUnknownEnumValue( + TStringBuilderBase* builder, + TStringBuf name, + i64 value); + +} // namespace NDetail + +template <class T> +std::optional<T> TryParseEnum(TStringBuf value) +{ + static_assert(TEnumTraits<T>::IsEnum); + + auto tryFromString = [] (TStringBuf value) -> std::optional<T> { + T result; + if (auto ok = TEnumTraits<T>::FindValueByLiteral(DecodeEnumValue(value), &result)) { + return result; + } + return {}; + }; + + if constexpr (TEnumTraits<T>::IsBitEnum) { + T result{}; + TStringBuf token; + while (value.NextTok('|', token)) { + if (auto scalar = tryFromString(StripString(token))) { + result |= *scalar; + } else { + return {}; + } + } + return result; + } else { + return tryFromString(value); + } +} + +template <class T> +T ParseEnum(TStringBuf value) +{ + if (auto optionalResult = TryParseEnum<T>(value)) { + return *optionalResult; + } + NDetail::ThrowMalformedEnumValueException(TEnumTraits<T>::GetTypeName(), value); +} + +template <class T> +void FormatEnum(TStringBuilderBase* builder, T value, bool lowerCase) +{ + static_assert(TEnumTraits<T>::IsEnum); + + auto formatScalarValue = [builder, lowerCase] (T value) { + auto* literal = TEnumTraits<T>::FindLiteralByValue(value); + if (!literal) { + YT_VERIFY(!TEnumTraits<T>::IsBitEnum); + NDetail::FormatUnknownEnumValue( + builder, + TEnumTraits<T>::GetTypeName(), + static_cast<typename TEnumTraits<T>::TUnderlying>(value)); + return; + } + + if (lowerCase) { + CamelCaseToUnderscoreCase(builder, *literal); + } else { + builder->AppendString(*literal); + } + }; + + if constexpr (TEnumTraits<T>::IsBitEnum) { + if (auto* literal = TEnumTraits<T>::FindLiteralByValue(value)) { + formatScalarValue(value); + return; + } + auto first = true; + for (auto scalarValue : TEnumTraits<T>::GetDomainValues()) { + if (Any(value & scalarValue)) { + if (!first) { + builder->AppendString(TStringBuf(" | ")); + } + first = false; + formatScalarValue(scalarValue); + } + } + } else { + formatScalarValue(value); + } +} + +template <class T> +TString FormatEnum(T value, typename TEnumTraits<T>::TType*) +{ + TStringBuilder builder; + FormatEnum(&builder, value, /*lowerCase*/ true); + return builder.Flush(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/string/enum.cpp b/library/cpp/yt/string/enum.cpp new file mode 100644 index 0000000000..7cb8e5c6b6 --- /dev/null +++ b/library/cpp/yt/string/enum.cpp @@ -0,0 +1,44 @@ +#include "enum.h" + +#include "format.h" + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +TString DecodeEnumValue(TStringBuf value) +{ + auto camelValue = UnderscoreCaseToCamelCase(value); + auto underscoreValue = CamelCaseToUnderscoreCase(camelValue); + if (value != underscoreValue) { + throw TSimpleException(Format("Enum value %Qv is not in a proper underscore case; did you mean %Qv?", + value, + underscoreValue)); + } + return camelValue; +} + +TString EncodeEnumValue(TStringBuf value) +{ + return CamelCaseToUnderscoreCase(value); +} + +namespace NDetail { + +void ThrowMalformedEnumValueException(TStringBuf typeName, TStringBuf value) +{ + throw TSimpleException(Format("Error parsing %v value %Qv", + typeName, + value)); +} + +void FormatUnknownEnumValue(TStringBuilderBase* builder, TStringBuf name, i64 value) +{ + builder->AppendFormat("%v(%v)", name, value); +} + +} // namespace NDetail + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/string/enum.h b/library/cpp/yt/string/enum.h new file mode 100644 index 0000000000..10dc02610f --- /dev/null +++ b/library/cpp/yt/string/enum.h @@ -0,0 +1,31 @@ +#pragma once + +#include "string.h" + +#include <library/cpp/yt/misc/enum.h> + +#include <optional> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +TString DecodeEnumValue(TStringBuf value); +TString EncodeEnumValue(TStringBuf value); + +template <class T> +T ParseEnum(TStringBuf value); + +template <class T> +void FormatEnum(TStringBuilderBase* builder, T value, bool lowerCase); + +template <class T> +TString FormatEnum(T value, typename TEnumTraits<T>::TType* = nullptr); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define ENUM_INL_H_ +#include "enum-inl.h" +#undef ENUM_INL_H_ diff --git a/library/cpp/yt/string/format-inl.h b/library/cpp/yt/string/format-inl.h new file mode 100644 index 0000000000..5484d4a216 --- /dev/null +++ b/library/cpp/yt/string/format-inl.h @@ -0,0 +1,744 @@ +#ifndef FORMAT_INL_H_ +#error "Direct inclusion of this file is not allowed, include format.h" +// For the sake of sane code completion. +#include "format.h" +#endif + +#include "enum.h" +#include "string.h" + +#include <library/cpp/yt/assert/assert.h> + +#include <library/cpp/yt/small_containers/compact_vector.h> + +#include <library/cpp/yt/misc/enum.h> + +#include <cctype> +#include <optional> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +static const char GenericSpecSymbol = 'v'; + +inline bool IsQuotationSpecSymbol(char symbol) +{ + return symbol == 'Q' || symbol == 'q'; +} + +// TStringBuf +inline void FormatValue(TStringBuilderBase* builder, TStringBuf value, TStringBuf format) +{ + if (!format) { + builder->AppendString(value); + return; + } + + // Parse alignment. + bool alignLeft = false; + const char* current = format.begin(); + if (*current == '-') { + alignLeft = true; + ++current; + } + + bool hasAlign = false; + int alignSize = 0; + while (*current >= '0' && *current <= '9') { + hasAlign = true; + alignSize = 10 * alignSize + (*current - '0'); + if (alignSize > 1000000) { + builder->AppendString(TStringBuf("<alignment overflow>")); + return; + } + ++current; + } + + int padding = 0; + bool padLeft = false; + bool padRight = false; + if (hasAlign) { + padding = alignSize - value.size(); + if (padding < 0) { + padding = 0; + } + padLeft = !alignLeft; + padRight = alignLeft; + } + + bool singleQuotes = false; + bool doubleQuotes = false; + while (current < format.end()) { + if (*current == 'q') { + singleQuotes = true; + } else if (*current == 'Q') { + doubleQuotes = true; + } + ++current; + } + + if (padLeft) { + builder->AppendChar(' ', padding); + } + + if (singleQuotes || doubleQuotes) { + for (const char* valueCurrent = value.begin(); valueCurrent < value.end(); ++valueCurrent) { + char ch = *valueCurrent; + if (ch == '\n') { + builder->AppendString("\\n"); + } else if (ch == '\t') { + builder->AppendString("\\t"); + } else if (ch < PrintableASCIILow || ch > PrintableASCIIHigh) { + builder->AppendString("\\x"); + builder->AppendChar(Int2Hex[static_cast<ui8>(ch) >> 4]); + builder->AppendChar(Int2Hex[static_cast<ui8>(ch) & 0xf]); + } else if ((singleQuotes && ch == '\'') || (doubleQuotes && ch == '\"')) { + builder->AppendChar('\\'); + builder->AppendChar(ch); + } else { + builder->AppendChar(ch); + } + } + } else { + builder->AppendString(value); + } + + if (padRight) { + builder->AppendChar(' ', padding); + } +} + +// TString +inline void FormatValue(TStringBuilderBase* builder, const TString& value, TStringBuf format) +{ + FormatValue(builder, TStringBuf(value), format); +} + +// const char* +inline void FormatValue(TStringBuilderBase* builder, const char* value, TStringBuf format) +{ + FormatValue(builder, TStringBuf(value), format); +} + +// char +inline void FormatValue(TStringBuilderBase* builder, char value, TStringBuf format) +{ + FormatValue(builder, TStringBuf(&value, 1), format); +} + +// bool +inline void FormatValue(TStringBuilderBase* builder, bool value, TStringBuf format) +{ + // Parse custom flags. + bool lowercase = false; + const char* current = format.begin(); + while (current != format.end()) { + if (*current == 'l') { + ++current; + lowercase = true; + } else if (IsQuotationSpecSymbol(*current)) { + ++current; + } else + break; + } + + auto str = lowercase + ? (value ? TStringBuf("true") : TStringBuf("false")) + : (value ? TStringBuf("True") : TStringBuf("False")); + + builder->AppendString(str); +} + +// Fallback to ToString +struct TToStringFallbackValueFormatterTag +{ }; + +template <class TValue, class = void> +struct TValueFormatter +{ + static TToStringFallbackValueFormatterTag Do(TStringBuilderBase* builder, const TValue& value, TStringBuf format) + { + using ::ToString; + FormatValue(builder, ToString(value), format); + return {}; + } +}; + +// Enum +template <class TEnum> +struct TValueFormatter<TEnum, typename std::enable_if<TEnumTraits<TEnum>::IsEnum>::type> +{ + static void Do(TStringBuilderBase* builder, TEnum value, TStringBuf format) + { + // Parse custom flags. + bool lowercase = false; + const char* current = format.begin(); + while (current != format.end()) { + if (*current == 'l') { + ++current; + lowercase = true; + } else if (IsQuotationSpecSymbol(*current)) { + ++current; + } else { + break; + } + } + + FormatEnum(builder, value, lowercase); + } +}; + +template <class TRange, class TFormatter> +typename TFormattableView<TRange, TFormatter>::TBegin TFormattableView<TRange, TFormatter>::begin() const +{ + return RangeBegin; +} + +template <class TRange, class TFormatter> +typename TFormattableView<TRange, TFormatter>::TEnd TFormattableView<TRange, TFormatter>::end() const +{ + return RangeEnd; +} + +template <class TRange, class TFormatter> +TFormattableView<TRange, TFormatter> MakeFormattableView( + const TRange& range, + TFormatter&& formatter) +{ + return TFormattableView<TRange, std::decay_t<TFormatter>>{range.begin(), range.end(), std::forward<TFormatter>(formatter)}; +} + +template <class TRange, class TFormatter> +TFormattableView<TRange, TFormatter> MakeShrunkFormattableView( + const TRange& range, + TFormatter&& formatter, + size_t limit) +{ + return TFormattableView<TRange, std::decay_t<TFormatter>>{range.begin(), range.end(), std::forward<TFormatter>(formatter), limit}; +} + +template <class TRange, class TFormatter> +void FormatRange(TStringBuilderBase* builder, const TRange& range, const TFormatter& formatter, size_t limit = std::numeric_limits<size_t>::max()) +{ + builder->AppendChar('['); + size_t index = 0; + for (const auto& item : range) { + if (index > 0) { + builder->AppendString(DefaultJoinToStringDelimiter); + } + if (index == limit) { + builder->AppendString(DefaultRangeEllipsisFormat); + break; + } + formatter(builder, item); + ++index; + } + builder->AppendChar(']'); +} + +template <class TRange, class TFormatter> +void FormatKeyValueRange(TStringBuilderBase* builder, const TRange& range, const TFormatter& formatter, size_t limit = std::numeric_limits<size_t>::max()) +{ + builder->AppendChar('{'); + size_t index = 0; + for (const auto& item : range) { + if (index > 0) { + builder->AppendString(DefaultJoinToStringDelimiter); + } + if (index == limit) { + builder->AppendString(DefaultRangeEllipsisFormat); + break; + } + formatter(builder, item.first); + builder->AppendString(DefaultKeyValueDelimiter); + formatter(builder, item.second); + ++index; + } + builder->AppendChar('}'); +} + +// TFormattableView +template <class TRange, class TFormatter> +struct TValueFormatter<TFormattableView<TRange, TFormatter>> +{ + static void Do(TStringBuilderBase* builder, const TFormattableView<TRange, TFormatter>& range, TStringBuf /*format*/) + { + FormatRange(builder, range, range.Formatter, range.Limit); + } +}; + +template <class TFormatter> +TFormatterWrapper<TFormatter> MakeFormatterWrapper( + TFormatter&& formatter) +{ + return TFormatterWrapper<TFormatter>{ + .Formatter = std::move(formatter) + }; +} + +// TFormatterWrapper +template <class TFormatter> +struct TValueFormatter<TFormatterWrapper<TFormatter>> +{ + static void Do(TStringBuilderBase* builder, const TFormatterWrapper<TFormatter>& wrapper, TStringBuf /*format*/) + { + wrapper.Formatter(builder); + } +}; + +// std::vector +template <class T, class TAllocator> +struct TValueFormatter<std::vector<T, TAllocator>> +{ + static void Do(TStringBuilderBase* builder, const std::vector<T, TAllocator>& collection, TStringBuf /*format*/) + { + FormatRange(builder, collection, TDefaultFormatter()); + } +}; + +// TCompactVector +template <class T, unsigned N> +struct TValueFormatter<TCompactVector<T, N>> +{ + static void Do(TStringBuilderBase* builder, const TCompactVector<T, N>& collection, TStringBuf /*format*/) + { + FormatRange(builder, collection, TDefaultFormatter()); + } +}; + +// std::set +template <class T> +struct TValueFormatter<std::set<T>> +{ + static void Do(TStringBuilderBase* builder, const std::set<T>& collection, TStringBuf /*format*/) + { + FormatRange(builder, collection, TDefaultFormatter()); + } +}; + +// std::map +template <class K, class V> +struct TValueFormatter<std::map<K, V>> +{ + static void Do(TStringBuilderBase* builder, const std::map<K, V>& collection, TStringBuf /*format*/) + { + FormatKeyValueRange(builder, collection, TDefaultFormatter()); + } +}; + +// std::multimap +template <class K, class V> +struct TValueFormatter<std::multimap<K, V>> +{ + static void Do(TStringBuilderBase* builder, const std::multimap<K, V>& collection, TStringBuf /*format*/) + { + FormatKeyValueRange(builder, collection, TDefaultFormatter()); + } +}; + +// THashSet +template <class T> +struct TValueFormatter<THashSet<T>> +{ + static void Do(TStringBuilderBase* builder, const THashSet<T>& collection, TStringBuf /*format*/) + { + FormatRange(builder, collection, TDefaultFormatter()); + } +}; + +// THashMultiSet +template <class T> +struct TValueFormatter<THashMultiSet<T>> +{ + static void Do(TStringBuilderBase* builder, const THashMultiSet<T>& collection, TStringBuf /*format*/) + { + FormatRange(builder, collection, TDefaultFormatter()); + } +}; + +// THashMap +template <class K, class V> +struct TValueFormatter<THashMap<K, V>> +{ + static void Do(TStringBuilderBase* builder, const THashMap<K, V>& collection, TStringBuf /*format*/) + { + FormatKeyValueRange(builder, collection, TDefaultFormatter()); + } +}; + +// THashMultiMap +template <class K, class V> +struct TValueFormatter<THashMultiMap<K, V>> +{ + static void Do(TStringBuilderBase* builder, const THashMultiMap<K, V>& collection, TStringBuf /*format*/) + { + FormatKeyValueRange(builder, collection, TDefaultFormatter()); + } +}; + +// TEnumIndexedVector +template <class E, class T> +struct TValueFormatter<TEnumIndexedVector<E, T>> +{ + static void Do(TStringBuilderBase* builder, const TEnumIndexedVector<E, T>& collection, TStringBuf format) + { + builder->AppendChar('{'); + bool firstItem = true; + for (const auto& index : TEnumTraits<E>::GetDomainValues()) { + if (!firstItem) { + builder->AppendString(DefaultJoinToStringDelimiter); + } + FormatValue(builder, index, format); + builder->AppendString(": "); + FormatValue(builder, collection[index], format); + firstItem = false; + } + builder->AppendChar('}'); + } +}; + +// std::pair +template <class T1, class T2> +struct TValueFormatter<std::pair<T1, T2>> +{ + static void Do(TStringBuilderBase* builder, const std::pair<T1, T2>& value, TStringBuf format) + { + builder->AppendChar('{'); + FormatValue(builder, value.first, format); + builder->AppendString(TStringBuf(", ")); + FormatValue(builder, value.second, format); + builder->AppendChar('}'); + } +}; + +// std::optional +inline void FormatValue(TStringBuilderBase* builder, std::nullopt_t, TStringBuf /*format*/) +{ + builder->AppendString(TStringBuf("<null>")); +} + +template <class T> +struct TValueFormatter<std::optional<T>> +{ + static void Do(TStringBuilderBase* builder, const std::optional<T>& value, TStringBuf format) + { + if (value) { + FormatValue(builder, *value, format); + } else { + FormatValue(builder, std::nullopt, format); + } + } +}; + +template <class TValue> +auto FormatValue(TStringBuilderBase* builder, const TValue& value, TStringBuf format) -> + decltype(TValueFormatter<TValue>::Do(builder, value, format)) +{ + return TValueFormatter<TValue>::Do(builder, value, format); +} + +template <class TValue> +void FormatValueViaSprintf( + TStringBuilderBase* builder, + TValue value, + TStringBuf format, + TStringBuf genericSpec) +{ + constexpr int MaxFormatSize = 64; + constexpr int SmallResultSize = 64; + + auto copyFormat = [] (char* destination, const char* source, int length) { + int position = 0; + for (int index = 0; index < length; ++index) { + if (IsQuotationSpecSymbol(source[index])) { + continue; + } + destination[position] = source[index]; + ++position; + } + return destination + position; + }; + + char formatBuf[MaxFormatSize]; + YT_VERIFY(format.length() >= 1 && format.length() <= MaxFormatSize - 2); // one for %, one for \0 + formatBuf[0] = '%'; + if (format[format.length() - 1] == GenericSpecSymbol) { + char* formatEnd = copyFormat(formatBuf + 1, format.begin(), format.length() - 1); + ::memcpy(formatEnd, genericSpec.begin(), genericSpec.length()); + formatEnd[genericSpec.length()] = '\0'; + } else { + char* formatEnd = copyFormat(formatBuf + 1, format.begin(), format.length()); + *formatEnd = '\0'; + } + + char* result = builder->Preallocate(SmallResultSize); + size_t resultSize = ::snprintf(result, SmallResultSize, formatBuf, value); + if (resultSize >= SmallResultSize) { + result = builder->Preallocate(resultSize + 1); + YT_VERIFY(::snprintf(result, resultSize + 1, formatBuf, value) == static_cast<int>(resultSize)); + } + builder->Advance(resultSize); +} + +template <class TValue> +char* WriteIntToBufferBackwards(char* buffer, TValue value); + +template <class TValue> +void FormatValueViaHelper(TStringBuilderBase* builder, TValue value, TStringBuf format, TStringBuf genericSpec) +{ + if (format == TStringBuf("v")) { + const int MaxResultSize = 64; + char buffer[MaxResultSize]; + char* end = buffer + MaxResultSize; + char* start = WriteIntToBufferBackwards(end, value); + builder->AppendString(TStringBuf(start, end)); + } else { + FormatValueViaSprintf(builder, value, format, genericSpec); + } +} + +#define XX(valueType, castType, genericSpec) \ + inline void FormatValue(TStringBuilderBase* builder, valueType value, TStringBuf format) \ + { \ + FormatValueViaHelper(builder, static_cast<castType>(value), format, genericSpec); \ + } + +XX(i8, int, TStringBuf("d")) +XX(ui8, unsigned int, TStringBuf("u")) +XX(i16, int, TStringBuf("d")) +XX(ui16, unsigned int, TStringBuf("u")) +XX(i32, int, TStringBuf("d")) +XX(ui32, unsigned int, TStringBuf("u")) +XX(long, long, TStringBuf("ld")) +XX(unsigned long, unsigned long, TStringBuf("lu")) + +#undef XX + +#define XX(valueType, castType, genericSpec) \ + inline void FormatValue(TStringBuilderBase* builder, valueType value, TStringBuf format) \ + { \ + FormatValueViaSprintf(builder, static_cast<castType>(value), format, genericSpec); \ + } + +XX(double, double, TStringBuf("lf")) +XX(float, float, TStringBuf("f")) + +#undef XX + +// Pointer +template <class T> +void FormatValue(TStringBuilderBase* builder, T* value, TStringBuf format) +{ + FormatValueViaSprintf(builder, value, format, TStringBuf("p")); +} + +// TDuration (specialize for performance reasons) +inline void FormatValue(TStringBuilderBase* builder, TDuration value, TStringBuf /*format*/) +{ + builder->AppendFormat("%vus", value.MicroSeconds()); +} + +// TInstant (specialize for TFormatTraits) +inline void FormatValue(TStringBuilderBase* builder, TInstant value, TStringBuf format) +{ + // TODO(babenko): optimize + builder->AppendFormat("%v", ToString(value), format); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class TArgFormatter> +void FormatImpl( + TStringBuilderBase* builder, + TStringBuf format, + const TArgFormatter& argFormatter) +{ + size_t argIndex = 0; + auto current = format.begin(); + while (true) { + // Scan verbatim part until stop symbol. + auto verbatimBegin = current; + auto verbatimEnd = verbatimBegin; + while (verbatimEnd != format.end() && *verbatimEnd != '%') { + ++verbatimEnd; + } + + // Copy verbatim part, if any. + size_t verbatimSize = verbatimEnd - verbatimBegin; + if (verbatimSize > 0) { + builder->AppendString(TStringBuf(verbatimBegin, verbatimSize)); + } + + // Handle stop symbol. + current = verbatimEnd; + if (current == format.end()) { + break; + } + + YT_ASSERT(*current == '%'); + ++current; + + if (*current == '%') { + // Verbatim %. + builder->AppendChar('%'); + ++current; + } else { + // Scan format part until stop symbol. + auto argFormatBegin = current; + auto argFormatEnd = argFormatBegin; + bool singleQuotes = false; + bool doubleQuotes = false; + + while ( + argFormatEnd != format.end() && + *argFormatEnd != GenericSpecSymbol && // value in generic format + *argFormatEnd != 'd' && // others are standard specifiers supported by printf + *argFormatEnd != 'i' && + *argFormatEnd != 'u' && + *argFormatEnd != 'o' && + *argFormatEnd != 'x' && + *argFormatEnd != 'X' && + *argFormatEnd != 'f' && + *argFormatEnd != 'F' && + *argFormatEnd != 'e' && + *argFormatEnd != 'E' && + *argFormatEnd != 'g' && + *argFormatEnd != 'G' && + *argFormatEnd != 'a' && + *argFormatEnd != 'A' && + *argFormatEnd != 'c' && + *argFormatEnd != 's' && + *argFormatEnd != 'p' && + *argFormatEnd != 'n') + { + if (*argFormatEnd == 'q') { + singleQuotes = true; + } else if (*argFormatEnd == 'Q') { + doubleQuotes = true; + } + ++argFormatEnd; + } + + // Handle end of format string. + if (argFormatEnd != format.end()) { + ++argFormatEnd; + } + + // 'n' means 'nothing'; skip the argument. + if (*argFormatBegin != 'n') { + // Format argument. + TStringBuf argFormat(argFormatBegin, argFormatEnd); + if (singleQuotes) { + builder->AppendChar('\''); + } + if (doubleQuotes) { + builder->AppendChar('"'); + } + argFormatter(argIndex++, builder, argFormat); + if (singleQuotes) { + builder->AppendChar('\''); + } + if (doubleQuotes) { + builder->AppendChar('"'); + } + } + + current = argFormatEnd; + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +struct TFormatTraits +{ + static constexpr bool HasCustomFormatValue = !std::is_same_v< + decltype(FormatValue( + static_cast<TStringBuilderBase*>(nullptr), + *static_cast<const T*>(nullptr), + TStringBuf())), + TToStringFallbackValueFormatterTag>; +}; + +//////////////////////////////////////////////////////////////////////////////// + +template <size_t IndexBase, class... TArgs> +struct TArgFormatterImpl; + +template <size_t IndexBase> +struct TArgFormatterImpl<IndexBase> +{ + void operator() (size_t /*index*/, TStringBuilderBase* builder, TStringBuf /*format*/) const + { + builder->AppendString(TStringBuf("<missing argument>")); + } +}; + +template <size_t IndexBase, class THeadArg, class... TTailArgs> +struct TArgFormatterImpl<IndexBase, THeadArg, TTailArgs...> +{ + explicit TArgFormatterImpl(const THeadArg& headArg, const TTailArgs&... tailArgs) + : HeadArg(headArg) + , TailFormatter(tailArgs...) + { } + + const THeadArg& HeadArg; + TArgFormatterImpl<IndexBase + 1, TTailArgs...> TailFormatter; + + void operator() (size_t index, TStringBuilderBase* builder, TStringBuf format) const + { + YT_ASSERT(index >= IndexBase); + if (index == IndexBase) { + FormatValue(builder, HeadArg, format); + } else { + TailFormatter(index, builder, format); + } + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +template <size_t Length, class... TArgs> +void Format( + TStringBuilderBase* builder, + const char (&format)[Length], + TArgs&&... args) +{ + Format(builder, TStringBuf(format, Length - 1), std::forward<TArgs>(args)...); +} + +template <class... TArgs> +void Format( + TStringBuilderBase* builder, + TStringBuf format, + TArgs&&... args) +{ + TArgFormatterImpl<0, TArgs...> argFormatter(args...); + FormatImpl(builder, format, argFormatter); +} + +template <size_t Length, class... TArgs> +TString Format( + const char (&format)[Length], + TArgs&&... args) +{ + TStringBuilder builder; + Format(&builder, format, std::forward<TArgs>(args)...); + return builder.Flush(); +} + +template <class... TArgs> +TString Format( + TStringBuf format, + TArgs&&... args) +{ + TStringBuilder builder; + Format(&builder, format, std::forward<TArgs>(args)...); + return builder.Flush(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/string/format.h b/library/cpp/yt/string/format.h new file mode 100644 index 0000000000..9708fe5906 --- /dev/null +++ b/library/cpp/yt/string/format.h @@ -0,0 +1,114 @@ +#pragma once + +#include "string_builder.h" + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +/* + * Format: a type-safe and fast formatting utility. + * + * Basically works as a type-safe analogue of |sprintf| and is expected to + * be backwards-compatible with the latter. + * + * Like Go's |Sprintf|, supports the ultimate format specifier |v| + * causing arguments to be emitted in default format. + * This is the default and preferred way of formatting things, + * which should be used in newer code. + * + * |Format| may currently invoke |sprintf| internally for emitting numeric and some other + * types. You can always write your own optimized implementation, if you wish :) + * + * In additional to the usual |sprintf|, supports a number of non-standard flags: + * + * |q| Causes the argument to be surrounded with single quotes (|'|). + * Applies to all types. + * + * |Q| Causes the argument to be surrounded with double quotes (|"|). + * Applies to all types. + * + * |l| The argument is emitted in "lowercase" style. + * Only applies to enums and bools. + * + * The following argument types are supported: + * + * Strings (including |const char*|, |TStringBuf|, and |TString|) and chars: + * Emitted as is. Fast. + * + * Numerics and pointers: + * Emitted using |sprintf|. Maybe not that fast. + * + * |bool|: + * Emitted either as |True| and |False| or |true| and |false| (if lowercase mode is ON). + * + * Enums: + * Emitted in either camel (|SomeName|) or in lowercase-with-underscores style + * (|some_name|, if lowercase mode is ON). + * + * Nullables: + * |std::nullopt| is emitted as |<null>|. + * + * All others: + * Emitted as strings by calling |ToString|. + * + */ + +template <size_t Length, class... TArgs> +void Format(TStringBuilderBase* builder, const char (&format)[Length], TArgs&&... args); +template <class... TArgs> +void Format(TStringBuilderBase* builder, TStringBuf format, TArgs&&... args); + +template <size_t Length, class... TArgs> +TString Format(const char (&format)[Length], TArgs&&... args); +template <class... TArgs> +TString Format(TStringBuf format, TArgs&&... args); + +//////////////////////////////////////////////////////////////////////////////// + +template <class TRange, class TFormatter> +struct TFormattableView +{ + using TBegin = std::decay_t<decltype(std::declval<const TRange>().begin())>; + using TEnd = std::decay_t<decltype(std::declval<const TRange>().end())>; + + TBegin RangeBegin; + TEnd RangeEnd; + TFormatter Formatter; + size_t Limit = std::numeric_limits<size_t>::max(); + + TBegin begin() const; + TEnd end() const; +}; + +//! Annotates a given #range with #formatter to be applied to each item. +template <class TRange, class TFormatter> +TFormattableView<TRange, TFormatter> MakeFormattableView( + const TRange& range, + TFormatter&& formatter); + +template <class TRange, class TFormatter> +TFormattableView<TRange, TFormatter> MakeShrunkFormattableView( + const TRange& range, + TFormatter&& formatter, + size_t limit); + +//////////////////////////////////////////////////////////////////////////////// + +template <class TFormatter> +struct TFormatterWrapper +{ + TFormatter Formatter; +}; + +template <class TFormatter> +TFormatterWrapper<TFormatter> MakeFormatterWrapper( + TFormatter&& formatter); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define FORMAT_INL_H_ +#include "format-inl.h" +#undef FORMAT_INL_H_ diff --git a/library/cpp/yt/string/guid.cpp b/library/cpp/yt/string/guid.cpp new file mode 100644 index 0000000000..6c133a9778 --- /dev/null +++ b/library/cpp/yt/string/guid.cpp @@ -0,0 +1,22 @@ +#include "guid.h" + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +void FormatValue(TStringBuilderBase* builder, TGuid value, TStringBuf /*format*/) +{ + char* begin = builder->Preallocate(MaxGuidStringSize); + char* end = WriteGuidToBuffer(begin, value); + builder->Advance(end - begin); +} + +TString ToString(TGuid guid) +{ + return ToStringViaBuilder(guid); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + diff --git a/library/cpp/yt/string/guid.h b/library/cpp/yt/string/guid.h new file mode 100644 index 0000000000..75edbce5db --- /dev/null +++ b/library/cpp/yt/string/guid.h @@ -0,0 +1,14 @@ +#include <library/cpp/yt/misc/guid.h> + +#include "format.h" + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +void FormatValue(TStringBuilderBase* builder, TGuid value, TStringBuf /*format*/); +TString ToString(TGuid guid); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/string/string.cpp b/library/cpp/yt/string/string.cpp new file mode 100644 index 0000000000..7440ac3fdd --- /dev/null +++ b/library/cpp/yt/string/string.cpp @@ -0,0 +1,272 @@ +#include "string.h" +#include "format.h" + +#include <library/cpp/yt/assert/assert.h> + +#include <util/generic/hash.h> + +#include <util/string/ascii.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +void UnderscoreCaseToCamelCase(TStringBuilderBase* builder, TStringBuf str) +{ + bool first = true; + bool upper = true; + for (char c : str) { + if (c == '_') { + upper = true; + } else { + if (upper) { + if (!std::isalpha(c) && !first) { + builder->AppendChar('_'); + } + c = std::toupper(c); + } + builder->AppendChar(c); + upper = false; + } + first = false; + } +} + +TString UnderscoreCaseToCamelCase(TStringBuf str) +{ + TStringBuilder builder; + UnderscoreCaseToCamelCase(&builder, str); + return builder.Flush(); +} + +void CamelCaseToUnderscoreCase(TStringBuilderBase* builder, TStringBuf str) +{ + bool first = true; + for (char c : str) { + if (std::isupper(c) && std::isalpha(c)) { + if (!first) { + builder->AppendChar('_'); + } + c = std::tolower(c); + } + builder->AppendChar(c); + first = false; + } +} + +TString CamelCaseToUnderscoreCase(TStringBuf str) +{ + TStringBuilder builder; + CamelCaseToUnderscoreCase(&builder, str); + return builder.Flush(); +} + +//////////////////////////////////////////////////////////////////////////////// + +TString TrimLeadingWhitespaces(const TString& str) +{ + for (int i = 0; i < static_cast<int>(str.size()); ++i) { + if (str[i] != ' ') { + return str.substr(i); + } + } + return ""; +} + +TString Trim(const TString& str, const TString& whitespaces) +{ + size_t end = str.size(); + while (end > 0) { + size_t i = end - 1; + bool isWhitespace = false; + for (auto c : whitespaces) { + if (str[i] == c) { + isWhitespace = true; + break; + } + } + if (!isWhitespace) { + break; + } + --end; + } + + if (end == 0) { + return ""; + } + + size_t begin = str.find_first_not_of(whitespaces); + YT_VERIFY(begin != TString::npos); + YT_VERIFY(begin < end); + return str.substr(begin, end - begin); +} + +//////////////////////////////////////////////////////////////////////////////// + +namespace { + +ui16 DecimalDigits2[100] = { + 12336, 12592, 12848, 13104, 13360, 13616, 13872, 14128, 14384, 14640, + 12337, 12593, 12849, 13105, 13361, 13617, 13873, 14129, 14385, 14641, + 12338, 12594, 12850, 13106, 13362, 13618, 13874, 14130, 14386, 14642, + 12339, 12595, 12851, 13107, 13363, 13619, 13875, 14131, 14387, 14643, + 12340, 12596, 12852, 13108, 13364, 13620, 13876, 14132, 14388, 14644, + 12341, 12597, 12853, 13109, 13365, 13621, 13877, 14133, 14389, 14645, + 12342, 12598, 12854, 13110, 13366, 13622, 13878, 14134, 14390, 14646, + 12343, 12599, 12855, 13111, 13367, 13623, 13879, 14135, 14391, 14647, + 12344, 12600, 12856, 13112, 13368, 13624, 13880, 14136, 14392, 14648, + 12345, 12601, 12857, 13113, 13369, 13625, 13881, 14137, 14393, 14649 +}; + +template <class T> +char* WriteSignedIntToBufferBackwardsImpl(char* ptr, T value, TStringBuf min) +{ + if (value == 0) { + --ptr; + *ptr = '0'; + return ptr; + } + + // The negative value handling code below works incorrectly for min values. + if (value == std::numeric_limits<T>::min()) { + ptr -= min.length(); + ::memcpy(ptr, min.begin(), min.length()); + return ptr; + } + + bool negative = false; + if (value < 0) { + negative = true; + value = -value; + } + + while (value >= 10) { + i64 rem = value % 100; + i64 quot = value / 100; + ptr -= 2; + ::memcpy(ptr, &DecimalDigits2[rem], 2); + value = quot; + } + + if (value > 0) { + --ptr; + *ptr = ('0' + value); + } + + if (negative) { + --ptr; + *ptr = '-'; + } + + return ptr; +} + +template <class T> +char* WriteUnsignedIntToBufferBackwardsImpl(char* ptr, T value) +{ + if (value == 0) { + --ptr; + *ptr = '0'; + return ptr; + } + + while (value >= 10) { + i64 rem = value % 100; + i64 quot = value / 100; + ptr -= 2; + ::memcpy(ptr, &DecimalDigits2[rem], 2); + value = quot; + } + + if (value > 0) { + --ptr; + *ptr = ('0' + value); + } + + return ptr; +} + +} // namespace + +template <> +char* WriteIntToBufferBackwards(char* ptr, i32 value) +{ + return WriteSignedIntToBufferBackwardsImpl(ptr, value, TStringBuf("-2147483647")); +} + +template <> +char* WriteIntToBufferBackwards(char* ptr, i64 value) +{ + return WriteSignedIntToBufferBackwardsImpl(ptr, value, TStringBuf("-9223372036854775808")); +} + +template <> +char* WriteIntToBufferBackwards(char* ptr, ui32 value) +{ + return WriteUnsignedIntToBufferBackwardsImpl(ptr, value); +} + +template <> +char* WriteIntToBufferBackwards(char* ptr, ui64 value) +{ + return WriteUnsignedIntToBufferBackwardsImpl(ptr, value); +} + +//////////////////////////////////////////////////////////////////////////////// + +size_t TCaseInsensitiveStringHasher::operator()(TStringBuf arg) const +{ + auto compute = [&] (char* buffer) { + for (size_t index = 0; index < arg.length(); ++index) { + buffer[index] = AsciiToLower(arg[index]); + } + return ComputeHash(TStringBuf(buffer, arg.length())); + }; + const size_t SmallSize = 256; + if (arg.length() <= SmallSize) { + std::array<char, SmallSize> stackBuffer; + return compute(stackBuffer.data()); + } else { + std::unique_ptr<char[]> heapBuffer(new char[arg.length()]); + return compute(heapBuffer.get()); + } +} + +bool TCaseInsensitiveStringEqualityComparer::operator()(TStringBuf lhs, TStringBuf rhs) const +{ + return AsciiEqualsIgnoreCase(lhs, rhs); +} + +//////////////////////////////////////////////////////////////////////////////// + +bool TryParseBool(TStringBuf value, bool* result) +{ + if (value == "true" || value == "1") { + *result = true; + return true; + } else if (value == "false" || value == "0") { + *result = false; + return true; + } else { + return false; + } +} + +bool ParseBool(TStringBuf value) +{ + bool result; + if (!TryParseBool(value, &result)) { + throw TSimpleException(Format("Error parsing boolean value %Qv", + value)); + } + return result; +} + +TStringBuf FormatBool(bool value) +{ + return value ? TStringBuf("true") : TStringBuf("false"); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/string/string.h b/library/cpp/yt/string/string.h new file mode 100644 index 0000000000..ae6c99caab --- /dev/null +++ b/library/cpp/yt/string/string.h @@ -0,0 +1,221 @@ +#pragma once + +#include "string_builder.h" + +#include <library/cpp/yt/exception/exception.h> + +#include <util/datetime/base.h> + +#include <util/generic/string.h> + +#include <util/string/strip.h> + +#include <vector> +#include <set> +#include <map> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +//! Formatters enable customizable way to turn an object into a string. +//! This default implementation uses |FormatValue|. +struct TDefaultFormatter +{ + template <class T> + void operator()(TStringBuilderBase* builder, const T& obj) const + { + FormatValue(builder, obj, TStringBuf("v")); + } +}; + +static constexpr TStringBuf DefaultJoinToStringDelimiter = ", "; +static constexpr TStringBuf DefaultKeyValueDelimiter = ": "; +static constexpr TStringBuf DefaultRangeEllipsisFormat = "..."; + +// ASCII characters from 0x20 = ' ' to 0x7e = '~' are printable. +static constexpr char PrintableASCIILow = 0x20; +static constexpr char PrintableASCIIHigh = 0x7e; +static constexpr TStringBuf Int2Hex = "0123456789abcdef"; + +//! Joins a range of items into a string intermixing them with the delimiter. +/*! + * \param builder String builder where the output goes. + * \param begin Iterator pointing to the first item (inclusive). + * \param end Iterator pointing to the last item (not inclusive). + * \param formatter Formatter to apply to the items. + * \param delimiter A delimiter to be inserted between items: ", " by default. + * \return The resulting combined string. + */ +template <class TIterator, class TFormatter> +void JoinToString( + TStringBuilderBase* builder, + const TIterator& begin, + const TIterator& end, + const TFormatter& formatter, + TStringBuf delimiter = DefaultJoinToStringDelimiter) +{ + for (auto current = begin; current != end; ++current) { + if (current != begin) { + builder->AppendString(delimiter); + } + formatter(builder, *current); + } +} + +template <class TIterator, class TFormatter> +TString JoinToString( + const TIterator& begin, + const TIterator& end, + const TFormatter& formatter, + TStringBuf delimiter = DefaultJoinToStringDelimiter) +{ + TStringBuilder builder; + JoinToString(&builder, begin, end, formatter, delimiter); + return builder.Flush(); +} + +//! A handy shortcut with default formatter. +template <class TIterator> +TString JoinToString( + const TIterator& begin, + const TIterator& end, + TStringBuf delimiter = DefaultJoinToStringDelimiter) +{ + return JoinToString(begin, end, TDefaultFormatter(), delimiter); +} + +//! Joins a collection of given items into a string intermixing them with the delimiter. +/*! + * \param collection A collection containing the items to be joined. + * \param formatter Formatter to apply to the items. + * \param delimiter A delimiter to be inserted between items; ", " by default. + */ +template <class TCollection, class TFormatter> +TString JoinToString( + const TCollection& collection, + const TFormatter& formatter, + TStringBuf delimiter = DefaultJoinToStringDelimiter) +{ + using std::begin; + using std::end; + return JoinToString(begin(collection), end(collection), formatter, delimiter); +} + +//! A handy shortcut with the default formatter. +template <class TCollection> +TString JoinToString( + const TCollection& collection, + TStringBuf delimiter = DefaultJoinToStringDelimiter) +{ + return JoinToString(collection, TDefaultFormatter(), delimiter); +} + +//! Concatenates a bunch of TStringBuf-like instances into TString. +template <class... Ts> +TString ConcatToString(Ts... args) +{ + size_t length = 0; + ((length += args.length()), ...); + + TString result; + result.reserve(length); + (result.append(args), ...); + + return result; +} + +//! Converts a range of items into strings. +template <class TIter, class TFormatter> +std::vector<TString> ConvertToStrings( + const TIter& begin, + const TIter& end, + const TFormatter& formatter, + size_t maxSize = std::numeric_limits<size_t>::max()) +{ + std::vector<TString> result; + for (auto it = begin; it != end; ++it) { + TStringBuilder builder; + formatter(&builder, *it); + result.push_back(builder.Flush()); + if (result.size() == maxSize) { + break; + } + } + return result; +} + +//! A handy shortcut with the default formatter. +template <class TIter> +std::vector<TString> ConvertToStrings( + const TIter& begin, + const TIter& end, + size_t maxSize = std::numeric_limits<size_t>::max()) +{ + return ConvertToStrings(begin, end, TDefaultFormatter(), maxSize); +} + +//! Converts a given collection of items into strings. +/*! + * \param collection A collection containing the items to be converted. + * \param formatter Formatter to apply to the items. + * \param maxSize Size limit for the resulting vector. + */ +template <class TCollection, class TFormatter> +std::vector<TString> ConvertToStrings( + const TCollection& collection, + const TFormatter& formatter, + size_t maxSize = std::numeric_limits<size_t>::max()) +{ + using std::begin; + using std::end; + return ConvertToStrings(begin(collection), end(collection), formatter, maxSize); +} + +//! A handy shortcut with default formatter. +template <class TCollection> +std::vector<TString> ConvertToStrings( + const TCollection& collection, + size_t maxSize = std::numeric_limits<size_t>::max()) +{ + return ConvertToStrings(collection, TDefaultFormatter(), maxSize); +} + +//////////////////////////////////////////////////////////////////////////////// + +void UnderscoreCaseToCamelCase(TStringBuilderBase* builder, TStringBuf str); +TString UnderscoreCaseToCamelCase(TStringBuf str); + +void CamelCaseToUnderscoreCase(TStringBuilderBase* builder, TStringBuf str); +TString CamelCaseToUnderscoreCase(TStringBuf str); + +TString TrimLeadingWhitespaces(const TString& str); +TString Trim(const TString& str, const TString& whitespaces); + +//////////////////////////////////////////////////////////////////////////////// + +//! Implemented for |[u]i(32|64)|. +template <class T> +char* WriteIntToBufferBackwards(char* ptr, T value); + +//////////////////////////////////////////////////////////////////////////////// + +struct TCaseInsensitiveStringHasher +{ + size_t operator()(TStringBuf arg) const; +}; + +struct TCaseInsensitiveStringEqualityComparer +{ + bool operator()(TStringBuf lhs, TStringBuf rhs) const; +}; + +//////////////////////////////////////////////////////////////////////////////// + +bool TryParseBool(TStringBuf value, bool* result); +bool ParseBool(TStringBuf value); +TStringBuf FormatBool(bool value); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/string/string_builder-inl.h b/library/cpp/yt/string/string_builder-inl.h new file mode 100644 index 0000000000..151fcabf7f --- /dev/null +++ b/library/cpp/yt/string/string_builder-inl.h @@ -0,0 +1,129 @@ +#ifndef STRING_BUILDER_INL_H_ +#error "Direct inclusion of this file is not allowed, include string.h" +// For the sake of sane code completion. +#include "string_builder.h" +#endif + +#include <library/cpp/yt/assert/assert.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +inline char* TStringBuilderBase::Preallocate(size_t size) +{ + if (Y_UNLIKELY(End_ - Current_ < static_cast<ssize_t>(size))) { + size_t length = GetLength(); + auto newLength = std::max(length + size, MinBufferLength); + DoPreallocate(newLength); + Current_ = Begin_ + length; + } + return Current_; +} + +inline size_t TStringBuilderBase::GetLength() const +{ + return Current_ ? Current_ - Begin_ : 0; +} + +inline TStringBuf TStringBuilderBase::GetBuffer() const +{ + return TStringBuf(Begin_, Current_); +} + +inline void TStringBuilderBase::Advance(size_t size) +{ + Current_ += size; + YT_ASSERT(Current_ <= End_); +} + +inline void TStringBuilderBase::AppendChar(char ch) +{ + *Preallocate(1) = ch; + Advance(1); +} + +inline void TStringBuilderBase::AppendChar(char ch, int n) +{ + YT_ASSERT(n >= 0); + if (Y_LIKELY(0 != n)) { + char* dst = Preallocate(n); + ::memset(dst, ch, n); + Advance(n); + } +} + +inline void TStringBuilderBase::AppendString(TStringBuf str) +{ + if (Y_LIKELY(str)) { + char* dst = Preallocate(str.length()); + ::memcpy(dst, str.begin(), str.length()); + Advance(str.length()); + } +} + +inline void TStringBuilderBase::AppendString(const char* str) +{ + AppendString(TStringBuf(str)); +} + +inline void TStringBuilderBase::Reset() +{ + Begin_ = Current_ = End_ = nullptr; + DoReset(); +} + +template <class... TArgs> +void TStringBuilderBase::AppendFormat(TStringBuf format, TArgs&& ... args) +{ + Format(this, format, std::forward<TArgs>(args)...); +} + +template <size_t Length, class... TArgs> +void TStringBuilderBase::AppendFormat(const char (&format)[Length], TArgs&& ... args) +{ + Format(this, format, std::forward<TArgs>(args)...); +} + +//////////////////////////////////////////////////////////////////////////////// + +inline TString TStringBuilder::Flush() +{ + Buffer_.resize(GetLength()); + auto result = std::move(Buffer_); + Reset(); + return result; +} + +inline void TStringBuilder::DoReset() +{ + Buffer_ = {}; +} + +inline void TStringBuilder::DoPreallocate(size_t newLength) +{ + Buffer_.ReserveAndResize(newLength); + auto capacity = Buffer_.capacity(); + Buffer_.ReserveAndResize(capacity); + Begin_ = &*Buffer_.begin(); + End_ = Begin_ + capacity; +} + +//////////////////////////////////////////////////////////////////////////////// + +inline void FormatValue(TStringBuilderBase* builder, const TStringBuilder& value, TStringBuf /*format*/) +{ + builder->AppendString(value.GetBuffer()); +} + +template <class T> +TString ToStringViaBuilder(const T& value, TStringBuf spec) +{ + TStringBuilder builder; + FormatValue(&builder, value, spec); + return builder.Flush(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/string/string_builder.h b/library/cpp/yt/string/string_builder.h new file mode 100644 index 0000000000..0e13e70904 --- /dev/null +++ b/library/cpp/yt/string/string_builder.h @@ -0,0 +1,116 @@ +#pragma once + +#include <util/generic/string.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +// Forward declarations. +class TStringBuilderBase; +class TStringBuilder; +class TDelimitedStringBuilderWrapper; + +template <size_t Length, class... TArgs> +void Format(TStringBuilderBase* builder, const char (&format)[Length], TArgs&&... args); +template <class... TArgs> +void Format(TStringBuilderBase* builder, TStringBuf format, TArgs&&... args); + +//////////////////////////////////////////////////////////////////////////////// + +//! A simple helper for constructing strings by a sequence of appends. +class TStringBuilderBase +{ +public: + virtual ~TStringBuilderBase() = default; + + char* Preallocate(size_t size); + + size_t GetLength() const; + + TStringBuf GetBuffer() const; + + void Advance(size_t size); + + void AppendChar(char ch); + void AppendChar(char ch, int n); + + void AppendString(TStringBuf str); + void AppendString(const char* str); + + template <size_t Length, class... TArgs> + void AppendFormat(const char (&format)[Length], TArgs&&... args); + template <class... TArgs> + void AppendFormat(TStringBuf format, TArgs&&... args); + + void Reset(); + +protected: + char* Begin_ = nullptr; + char* Current_ = nullptr; + char* End_ = nullptr; + + virtual void DoReset() = 0; + virtual void DoPreallocate(size_t newLength) = 0; + + // -64 must account for any reasonable overhead in dynamic string allocation. + static constexpr size_t MinBufferLength = 1024 - 64; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class TStringBuilder + : public TStringBuilderBase +{ +public: + TString Flush(); + +protected: + TString Buffer_; + + void DoReset() override; + void DoPreallocate(size_t size) override; +}; + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +TString ToStringViaBuilder(const T& value, TStringBuf spec = TStringBuf("v")); + +//////////////////////////////////////////////////////////////////////////////// + +//! Appends a certain delimiter starting from the second call. +class TDelimitedStringBuilderWrapper + : private TNonCopyable +{ +public: + TDelimitedStringBuilderWrapper( + TStringBuilderBase* builder, + TStringBuf delimiter = TStringBuf(", ")) + : Builder_(builder) + , Delimiter_(delimiter) + { } + + TStringBuilderBase* operator->() + { + if (!FirstCall_) { + Builder_->AppendString(Delimiter_); + } + FirstCall_ = false; + return Builder_; + } + +private: + TStringBuilderBase* const Builder_; + const TStringBuf Delimiter_; + + bool FirstCall_ = true; +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define STRING_BUILDER_INL_H_ +#include "string_builder-inl.h" +#undef STRING_BUILDER_INL_H_ diff --git a/library/cpp/yt/string/unittests/enum_ut.cpp b/library/cpp/yt/string/unittests/enum_ut.cpp new file mode 100644 index 0000000000..b8076fd8ee --- /dev/null +++ b/library/cpp/yt/string/unittests/enum_ut.cpp @@ -0,0 +1,61 @@ +#include <library/cpp/testing/gtest/gtest.h> + +#include <library/cpp/yt/string/enum.h> +#include <library/cpp/yt/string/format.h> + +#include <limits> + +namespace NYT { +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +// Some compile-time sanity checks. +DEFINE_ENUM(ESample, (One)(Two)); +static_assert(TFormatTraits<ESample>::HasCustomFormatValue); +static_assert(TFormatTraits<TEnumIndexedVector<ESample, int>>::HasCustomFormatValue); + +DEFINE_ENUM(EColor, + (Red) + (BlackAndWhite) +); + +DEFINE_BIT_ENUM(ELangs, + ((None) (0x00)) + ((Cpp) (0x01)) + ((Go) (0x02)) + ((Rust) (0x04)) + ((Python) (0x08)) + ((JavaScript) (0x10)) +) + +TEST(TFormatTest, Enum) +{ + EXPECT_EQ("Red", Format("%v", EColor::Red)); + EXPECT_EQ("red", Format("%lv", EColor::Red)); + + EXPECT_EQ("BlackAndWhite", Format("%v", EColor::BlackAndWhite)); + EXPECT_EQ("black_and_white", Format("%lv", EColor::BlackAndWhite)); + + EXPECT_EQ("EColor(100)", Format("%v", EColor(100))); + + EXPECT_EQ("JavaScript", Format("%v", ELangs::JavaScript)); + EXPECT_EQ("java_script", Format("%lv", ELangs::JavaScript)); + + EXPECT_EQ("None", Format("%v", ELangs::None)); + EXPECT_EQ("none", Format("%lv", ELangs::None)); + + EXPECT_EQ("Cpp | Go", Format("%v", ELangs::Cpp | ELangs::Go)); + EXPECT_EQ("cpp | go", Format("%lv", ELangs::Cpp | ELangs::Go)); + + auto four = ELangs::Cpp | ELangs::Go | ELangs::Python | ELangs::JavaScript; + EXPECT_EQ("Cpp | Go | Python | JavaScript", Format("%v", four)); + EXPECT_EQ("cpp | go | python | java_script", Format("%lv", four)); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace +} // namespace NYT + + diff --git a/library/cpp/yt/string/unittests/format_ut.cpp b/library/cpp/yt/string/unittests/format_ut.cpp new file mode 100644 index 0000000000..ee069bb2c0 --- /dev/null +++ b/library/cpp/yt/string/unittests/format_ut.cpp @@ -0,0 +1,149 @@ +#include <library/cpp/testing/gtest/gtest.h> + +#include <library/cpp/yt/string/format.h> + +#include <library/cpp/yt/small_containers/compact_vector.h> + +#include <limits> + +namespace NYT { +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +// Some compile-time sanity checks. +static_assert(TFormatTraits<int>::HasCustomFormatValue); +static_assert(TFormatTraits<double>::HasCustomFormatValue); +static_assert(TFormatTraits<void*>::HasCustomFormatValue); +static_assert(TFormatTraits<const char*>::HasCustomFormatValue); +static_assert(TFormatTraits<TStringBuf>::HasCustomFormatValue); +static_assert(TFormatTraits<TString>::HasCustomFormatValue); +static_assert(TFormatTraits<std::vector<int>>::HasCustomFormatValue); + +// N.B. TCompactVector<int, 1> is not buildable on Windows +static_assert(TFormatTraits<TCompactVector<int, 2>>::HasCustomFormatValue); +static_assert(TFormatTraits<std::set<int>>::HasCustomFormatValue); +static_assert(TFormatTraits<std::map<int, int>>::HasCustomFormatValue); +static_assert(TFormatTraits<std::multimap<int, int>>::HasCustomFormatValue); +static_assert(TFormatTraits<THashSet<int>>::HasCustomFormatValue); +static_assert(TFormatTraits<THashMap<int, int>>::HasCustomFormatValue); +static_assert(TFormatTraits<THashMultiSet<int>>::HasCustomFormatValue); +static_assert(TFormatTraits<std::pair<int, int>>::HasCustomFormatValue); +static_assert(TFormatTraits<std::optional<int>>::HasCustomFormatValue); +static_assert(TFormatTraits<TDuration>::HasCustomFormatValue); +static_assert(TFormatTraits<TInstant>::HasCustomFormatValue); + +struct TUnformattable +{ }; +static_assert(!TFormatTraits<TUnformattable>::HasCustomFormatValue); + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TFormatTest, Nothing) +{ + EXPECT_EQ("abc", Format("a%nb%nc", 1, 2)); +} + +TEST(TFormatTest, Verbatim) +{ + EXPECT_EQ("", Format("")); + EXPECT_EQ("test", Format("test")); + EXPECT_EQ("%", Format("%%")); + EXPECT_EQ("%hello%world%", Format("%%hello%%world%%")); +} + +TEST(TFormatTest, MultipleArgs) +{ + EXPECT_EQ("2+2=4", Format("%v+%v=%v", 2, 2, 4)); +} + +TEST(TFormatTest, Strings) +{ + EXPECT_EQ("test", Format("%s", "test")); + EXPECT_EQ("test", Format("%s", TStringBuf("test"))); + EXPECT_EQ("test", Format("%s", TString("test"))); + + EXPECT_EQ(" abc", Format("%6s", TString("abc"))); + EXPECT_EQ("abc ", Format("%-6s", TString("abc"))); + EXPECT_EQ(" abc", Format("%10v", TString("abc"))); + EXPECT_EQ("abc ", Format("%-10v", TString("abc"))); + EXPECT_EQ("abc", Format("%2s", TString("abc"))); + EXPECT_EQ("abc", Format("%-2s", TString("abc"))); + EXPECT_EQ("abc", Format("%0s", TString("abc"))); + EXPECT_EQ("abc", Format("%-0s", TString("abc"))); + EXPECT_EQ(100, std::ssize(Format("%100v", "abc"))); +} + +TEST(TFormatTest, Integers) +{ + EXPECT_EQ("123", Format("%d", 123)); + EXPECT_EQ("123", Format("%v", 123)); + + EXPECT_EQ("042", Format("%03d", 42)); + EXPECT_EQ("42", Format("%01d", 42)); + + EXPECT_EQ("2147483647", Format("%d", std::numeric_limits<i32>::max())); + EXPECT_EQ("-2147483648", Format("%d", std::numeric_limits<i32>::min())); + + EXPECT_EQ("0", Format("%u", 0U)); + EXPECT_EQ("0", Format("%v", 0U)); + EXPECT_EQ("4294967295", Format("%u", std::numeric_limits<ui32>::max())); + EXPECT_EQ("4294967295", Format("%v", std::numeric_limits<ui32>::max())); + + EXPECT_EQ("9223372036854775807", Format("%" PRId64, std::numeric_limits<i64>::max())); + EXPECT_EQ("9223372036854775807", Format("%v", std::numeric_limits<i64>::max())); + EXPECT_EQ("-9223372036854775808", Format("%" PRId64, std::numeric_limits<i64>::min())); + EXPECT_EQ("-9223372036854775808", Format("%v", std::numeric_limits<i64>::min())); + + EXPECT_EQ("0", Format("%" PRIu64, 0ULL)); + EXPECT_EQ("0", Format("%v", 0ULL)); + EXPECT_EQ("18446744073709551615", Format("%" PRIu64, std::numeric_limits<ui64>::max())); + EXPECT_EQ("18446744073709551615", Format("%v", std::numeric_limits<ui64>::max())); +} + +TEST(TFormatTest, Floats) +{ + EXPECT_EQ("3.14", Format("%.2f", 3.1415F)); + EXPECT_EQ("3.14", Format("%.2v", 3.1415F)); + EXPECT_EQ("3.14", Format("%.2lf", 3.1415)); + EXPECT_EQ("3.14", Format("%.2v", 3.1415)); + EXPECT_EQ(TString(std::to_string(std::numeric_limits<double>::max())), + Format("%lF", std::numeric_limits<double>::max())); +} + +TEST(TFormatTest, Bool) +{ + EXPECT_EQ("True", Format("%v", true)); + EXPECT_EQ("False", Format("%v", false)); + EXPECT_EQ("true", Format("%lv", true)); + EXPECT_EQ("false", Format("%lv", false)); +} + +TEST(TFormatTest, Quotes) +{ + EXPECT_EQ("\"True\"", Format("%Qv", true)); + EXPECT_EQ("'False'", Format("%qv", false)); + EXPECT_EQ("'\\\'\"'", Format("%qv", "\'\"")); + EXPECT_EQ("\"\\x01\"", Format("%Qv", "\x1")); + EXPECT_EQ("'\\x1b'", Format("%qv", '\x1b')); +} + +TEST(TFormatTest, Nullable) +{ + EXPECT_EQ("1", Format("%v", std::make_optional<int>(1))); + EXPECT_EQ("<null>", Format("%v", std::nullopt)); + EXPECT_EQ("<null>", Format("%v", std::optional<int>())); + EXPECT_EQ("3.14", Format("%.2f", std::optional<double>(3.1415))); +} + +TEST(TFormatTest, Pointers) +{ + // No idea if pointer format is standardized, check against Sprintf. + auto p = reinterpret_cast<void*>(123); + EXPECT_EQ(Sprintf("%p", reinterpret_cast<void*>(123)), Format("%p", p)); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace +} // namespace NYT diff --git a/library/cpp/yt/string/unittests/guid_ut.cpp b/library/cpp/yt/string/unittests/guid_ut.cpp new file mode 100644 index 0000000000..4b5eebea16 --- /dev/null +++ b/library/cpp/yt/string/unittests/guid_ut.cpp @@ -0,0 +1,58 @@ +#include <library/cpp/testing/gtest/gtest.h> + +#include <library/cpp/yt/string/guid.h> +#include <library/cpp/yt/string/format.h> + +#include <util/string/hex.h> + +namespace NYT { +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +static_assert(TFormatTraits<TGuid>::HasCustomFormatValue); + +TString CanonicalToString(TGuid value) +{ + return Sprintf("%x-%x-%x-%x", + value.Parts32[3], + value.Parts32[2], + value.Parts32[1], + value.Parts32[0]); +} + +const ui32 TrickyValues[] = { + 0, 0x1, 0x12, 0x123, 0x1234, 0x12345, 0x123456, 0x1234567, 0x12345678 +}; + +TEST(TGuidTest, FormatAllTricky) +{ + for (ui32 a : TrickyValues) { + for (ui32 b : TrickyValues) { + for (ui32 c : TrickyValues) { + for (ui32 d : TrickyValues) { + auto value = TGuid(a, b, c, d); + EXPECT_EQ(CanonicalToString(value), ToString(value)); + } + } + } + } +} + +TEST(TGuidTest, FormatAllSymbols) +{ + const auto Value = TGuid::FromString("12345678-abcdef01-12345678-abcdef01"); + EXPECT_EQ(CanonicalToString(Value), ToString(Value)); +} + +TEST(TGuidTest, ByteOrder) +{ + auto guid = TGuid::FromStringHex32("12345678ABCDEF0112345678ABCDEF01"); + TString bytes{reinterpret_cast<const char*>(&(guid.Parts32[0])), 16}; + EXPECT_EQ(HexEncode(bytes), "01EFCDAB7856341201EFCDAB78563412"); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace +} // namespace NYT diff --git a/library/cpp/yt/string/unittests/string_ut.cpp b/library/cpp/yt/string/unittests/string_ut.cpp new file mode 100644 index 0000000000..3e12312af0 --- /dev/null +++ b/library/cpp/yt/string/unittests/string_ut.cpp @@ -0,0 +1,52 @@ +#include <library/cpp/testing/gtest/gtest.h> + +#include <library/cpp/yt/string/string.h> + +namespace NYT { +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +struct TTestCase +{ + const char* UnderCase; + const char* CamelCase; +}; + +static std::vector<TTestCase> TestCases { + { "kenny", "Kenny" }, + { "south_park", "SouthPark" }, + { "a", "A" }, + { "a_b_c", "ABC" }, + { "reed_solomon_6_3", "ReedSolomon_6_3" }, + { "lrc_12_2_2", "Lrc_12_2_2" }, + { "0", "0" }, + { "0_1_2", "0_1_2" }, + { "int64", "Int64" } +}; + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TStringTest, UnderscoreCaseToCamelCase) +{ + for (const auto& testCase : TestCases) { + auto result = UnderscoreCaseToCamelCase(testCase.UnderCase); + EXPECT_STREQ(testCase.CamelCase, result.c_str()) + << "Original: \"" << testCase.UnderCase << '"'; + } +} + +TEST(TStringTest, CamelCaseToUnderscoreCase) +{ + for (const auto& testCase : TestCases) { + auto result = CamelCaseToUnderscoreCase(testCase.CamelCase); + EXPECT_STREQ(testCase.UnderCase, result.c_str()) + << "Original: \"" << testCase.CamelCase << '"'; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace +} // namespace NYT + diff --git a/library/cpp/yt/string/unittests/ya.make b/library/cpp/yt/string/unittests/ya.make new file mode 100644 index 0000000000..9d539758d1 --- /dev/null +++ b/library/cpp/yt/string/unittests/ya.make @@ -0,0 +1,17 @@ +GTEST(unittester-library-string-helpers) + +OWNER(g:yt) + +SRCS( + enum_ut.cpp + format_ut.cpp + guid_ut.cpp + string_ut.cpp +) + +PEERDIR( + library/cpp/yt/string + library/cpp/testing/gtest +) + +END() diff --git a/library/cpp/yt/string/ya.make b/library/cpp/yt/string/ya.make new file mode 100644 index 0000000000..83efd5eb2f --- /dev/null +++ b/library/cpp/yt/string/ya.make @@ -0,0 +1,30 @@ +LIBRARY() + +SRCS( + enum.cpp + guid.cpp + string.cpp +) + +PEERDIR( + library/cpp/yt/assert + library/cpp/yt/exception + library/cpp/yt/misc +) + +CHECK_DEPENDENT_DIRS( + ALLOW_ONLY ALL + build + contrib + library + util + library/cpp/yt/assert + library/cpp/yt/misc + library/cpp/yt/small_containers +) + +END() + +RECURSE_FOR_TESTS( + unittests +) diff --git a/library/cpp/yt/ya.make b/library/cpp/yt/ya.make new file mode 100644 index 0000000000..f4d43806f4 --- /dev/null +++ b/library/cpp/yt/ya.make @@ -0,0 +1,24 @@ +RECURSE( + assert + coding + exception + misc + string + system + yson + yson_string +) + +IF (NOT OS_WINDOWS) + RECURSE( + containers + cpu_clock + logging + malloc + memory + mlock + phdr_cache + small_containers + threading + ) +ENDIF() diff --git a/library/cpp/yt/yson/consumer.cpp b/library/cpp/yt/yson/consumer.cpp new file mode 100644 index 0000000000..9b68ee8a22 --- /dev/null +++ b/library/cpp/yt/yson/consumer.cpp @@ -0,0 +1,16 @@ +#include "consumer.h" + +#include <library/cpp/yt/yson_string/string.h> + +namespace NYT::NYson { + +//////////////////////////////////////////////////////////////////////////////// + +void IYsonConsumer::OnRaw(const TYsonStringBuf& yson) +{ + OnRaw(yson.AsStringBuf(), yson.GetType()); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NYson diff --git a/library/cpp/yt/yson/consumer.h b/library/cpp/yt/yson/consumer.h new file mode 100644 index 0000000000..ea5f586b91 --- /dev/null +++ b/library/cpp/yt/yson/consumer.h @@ -0,0 +1,111 @@ +#pragma once + +#include <util/generic/strbuf.h> + +#include <util/system/defaults.h> + +#include <library/cpp/yt/yson_string/public.h> + +namespace NYT::NYson { + +//////////////////////////////////////////////////////////////////////////////// + +//! A SAX-like interface for parsing a YSON stream. +struct IYsonConsumer +{ + virtual ~IYsonConsumer() = default; + + //! The current item is a string scalar (IStringNode). + /*! + * \param value A scalar value. + */ + virtual void OnStringScalar(TStringBuf value) = 0; + + //! The current item is an integer scalar (IInt64Node). + /*! + * \param value A scalar value. + */ + virtual void OnInt64Scalar(i64 value) = 0; + + //! The current item is an integer scalar (IUint64Node). + /*! + * \param value A scalar value. + */ + virtual void OnUint64Scalar(ui64 scalar) = 0; + + //! The current item is an FP scalar (IDoubleNode). + /*! + * \param value A scalar value. + */ + virtual void OnDoubleScalar(double value) = 0; + + //! The current item is an boolean scalar (IBooleanNode). + /*! + * \param value A scalar value. + */ + virtual void OnBooleanScalar(bool value) = 0; + + //! The current item is an entity (IEntityNode). + virtual void OnEntity() = 0; + + //! Starts a list (IListNode). + /*! + * The events describing a list are raised as follows: + * - #OnBeginList + * - For each item: #OnListItem followed by the description of the item + * - #OnEndList + */ + virtual void OnBeginList() = 0; + + //! Designates a list item. + virtual void OnListItem() = 0; + + //! Ends the current list. + virtual void OnEndList() = 0; + + //! Starts a map (IMapNode). + /*! + * The events describing a map are raised as follows: + * - #OnBeginMap + * - For each item: #OnKeyedItem followed by the description of the item + * - #OnEndMap + */ + virtual void OnBeginMap() = 0; + + //! Designates a keyed item (in map or in attributes). + /*! + * \param key Item key in the map. + */ + virtual void OnKeyedItem(TStringBuf key) = 0; + + //! Ends the current map. + virtual void OnEndMap() = 0; + + //! Starts attributes. + /*! + * An arbitrary node may be preceeded by attributes. + * + * The events describing attributes are raised as follows: + * - #OnBeginAttributes + * - For each item: #OnKeyedItem followed by the description of the item + * - #OnEndAttributes + */ + virtual void OnBeginAttributes() = 0; + + //! Ends the current attribute list. + virtual void OnEndAttributes() = 0; + + //! Inserts YSON-serialized node or fragment. + /*! + * \param yson Serialized data. + * \param type Type of data. + */ + virtual void OnRaw(TStringBuf yson, EYsonType type) = 0; + + // Extension methods. + void OnRaw(const TYsonStringBuf& yson); +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NYson diff --git a/library/cpp/yt/yson/public.h b/library/cpp/yt/yson/public.h new file mode 100644 index 0000000000..68cdcd38c1 --- /dev/null +++ b/library/cpp/yt/yson/public.h @@ -0,0 +1,11 @@ +#pragma once + +namespace NYT::NYson { + +//////////////////////////////////////////////////////////////////////////////// + +struct IYsonConsumer; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NYson diff --git a/library/cpp/yt/yson/ya.make b/library/cpp/yt/yson/ya.make new file mode 100644 index 0000000000..d914352c4b --- /dev/null +++ b/library/cpp/yt/yson/ya.make @@ -0,0 +1,11 @@ +LIBRARY() + +SRCS( + consumer.cpp +) + +PEERDIR( + library/cpp/yt/yson_string +) + +END() diff --git a/library/cpp/yt/yson_string/convert.cpp b/library/cpp/yt/yson_string/convert.cpp new file mode 100644 index 0000000000..27f5c30d01 --- /dev/null +++ b/library/cpp/yt/yson_string/convert.cpp @@ -0,0 +1,381 @@ +#include "convert.h" +#include "format.h" + +#include <library/cpp/yt/assert/assert.h> + +#include <library/cpp/yt/string/format.h> + +#include <library/cpp/yt/coding/varint.h> + +#include <library/cpp/yt/misc/cast.h> + +#include <array> + +#include <util/stream/mem.h> + +namespace NYT::NYson { + +//////////////////////////////////////////////////////////////////////////////// + +template <> +TYsonString ConvertToYsonString<i8>(const i8& value) +{ + return ConvertToYsonString(static_cast<i64>(value)); +} + +template <> +TYsonString ConvertToYsonString<i32>(const i32& value) +{ + return ConvertToYsonString(static_cast<i64>(value)); +} + +template <> +TYsonString ConvertToYsonString<i64>(const i64& value) +{ + std::array<char, 1 + MaxVarInt64Size> buffer; + auto* ptr = buffer.data(); + *ptr++ = NDetail::Int64Marker; + ptr += WriteVarInt64(ptr, value); + return TYsonString(TStringBuf(buffer.data(), ptr - buffer.data())); +} + +template <> +TYsonString ConvertToYsonString<ui8>(const ui8& value) +{ + return ConvertToYsonString(static_cast<ui64>(value)); +} + +template <> +TYsonString ConvertToYsonString<ui32>(const ui32& value) +{ + return ConvertToYsonString(static_cast<ui64>(value)); +} + +template <> +TYsonString ConvertToYsonString<ui64>(const ui64& value) +{ + std::array<char, 1 + MaxVarInt64Size> buffer; + auto* ptr = buffer.data(); + *ptr++ = NDetail::Uint64Marker; + ptr += WriteVarUint64(ptr, value); + return TYsonString(TStringBuf(buffer.data(), ptr - buffer.data())); +} + +template <> +TYsonString ConvertToYsonString<TString>(const TString& value) +{ + return ConvertToYsonString(static_cast<TStringBuf>(value)); +} + +struct TConvertStringToYsonStringTag +{ }; + +template <> +TYsonString ConvertToYsonString<TStringBuf>(const TStringBuf& value) +{ + auto buffer = TSharedMutableRef::Allocate<TConvertStringToYsonStringTag>( + 1 + MaxVarInt64Size + value.length(), + /*initializeStorage*/ false); + auto* ptr = buffer.Begin(); + *ptr++ = NDetail::StringMarker; + ptr += WriteVarInt64(ptr, static_cast<i64>(value.length())); + ::memcpy(ptr, value.data(), value.length()); + ptr += value.length(); + return TYsonString(buffer.Slice(buffer.Begin(), ptr)); +} + +TYsonString ConvertToYsonString(const char* value) +{ + return ConvertToYsonString(TStringBuf(value)); +} + +template <> +TYsonString ConvertToYsonString<float>(const float& value) +{ + return ConvertToYsonString(static_cast<double>(value)); +} + +template <> +TYsonString ConvertToYsonString<double>(const double& value) +{ + std::array<char, 1 + sizeof(double)> buffer; + auto* ptr = buffer.data(); + *ptr++ = NDetail::DoubleMarker; + ::memcpy(ptr, &value, sizeof(value)); + ptr += sizeof(value); + return TYsonString(TStringBuf(buffer.data(), ptr - buffer.data())); +} + +template <> +TYsonString ConvertToYsonString<bool>(const bool& value) +{ + char ch = value ? NDetail::TrueMarker : NDetail::FalseMarker; + return TYsonString(TStringBuf(&ch, 1)); +} + +template <> +TYsonString ConvertToYsonString<TInstant>(const TInstant& value) +{ + return ConvertToYsonString(value.ToString()); +} + +template <> +TYsonString ConvertToYsonString<TDuration>(const TDuration& value) +{ + return ConvertToYsonString(value.MilliSeconds()); +} + +template <> +TYsonString ConvertToYsonString<TGuid>(const TGuid& value) +{ + std::array<char, MaxGuidStringSize> guidBuffer; + auto guidLength = WriteGuidToBuffer(guidBuffer.data(), value) - guidBuffer.data(); + std::array<char, 1 + MaxVarInt64Size + MaxGuidStringSize> ysonBuffer; + auto* ptr = ysonBuffer.data(); + *ptr++ = NDetail::StringMarker; + ptr += WriteVarInt64(ptr, static_cast<i64>(guidLength)); + ::memcpy(ptr, guidBuffer.data(), guidLength); + ptr += guidLength; + return TYsonString(TStringBuf(ysonBuffer.data(), ptr - ysonBuffer.data())); +} + +//////////////////////////////////////////////////////////////////////////////// + +namespace { + +TString FormatUnexpectedMarker(char ch) +{ + switch (ch) { + case NDetail::BeginListSymbol: + return "list"; + case NDetail::BeginMapSymbol: + return "map"; + case NDetail::BeginAttributesSymbol: + return "attributes"; + case NDetail::EntitySymbol: + return "\"entity\" literal"; + case NDetail::StringMarker: + return "\"string\" literal"; + case NDetail::Int64Marker: + return "\"int64\" literal"; + case NDetail::DoubleMarker: + return "\"double\" literal"; + case NDetail::FalseMarker: + case NDetail::TrueMarker: + return "\"boolean\" literal"; + case NDetail::Uint64Marker: + return "\"uint64\" literal"; + default: + return Format("unexpected symbol %qv", ch); + } +} + +i64 ParseInt64FromYsonString(const TYsonStringBuf& str) +{ + YT_ASSERT(str.GetType() == EYsonType::Node); + auto strBuf = str.AsStringBuf(); + TMemoryInput input(strBuf.data(), strBuf.length()); + char ch; + if (!input.ReadChar(ch)) { + throw TYsonLiteralParseException("Missing type marker"); + } + if (ch != NDetail::Int64Marker) { + throw TYsonLiteralParseException(Format("Unexpected %v", + FormatUnexpectedMarker(ch))); + } + i64 result; + try { + ReadVarInt64(&input, &result); + } catch (const std::exception& ex) { + throw TYsonLiteralParseException(ex, "Failed to decode \"int64\" value"); + } + return result; +} + +ui64 ParseUint64FromYsonString(const TYsonStringBuf& str) +{ + YT_ASSERT(str.GetType() == EYsonType::Node); + auto strBuf = str.AsStringBuf(); + TMemoryInput input(strBuf.data(), strBuf.length()); + char ch; + if (!input.ReadChar(ch)) { + throw TYsonLiteralParseException("Missing type marker"); + } + if (ch != NDetail::Uint64Marker) { + throw TYsonLiteralParseException(Format("Unexpected %v", + FormatUnexpectedMarker(ch))); + } + ui64 result; + try { + ReadVarUint64(&input, &result); + } catch (const std::exception& ex) { + throw TYsonLiteralParseException(ex, "Failed to decode \"uint64\" value"); + } + return result; +} + +TString ParseStringFromYsonString(const TYsonStringBuf& str) +{ + YT_ASSERT(str.GetType() == EYsonType::Node); + auto strBuf = str.AsStringBuf(); + TMemoryInput input(strBuf.data(), strBuf.length()); + char ch; + if (!input.ReadChar(ch)) { + throw TYsonLiteralParseException("Missing type marker"); + } + if (ch != NDetail::StringMarker) { + throw TYsonLiteralParseException(Format("Unexpected %v", + FormatUnexpectedMarker(ch))); + } + i64 length; + try { + ReadVarInt64(&input, &length); + } catch (const std::exception& ex) { + throw TYsonLiteralParseException(ex, "Failed to decode string length"); + } + if (length < 0) { + throw TYsonLiteralParseException(Format("Negative string length ", + length)); + } + if (static_cast<i64>(input.Avail()) != length) { + throw TYsonLiteralParseException(Format("Incorrect remaining string length: expected %v, got %v", + length, + input.Avail())); + } + TString result; + result.ReserveAndResize(length); + YT_VERIFY(static_cast<i64>(input.Read(result.Detach(), length)) == length); + return result; +} + +double ParseDoubleFromYsonString(const TYsonStringBuf& str) +{ + YT_ASSERT(str.GetType() == EYsonType::Node); + auto strBuf = str.AsStringBuf(); + TMemoryInput input(strBuf.data(), strBuf.length()); + char ch; + if (!input.ReadChar(ch)) { + throw TYsonLiteralParseException("Missing type marker"); + } + if (ch != NDetail::DoubleMarker) { + throw TYsonLiteralParseException(Format("Unexpected %v", + FormatUnexpectedMarker(ch))); + } + if (input.Avail() != sizeof(double)) { + throw TYsonLiteralParseException(Format("Incorrect remaining string length: expected %v, got %v", + sizeof(double), + input.Avail())); + } + double result; + YT_VERIFY(input.Read(&result, sizeof(result))); + return result; +} + +} // namespace + +#define PARSE(type, underlyingType) \ + template <> \ + type ConvertFromYsonString<type>(const TYsonStringBuf& str) \ + { \ + try { \ + return CheckedIntegralCast<type>(Parse ## underlyingType ## FromYsonString(str)); \ + } catch (const std::exception& ex) { \ + throw TYsonLiteralParseException(ex, "Error parsing \"" #type "\" value from YSON"); \ + } \ + } + +PARSE(i8, Int64 ) +PARSE(i16, Int64 ) +PARSE(i32, Int64 ) +PARSE(i64, Int64 ) +PARSE(ui8, Uint64) +PARSE(ui16, Uint64) +PARSE(ui32, Uint64) +PARSE(ui64, Uint64) + +#undef PARSE + +template <> +TString ConvertFromYsonString<TString>(const TYsonStringBuf& str) +{ + try { + return ParseStringFromYsonString(str); + } catch (const std::exception& ex) { + throw TYsonLiteralParseException(ex, "Error parsing \"string\" value from YSON"); + } +} + +template <> +float ConvertFromYsonString<float>(const TYsonStringBuf& str) +{ + try { + return static_cast<float>(ParseDoubleFromYsonString(str)); + } catch (const std::exception& ex) { + throw TYsonLiteralParseException(ex, "Error parsing \"float\" value from YSON"); + } +} + +template <> +double ConvertFromYsonString<double>(const TYsonStringBuf& str) +{ + try { + return ParseDoubleFromYsonString(str); + } catch (const std::exception& ex) { + throw TYsonLiteralParseException(ex, "Error parsing \"double\" value from YSON"); + } +} + +template <> +bool ConvertFromYsonString<bool>(const TYsonStringBuf& str) +{ + try { + YT_ASSERT(str.GetType() == EYsonType::Node); + auto strBuf = str.AsStringBuf(); + TMemoryInput input(strBuf.data(), strBuf.length()); + char ch; + if (!input.ReadChar(ch)) { + throw TYsonLiteralParseException("Missing type marker"); + } + if (ch != NDetail::TrueMarker && ch != NDetail::FalseMarker) { + throw TYsonLiteralParseException(Format("Unexpected %v", + FormatUnexpectedMarker(ch))); + } + return ch == NDetail::TrueMarker; + } catch (const std::exception& ex) { + throw TYsonLiteralParseException(ex, "Error parsing \"boolean\" value from YSON"); + } +} + +template <> +TInstant ConvertFromYsonString<TInstant>(const TYsonStringBuf& str) +{ + try { + return TInstant::ParseIso8601(ParseStringFromYsonString(str)); + } catch (const std::exception& ex) { + throw TYsonLiteralParseException(ex, "Error parsing \"instant\" value from YSON"); + } +} + +template <> +TDuration ConvertFromYsonString<TDuration>(const TYsonStringBuf& str) +{ + try { + return TDuration::MilliSeconds(ParseUint64FromYsonString(str)); + } catch (const std::exception& ex) { + throw TYsonLiteralParseException(ex, "Error parsing \"duration\" value from YSON"); + } +} + +template <> +TGuid ConvertFromYsonString<TGuid>(const TYsonStringBuf& str) +{ + try { + return TGuid::FromString(ParseStringFromYsonString(str)); + } catch (const std::exception& ex) { + throw TYsonLiteralParseException(ex, "Error parsing \"guid\" value from YSON"); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NYson diff --git a/library/cpp/yt/yson_string/convert.h b/library/cpp/yt/yson_string/convert.h new file mode 100644 index 0000000000..3c2cc7d284 --- /dev/null +++ b/library/cpp/yt/yson_string/convert.h @@ -0,0 +1,114 @@ +#pragma once + +#include "string.h" + +#include <library/cpp/yt/misc/guid.h> + +#include <library/cpp/yt/exception/exception.h> + +#include <util/generic/string.h> + +#include <util/datetime/base.h> + +namespace NYT::NYson { + +//////////////////////////////////////////////////////////////////////////////// +// Generic forward declarations. + +template <class T> +TYsonString ConvertToYsonString(const T& value); + +template <class T> +TYsonString ConvertToYsonString(const T& value, EYsonFormat format); + +template <class T> +T ConvertFromYsonString(const TYsonStringBuf& str); + +//////////////////////////////////////////////////////////////////////////////// +// Basic specializations for ConvertToYsonString. + +template <> +TYsonString ConvertToYsonString<i8>(const i8& value); +template <> +TYsonString ConvertToYsonString<i32>(const i32& value); +template <> +TYsonString ConvertToYsonString<i64>(const i64& value); + +template <> +TYsonString ConvertToYsonString<ui8>(const ui8& value); +template <> +TYsonString ConvertToYsonString<ui32>(const ui32& value); +template <> +TYsonString ConvertToYsonString<ui64>(const ui64& value); + +template <> +TYsonString ConvertToYsonString<TString>(const TString& value); +template <> +TYsonString ConvertToYsonString<TStringBuf>(const TStringBuf& value); +TYsonString ConvertToYsonString(const char* value); + +template <> +TYsonString ConvertToYsonString<float>(const float& value); +template <> +TYsonString ConvertToYsonString<double>(const double& value); + +template <> +TYsonString ConvertToYsonString<bool>(const bool& value); + +template <> +TYsonString ConvertToYsonString<TInstant>(const TInstant& value); + +template <> +TYsonString ConvertToYsonString<TDuration>(const TDuration& value); + +template <> +TYsonString ConvertToYsonString<TGuid>(const TGuid& value); + +//////////////////////////////////////////////////////////////////////////////// +// Basic specializations for ConvertFromYsonString. +// Note: these currently support a subset of NYT::NYTree::Convert features. + +class TYsonLiteralParseException + : public TCompositeException +{ +public: + using TCompositeException::TCompositeException; +}; + +template <> +i8 ConvertFromYsonString<i8>(const TYsonStringBuf& str); +template <> +i32 ConvertFromYsonString<i32>(const TYsonStringBuf& str); +template <> +i64 ConvertFromYsonString<i64>(const TYsonStringBuf& str); + +template <> +ui8 ConvertFromYsonString<ui8>(const TYsonStringBuf& str); +template <> +ui32 ConvertFromYsonString<ui32>(const TYsonStringBuf& str); +template <> +ui64 ConvertFromYsonString<ui64>(const TYsonStringBuf& str); + +template <> +TString ConvertFromYsonString<TString>(const TYsonStringBuf& str); + +template <> +float ConvertFromYsonString<float>(const TYsonStringBuf& str); +template <> +double ConvertFromYsonString<double>(const TYsonStringBuf& str); + +template <> +bool ConvertFromYsonString<bool>(const TYsonStringBuf& str); + +template <> +TInstant ConvertFromYsonString<TInstant>(const TYsonStringBuf& str); + +template <> +TDuration ConvertFromYsonString<TDuration>(const TYsonStringBuf& str); + +template <> +TGuid ConvertFromYsonString<TGuid>(const TYsonStringBuf& str); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NYson diff --git a/library/cpp/yt/yson_string/format.h b/library/cpp/yt/yson_string/format.h new file mode 100644 index 0000000000..2efd4fa39a --- /dev/null +++ b/library/cpp/yt/yson_string/format.h @@ -0,0 +1,44 @@ +#pragma once + +namespace NYT::NYson::NDetail { + +//////////////////////////////////////////////////////////////////////////////// + +//! Indicates the beginning of a list. +constexpr char BeginListSymbol = '['; +//! Indicates the end of a list. +constexpr char EndListSymbol = ']'; + +//! Indicates the beginning of a map. +constexpr char BeginMapSymbol = '{'; +//! Indicates the end of a map. +constexpr char EndMapSymbol = '}'; + +//! Indicates the beginning of an attribute map. +constexpr char BeginAttributesSymbol = '<'; +//! Indicates the end of an attribute map. +constexpr char EndAttributesSymbol = '>'; + +//! Separates items in lists, maps, attributes. +constexpr char ItemSeparatorSymbol = ';'; +//! Separates keys from values in maps. +constexpr char KeyValueSeparatorSymbol = '='; + +//! Indicates an entity. +constexpr char EntitySymbol = '#'; +//! Marks the beginning of a binary string literal. +constexpr char StringMarker = '\x01'; +//! Marks the beginning of a binary i64 literal. +constexpr char Int64Marker = '\x02'; +//! Marks the beginning of a binary double literal. +constexpr char DoubleMarker = '\x03'; +//! Marks |false| boolean value. +constexpr char FalseMarker = '\x04'; +//! Marks |true| boolean value. +constexpr char TrueMarker = '\x05'; +//! Marks the beginning of a binary ui64 literal. +constexpr char Uint64Marker = '\x06'; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NYson::NDetail diff --git a/library/cpp/yt/yson_string/public.h b/library/cpp/yt/yson_string/public.h new file mode 100644 index 0000000000..42c1ce80bb --- /dev/null +++ b/library/cpp/yt/yson_string/public.h @@ -0,0 +1,39 @@ +#pragma once + +#include <library/cpp/yt/misc/enum.h> + +namespace NYT::NYson { + +//////////////////////////////////////////////////////////////////////////////// + +//! The data format. +DEFINE_ENUM(EYsonFormat, + // Binary. + // Most compact but not human-readable. + (Binary) + + // Text. + // Not so compact but human-readable. + // Does not use indentation. + // Uses escaping for non-text characters. + (Text) + + // Text with indentation. + // Extremely verbose but human-readable. + // Uses escaping for non-text characters. + (Pretty) +); + +// NB: -1 is used for serializing null TYsonString. +DEFINE_ENUM_WITH_UNDERLYING_TYPE(EYsonType, i8, + ((Node) (0)) + ((ListFragment) (1)) + ((MapFragment) (2)) +); + +class TYsonString; +class TYsonStringBuf; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NYson diff --git a/library/cpp/yt/yson_string/string-inl.h b/library/cpp/yt/yson_string/string-inl.h new file mode 100644 index 0000000000..5c41629cc0 --- /dev/null +++ b/library/cpp/yt/yson_string/string-inl.h @@ -0,0 +1,93 @@ +#ifndef STRING_INL_H_ +#error "Direct inclusion of this file is not allowed, include string.h" +// For the sake of sane code completion. +#include "string.h" +#endif + +namespace NYT::NYson { + +//////////////////////////////////////////////////////////////////////////////// + +namespace NDetail { + +template <typename TLeft, typename TRight> +bool Equals(const TLeft& lhs, const TRight& rhs) +{ + auto lhsNull = !lhs.operator bool(); + auto rhsNull = !rhs.operator bool(); + if (lhsNull != rhsNull) { + return false; + } + if (lhsNull && rhsNull) { + return true; + } + return + lhs.AsStringBuf() == rhs.AsStringBuf() && + lhs.GetType() == rhs.GetType(); +} + +} // namespace NDetail + +inline bool operator == (const TYsonString& lhs, const TYsonString& rhs) +{ + return NDetail::Equals(lhs, rhs); +} + +inline bool operator == (const TYsonString& lhs, const TYsonStringBuf& rhs) +{ + return NDetail::Equals(lhs, rhs); +} + +inline bool operator == (const TYsonStringBuf& lhs, const TYsonString& rhs) +{ + return NDetail::Equals(lhs, rhs); +} + +inline bool operator == (const TYsonStringBuf& lhs, const TYsonStringBuf& rhs) +{ + return NDetail::Equals(lhs, rhs); +} + +inline bool operator != (const TYsonString& lhs, const TYsonString& rhs) +{ + return !(lhs == rhs); +} + +inline bool operator != (const TYsonString& lhs, const TYsonStringBuf& rhs) +{ + return !(lhs == rhs); +} + +inline bool operator != (const TYsonStringBuf& lhs, const TYsonString& rhs) +{ + return !(lhs == rhs); +} + +inline bool operator != (const TYsonStringBuf& lhs, const TYsonStringBuf& rhs) +{ + return !(lhs == rhs); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NYson + +//! A hasher for TYsonString +template <> +struct THash<NYT::NYson::TYsonString> +{ + size_t operator () (const NYT::NYson::TYsonString& str) const + { + return str.ComputeHash(); + } +}; + +//! A hasher for TYsonStringBuf +template <> +struct THash<NYT::NYson::TYsonStringBuf> +{ + size_t operator () (const NYT::NYson::TYsonStringBuf& str) const + { + return THash<TStringBuf>()(str.AsStringBuf()); + } +}; diff --git a/library/cpp/yt/yson_string/string.cpp b/library/cpp/yt/yson_string/string.cpp new file mode 100644 index 0000000000..99d45e8616 --- /dev/null +++ b/library/cpp/yt/yson_string/string.cpp @@ -0,0 +1,185 @@ +#include "string.h" + +#include <library/cpp/yt/assert/assert.h> + +#include <library/cpp/yt/misc/variant.h> + +#include <library/cpp/yt/memory/new.h> + +namespace NYT::NYson { + +//////////////////////////////////////////////////////////////////////////////// + +TYsonStringBuf::TYsonStringBuf() +{ + Type_ = EYsonType::Node; // fake + Null_ = true; +} + +TYsonStringBuf::TYsonStringBuf(const TYsonString& ysonString) +{ + if (ysonString) { + Data_ = ysonString.AsStringBuf(); + Type_ = ysonString.GetType(); + Null_ = false; + } else { + Type_ = EYsonType::Node; // fake + Null_ = true; + } +} + +TYsonStringBuf::TYsonStringBuf(const TString& data, EYsonType type) + : TYsonStringBuf(TStringBuf(data), type) +{ } + +TYsonStringBuf::TYsonStringBuf(TStringBuf data, EYsonType type) + : Data_(data) + , Type_(type) + , Null_(false) +{ } + +TYsonStringBuf::TYsonStringBuf(const char* data, EYsonType type) + : TYsonStringBuf(TStringBuf(data), type) +{ } + +TYsonStringBuf::operator bool() const +{ + return !Null_; +} + +TStringBuf TYsonStringBuf::AsStringBuf() const +{ + YT_VERIFY(*this); + return Data_; +} + +EYsonType TYsonStringBuf::GetType() const +{ + YT_VERIFY(*this); + return Type_; +} + +//////////////////////////////////////////////////////////////////////////////// + +TYsonString::TYsonString() +{ + Begin_ = nullptr; + Size_ = 0; + Type_ = EYsonType::Node; // fake +} + +TYsonString::TYsonString(const TYsonStringBuf& ysonStringBuf) +{ + if (ysonStringBuf) { + struct TCapturedYsonStringPayload + : public TRefCounted + , public TWithExtraSpace<TCapturedYsonStringPayload> + { + char* GetData() + { + return static_cast<char*>(GetExtraSpacePtr()); + } + }; + + auto data = ysonStringBuf.AsStringBuf(); + auto payload = NewWithExtraSpace<TCapturedYsonStringPayload>(data.length()); + ::memcpy(payload->GetData(), data.data(), data.length()); + Payload_ = payload; + Begin_ = payload->GetData(); + Size_ = data.Size(); + Type_ = ysonStringBuf.GetType(); + } else { + Begin_ = nullptr; + Size_ = 0; + Type_ = EYsonType::Node; // fake + } +} + +TYsonString::TYsonString( + TStringBuf data, + EYsonType type) + : TYsonString(TYsonStringBuf(data, type)) +{ } + +#ifdef TSTRING_IS_STD_STRING +TYsonString::TYsonString( + const TString& data, + EYsonType type) + : TYsonString(TYsonStringBuf(data, type)) +{ } +#else +TYsonString::TYsonString( + const TString& data, + EYsonType type) +{ + // NOTE: CoW TString implementation is assumed + // Moving the payload MUST NOT invalidate its internal pointers + Payload_ = data; + Begin_ = data.data(); + Size_ = data.length(); + Type_ = type; +} +#endif + +TYsonString::TYsonString( + const TSharedRef& data, + EYsonType type) +{ + Payload_ = data.GetHolder(); + Begin_ = data.Begin(); + Size_ = data.Size(); + Type_ = type; +} + +TYsonString::operator bool() const +{ + return !std::holds_alternative<TNullPayload>(Payload_); +} + +EYsonType TYsonString::GetType() const +{ + YT_VERIFY(*this); + return Type_; +} + +TStringBuf TYsonString::AsStringBuf() const +{ + YT_VERIFY(*this); + return TStringBuf(Begin_, Begin_ + Size_); +} + +TString TYsonString::ToString() const +{ + return Visit( + Payload_, + [] (const TNullPayload&) -> TString { + YT_ABORT(); + }, + [&] (const TRefCountedPtr&) { + return TString(AsStringBuf()); + }, + [] (const TString& payload) { + return payload; + }); +} + +size_t TYsonString::ComputeHash() const +{ + return THash<TStringBuf>()(TStringBuf(Begin_, Begin_ + Size_)); +} + +//////////////////////////////////////////////////////////////////////////////// + +TString ToString(const TYsonString& yson) +{ + return yson.ToString(); +} + +TString ToString(const TYsonStringBuf& yson) +{ + return TString(yson.AsStringBuf()); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NYson diff --git a/library/cpp/yt/yson_string/string.h b/library/cpp/yt/yson_string/string.h new file mode 100644 index 0000000000..e13af37a6d --- /dev/null +++ b/library/cpp/yt/yson_string/string.h @@ -0,0 +1,140 @@ +#pragma once + +#include "public.h" + +#include <library/cpp/yt/memory/ref.h> + +#include <variant> + +namespace NYT::NYson { + +//////////////////////////////////////////////////////////////////////////////// + +//! Contains a sequence of bytes in YSON encoding annotated with EYsonType describing +//! the content. Could be null. Non-owning. +class TYsonStringBuf +{ +public: + //! Constructs a null instance. + TYsonStringBuf(); + + //! Constructs an instance from TYsonString. + TYsonStringBuf(const TYsonString& ysonString); + + //! Constructs a non-null instance with given type and content. + explicit TYsonStringBuf( + const TString& data, + EYsonType type = EYsonType::Node); + + //! Constructs a non-null instance with given type and content. + explicit TYsonStringBuf( + TStringBuf data, + EYsonType type = EYsonType::Node); + + //! Constructs a non-null instance with given type and content + //! (without this overload there is no way to construct TYsonStringBuf from + //! string literal). + explicit TYsonStringBuf( + const char* data, + EYsonType type = EYsonType::Node); + + //! Returns |true| if the instance is not null. + explicit operator bool() const; + + //! Returns the underlying YSON bytes. The instance must be non-null. + TStringBuf AsStringBuf() const; + + //! Returns type of YSON contained here. The instance must be non-null. + EYsonType GetType() const; + +protected: + TStringBuf Data_; + EYsonType Type_; + bool Null_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +//! An owning version of TYsonStringBuf. +/*! + * Internally captures the data either via TString or a polymorphic ref-counted holder. + */ +class TYsonString +{ +public: + //! Constructs a null instance. + TYsonString(); + + //! Constructs an instance from TYsonStringBuf. + //! Copies the data into a ref-counted payload. + explicit TYsonString(const TYsonStringBuf& ysonStringBuf); + + //! Constructs an instance from TStringBuf. + //! Copies the data into a ref-counted payload. + explicit TYsonString( + TStringBuf data, + EYsonType type = EYsonType::Node); + + //! Constructs an instance from TString. + //! Zero-copy for CoW TString: retains the reference to TString in payload. + explicit TYsonString( + const TString& data, + EYsonType type = EYsonType::Node); + + //! Constructs an instance from TSharedRef. + //! Zero-copy; retains the reference to TSharedRef holder in payload. + explicit TYsonString( + const TSharedRef& ref, + EYsonType type = EYsonType::Node); + + //! Returns |true| if the instance is not null. + explicit operator bool() const; + + //! Returns type of YSON contained here. The instance must be non-null. + EYsonType GetType() const; + + //! Returns the non-owning data. The instance must be non-null. + TStringBuf AsStringBuf() const; + + //! Returns the data represented by TString. The instance must be non-null. + //! Copies the data in case the payload is not TString. + TString ToString() const; + + //! Computes the hash code. + size_t ComputeHash() const; + +private: + struct TNullPayload + { }; + + using THolder = TRefCountedPtr; + + std::variant<TNullPayload, THolder, TString> Payload_; + + const char* Begin_; + ui64 Size_ : 56; + EYsonType Type_ : 8; +}; + +//////////////////////////////////////////////////////////////////////////////// + +bool operator == (const TYsonString& lhs, const TYsonString& rhs); +bool operator == (const TYsonString& lhs, const TYsonStringBuf& rhs); +bool operator == (const TYsonStringBuf& lhs, const TYsonString& rhs); +bool operator == (const TYsonStringBuf& lhs, const TYsonStringBuf& rhs); + +bool operator != (const TYsonString& lhs, const TYsonString& rhs); +bool operator != (const TYsonString& lhs, const TYsonStringBuf& rhs); +bool operator != (const TYsonStringBuf& lhs, const TYsonString& rhs); +bool operator != (const TYsonStringBuf& lhs, const TYsonStringBuf& rhs); + +TString ToString(const TYsonString& yson); +TString ToString(const TYsonStringBuf& yson); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NYson + +#define STRING_INL_H_ +#include "string-inl.h" +#undef STRING_INL_H_ diff --git a/library/cpp/yt/yson_string/unittests/convert_ut.cpp b/library/cpp/yt/yson_string/unittests/convert_ut.cpp new file mode 100644 index 0000000000..3a64f63896 --- /dev/null +++ b/library/cpp/yt/yson_string/unittests/convert_ut.cpp @@ -0,0 +1,79 @@ +#include <library/cpp/testing/gtest/gtest.h> + +#include <library/cpp/testing/gtest_extensions/assertions.h> + +#include <library/cpp/yt/yson_string/convert.h> + +#include <thread> + +namespace NYT::NYson { +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T, class R = T, class U> +void Check(const U& value) +{ + auto str = ConvertToYsonString(static_cast<T>(value)); + auto anotherValue = ConvertFromYsonString<R>(str); + EXPECT_EQ(static_cast<T>(value), anotherValue); +} + +TEST(TConvertTest, Basic) +{ + Check<i8>(13); + Check<i32>(13); + Check<i64>(13); + Check<i8>(-13); + Check<i32>(-13); + Check<i64>(-13); + Check<ui8>(13); + Check<ui32>(13); + Check<ui64>(13); + Check<TString>(""); + Check<TString>("hello"); + Check<TStringBuf, TString>("hello"); + Check<const char*, TString>("hello"); + Check<float>(3.14); + Check<double>(3.14); + Check<bool>(true); + Check<bool>(false); + Check<TInstant>(TInstant::Now()); + Check<TDuration>(TDuration::Seconds(123)); + Check<TGuid>(TGuid::FromString("12345678-12345678-abcdabcd-fefefefe")); +} + +TEST(TConvertTest, InRange) +{ + EXPECT_EQ(ConvertFromYsonString<i16>(ConvertToYsonString(static_cast<i64>(-123))), -123); + EXPECT_EQ(ConvertFromYsonString<ui16>(ConvertToYsonString(static_cast<ui64>(123))), 123U); +} + +TEST(TConvertTest, OutOfRange) +{ + EXPECT_THROW_MESSAGE_HAS_SUBSTR( + ConvertFromYsonString<i8>(ConvertToYsonString(static_cast<i64>(128))), + TYsonLiteralParseException, + "is out of expected range"); + EXPECT_THROW_MESSAGE_HAS_SUBSTR( + ConvertFromYsonString<ui8>(ConvertToYsonString(static_cast<ui64>(256))), + TYsonLiteralParseException, + "is out of expected range"); +} + +TEST(TConvertTest, MalformedValues) +{ + EXPECT_THROW_MESSAGE_HAS_SUBSTR( + ConvertFromYsonString<TInstant>(ConvertToYsonString(TStringBuf("sometime"))), + TYsonLiteralParseException, + "Error parsing \"instant\" value"); + EXPECT_THROW_MESSAGE_HAS_SUBSTR( + ConvertFromYsonString<TGuid>(ConvertToYsonString(TStringBuf("1-2-3-g"))), + TYsonLiteralParseException, + "Error parsing \"guid\" value"); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace +} // namespace NYT::NYson diff --git a/library/cpp/yt/yson_string/unittests/ya.make b/library/cpp/yt/yson_string/unittests/ya.make new file mode 100644 index 0000000000..f327d298f1 --- /dev/null +++ b/library/cpp/yt/yson_string/unittests/ya.make @@ -0,0 +1,15 @@ +GTEST() + +OWNER(g:yt) + +SRCS( + convert_ut.cpp +) + +PEERDIR( + library/cpp/yt/yson_string + library/cpp/testing/gtest + library/cpp/testing/gtest_extensions +) + +END() diff --git a/library/cpp/yt/yson_string/ya.make b/library/cpp/yt/yson_string/ya.make new file mode 100644 index 0000000000..b7447d89ff --- /dev/null +++ b/library/cpp/yt/yson_string/ya.make @@ -0,0 +1,21 @@ +LIBRARY() + +SRCS( + convert.cpp + string.cpp +) + +PEERDIR( + library/cpp/yt/assert + library/cpp/yt/coding + library/cpp/yt/exception + library/cpp/yt/string + library/cpp/yt/memory + library/cpp/yt/misc +) + +END() + +RECURSE_FOR_TESTS( + unittests +) |