|
||||
File indexing completed on 2024-11-15 09:01:15
0001 // Copyright 2021 The Abseil Authors 0002 // 0003 // Licensed under the Apache License, Version 2.0 (the "License"); 0004 // you may not use this file except in compliance with the License. 0005 // You may obtain a copy of the License at 0006 // 0007 // https://www.apache.org/licenses/LICENSE-2.0 0008 // 0009 // Unless required by applicable law or agreed to in writing, software 0010 // distributed under the License is distributed on an "AS IS" BASIS, 0011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 0012 // See the License for the specific language governing permissions and 0013 // limitations under the License. 0014 // 0015 // ----------------------------------------------------------------------------- 0016 // File: cord_buffer.h 0017 // ----------------------------------------------------------------------------- 0018 // 0019 // This file defines an `absl::CordBuffer` data structure to hold data for 0020 // eventual inclusion within an existing `Cord` data structure. Cord buffers are 0021 // useful for building large Cords that may require custom allocation of its 0022 // associated memory. 0023 // 0024 #ifndef ABSL_STRINGS_CORD_BUFFER_H_ 0025 #define ABSL_STRINGS_CORD_BUFFER_H_ 0026 0027 #include <algorithm> 0028 #include <cassert> 0029 #include <cstddef> 0030 #include <cstdint> 0031 #include <memory> 0032 #include <utility> 0033 0034 #include "absl/base/config.h" 0035 #include "absl/base/macros.h" 0036 #include "absl/numeric/bits.h" 0037 #include "absl/strings/internal/cord_internal.h" 0038 #include "absl/strings/internal/cord_rep_flat.h" 0039 #include "absl/types/span.h" 0040 0041 namespace absl { 0042 ABSL_NAMESPACE_BEGIN 0043 0044 class Cord; 0045 class CordBufferTestPeer; 0046 0047 // CordBuffer 0048 // 0049 // CordBuffer manages memory buffers for purposes such as zero-copy APIs as well 0050 // as applications building cords with large data requiring granular control 0051 // over the allocation and size of cord data. For example, a function creating 0052 // a cord of random data could use a CordBuffer as follows: 0053 // 0054 // absl::Cord CreateRandomCord(size_t length) { 0055 // absl::Cord cord; 0056 // while (length > 0) { 0057 // CordBuffer buffer = CordBuffer::CreateWithDefaultLimit(length); 0058 // absl::Span<char> data = buffer.available_up_to(length); 0059 // FillRandomValues(data.data(), data.size()); 0060 // buffer.IncreaseLengthBy(data.size()); 0061 // cord.Append(std::move(buffer)); 0062 // length -= data.size(); 0063 // } 0064 // return cord; 0065 // } 0066 // 0067 // CordBuffer instances are by default limited to a capacity of `kDefaultLimit` 0068 // bytes. `kDefaultLimit` is currently just under 4KiB, but this default may 0069 // change in the future and/or for specific architectures. The default limit is 0070 // aimed to provide a good trade-off between performance and memory overhead. 0071 // Smaller buffers typically incur more compute cost while larger buffers are 0072 // more CPU efficient but create significant memory overhead because of such 0073 // allocations being less granular. Using larger buffers may also increase the 0074 // risk of memory fragmentation. 0075 // 0076 // Applications create a buffer using one of the `CreateWithDefaultLimit()` or 0077 // `CreateWithCustomLimit()` methods. The returned instance will have a non-zero 0078 // capacity and a zero length. Applications use the `data()` method to set the 0079 // contents of the managed memory, and once done filling the buffer, use the 0080 // `IncreaseLengthBy()` or 'SetLength()' method to specify the length of the 0081 // initialized data before adding the buffer to a Cord. 0082 // 0083 // The `CreateWithCustomLimit()` method is intended for applications needing 0084 // larger buffers than the default memory limit, allowing the allocation of up 0085 // to a capacity of `kCustomLimit` bytes minus some minimum internal overhead. 0086 // The usage of `CreateWithCustomLimit()` should be limited to only those use 0087 // cases where the distribution of the input is relatively well known, and/or 0088 // where the trade-off between the efficiency gains outweigh the risk of memory 0089 // fragmentation. See the documentation for `CreateWithCustomLimit()` for more 0090 // information on using larger custom limits. 0091 // 0092 // The capacity of a `CordBuffer` returned by one of the `Create` methods may 0093 // be larger than the requested capacity due to rounding, alignment and 0094 // granularity of the memory allocator. Applications should use the `capacity` 0095 // method to obtain the effective capacity of the returned instance as 0096 // demonstrated in the provided example above. 0097 // 0098 // CordBuffer is a move-only class. All references into the managed memory are 0099 // invalidated when an instance is moved into either another CordBuffer instance 0100 // or a Cord. Writing to a location obtained by a previous call to `data()` 0101 // after an instance was moved will lead to undefined behavior. 0102 // 0103 // A `moved from` CordBuffer instance will have a valid, but empty state. 0104 // CordBuffer is thread compatible. 0105 class CordBuffer { 0106 public: 0107 // kDefaultLimit 0108 // 0109 // Default capacity limits of allocated CordBuffers. 0110 // See the class comments for more information on allocation limits. 0111 static constexpr size_t kDefaultLimit = cord_internal::kMaxFlatLength; 0112 0113 // kCustomLimit 0114 // 0115 // Maximum size for CreateWithCustomLimit() allocated buffers. 0116 // Note that the effective capacity may be slightly less 0117 // because of internal overhead of internal cord buffers. 0118 static constexpr size_t kCustomLimit = 64U << 10; 0119 0120 // Constructors, Destructors and Assignment Operators 0121 0122 // Creates an empty CordBuffer. 0123 CordBuffer() = default; 0124 0125 // Destroys this CordBuffer instance and, if not empty, releases any memory 0126 // managed by this instance, invalidating previously returned references. 0127 ~CordBuffer(); 0128 0129 // CordBuffer is move-only 0130 CordBuffer(CordBuffer&& rhs) noexcept; 0131 CordBuffer& operator=(CordBuffer&&) noexcept; 0132 CordBuffer(const CordBuffer&) = delete; 0133 CordBuffer& operator=(const CordBuffer&) = delete; 0134 0135 // CordBuffer::MaximumPayload() 0136 // 0137 // Returns the guaranteed maximum payload for a CordBuffer returned by the 0138 // `CreateWithDefaultLimit()` method. While small, each internal buffer inside 0139 // a Cord incurs an overhead to manage the length, type and reference count 0140 // for the buffer managed inside the cord tree. Applications can use this 0141 // method to get approximate number of buffers required for a given byte 0142 // size, etc. 0143 // 0144 // For example: 0145 // const size_t payload = absl::CordBuffer::MaximumPayload(); 0146 // const size_t buffer_count = (total_size + payload - 1) / payload; 0147 // buffers.reserve(buffer_count); 0148 static constexpr size_t MaximumPayload(); 0149 0150 // Overload to the above `MaximumPayload()` except that it returns the 0151 // maximum payload for a CordBuffer returned by the `CreateWithCustomLimit()` 0152 // method given the provided `block_size`. 0153 static constexpr size_t MaximumPayload(size_t block_size); 0154 0155 // CordBuffer::CreateWithDefaultLimit() 0156 // 0157 // Creates a CordBuffer instance of the desired `capacity`, capped at the 0158 // default limit `kDefaultLimit`. The returned buffer has a guaranteed 0159 // capacity of at least `min(kDefaultLimit, capacity)`. See the class comments 0160 // for more information on buffer capacities and intended usage. 0161 static CordBuffer CreateWithDefaultLimit(size_t capacity); 0162 0163 // CordBuffer::CreateWithCustomLimit() 0164 // 0165 // Creates a CordBuffer instance of the desired `capacity` rounded to an 0166 // appropriate power of 2 size less than, or equal to `block_size`. 0167 // Requires `block_size` to be a power of 2. 0168 // 0169 // If `capacity` is less than or equal to `kDefaultLimit`, then this method 0170 // behaves identical to `CreateWithDefaultLimit`, which means that the caller 0171 // is guaranteed to get a buffer of at least the requested capacity. 0172 // 0173 // If `capacity` is greater than or equal to `block_size`, then this method 0174 // returns a buffer with an `allocated size` of `block_size` bytes. Otherwise, 0175 // this methods returns a buffer with a suitable smaller power of 2 block size 0176 // to satisfy the request. The actual size depends on a number of factors, and 0177 // is typically (but not necessarily) the highest or second highest power of 2 0178 // value less than or equal to `capacity`. 0179 // 0180 // The 'allocated size' includes a small amount of overhead required for 0181 // internal state, which is currently 13 bytes on 64-bit platforms. For 0182 // example: a buffer created with `block_size` and `capacity' set to 8KiB 0183 // will have an allocated size of 8KiB, and an effective internal `capacity` 0184 // of 8KiB - 13 = 8179 bytes. 0185 // 0186 // To demonstrate this in practice, let's assume we want to read data from 0187 // somewhat larger files using approximately 64KiB buffers: 0188 // 0189 // absl::Cord ReadFromFile(int fd, size_t n) { 0190 // absl::Cord cord; 0191 // while (n > 0) { 0192 // CordBuffer buffer = CordBuffer::CreateWithCustomLimit(64 << 10, n); 0193 // absl::Span<char> data = buffer.available_up_to(n); 0194 // ReadFileDataOrDie(fd, data.data(), data.size()); 0195 // buffer.IncreaseLengthBy(data.size()); 0196 // cord.Append(std::move(buffer)); 0197 // n -= data.size(); 0198 // } 0199 // return cord; 0200 // } 0201 // 0202 // If we'd use this function to read a file of 659KiB, we may get the 0203 // following pattern of allocated cord buffer sizes: 0204 // 0205 // CreateWithCustomLimit(64KiB, 674816) --> ~64KiB (65523) 0206 // CreateWithCustomLimit(64KiB, 674816) --> ~64KiB (65523) 0207 // ... 0208 // CreateWithCustomLimit(64KiB, 19586) --> ~16KiB (16371) 0209 // CreateWithCustomLimit(64KiB, 3215) --> 3215 (at least 3215) 0210 // 0211 // The reason the method returns a 16K buffer instead of a roughly 19K buffer 0212 // is to reduce memory overhead and fragmentation risks. Using carefully 0213 // chosen power of 2 values reduces the entropy of allocated memory sizes. 0214 // 0215 // Additionally, let's assume we'd use the above function on files that are 0216 // generally smaller than 64K. If we'd use 'precise' sized buffers for such 0217 // files, than we'd get a very wide distribution of allocated memory sizes 0218 // rounded to 4K page sizes, and we'd end up with a lot of unused capacity. 0219 // 0220 // In general, application should only use custom sizes if the data they are 0221 // consuming or storing is expected to be many times the chosen block size, 0222 // and be based on objective data and performance metrics. For example, a 0223 // compress function may work faster and consume less CPU when using larger 0224 // buffers. Such an application should pick a size offering a reasonable 0225 // trade-off between expected data size, compute savings with larger buffers, 0226 // and the cost or fragmentation effect of larger buffers. 0227 // Applications must pick a reasonable spot on that curve, and make sure their 0228 // data meets their expectations in size distributions such as "mostly large". 0229 static CordBuffer CreateWithCustomLimit(size_t block_size, size_t capacity); 0230 0231 // CordBuffer::available() 0232 // 0233 // Returns the span delineating the available capacity in this buffer 0234 // which is defined as `{ data() + length(), capacity() - length() }`. 0235 absl::Span<char> available(); 0236 0237 // CordBuffer::available_up_to() 0238 // 0239 // Returns the span delineating the available capacity in this buffer limited 0240 // to `size` bytes. This is equivalent to `available().subspan(0, size)`. 0241 absl::Span<char> available_up_to(size_t size); 0242 0243 // CordBuffer::data() 0244 // 0245 // Returns a non-null reference to the data managed by this instance. 0246 // Applications are allowed to write up to `capacity` bytes of instance data. 0247 // CordBuffer data is uninitialized by default. Reading data from an instance 0248 // that has not yet been initialized will lead to undefined behavior. 0249 char* data(); 0250 const char* data() const; 0251 0252 // CordBuffer::length() 0253 // 0254 // Returns the length of this instance. The default length of a CordBuffer is 0255 // 0, indicating an 'empty' CordBuffer. Applications must specify the length 0256 // of the data in a CordBuffer before adding it to a Cord. 0257 size_t length() const; 0258 0259 // CordBuffer::capacity() 0260 // 0261 // Returns the capacity of this instance. All instances have a non-zero 0262 // capacity: default and `moved from` instances have a small internal buffer. 0263 size_t capacity() const; 0264 0265 // CordBuffer::IncreaseLengthBy() 0266 // 0267 // Increases the length of this buffer by the specified 'n' bytes. 0268 // Applications must make sure all data in this buffer up to the new length 0269 // has been initialized before adding a CordBuffer to a Cord: failure to do so 0270 // will lead to undefined behavior. Requires `length() + n <= capacity()`. 0271 // Typically, applications will use 'available_up_to()` to get a span of the 0272 // desired capacity, and use `span.size()` to increase the length as in: 0273 // absl::Span<char> span = buffer.available_up_to(desired); 0274 // buffer.IncreaseLengthBy(span.size()); 0275 // memcpy(span.data(), src, span.size()); 0276 // etc... 0277 void IncreaseLengthBy(size_t n); 0278 0279 // CordBuffer::SetLength() 0280 // 0281 // Sets the data length of this instance. Applications must make sure all data 0282 // of the specified length has been initialized before adding a CordBuffer to 0283 // a Cord: failure to do so will lead to undefined behavior. 0284 // Setting the length to a small value or zero does not release any memory 0285 // held by this CordBuffer instance. Requires `length <= capacity()`. 0286 // Applications should preferably use the `IncreaseLengthBy()` method above 0287 // in combination with the 'available()` or `available_up_to()` methods. 0288 void SetLength(size_t length); 0289 0290 private: 0291 // Make sure we don't accidentally over promise. 0292 static_assert(kCustomLimit <= cord_internal::kMaxLargeFlatSize, ""); 0293 0294 // Assume the cost of an 'uprounded' allocation to CeilPow2(size) versus 0295 // the cost of allocating at least 1 extra flat <= 4KB: 0296 // - Flat overhead = 13 bytes 0297 // - Btree amortized cost / node =~ 13 bytes 0298 // - 64 byte granularity of tcmalloc at 4K =~ 32 byte average 0299 // CPU cost and efficiency requires we should at least 'save' something by 0300 // splitting, as a poor man's measure, we say the slop needs to be 0301 // at least double the cost offset to make it worth splitting: ~128 bytes. 0302 static constexpr size_t kMaxPageSlop = 128; 0303 0304 // Overhead for allocation a flat. 0305 static constexpr size_t kOverhead = cord_internal::kFlatOverhead; 0306 0307 using CordRepFlat = cord_internal::CordRepFlat; 0308 0309 // `Rep` is the internal data representation of a CordBuffer. The internal 0310 // representation has an internal small size optimization similar to 0311 // std::string (SSO). 0312 struct Rep { 0313 // Inline SSO size of a CordBuffer 0314 static constexpr size_t kInlineCapacity = sizeof(intptr_t) * 2 - 1; 0315 0316 // Creates a default instance with kInlineCapacity. 0317 Rep() : short_rep{} {} 0318 0319 // Creates an instance managing an allocated non zero CordRep. 0320 explicit Rep(cord_internal::CordRepFlat* rep) : long_rep{rep} { 0321 assert(rep != nullptr); 0322 } 0323 0324 // Returns true if this instance manages the SSO internal buffer. 0325 bool is_short() const { 0326 constexpr size_t offset = offsetof(Short, raw_size); 0327 return (reinterpret_cast<const char*>(this)[offset] & 1) != 0; 0328 } 0329 0330 // Returns the available area of the internal SSO data 0331 absl::Span<char> short_available() { 0332 const size_t length = short_length(); 0333 return absl::Span<char>(short_rep.data + length, 0334 kInlineCapacity - length); 0335 } 0336 0337 // Returns the available area of the internal SSO data 0338 absl::Span<char> long_available() const { 0339 assert(!is_short()); 0340 const size_t length = long_rep.rep->length; 0341 return absl::Span<char>(long_rep.rep->Data() + length, 0342 long_rep.rep->Capacity() - length); 0343 } 0344 0345 // Returns the length of the internal SSO data. 0346 size_t short_length() const { 0347 assert(is_short()); 0348 return static_cast<size_t>(short_rep.raw_size >> 1); 0349 } 0350 0351 // Sets the length of the internal SSO data. 0352 // Disregards any previously set CordRep instance. 0353 void set_short_length(size_t length) { 0354 short_rep.raw_size = static_cast<char>((length << 1) + 1); 0355 } 0356 0357 // Adds `n` to the current short length. 0358 void add_short_length(size_t n) { 0359 assert(is_short()); 0360 short_rep.raw_size += static_cast<char>(n << 1); 0361 } 0362 0363 // Returns reference to the internal SSO data buffer. 0364 char* data() { 0365 assert(is_short()); 0366 return short_rep.data; 0367 } 0368 const char* data() const { 0369 assert(is_short()); 0370 return short_rep.data; 0371 } 0372 0373 // Returns a pointer the external CordRep managed by this instance. 0374 cord_internal::CordRepFlat* rep() const { 0375 assert(!is_short()); 0376 return long_rep.rep; 0377 } 0378 0379 // The internal representation takes advantage of the fact that allocated 0380 // memory is always on an even address, and uses the least significant bit 0381 // of the first or last byte (depending on endianness) as the inline size 0382 // indicator overlapping with the least significant byte of the CordRep*. 0383 #if defined(ABSL_IS_BIG_ENDIAN) 0384 struct Long { 0385 explicit Long(cord_internal::CordRepFlat* rep_arg) : rep(rep_arg) {} 0386 void* padding; 0387 cord_internal::CordRepFlat* rep; 0388 }; 0389 struct Short { 0390 char data[sizeof(Long) - 1]; 0391 char raw_size = 1; 0392 }; 0393 #else 0394 struct Long { 0395 explicit Long(cord_internal::CordRepFlat* rep_arg) : rep(rep_arg) {} 0396 cord_internal::CordRepFlat* rep; 0397 void* padding; 0398 }; 0399 struct Short { 0400 char raw_size = 1; 0401 char data[sizeof(Long) - 1]; 0402 }; 0403 #endif 0404 0405 union { 0406 Long long_rep; 0407 Short short_rep; 0408 }; 0409 }; 0410 0411 // Power2 functions 0412 static bool IsPow2(size_t size) { return absl::has_single_bit(size); } 0413 static size_t Log2Floor(size_t size) { 0414 return static_cast<size_t>(absl::bit_width(size) - 1); 0415 } 0416 static size_t Log2Ceil(size_t size) { 0417 return static_cast<size_t>(absl::bit_width(size - 1)); 0418 } 0419 0420 // Implementation of `CreateWithCustomLimit()`. 0421 // This implementation allows for future memory allocation hints to 0422 // be passed down into the CordRepFlat allocation function. 0423 template <typename... AllocationHints> 0424 static CordBuffer CreateWithCustomLimitImpl(size_t block_size, 0425 size_t capacity, 0426 AllocationHints... hints); 0427 0428 // Consumes the value contained in this instance and resets the instance. 0429 // This method returns a non-null Cordrep* if the current instances manages a 0430 // CordRep*, and resets the instance to an empty SSO instance. If the current 0431 // instance is an SSO instance, then this method returns nullptr and sets 0432 // `short_value` to the inlined data value. In either case, the current 0433 // instance length is reset to zero. 0434 // This method is intended to be used by Cord internal functions only. 0435 cord_internal::CordRep* ConsumeValue(absl::string_view& short_value) { 0436 cord_internal::CordRep* rep = nullptr; 0437 if (rep_.is_short()) { 0438 short_value = absl::string_view(rep_.data(), rep_.short_length()); 0439 } else { 0440 rep = rep_.rep(); 0441 } 0442 rep_.set_short_length(0); 0443 return rep; 0444 } 0445 0446 // Internal constructor. 0447 explicit CordBuffer(cord_internal::CordRepFlat* rep) : rep_(rep) { 0448 assert(rep != nullptr); 0449 } 0450 0451 Rep rep_; 0452 0453 friend class Cord; 0454 friend class CordBufferTestPeer; 0455 }; 0456 0457 inline constexpr size_t CordBuffer::MaximumPayload() { 0458 return cord_internal::kMaxFlatLength; 0459 } 0460 0461 inline constexpr size_t CordBuffer::MaximumPayload(size_t block_size) { 0462 return (std::min)(kCustomLimit, block_size) - cord_internal::kFlatOverhead; 0463 } 0464 0465 inline CordBuffer CordBuffer::CreateWithDefaultLimit(size_t capacity) { 0466 if (capacity > Rep::kInlineCapacity) { 0467 auto* rep = cord_internal::CordRepFlat::New(capacity); 0468 rep->length = 0; 0469 return CordBuffer(rep); 0470 } 0471 return CordBuffer(); 0472 } 0473 0474 template <typename... AllocationHints> 0475 inline CordBuffer CordBuffer::CreateWithCustomLimitImpl( 0476 size_t block_size, size_t capacity, AllocationHints... hints) { 0477 assert(IsPow2(block_size)); 0478 capacity = (std::min)(capacity, kCustomLimit); 0479 block_size = (std::min)(block_size, kCustomLimit); 0480 if (capacity + kOverhead >= block_size) { 0481 capacity = block_size; 0482 } else if (capacity <= kDefaultLimit) { 0483 capacity = capacity + kOverhead; 0484 } else if (!IsPow2(capacity)) { 0485 // Check if rounded up to next power 2 is a good enough fit 0486 // with limited waste making it an acceptable direct fit. 0487 const size_t rounded_up = size_t{1} << Log2Ceil(capacity); 0488 const size_t slop = rounded_up - capacity; 0489 if (slop >= kOverhead && slop <= kMaxPageSlop + kOverhead) { 0490 capacity = rounded_up; 0491 } else { 0492 // Round down to highest power of 2 <= capacity. 0493 // Consider a more aggressive step down if that may reduce the 0494 // risk of fragmentation where 'people are holding it wrong'. 0495 const size_t rounded_down = size_t{1} << Log2Floor(capacity); 0496 capacity = rounded_down; 0497 } 0498 } 0499 const size_t length = capacity - kOverhead; 0500 auto* rep = CordRepFlat::New(CordRepFlat::Large(), length, hints...); 0501 rep->length = 0; 0502 return CordBuffer(rep); 0503 } 0504 0505 inline CordBuffer CordBuffer::CreateWithCustomLimit(size_t block_size, 0506 size_t capacity) { 0507 return CreateWithCustomLimitImpl(block_size, capacity); 0508 } 0509 0510 inline CordBuffer::~CordBuffer() { 0511 if (!rep_.is_short()) { 0512 cord_internal::CordRepFlat::Delete(rep_.rep()); 0513 } 0514 } 0515 0516 inline CordBuffer::CordBuffer(CordBuffer&& rhs) noexcept : rep_(rhs.rep_) { 0517 rhs.rep_.set_short_length(0); 0518 } 0519 0520 inline CordBuffer& CordBuffer::operator=(CordBuffer&& rhs) noexcept { 0521 if (!rep_.is_short()) cord_internal::CordRepFlat::Delete(rep_.rep()); 0522 rep_ = rhs.rep_; 0523 rhs.rep_.set_short_length(0); 0524 return *this; 0525 } 0526 0527 inline absl::Span<char> CordBuffer::available() { 0528 return rep_.is_short() ? rep_.short_available() : rep_.long_available(); 0529 } 0530 0531 inline absl::Span<char> CordBuffer::available_up_to(size_t size) { 0532 return available().subspan(0, size); 0533 } 0534 0535 inline char* CordBuffer::data() { 0536 return rep_.is_short() ? rep_.data() : rep_.rep()->Data(); 0537 } 0538 0539 inline const char* CordBuffer::data() const { 0540 return rep_.is_short() ? rep_.data() : rep_.rep()->Data(); 0541 } 0542 0543 inline size_t CordBuffer::capacity() const { 0544 return rep_.is_short() ? Rep::kInlineCapacity : rep_.rep()->Capacity(); 0545 } 0546 0547 inline size_t CordBuffer::length() const { 0548 return rep_.is_short() ? rep_.short_length() : rep_.rep()->length; 0549 } 0550 0551 inline void CordBuffer::SetLength(size_t length) { 0552 ABSL_HARDENING_ASSERT(length <= capacity()); 0553 if (rep_.is_short()) { 0554 rep_.set_short_length(length); 0555 } else { 0556 rep_.rep()->length = length; 0557 } 0558 } 0559 0560 inline void CordBuffer::IncreaseLengthBy(size_t n) { 0561 ABSL_HARDENING_ASSERT(n <= capacity() && length() + n <= capacity()); 0562 if (rep_.is_short()) { 0563 rep_.add_short_length(n); 0564 } else { 0565 rep_.rep()->length += n; 0566 } 0567 } 0568 0569 ABSL_NAMESPACE_END 0570 } // namespace absl 0571 0572 #endif // ABSL_STRINGS_CORD_BUFFER_H_
[ Source navigation ] | [ Diff markup ] | [ Identifier search ] | [ general search ] |
This page was automatically generated by the 2.3.7 LXR engine. The LXR team |