blob: 4d3ef3f0e075438325e4c354449a13ffbd1bd002 [file] [log] [blame]
// 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_