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