diff --git a/src/support/inplace_vector.h b/src/support/inplace_vector.h new file mode 100644 index 00000000000..21826ea737d --- /dev/null +++ b/src/support/inplace_vector.h @@ -0,0 +1,170 @@ +/* + * Copyright 2026 WebAssembly Community Group participants + * + * 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. + */ + +// +// A vector of elements with a maximum size, storing them all in-place. This is +// similar to c++26's inplace_vector, and is basically a small_vector, except +// there is never any dynamic storage. +// TODO: remove when we have c++26 +// + +#ifndef wasm_support_inplace_vector_h +#define wasm_support_inplace_vector_h + +#include +#include +#include + +#include "support/parent_index_iterator.h" + +namespace wasm { + +template class inplace_vector { + // fixed-space storage + size_t usedFixed = 0; + std::array fixed{}; + +public: + using value_type = T; + + inplace_vector() {} + inplace_vector(const inplace_vector& other) + : usedFixed(other.usedFixed), fixed(other.fixed) {} + inplace_vector(inplace_vector&& other) + : usedFixed(other.usedFixed), fixed(std::move(other.fixed)) {} + inplace_vector(std::initializer_list init) { + for (const T& item : init) { + push_back(item); + } + } + inplace_vector(size_t initialSize) { resize(initialSize); } + + inplace_vector& operator=(const inplace_vector& other) { + usedFixed = other.usedFixed; + fixed = other.fixed; + return *this; + } + + inplace_vector& operator=(inplace_vector&& other) { + usedFixed = other.usedFixed; + fixed = std::move(other.fixed); + return *this; + } + + T& operator[](size_t i) { return fixed[i]; } + + const T& operator[](size_t i) const { + return const_cast&>(*this)[i]; + } + + void push_back(const T& x) { + assert(usedFixed < N); + fixed[usedFixed++] = x; + } + + template void emplace_back(ArgTypes&&... Args) { + assert(usedFixed < N); + new (&fixed[usedFixed++]) T(std::forward(Args)...); + } + + void pop_back() { + assert(usedFixed > 0); + usedFixed--; + } + + T& back() { + assert(usedFixed > 0); + return fixed[usedFixed - 1]; + } + + const T& back() const { + assert(usedFixed > 0); + return fixed[usedFixed - 1]; + } + + size_t size() const { return usedFixed; } + + bool empty() const { return size() == 0; } + + void clear() { usedFixed = 0; } + + void resize(size_t newSize) { + assert(newSize <= N); + usedFixed = newSize; + } + + size_t capacity() const { return N; } + + bool operator==(const inplace_vector& other) const { + if (usedFixed != other.usedFixed) { + return false; + } + for (size_t i = 0; i < usedFixed; i++) { + if (fixed[i] != other.fixed[i]) { + return false; + } + } + return true; + } + + bool operator!=(const inplace_vector& other) const { + return !(*this == other); + } + + // iteration + + struct Iterator : wasm::ParentIndexIterator*, Iterator> { + using value_type = T; + using pointer = T*; + using reference = T&; + + Iterator(inplace_vector* parent, size_t index) + : wasm::ParentIndexIterator*, Iterator>{parent, + index} {} + Iterator(const Iterator& other) = default; + + T& operator*() { return (*this->parent)[this->index]; } + }; + + struct ConstIterator + : wasm::ParentIndexIterator*, ConstIterator> { + using value_type = const T; + using pointer = const T*; + using reference = const T&; + + ConstIterator(const inplace_vector* parent, size_t index) + : wasm::ParentIndexIterator*, ConstIterator>{ + parent, index} {} + ConstIterator(const ConstIterator& other) = default; + + const T& operator*() const { return (*this->parent)[this->index]; } + }; + + Iterator begin() { return Iterator(this, 0); } + Iterator end() { return Iterator(this, size()); } + ConstIterator begin() const { return ConstIterator(this, 0); } + ConstIterator end() const { return ConstIterator(this, size()); } + + void erase(Iterator a, Iterator b) { + // Atm we only support erasing at the end, which is very efficient. + assert(b == end()); + resize(a.index); + } +}; + +} // namespace wasm + +#endif // wasm_support_inplace_vector_h diff --git a/src/support/small_vector.h b/src/support/small_vector.h index 8cad6ade29e..f4a1560d21f 100644 --- a/src/support/small_vector.h +++ b/src/support/small_vector.h @@ -49,7 +49,7 @@ template class SmallVector { : usedFixed(other.usedFixed), fixed(std::move(other.fixed)), flexible(std::move(other.flexible)) {} SmallVector(std::initializer_list init) { - for (T item : init) { + for (const T& item : init) { push_back(item); } } diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index 05ce44006a6..4bd358032d7 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -17,6 +17,7 @@ set(unittest_SOURCES int128.cpp leaves.cpp glbs.cpp + inplace_vector.cpp interpreter.cpp intervals.cpp istring.cpp diff --git a/test/gtest/inplace_vector.cpp b/test/gtest/inplace_vector.cpp new file mode 100644 index 00000000000..34c1a3d3e1b --- /dev/null +++ b/test/gtest/inplace_vector.cpp @@ -0,0 +1,44 @@ +#include "support/inplace_vector.h" +#include "gtest/gtest.h" + +using InplaceVectorTest = ::testing::Test; + +using namespace wasm; + +TEST_F(InplaceVectorTest, Size) { + inplace_vector vec; + // An inplace_vector is just a size plus the in-place storage. + EXPECT_EQ(sizeof(vec), sizeof(size_t) + 10 * sizeof(int64_t)); +} + +TEST_F(InplaceVectorTest, Basics) { + inplace_vector vec; + + EXPECT_EQ(vec.size(), 0); + EXPECT_TRUE(vec.empty()); + vec.push_back(10); + EXPECT_EQ(vec[0], 10); + EXPECT_EQ(vec.size(), 1); + + vec.resize(3); + EXPECT_EQ(vec.size(), 3); + + vec[1] = 20; + vec[2] = 30; + EXPECT_EQ(vec[1], 20); + EXPECT_EQ(vec[2], 30); + + vec.pop_back(); + EXPECT_EQ(vec.size(), 2); +} + +TEST_F(InplaceVectorTest, I) { + inplace_vector vec{10, 20, 30}; + std::vector normal; + + for (auto x : vec) { + normal.push_back(x); + } + + EXPECT_EQ(normal, std::vector({10, 20, 30})); +}