// Copyright (C) 2020-2023 Free Software Foundation, Inc. // This file is part of GCC. // GCC is free software; you can redistribute it and/or modify it under // the terms of the GNU General Public License as published by the Free // Software Foundation; either version 3, or (at your option) any later // version. // GCC is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License // for more details. // You should have received a copy of the GNU General Public License // along with GCC; see the file COPYING3. If not see // . #ifndef RUST_BUFFERED_QUEUE_H #define RUST_BUFFERED_QUEUE_H #include "rust-system.h" namespace Rust { /* Buffered queue implementation. Items are of type T, queue source is of type * Source. Note that this is owning of the source. */ template class buffered_queue { public: // Construct empty queue from Source src. buffered_queue (Source src) : source (src), start (0), end (0), buffer () {} /* disable copying (since source is probably non-copyable) * TODO is this actually a good idea? If source is non-copyable, it would * just delete the copy constructor anyway.*/ buffered_queue (const buffered_queue &other) = delete; buffered_queue &operator= (const buffered_queue &other) = delete; // enable moving buffered_queue (buffered_queue &&other) = default; buffered_queue &operator= (buffered_queue &&other) = default; // Returns token at position start + n (i.e. n tokens ahead). T peek (int n) { // n should not be behind rust_assert (n >= 0); int num_queued_items = end - start; int num_items_required = n + 1; // if required items go past end of queue, add them to queue if (num_items_required > num_queued_items) { int num_items_to_read = num_items_required - num_queued_items; /* if queue length + extra items is larger than buffer size, expand * buffer */ if (end + num_items_to_read > (int) buffer.size ()) { // Resize the buffer by 1.5x int new_size = (buffer.size () + num_items_to_read); new_size += (new_size >> 1); // old method: /* // create new queue buffer with new size std::vector new_queue (new_size); std::copy (buffer.begin () + start, buffer.begin () + end, new_queue.begin ()); start = 0; end = num_queued_items; // TODO: would move be better here? optimisation for move with // shared pointer? // swap member buffer and new queue buffer std::swap (buffer, new_queue); */ // TODO: determine overhead of this approach vs copy. Should be // lower. std::vector new_queue; new_queue.reserve (new_size); new_queue.insert (new_queue.begin (), std::make_move_iterator (buffer.begin () + start), std::make_move_iterator (buffer.begin () + end)); start = 0; end = num_queued_items; // fill up rest of vector with junk so that indexing can work new_queue.insert (new_queue.begin () + end, new_size - new_queue.size (), T ()); buffer = std::move (new_queue); /* this should be best method - std::move(range) would have * allocation problems; initial construction would require * reallocation upon resizing */ // validate that buffer is large enough now rust_assert (end + num_items_to_read <= (int) buffer.size ()); } /* iterate through buffer and invoke operator () on source on values * past original end */ for (int i = 0; i < num_items_to_read; i++) buffer[end + i] = source.next (); // move end based on additional items added end += num_items_to_read; } rust_assert (0 <= start); rust_assert (start <= end); rust_assert (end <= (int) buffer.size ()); rust_assert (start + n < end); // return value at start + n in buffer return buffer[start + n]; } /* TODO: add faster peek current token to remove overhead of conditional * branches? */ // Advances start by n + 1. void skip (int n) { // Call peek to ensure requested n is actually in queue. peek (n); // Clear queue values from start to n (inclusive). for (int i = 0; i < (n + 1); i++) buffer[start + i] = T (); // Move start forward by n + 1. start += (n + 1); // Ensure start is not impossible somehow rust_assert (0 <= start); rust_assert (start <= end); // Compact buffer if empty if (start == end) start = end = 0; } /* Inserts element at front of vector. Really dirty hack with terrible * performance, only use when really needed. */ void insert_at_front (T elem_to_insert) { // TODO: test as this may not work properly // Insert actual element in buffer at start. buffer.insert (buffer.begin (), elem_to_insert); /* Increase the end number since added element means all others have shifted * one along */ end++; } // Insert at arbitrary position (attempt) void insert (int index, T elem_to_insert) { // TODO: test as this may not work properly // n should not be behind rust_assert (index >= 0); // call peek to ensure that the items behind this (at least) are in queue if (index >= 1) peek (index - 1); else peek (index); buffer.insert (buffer.begin () + start + index, std::move (elem_to_insert)); end++; } // Replaces the current value in the buffer. Total HACK. void replace_current_value (T replacement) { // call peek to ensure value exists peek (0); buffer[start] = std::move (replacement); // don't move start or end } private: // Source of tokens for queue. Source source; // Begin of range in buffer, inclusive. int start; // End of range in buffer, exclusive. int end; // Queue buffer. std::vector buffer; }; } // namespace Rust #endif