Skip to content

Commit 6331e09

Browse files
authored
Parallelize istring creation (#1008)
* parallelize istring creation, by having a thread-local set and a global set guarded by a mutex. each time a new string shows up in a thread, it will be added to that thread's set, after accessing the global set through the lock first, which means we lock at most once per new string per thread * don't leak strings in istring store * since we now create names in a parallel thread-safe manner, we don't need to pre-create names in RelooperJumpThreading
1 parent 77802a6 commit 6331e09

File tree

2 files changed

+37
-42
lines changed

2 files changed

+37
-42
lines changed

src/emscripten-optimizer/istring.h

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <assert.h>
3131

3232
#include "support/threads.h"
33+
#include "support/utilities.h"
3334

3435
namespace cashew {
3536

@@ -66,21 +67,35 @@ struct IString {
6667

6768
void set(const char *s, bool reuse=true) {
6869
typedef std::unordered_set<const char *, CStringHash, CStringEqual> StringSet;
69-
static StringSet* strings = new StringSet();
70-
71-
auto existing = strings->find(s);
72-
73-
if (existing == strings->end()) {
74-
// the StringSet cache is a global shared structure, which should
75-
// not be modified by multiple threads at once.
76-
assert(!wasm::ThreadPool::isRunning());
77-
if (!reuse) {
78-
size_t len = strlen(s) + 1;
79-
char *copy = (char*)malloc(len); // XXX leaked
80-
strncpy(copy, s, len);
81-
s = copy;
70+
// one global store of strings per thread, we must not access this
71+
// in parallel
72+
thread_local static StringSet strings;
73+
74+
auto existing = strings.find(s);
75+
76+
if (existing == strings.end()) {
77+
// if the string isn't already known, we must use a single global
78+
// storage location, guarded by a mutex, so each string is allocated
79+
// exactly once
80+
static std::mutex mutex;
81+
std::unique_lock<std::mutex> lock(mutex);
82+
// a single global set contains the actual strings, so we allocate each one
83+
// exactly once.
84+
static StringSet globalStrings;
85+
auto globalExisting = globalStrings.find(s);
86+
if (globalExisting == globalStrings.end()) {
87+
if (!reuse) {
88+
static std::vector<std::unique_ptr<std::string>> allocated;
89+
allocated.emplace_back(wasm::make_unique<std::string>(s));
90+
s = allocated.back()->c_str(); // we'll never modify it, so this is ok
91+
}
92+
// insert into global set
93+
globalStrings.insert(s);
94+
} else {
95+
s = *globalExisting;
8296
}
83-
strings->insert(s);
97+
// add the string to our thread-local set
98+
strings.insert(s);
8499
} else {
85100
s = *existing;
86101
}

src/passes/RelooperJumpThreading.cpp

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,13 @@ namespace wasm {
2727

2828
static Name LABEL("label");
2929

30-
// We need to use new label names, which we cannot create in parallel, so pre-create them
31-
32-
const Index MAX_NAME_INDEX = 10000;
33-
34-
std::vector<Name>* innerNames = nullptr;
35-
std::vector<Name>* outerNames = nullptr;
30+
static Name getInnerName(int i) {
31+
return Name(std::string("__rjti$") + std::to_string(i));
32+
}
3633

37-
struct NameEnsurer {
38-
NameEnsurer() {
39-
assert(!innerNames);
40-
assert(!outerNames);
41-
innerNames = new std::vector<Name>;
42-
outerNames = new std::vector<Name>;
43-
for (Index i = 0; i < MAX_NAME_INDEX; i++) {
44-
innerNames->push_back(Name(std::string("__rjti$") + std::to_string(i)));
45-
outerNames->push_back(Name(std::string("__rjto$") + std::to_string(i)));
46-
}
47-
}
48-
};
34+
static Name getOuterName(int i) {
35+
return Name(std::string("__rjto$") + std::to_string(i));
36+
}
4937

5038
static If* isLabelCheckingIf(Expression* curr, Index labelIndex) {
5139
if (!curr) return nullptr;
@@ -99,10 +87,6 @@ struct RelooperJumpThreading : public WalkerPass<ExpressionStackWalker<RelooperJ
9987

10088
Pass* create() override { return new RelooperJumpThreading; }
10189

102-
void prepareToRun(PassRunner* runner, Module* module) override {
103-
static NameEnsurer ensurer;
104-
}
105-
10690
std::map<Index, Index> labelChecks;
10791
std::map<Index, Index> labelSets;
10892

@@ -208,17 +192,13 @@ struct RelooperJumpThreading : public WalkerPass<ExpressionStackWalker<RelooperJ
208192
// * iff is the if
209193
void optimizeJumpsToLabelCheck(Expression*& origin, If* iff) {
210194
Index nameCounter = newNameCounter++;
211-
if (nameCounter >= MAX_NAME_INDEX) {
212-
std::cerr << "too many names in RelooperJumpThreading :(\n";
213-
return;
214-
}
215195
Index num = getCheckedLabelValue(iff);
216196
// create a new block for this jump target
217197
Builder builder(*getModule());
218198
// origin is where all jumps to this target must come from - the element right before this if
219199
// we break out of inner to reach the target. instead of flowing out of normally, we break out of the outer, so we skip the target.
220-
auto innerName = innerNames->at(nameCounter);
221-
auto outerName = outerNames->at(nameCounter);
200+
auto innerName = getInnerName(nameCounter);
201+
auto outerName = getOuterName(nameCounter);
222202
auto* ifFalse = iff->ifFalse;
223203
// all assignments of label to the target can be replaced with breaks to the target, via innerName
224204
struct JumpUpdater : public PostWalker<JumpUpdater> {

0 commit comments

Comments
 (0)