e443b89f23
Includes support for modifier spans and duplicate named capture groups. Drops the flow graph implementation to ease maintenance. TEST=corelib/regexp Bug: https://github.com/dart-lang/sdk/issues/56573 Bug: https://github.com/dart-lang/sdk/issues/61337 Bug: https://github.com/dart-lang/sdk/issues/62349 Bug: https://github.com/dart-lang/sdk/issues/62708 Change-Id: I05640ba945a4fa5476e7ad463738f4f39d842c14 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/480121 Reviewed-by: Lasse Nielsen <lrn@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
388 lines
12 KiB
C++
388 lines
12 KiB
C++
// Copyright 2018 the V8 project 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 V8_BASE_SMALL_VECTOR_H_
|
|
#define V8_BASE_SMALL_VECTOR_H_
|
|
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
#include "platform/utils.h"
|
|
#include "vm/regexp/memcopy.h"
|
|
#include "vm/regexp/vector.h"
|
|
|
|
#define V8_NO_UNIQUE_ADDRESS
|
|
|
|
namespace base {
|
|
|
|
// Minimal SmallVector implementation. Uses inline storage first, switches to
|
|
// dynamic storage when it overflows.
|
|
template <typename T, size_t kSize, typename Allocator = std::allocator<T>>
|
|
class SmallVector {
|
|
// TODO(mliedtke): Remove kHasTrivialElement and replace usages with the
|
|
// proper conditions.
|
|
static constexpr bool kHasTrivialElement =
|
|
std::is_trivially_copyable<T>::value &&
|
|
std::is_trivially_destructible<T>::value;
|
|
|
|
public:
|
|
static constexpr size_t kInlineSize = kSize;
|
|
using value_type = T;
|
|
using reference = T&;
|
|
using const_reference = const T&;
|
|
using iterator = T*;
|
|
using const_iterator = const T*;
|
|
using difference_type = std::ptrdiff_t;
|
|
using size_type = std::size_t;
|
|
|
|
SmallVector() = default;
|
|
explicit SmallVector(const Allocator& allocator) : allocator_(allocator) {}
|
|
// Constructs a SmallVector with `size` elements. These elements will be
|
|
// default-initialized(!), differently to e.g. `std::vector`. If
|
|
// value-initialization is desired, use the constructor overload with an
|
|
// explicit `initial_value` instead.
|
|
explicit V8_INLINE SmallVector(size_t size,
|
|
const Allocator& allocator = Allocator())
|
|
requires std::default_initializable<T>
|
|
: allocator_(allocator) {
|
|
resize(size);
|
|
}
|
|
explicit V8_INLINE SmallVector(size_t size,
|
|
const T& initial_value,
|
|
const Allocator& allocator = Allocator())
|
|
: allocator_(allocator) {
|
|
resize(size, initial_value);
|
|
}
|
|
SmallVector(const SmallVector& other) V8_NOEXCEPT
|
|
: allocator_(other.allocator_) {
|
|
*this = other;
|
|
}
|
|
SmallVector(const SmallVector& other, const Allocator& allocator) V8_NOEXCEPT
|
|
: allocator_(allocator) {
|
|
*this = other;
|
|
}
|
|
SmallVector(SmallVector&& other) V8_NOEXCEPT
|
|
: allocator_(std::move(other.allocator_)) {
|
|
*this = std::move(other);
|
|
}
|
|
SmallVector(SmallVector&& other, const Allocator& allocator) V8_NOEXCEPT
|
|
: allocator_(allocator) {
|
|
*this = std::move(other);
|
|
}
|
|
V8_INLINE SmallVector(std::initializer_list<T> init,
|
|
const Allocator& allocator = Allocator())
|
|
: allocator_(allocator) {
|
|
if (init.size() > capacity()) Grow(init.size());
|
|
DCHECK_GE(capacity(), init.size()); // Sanity check.
|
|
std::uninitialized_move(init.begin(), init.end(), begin_);
|
|
end_ = begin_ + init.size();
|
|
}
|
|
explicit V8_INLINE SmallVector(base::Vector<const T> init,
|
|
const Allocator& allocator = Allocator())
|
|
: allocator_(allocator) {
|
|
if (init.size() > capacity()) Grow(init.size());
|
|
DCHECK_GE(capacity(), init.size()); // Sanity check.
|
|
std::uninitialized_copy(init.begin(), init.end(), begin_);
|
|
end_ = begin_ + init.size();
|
|
}
|
|
|
|
~SmallVector() { FreeStorage(); }
|
|
|
|
SmallVector& operator=(const SmallVector& other) V8_NOEXCEPT {
|
|
if (this == &other) return *this;
|
|
size_t other_size = other.size();
|
|
if (capacity() < other_size) {
|
|
// Create large-enough heap-allocated storage.
|
|
FreeStorage();
|
|
begin_ = AllocateDynamicStorage(other_size);
|
|
end_of_storage_ = begin_ + other_size;
|
|
std::uninitialized_copy(other.begin_, other.end_, begin_);
|
|
} else if constexpr (kHasTrivialElement) {
|
|
std::copy(other.begin_, other.end_, begin_);
|
|
} else {
|
|
ptrdiff_t to_copy =
|
|
std::min(static_cast<ptrdiff_t>(other_size), end_ - begin_);
|
|
std::copy(other.begin_, other.begin_ + to_copy, begin_);
|
|
if (other.begin_ + to_copy < other.end_) {
|
|
std::uninitialized_copy(other.begin_ + to_copy, other.end_,
|
|
begin_ + to_copy);
|
|
} else {
|
|
std::destroy_n(begin_ + to_copy, size() - to_copy);
|
|
}
|
|
}
|
|
end_ = begin_ + other_size;
|
|
return *this;
|
|
}
|
|
|
|
SmallVector& operator=(SmallVector&& other) V8_NOEXCEPT {
|
|
if (this == &other) return *this;
|
|
if (other.is_big()) {
|
|
FreeStorage();
|
|
begin_ = other.begin_;
|
|
end_ = other.end_;
|
|
end_of_storage_ = other.end_of_storage_;
|
|
} else {
|
|
DCHECK_GE(capacity(), other.size()); // Sanity check.
|
|
size_t other_size = other.size();
|
|
if constexpr (kHasTrivialElement) {
|
|
// Ranges cannot overlap and we can just emit a trivial memcpy.
|
|
base::MemCopy(begin_, other.begin_, other_size * sizeof(T));
|
|
} else {
|
|
ptrdiff_t to_move =
|
|
std::min(static_cast<ptrdiff_t>(other_size), end_ - begin_);
|
|
std::move(other.begin_, other.begin_ + to_move, begin_);
|
|
if (other.begin_ + to_move < other.end_) {
|
|
std::uninitialized_move(other.begin_ + to_move, other.end_,
|
|
begin_ + to_move);
|
|
} else {
|
|
std::destroy_n(begin_ + to_move, size() - to_move);
|
|
}
|
|
}
|
|
end_ = begin_ + other_size;
|
|
}
|
|
other.reset_to_inline_storage();
|
|
return *this;
|
|
}
|
|
|
|
T* data() { return begin_; }
|
|
const T* data() const { return begin_; }
|
|
|
|
T* begin() { return begin_; }
|
|
const T* begin() const { return begin_; }
|
|
|
|
T* end() { return end_; }
|
|
const T* end() const { return end_; }
|
|
|
|
auto rbegin() { return std::make_reverse_iterator(end_); }
|
|
auto rbegin() const { return std::make_reverse_iterator(end_); }
|
|
|
|
auto rend() { return std::make_reverse_iterator(begin_); }
|
|
auto rend() const { return std::make_reverse_iterator(begin_); }
|
|
|
|
size_t size() const { return end_ - begin_; }
|
|
bool empty() const { return end_ == begin_; }
|
|
size_t capacity() const { return end_of_storage_ - begin_; }
|
|
|
|
T& front() {
|
|
DCHECK_NE(0, size());
|
|
return begin_[0];
|
|
}
|
|
const T& front() const {
|
|
DCHECK_NE(0, size());
|
|
return begin_[0];
|
|
}
|
|
|
|
T& back() {
|
|
DCHECK_NE(0, size());
|
|
return end_[-1];
|
|
}
|
|
const T& back() const {
|
|
DCHECK_NE(0, size());
|
|
return end_[-1];
|
|
}
|
|
|
|
T& at(size_t index) {
|
|
DCHECK_GT(size(), index);
|
|
return begin_[index];
|
|
}
|
|
|
|
T& operator[](size_t index) {
|
|
DCHECK_GT(size(), index);
|
|
return begin_[index];
|
|
}
|
|
|
|
const T& at(size_t index) const {
|
|
DCHECK_GT(size(), index);
|
|
return begin_[index];
|
|
}
|
|
|
|
const T& operator[](size_t index) const { return at(index); }
|
|
|
|
template <typename... Args>
|
|
void emplace_back(Args&&... args) {
|
|
if (V8_UNLIKELY(end_ == end_of_storage_)) Grow();
|
|
void* storage = end_;
|
|
end_ += 1;
|
|
new (storage) T(std::forward<Args>(args)...);
|
|
}
|
|
|
|
void push_back(T x) { emplace_back(std::move(x)); }
|
|
|
|
void pop_back(size_t count = 1) {
|
|
DCHECK_GE(size(), count);
|
|
end_ -= count;
|
|
std::destroy_n(end_, count);
|
|
}
|
|
|
|
T* insert(T* pos, const T& value) {
|
|
return insert(pos, static_cast<size_t>(1), value);
|
|
}
|
|
T* insert(T* pos, size_t count, const T& value) {
|
|
DCHECK_LE(pos, end_);
|
|
size_t offset = pos - begin_;
|
|
size_t old_size = size();
|
|
resize(old_size + count);
|
|
pos = begin_ + offset;
|
|
T* old_end = begin_ + old_size;
|
|
DCHECK_LE(old_end, end_);
|
|
std::move_backward(pos, old_end, end_);
|
|
std::fill_n(pos, count, value);
|
|
return pos;
|
|
}
|
|
template <typename It>
|
|
T* insert(T* pos, It begin, It end) {
|
|
DCHECK_LE(pos, end_);
|
|
size_t offset = pos - begin_;
|
|
size_t count = std::distance(begin, end);
|
|
size_t old_size = size();
|
|
resize(old_size + count);
|
|
pos = begin_ + offset;
|
|
T* old_end = begin_ + old_size;
|
|
DCHECK_LE(old_end, end_);
|
|
std::move_backward(pos, old_end, end_);
|
|
std::copy(begin, end, pos);
|
|
return pos;
|
|
}
|
|
|
|
T* insert(T* pos, std::initializer_list<const T> values) {
|
|
return insert(pos, values.begin(), values.end());
|
|
}
|
|
|
|
template <typename Container>
|
|
requires requires(const Container& v) {
|
|
std::is_same_v<decltype(std::begin(v)), decltype(std::end(v))>;
|
|
}
|
|
T* insert(T* pos, const Container& values) {
|
|
return insert(pos, std::begin(values), std::end(values));
|
|
}
|
|
|
|
T* erase(T* erase_start, T* erase_end) {
|
|
DCHECK_GE(erase_start, begin_);
|
|
DCHECK_LE(erase_start, erase_end);
|
|
DCHECK_LE(erase_end, end_);
|
|
T* new_end = std::move(erase_end, end_, erase_start);
|
|
std::destroy(new_end, end_);
|
|
end_ = new_end;
|
|
return erase_start;
|
|
}
|
|
|
|
T* erase(T* pos) { return erase(pos, pos + 1); }
|
|
|
|
// Resizes the SmallVector to the provided `new_size`. If `new_size` is larger
|
|
// than the current size, the new elements will not be default-initialized,
|
|
// (meaning the objects will only be allocated, not constructed.)
|
|
// This is only valid if `T` is an implicit lifetime type.
|
|
void resize_no_init(size_t new_size)
|
|
requires kHasTrivialElement
|
|
{
|
|
if (new_size > capacity()) Grow(new_size);
|
|
end_ = begin_ + new_size;
|
|
}
|
|
|
|
// Resizes the SmallVector to the provided `new_size`. If `new_size` is larger
|
|
// than the current size, the new elements will be default-initialized.
|
|
void resize(size_t new_size)
|
|
requires std::default_initializable<T>
|
|
{
|
|
if (new_size > capacity()) Grow(new_size);
|
|
T* new_end = begin_ + new_size;
|
|
if (new_end > end_) {
|
|
std::uninitialized_default_construct(end_, new_end);
|
|
} else {
|
|
std::destroy(new_end, end_);
|
|
}
|
|
end_ = new_end;
|
|
}
|
|
|
|
void resize(size_t new_size, const T& initial_value) {
|
|
if (new_size > capacity()) Grow(new_size);
|
|
T* new_end = begin_ + new_size;
|
|
if (new_end > end_) {
|
|
std::uninitialized_fill(end_, new_end, initial_value);
|
|
} else {
|
|
std::destroy(new_end, end_);
|
|
}
|
|
end_ = new_end;
|
|
}
|
|
|
|
void reserve(size_t new_capacity) {
|
|
if (new_capacity > capacity()) Grow(new_capacity);
|
|
}
|
|
|
|
// Clear without reverting back to inline storage.
|
|
void clear() {
|
|
std::destroy(begin_, end_);
|
|
end_ = begin_;
|
|
}
|
|
|
|
Allocator get_allocator() const { return allocator_; }
|
|
|
|
private:
|
|
// Grows the backing store by a factor of two. Returns the new end of the used
|
|
// storage (this reduces binary size).
|
|
V8_NOINLINE V8_PRESERVE_MOST void Grow() { Grow(0); }
|
|
|
|
// Grows the backing store by a factor of two, and at least to {min_capacity}.
|
|
V8_NOINLINE V8_PRESERVE_MOST void Grow(size_t min_capacity) {
|
|
size_t in_use = end_ - begin_;
|
|
size_t new_capacity = dart::Utils::RoundUpToPowerOfTwo(
|
|
std::max(min_capacity, 2 * capacity()));
|
|
T* new_storage = AllocateDynamicStorage(new_capacity);
|
|
if (new_storage == nullptr) {
|
|
FATAL("OOM: base::SmallVector::Grow");
|
|
}
|
|
std::uninitialized_move(begin_, end_, new_storage);
|
|
FreeStorage();
|
|
begin_ = new_storage;
|
|
end_ = new_storage + in_use;
|
|
end_of_storage_ = new_storage + new_capacity;
|
|
}
|
|
|
|
T* AllocateDynamicStorage(size_t number_of_elements) {
|
|
return allocator_.allocate(number_of_elements);
|
|
}
|
|
|
|
V8_NOINLINE V8_PRESERVE_MOST void FreeStorage() {
|
|
std::destroy(begin_, end_);
|
|
if (is_big()) allocator_.deallocate(begin_, end_of_storage_ - begin_);
|
|
}
|
|
|
|
// Clear and go back to inline storage. Dynamic storage is *not* freed. For
|
|
// internal use only.
|
|
void reset_to_inline_storage() {
|
|
if constexpr (!kHasTrivialElement) {
|
|
if (!is_big()) std::destroy(begin_, end_);
|
|
}
|
|
begin_ = inline_storage_begin();
|
|
end_ = begin_;
|
|
end_of_storage_ = begin_ + kInlineSize;
|
|
}
|
|
|
|
bool is_big() const { return begin_ != inline_storage_begin(); }
|
|
|
|
T* inline_storage_begin() { return reinterpret_cast<T*>(inline_storage_); }
|
|
const T* inline_storage_begin() const {
|
|
return reinterpret_cast<const T*>(inline_storage_);
|
|
}
|
|
|
|
V8_NO_UNIQUE_ADDRESS Allocator allocator_;
|
|
|
|
// Invariants:
|
|
// 1. The elements in the range between `begin_` (included) and `end_` (not
|
|
// included) will be initialized at all times.
|
|
// 2. All other elements outside the range, both in the inline storage and in
|
|
// the dynamic storage (if it exists), will be uninitialized at all times.
|
|
|
|
T* begin_ = inline_storage_begin();
|
|
T* end_ = begin_;
|
|
T* end_of_storage_ = begin_ + kInlineSize;
|
|
alignas(T) char inline_storage_[sizeof(T) * kInlineSize];
|
|
};
|
|
|
|
} // namespace base
|
|
|
|
#endif // V8_BASE_SMALL_VECTOR_H_
|