Skip to content

Commit 605e2b7

Browse files
authored
SmallVector (#1912)
Trying to refactor the code to be simpler and less redundant, I ran into some perf issues that it seems like a small vector, with fixed-size storage and optional additional storage as needed, might help with. This implements that class and uses it in a few places. This seems to help, I see some 1-2% fewer instructions and cycles in `perf stat`, but it's hard to tell if it really makes a noticeable difference.
1 parent f11b7e7 commit 605e2b7

File tree

7 files changed

+256
-9
lines changed

7 files changed

+256
-9
lines changed

src/ir/ExpressionAnalyzer.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ namespace wasm {
2424
// Given a stack of expressions, checks if the topmost is used as a result.
2525
// For example, if the parent is a block and the node is before the last position,
2626
// it is not used.
27-
bool ExpressionAnalyzer::isResultUsed(std::vector<Expression*> stack, Function* func) {
27+
bool ExpressionAnalyzer::isResultUsed(ExpressionStack& stack, Function* func) {
2828
for (int i = int(stack.size()) - 2; i >= 0; i--) {
2929
auto* curr = stack[i];
3030
auto* above = stack[i + 1];
@@ -52,7 +52,7 @@ bool ExpressionAnalyzer::isResultUsed(std::vector<Expression*> stack, Function*
5252
}
5353

5454
// Checks if a value is dropped.
55-
bool ExpressionAnalyzer::isResultDropped(std::vector<Expression*> stack) {
55+
bool ExpressionAnalyzer::isResultDropped(ExpressionStack& stack) {
5656
for (int i = int(stack.size()) - 2; i >= 0; i--) {
5757
auto* curr = stack[i];
5858
auto* above = stack[i + 1];

src/ir/utils.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ struct ExpressionAnalyzer {
4545
// Given a stack of expressions, checks if the topmost is used as a result.
4646
// For example, if the parent is a block and the node is before the last position,
4747
// it is not used.
48-
static bool isResultUsed(std::vector<Expression*> stack, Function* func);
48+
static bool isResultUsed(ExpressionStack& stack, Function* func);
4949

5050
// Checks if a value is dropped.
51-
static bool isResultDropped(std::vector<Expression*> stack);
51+
static bool isResultDropped(ExpressionStack& stack);
5252

5353
// Checks if a break is a simple - no condition, no value, just a plain branching
5454
static bool isSimple(Break* curr) {
@@ -212,7 +212,7 @@ struct ReFinalizeNode : public OverriddenVisitor<ReFinalizeNode> {
212212
void visitModule(Module* curr) { WASM_UNREACHABLE(); }
213213

214214
// given a stack of nested expressions, update them all from child to parent
215-
static void updateStack(std::vector<Expression*>& expressionStack) {
215+
static void updateStack(ExpressionStack& expressionStack) {
216216
for (int i = int(expressionStack.size()) - 1; i >= 0; i--) {
217217
auto* curr = expressionStack[i];
218218
ReFinalizeNode().visit(curr);

src/passes/SimplifyLocals.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ struct SimplifyLocals : public WalkerPass<LinearExecutionWalker<SimplifyLocals<a
271271

272272
// a full expression stack is used when !allowNesting, so that we can check if
273273
// a sink would cause nesting
274-
std::vector<Expression*> expressionStack;
274+
ExpressionStack expressionStack;
275275

276276
static void visitPre(SimplifyLocals<allowTee, allowStructure, allowNesting>* self, Expression** currp) {
277277
Expression* curr = *currp;

src/support/small_vector.h

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/*
2+
* Copyright 2019 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
//
18+
// A vector of elements, which may be small, and uses a fixed space
19+
// for those small elements.
20+
//
21+
22+
#ifndef wasm_support_small_vector_h
23+
#define wasm_support_small_vector_h
24+
25+
#include <array>
26+
#include <iterator>
27+
#include <vector>
28+
29+
namespace wasm {
30+
31+
template<typename T, size_t N>
32+
class SmallVector {
33+
// fixed-space storage
34+
size_t usedFixed = 0;
35+
std::array<T, N> fixed;
36+
37+
// flexible additional storage
38+
std::vector<T> flexible;
39+
40+
public:
41+
SmallVector() {}
42+
43+
T& operator[](size_t i) {
44+
if (i < N) {
45+
return fixed[i];
46+
} else {
47+
return flexible[i - N];
48+
}
49+
}
50+
51+
T operator[](size_t i) const {
52+
if (i < N) {
53+
return fixed[i];
54+
} else {
55+
return flexible[i - N];
56+
}
57+
}
58+
59+
void push_back(const T& x) {
60+
if (usedFixed < N) {
61+
fixed[usedFixed++] = x;
62+
} else {
63+
flexible.push_back(x);
64+
}
65+
}
66+
67+
template <typename... ArgTypes>
68+
void emplace_back(ArgTypes &&... Args) {
69+
if (usedFixed < N) {
70+
new(&fixed[usedFixed++]) T(std::forward<ArgTypes>(Args)...);
71+
} else {
72+
flexible.emplace_back(std::forward<ArgTypes>(Args)...);
73+
}
74+
}
75+
76+
void pop_back() {
77+
if (flexible.empty()) {
78+
assert(usedFixed > 0);
79+
usedFixed--;
80+
} else {
81+
flexible.pop_back();
82+
}
83+
}
84+
85+
T& back() {
86+
if (flexible.empty()) {
87+
assert(usedFixed > 0);
88+
return fixed[usedFixed - 1];
89+
} else {
90+
return flexible.back();
91+
}
92+
}
93+
94+
const T& back() const {
95+
if (flexible.empty()) {
96+
assert(usedFixed > 0);
97+
return fixed[usedFixed - 1];
98+
} else {
99+
return flexible.back();
100+
}
101+
}
102+
103+
size_t size() const {
104+
return usedFixed + flexible.size();
105+
}
106+
107+
bool empty() const {
108+
return size() == 0;
109+
}
110+
111+
void clear() {
112+
usedFixed = 0;
113+
flexible.clear();
114+
}
115+
116+
bool operator==(const SmallVector<T, N>& other) const {
117+
if (usedFixed != other.usedFixed) return false;
118+
for (size_t i = 0; i < usedFixed; i++) {
119+
if (fixed[i] != other.fixed[i]) return false;
120+
}
121+
return flexible == other.flexible;
122+
}
123+
124+
bool operator!=(const SmallVector<T, N>& other) const {
125+
return !(*this == other);
126+
}
127+
128+
// iteration
129+
130+
struct Iterator {
131+
typedef T value_type;
132+
typedef long difference_type;
133+
typedef T& reference;
134+
135+
const SmallVector<T, N>* parent;
136+
size_t index;
137+
138+
Iterator(const SmallVector<T, N>* parent, size_t index) : parent(parent), index(index) {}
139+
140+
bool operator!=(const Iterator& other) const {
141+
return index != other.index || parent != other.parent;
142+
}
143+
144+
void operator++() {
145+
index++;
146+
}
147+
148+
Iterator& operator+=(difference_type off) {
149+
index += off;
150+
return *this;
151+
}
152+
153+
const Iterator operator+(difference_type off) const {
154+
return Iterator(*this) += off;
155+
}
156+
157+
const value_type operator*() const {
158+
return (*parent)[index];
159+
}
160+
};
161+
162+
Iterator begin() const {
163+
return Iterator(static_cast<const SmallVector<T, N>*>(this), 0);
164+
}
165+
Iterator end() const {
166+
return Iterator(static_cast<const SmallVector<T, N>*>(this), size());
167+
}
168+
};
169+
170+
} // namespace wasm
171+
172+
#endif // wasm_support_small_vector_h

src/wasm-traversal.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#define wasm_wasm_traversal_h
2929

3030
#include "wasm.h"
31+
#include "support/small_vector.h"
3132
#include "support/threads.h"
3233

3334
namespace wasm {
@@ -408,6 +409,7 @@ struct Walker : public VisitorType {
408409
struct Task {
409410
TaskFunc func;
410411
Expression** currp;
412+
Task() {}
411413
Task(TaskFunc func, Expression** currp) : func(func), currp(currp) {}
412414
};
413415

@@ -488,7 +490,7 @@ struct Walker : public VisitorType {
488490

489491
private:
490492
Expression** replacep = nullptr; // the address of the current node, used to replace it
491-
std::vector<Task> stack; // stack of tasks
493+
SmallVector<Task, 10> stack; // stack of tasks
492494
Function* currFunction = nullptr; // current function being processed
493495
Module* currModule = nullptr; // current module being processed
494496
};
@@ -716,13 +718,17 @@ struct PostWalker : public Walker<SubType, VisitorType> {
716718
}
717719
};
718720

721+
// Stacks of expressions tend to be limited in size (although, sometimes
722+
// super-nested blocks exist for br_table).
723+
typedef SmallVector<Expression*, 10> ExpressionStack;
724+
719725
// Traversal with a control-flow stack.
720726

721727
template<typename SubType, typename VisitorType = Visitor<SubType>>
722728
struct ControlFlowWalker : public PostWalker<SubType, VisitorType> {
723729
ControlFlowWalker() = default;
724730

725-
std::vector<Expression*> controlFlowStack; // contains blocks, loops, and ifs
731+
ExpressionStack controlFlowStack; // contains blocks, loops, and ifs
726732

727733
// Uses the control flow stack to find the target of a break to a name
728734
Expression* findBreakTarget(Name name) {
@@ -785,7 +791,7 @@ template<typename SubType, typename VisitorType = Visitor<SubType>>
785791
struct ExpressionStackWalker : public PostWalker<SubType, VisitorType> {
786792
ExpressionStackWalker() = default;
787793

788-
std::vector<Expression*> expressionStack;
794+
ExpressionStack expressionStack;
789795

790796
// Uses the control flow stack to find the target of a break to a name
791797
Expression* findBreakTarget(Name name) {

test/example/small_vector.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#include <iostream>
2+
#include <cassert>
3+
4+
#include "support/small_vector.h"
5+
6+
using namespace wasm;
7+
8+
template<typename T>
9+
void test() {
10+
{
11+
T t;
12+
// build up
13+
assert(t.empty());
14+
assert(t.size() == 0);
15+
t.push_back(1);
16+
assert(!t.empty());
17+
assert(t.size() == 1);
18+
t.push_back(2);
19+
assert(!t.empty());
20+
assert(t.size() == 2);
21+
t.push_back(3);
22+
assert(!t.empty());
23+
// unwind
24+
assert(t.size() == 3);
25+
assert(t.back() == 3);
26+
t.pop_back();
27+
assert(t.size() == 2);
28+
assert(t.back() == 2);
29+
t.pop_back();
30+
assert(t.size() == 1);
31+
assert(t.back() == 1);
32+
t.pop_back();
33+
assert(t.size() == 0);
34+
assert(t.empty());
35+
}
36+
{
37+
T t;
38+
// build up
39+
t.push_back(1);
40+
t.push_back(2);
41+
t.push_back(3);
42+
// unwind
43+
t.clear();
44+
assert(t.size() == 0);
45+
assert(t.empty());
46+
}
47+
{
48+
T t, u;
49+
assert(t == u);
50+
t.push_back(1);
51+
assert(t != u);
52+
u.push_back(1);
53+
assert(t == u);
54+
u.pop_back();
55+
assert(t != u);
56+
u.push_back(2);
57+
assert(t != u);
58+
}
59+
}
60+
61+
int main() {
62+
test<SmallVector<int, 0>>();
63+
test<SmallVector<int, 1>>();
64+
test<SmallVector<int, 2>>();
65+
test<SmallVector<int, 10>>();
66+
std::cout << "ok.\n";
67+
}
68+

test/example/small_vector.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ok.

0 commit comments

Comments
 (0)