Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions src/support/inplace_vector.h
Original file line number Diff line number Diff line change
@@ -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 <array>
#include <cassert>
#include <vector>

#include "support/parent_index_iterator.h"

namespace wasm {

template<typename T, size_t N> class inplace_vector {
// fixed-space storage
size_t usedFixed = 0;
std::array<T, N> fixed{};

public:
using value_type = T;

inplace_vector() {}
inplace_vector(const inplace_vector<T, N>& other)
: usedFixed(other.usedFixed), fixed(other.fixed) {}
inplace_vector(inplace_vector<T, N>&& other)
: usedFixed(other.usedFixed), fixed(std::move(other.fixed)) {}
inplace_vector(std::initializer_list<T> init) {
for (const T& item : init) {
push_back(item);
}
}
inplace_vector(size_t initialSize) { resize(initialSize); }

inplace_vector<T, N>& operator=(const inplace_vector<T, N>& other) {
usedFixed = other.usedFixed;
fixed = other.fixed;
return *this;
}

inplace_vector<T, N>& operator=(inplace_vector<T, N>&& 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<inplace_vector<T, N>&>(*this)[i];
}

void push_back(const T& x) {
assert(usedFixed < N);
fixed[usedFixed++] = x;
}

template<typename... ArgTypes> void emplace_back(ArgTypes&&... Args) {
assert(usedFixed < N);
new (&fixed[usedFixed++]) T(std::forward<ArgTypes>(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<T, N>& 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<T, N>& other) const {
return !(*this == other);
}

// iteration

struct Iterator : wasm::ParentIndexIterator<inplace_vector<T, N>*, Iterator> {
using value_type = T;
using pointer = T*;
using reference = T&;

Iterator(inplace_vector<T, N>* parent, size_t index)
: wasm::ParentIndexIterator<inplace_vector<T, N>*, Iterator>{parent,
index} {}
Iterator(const Iterator& other) = default;

T& operator*() { return (*this->parent)[this->index]; }
};

struct ConstIterator
: wasm::ParentIndexIterator<const inplace_vector<T, N>*, ConstIterator> {
using value_type = const T;
using pointer = const T*;
using reference = const T&;

ConstIterator(const inplace_vector<T, N>* parent, size_t index)
: wasm::ParentIndexIterator<const inplace_vector<T, N>*, 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
2 changes: 1 addition & 1 deletion src/support/small_vector.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ template<typename T, size_t N> class SmallVector {
: usedFixed(other.usedFixed), fixed(std::move(other.fixed)),
flexible(std::move(other.flexible)) {}
SmallVector(std::initializer_list<T> init) {
for (T item : init) {
for (const T& item : init) {
push_back(item);
}
}
Expand Down
1 change: 1 addition & 0 deletions test/gtest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ set(unittest_SOURCES
int128.cpp
leaves.cpp
glbs.cpp
inplace_vector.cpp
interpreter.cpp
intervals.cpp
istring.cpp
Expand Down
44 changes: 44 additions & 0 deletions test/gtest/inplace_vector.cpp
Original file line number Diff line number Diff line change
@@ -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<int64_t, 10> 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<int, 3> 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);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also check that the size is 0 and then 1 before doing this resize.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


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<int, 3> vec{10, 20, 30};
std::vector<int> normal;

for (auto x : vec) {
normal.push_back(x);
}

EXPECT_EQ(normal, std::vector<int>({10, 20, 30}));
}
Loading