/* * Copyright 2021 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef FLATBUFFERS_VECTOR_DOWNWARD_H_ #define FLATBUFFERS_VECTOR_DOWNWARD_H_ #include "flatbuffers/base.h" #include "flatbuffers/default_allocator.h" #include "flatbuffers/detached_buffer.h" namespace flatbuffers { // This is a minimal replication of std::vector functionality, // except growing from higher to lower addresses. i.e. push_back() inserts data // in the lowest address in the vector. // Since this vector leaves the lower part unused, we support a "scratch-pad" // that can be stored there for temporary data, to share the allocated space. // Essentially, this supports 2 std::vectors in a single buffer. class vector_downward { public: explicit vector_downward(size_t initial_size, Allocator *allocator, bool own_allocator, size_t buffer_minalign) : allocator_(allocator), own_allocator_(own_allocator), initial_size_(initial_size), buffer_minalign_(buffer_minalign), reserved_(0), size_(0), buf_(nullptr), cur_(nullptr), scratch_(nullptr) {} vector_downward(vector_downward &&other) // clang-format on : allocator_(other.allocator_), own_allocator_(other.own_allocator_), initial_size_(other.initial_size_), buffer_minalign_(other.buffer_minalign_), reserved_(other.reserved_), size_(other.size_), buf_(other.buf_), cur_(other.cur_), scratch_(other.scratch_) { // No change in other.allocator_ // No change in other.initial_size_ // No change in other.buffer_minalign_ other.own_allocator_ = false; other.reserved_ = 0; other.buf_ = nullptr; other.cur_ = nullptr; other.scratch_ = nullptr; } vector_downward &operator=(vector_downward &&other) { // Move construct a temporary and swap idiom vector_downward temp(std::move(other)); swap(temp); return *this; } ~vector_downward() { clear_buffer(); clear_allocator(); } void reset() { clear_buffer(); clear(); } void clear() { if (buf_) { cur_ = buf_ + reserved_; } else { reserved_ = 0; cur_ = nullptr; } size_ = 0; clear_scratch(); } void clear_scratch() { scratch_ = buf_; } void clear_allocator() { if (own_allocator_ && allocator_) { delete allocator_; } allocator_ = nullptr; own_allocator_ = false; } void clear_buffer() { if (buf_) Deallocate(allocator_, buf_, reserved_); buf_ = nullptr; } // Relinquish the pointer to the caller. uint8_t *release_raw(size_t &allocated_bytes, size_t &offset) { auto *buf = buf_; allocated_bytes = reserved_; offset = static_cast(cur_ - buf_); // release_raw only relinquishes the buffer ownership. // Does not deallocate or reset the allocator. Destructor will do that. buf_ = nullptr; clear(); return buf; } // Relinquish the pointer to the caller. DetachedBuffer release() { // allocator ownership (if any) is transferred to DetachedBuffer. DetachedBuffer fb(allocator_, own_allocator_, buf_, reserved_, cur_, size()); if (own_allocator_) { allocator_ = nullptr; own_allocator_ = false; } buf_ = nullptr; clear(); return fb; } size_t ensure_space(size_t len) { FLATBUFFERS_ASSERT(cur_ >= scratch_ && scratch_ >= buf_); if (len > static_cast(cur_ - scratch_)) { reallocate(len); } // Beyond this, signed offsets may not have enough range: // (FlatBuffers > 2GB not supported). FLATBUFFERS_ASSERT(size() < FLATBUFFERS_MAX_BUFFER_SIZE); return len; } inline uint8_t *make_space(size_t len) { if (len) { ensure_space(len); cur_ -= len; size_ += static_cast(len); } return cur_; } // Returns nullptr if using the DefaultAllocator. Allocator *get_custom_allocator() { return allocator_; } inline uoffset_t size() const { return size_; } uoffset_t scratch_size() const { return static_cast(scratch_ - buf_); } size_t capacity() const { return reserved_; } uint8_t *data() const { FLATBUFFERS_ASSERT(cur_); return cur_; } uint8_t *scratch_data() const { FLATBUFFERS_ASSERT(buf_); return buf_; } uint8_t *scratch_end() const { FLATBUFFERS_ASSERT(scratch_); return scratch_; } uint8_t *data_at(size_t offset) const { return buf_ + reserved_ - offset; } void push(const uint8_t *bytes, size_t num) { if (num > 0) { memcpy(make_space(num), bytes, num); } } // Specialized version of push() that avoids memcpy call for small data. template void push_small(const T &little_endian_t) { make_space(sizeof(T)); *reinterpret_cast(cur_) = little_endian_t; } template void scratch_push_small(const T &t) { ensure_space(sizeof(T)); *reinterpret_cast(scratch_) = t; scratch_ += sizeof(T); } // fill() is most frequently called with small byte counts (<= 4), // which is why we're using loops rather than calling memset. void fill(size_t zero_pad_bytes) { make_space(zero_pad_bytes); for (size_t i = 0; i < zero_pad_bytes; i++) cur_[i] = 0; } // Version for when we know the size is larger. // Precondition: zero_pad_bytes > 0 void fill_big(size_t zero_pad_bytes) { memset(make_space(zero_pad_bytes), 0, zero_pad_bytes); } void pop(size_t bytes_to_remove) { cur_ += bytes_to_remove; size_ -= static_cast(bytes_to_remove); } void scratch_pop(size_t bytes_to_remove) { scratch_ -= bytes_to_remove; } void swap(vector_downward &other) { using std::swap; swap(allocator_, other.allocator_); swap(own_allocator_, other.own_allocator_); swap(initial_size_, other.initial_size_); swap(buffer_minalign_, other.buffer_minalign_); swap(reserved_, other.reserved_); swap(size_, other.size_); swap(buf_, other.buf_); swap(cur_, other.cur_); swap(scratch_, other.scratch_); } void swap_allocator(vector_downward &other) { using std::swap; swap(allocator_, other.allocator_); swap(own_allocator_, other.own_allocator_); } private: // You shouldn't really be copying instances of this class. FLATBUFFERS_DELETE_FUNC(vector_downward(const vector_downward &)); FLATBUFFERS_DELETE_FUNC(vector_downward &operator=(const vector_downward &)); Allocator *allocator_; bool own_allocator_; size_t initial_size_; size_t buffer_minalign_; size_t reserved_; uoffset_t size_; uint8_t *buf_; uint8_t *cur_; // Points at location between empty (below) and used (above). uint8_t *scratch_; // Points to the end of the scratchpad in use. void reallocate(size_t len) { auto old_reserved = reserved_; auto old_size = size(); auto old_scratch_size = scratch_size(); reserved_ += (std::max)(len, old_reserved ? old_reserved / 2 : initial_size_); reserved_ = (reserved_ + buffer_minalign_ - 1) & ~(buffer_minalign_ - 1); if (buf_) { buf_ = ReallocateDownward(allocator_, buf_, old_reserved, reserved_, old_size, old_scratch_size); } else { buf_ = Allocate(allocator_, reserved_); } cur_ = buf_ + reserved_ - old_size; scratch_ = buf_ + old_scratch_size; } }; } // namespace flatbuffers #endif // FLATBUFFERS_VECTOR_DOWNWARD_H_