Introduce QuicInlinedStringView. This will be used as the building block for adding inlining to QuicStreamSendBuffer. PiperOrigin-RevId: 762552035
diff --git a/build/source_list.bzl b/build/source_list.bzl index 37e3f2b..fac6ffe 100644 --- a/build/source_list.bzl +++ b/build/source_list.bzl
@@ -336,6 +336,7 @@ "quic/core/quic_framer.h", "quic/core/quic_generic_session.h", "quic/core/quic_idle_network_detector.h", + "quic/core/quic_inlined_string_view.h", "quic/core/quic_interval.h", "quic/core/quic_interval_deque.h", "quic/core/quic_interval_set.h", @@ -1312,6 +1313,7 @@ "quic/core/quic_framer_test.cc", "quic/core/quic_generic_session_test.cc", "quic/core/quic_idle_network_detector_test.cc", + "quic/core/quic_inlined_string_view_test.cc", "quic/core/quic_interval_deque_test.cc", "quic/core/quic_interval_set_test.cc", "quic/core/quic_interval_test.cc",
diff --git a/build/source_list.gni b/build/source_list.gni index 6a24274..93a4c55 100644 --- a/build/source_list.gni +++ b/build/source_list.gni
@@ -336,6 +336,7 @@ "src/quiche/quic/core/quic_framer.h", "src/quiche/quic/core/quic_generic_session.h", "src/quiche/quic/core/quic_idle_network_detector.h", + "src/quiche/quic/core/quic_inlined_string_view.h", "src/quiche/quic/core/quic_interval.h", "src/quiche/quic/core/quic_interval_deque.h", "src/quiche/quic/core/quic_interval_set.h", @@ -1313,6 +1314,7 @@ "src/quiche/quic/core/quic_framer_test.cc", "src/quiche/quic/core/quic_generic_session_test.cc", "src/quiche/quic/core/quic_idle_network_detector_test.cc", + "src/quiche/quic/core/quic_inlined_string_view_test.cc", "src/quiche/quic/core/quic_interval_deque_test.cc", "src/quiche/quic/core/quic_interval_set_test.cc", "src/quiche/quic/core/quic_interval_test.cc",
diff --git a/build/source_list.json b/build/source_list.json index d7a7523..1cabec8 100644 --- a/build/source_list.json +++ b/build/source_list.json
@@ -335,6 +335,7 @@ "quiche/quic/core/quic_framer.h", "quiche/quic/core/quic_generic_session.h", "quiche/quic/core/quic_idle_network_detector.h", + "quiche/quic/core/quic_inlined_string_view.h", "quiche/quic/core/quic_interval.h", "quiche/quic/core/quic_interval_deque.h", "quiche/quic/core/quic_interval_set.h", @@ -1312,6 +1313,7 @@ "quiche/quic/core/quic_framer_test.cc", "quiche/quic/core/quic_generic_session_test.cc", "quiche/quic/core/quic_idle_network_detector_test.cc", + "quiche/quic/core/quic_inlined_string_view_test.cc", "quiche/quic/core/quic_interval_deque_test.cc", "quiche/quic/core/quic_interval_set_test.cc", "quiche/quic/core/quic_interval_test.cc",
diff --git a/quiche/quic/core/quic_inlined_string_view.h b/quiche/quic/core/quic_inlined_string_view.h new file mode 100644 index 0000000..4d3ef3f --- /dev/null +++ b/quiche/quic/core/quic_inlined_string_view.h
@@ -0,0 +1,147 @@ +// Copyright 2025 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_QUIC_CORE_QUIC_INLINED_STRING_VIEW_H_ +#define QUICHE_QUIC_CORE_QUIC_INLINED_STRING_VIEW_H_ + +#include <cstddef> +#include <cstdint> +#include <limits> +#include <type_traits> + +#include "absl/numeric/bits.h" +#include "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_export.h" +#include "quiche/common/platform/api/quiche_logging.h" + +namespace quic { + +// QuicInlinedStringView<kSize> is a class that is similar to absl::string_view, +// with a notable distinction that it can inline up to `kSize` characters +// (between 15 and 127). +// +// Important use notes: +// - QuicInlinedStringView makes no assumptions about ownership of non-inlined +// data; its primary purpose is to be a building block for other data +// structures. +// - Unlike a regular string_view, the data pointer for QuicInlinedStringView +// will start pointing to a different location if inlined. +// - The string will be inlined iff its size is strictly below kSize; this is +// a guaranteeed API behavior. +template <size_t kSize> +class QUICHE_NO_EXPORT QuicInlinedStringView { + public: + // The largest size of a string that can be inlined. + static constexpr size_t kMaxInlinedSize = kSize - 1; + + static_assert(kSize >= 16); + static_assert(kSize < 255); + + QuicInlinedStringView() { clear(); } + explicit QuicInlinedStringView(absl::string_view source) { + // Special-case empty strings, since memcpy has weird edge case behaviors + // around null pointers. + if (source.empty()) { + clear(); + return; + } + + QUICHE_DCHECK_EQ(source.size() & (~kLengthMask), 0); + if (source.size() > kMaxInlinedSize) { + ViewRep rep; + rep.data = source.data(); + rep.size = source.size(); + memcpy(&data_, &rep, sizeof(rep)); + set_last_byte(kNotInlinedMarker); + return; + } + memcpy(data_, source.data(), source.size()); + set_last_byte(source.size()); + } + + // QuicInlinedStringView is trivially copyable and assignable; see, however, + // the warning above regarding the pointer stability. + QuicInlinedStringView(const QuicInlinedStringView&) = default; + QuicInlinedStringView(QuicInlinedStringView&&) = default; + QuicInlinedStringView& operator=(const QuicInlinedStringView&) = default; + QuicInlinedStringView& operator=(QuicInlinedStringView&&) = default; + + // Returns true if the string is inlined into the view. + bool IsInlined() const { return last_byte() != kNotInlinedMarker; } + + // string_view-compatible API. + const char* data() const { + if (!IsInlined()) { + return view_rep_data(); + } + return last_byte() == 0 ? nullptr : data_; + } + size_t size() const { + return IsInlined() ? last_byte() : (view_rep_size() & kLengthMask); + } + bool empty() const { return size() == 0; } + + absl::string_view view() const { return absl::string_view(data(), size()); } + void clear() { set_last_byte(0); } + + private: + // On 64-bit platforms, we want to support kSize of 16, so we take the top + // byte of the length, and use it for inlining. On 32-bit platforms, that + // would limit us to 24-bit lengths, which is too short, so we just require + // the length to not overlap with the last byte (by setting minimum size to 16 + // bytes), and no masking is necessary. + static_assert(sizeof(size_t) == 4 || sizeof(size_t) == 8); + // Here, we have to spell out kLengthMask in terms of numeric_limits, since + // specifying the value directly would fail compile-time overflow check on + // 32-bit platforms. + static constexpr size_t kLengthMask = + sizeof(size_t) > 4 ? ((std::numeric_limits<size_t>::max() << 8) >> 8) + : 0xffffffff; + static constexpr size_t kNotInlinedMarker = 0xff; + +#if defined(__x86_64__) + static_assert(kLengthMask == 0x00ffffffffffffff); +#endif + + // Representation of the string view when it is not inlined. + struct ViewRep { + const char* data; + size_t size; + }; + static_assert(sizeof(ViewRep) <= kSize); + static_assert(absl::endian::native == absl::endian::little); + + // Accessors for ViewRep; necessary to work around C++ strict aliasing + // limitations. Clang will turn this into direct field access at `-O1`. + const char* view_rep_data() const { + ViewRep rep; + memcpy(&rep, data_, sizeof(rep)); + return rep.data; + } + size_t view_rep_size() const { + ViewRep rep; + memcpy(&rep, data_, sizeof(rep)); + return rep.size; + } + // Those casts should be valid as long as uint8_t is unsigned char. + const uint8_t& last_byte() const { + return *reinterpret_cast<const uint8_t*>(data_ + kSize - 1); + } + void set_last_byte(uint8_t byte) { + *reinterpret_cast<uint8_t*>(data_ + kSize - 1) = byte; + } + + // Internal representation: if the string is inlined, the last byte is the + // length of the inlined string, and all of the preceding bytes are the + // inlined string. If the string is not inlined, the `ViewRep` is at the + // front, and 0xff is at the end (on 64-bit platforms, those may overlap). + alignas(ViewRep) char data_[kSize]; +}; + +static_assert(std::is_trivially_destructible_v<QuicInlinedStringView<16>>); +static_assert(std::is_trivially_copyable_v<QuicInlinedStringView<16>>); + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_QUIC_INLINED_STRING_VIEW_H_
diff --git a/quiche/quic/core/quic_inlined_string_view_test.cc b/quiche/quic/core/quic_inlined_string_view_test.cc new file mode 100644 index 0000000..66b062a --- /dev/null +++ b/quiche/quic/core/quic_inlined_string_view_test.cc
@@ -0,0 +1,74 @@ +// Copyright 2025 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/quic_inlined_string_view.h" + +#include <cstddef> +#include <cstdint> +#include <string> + +#include "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_test.h" + +namespace quic { +namespace { + +TEST(QuicInlinedStringViewTest, DefaultConstructor) { + QuicInlinedStringView<24> view; + EXPECT_EQ(view.data(), nullptr); + EXPECT_EQ(view.size(), 0); + EXPECT_EQ(view.view(), absl::string_view()); + EXPECT_TRUE(view.IsInlined()); + + QuicInlinedStringView<24> view_from_empty(""); + EXPECT_EQ(view_from_empty.data(), nullptr); + EXPECT_EQ(view_from_empty.size(), 0); + EXPECT_EQ(view_from_empty.view(), absl::string_view()); + EXPECT_TRUE(view_from_empty.IsInlined()); +} + +TEST(QuicInlinedStringViewTest, RangeTest) { + constexpr size_t kBufSize = 32; + for (size_t size = 1; size < 1024; ++size) { + std::string example(size, 'a'); + QuicInlinedStringView<kBufSize> view(example); + EXPECT_NE(view.data(), nullptr); + EXPECT_EQ(view.size(), size); + EXPECT_EQ(view.view(), example); + EXPECT_EQ(view.IsInlined(), size < kBufSize); + } +} + +// Test 16 bytes specifically, since on 64-bit platforms, that is where the size +// byte overlaps with the inlined marker. +TEST(QuicInlinedStringViewTest, RangeTest16) { + constexpr size_t kBufSize = 16; + for (size_t size = 1; size < 1024; ++size) { + std::string example(size, 'a'); + QuicInlinedStringView<kBufSize> view(example); + EXPECT_NE(view.data(), nullptr); + EXPECT_EQ(view.size(), size); + EXPECT_EQ(view.view(), example); + EXPECT_EQ(view.IsInlined(), size < kBufSize); + } +} + +TEST(QuicInlinedStringViewTest, CopyConstructor) { + QuicInlinedStringView<24> view_inlined("aaa"); + ASSERT_TRUE(view_inlined.IsInlined()); + QuicInlinedStringView<24> view_inlined_copy = view_inlined; + EXPECT_EQ(view_inlined.view(), view_inlined_copy.view()); + EXPECT_NE(reinterpret_cast<uintptr_t>(view_inlined.data()), + reinterpret_cast<uintptr_t>(view_inlined_copy.data())); + + QuicInlinedStringView<24> view_external("aaaaaaaaaaaaaaaaaaaaaaaaa"); + ASSERT_FALSE(view_external.IsInlined()); + QuicInlinedStringView<24> view_external_copy = view_external; + EXPECT_EQ(view_external.view(), view_external_copy.view()); + EXPECT_EQ(reinterpret_cast<uintptr_t>(view_external.data()), + reinterpret_cast<uintptr_t>(view_external_copy.data())); +} + +} // namespace +} // namespace quic