summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/binaryen-c.cpp26
-rw-r--r--src/binaryen-c.h8
-rw-r--r--src/gen-s-parser.inc29
-rw-r--r--src/ir/ExpressionAnalyzer.cpp2
-rw-r--r--src/ir/ExpressionManipulator.cpp4
-rw-r--r--src/ir/ReFinalize.cpp2
-rw-r--r--src/ir/effects.h4
-rw-r--r--src/ir/utils.h4
-rw-r--r--src/js/binaryen.js-post.js12
-rw-r--r--src/mixed_arena.h81
-rw-r--r--src/passes/CMakeLists.txt1
-rw-r--r--src/passes/DeadCodeElimination.cpp2
-rw-r--r--src/passes/Directize.cpp132
-rw-r--r--src/passes/Precompute.cpp2
-rw-r--r--src/passes/Print.cpp10
-rw-r--r--src/passes/RemoveUnusedModuleElements.cpp2
-rw-r--r--src/passes/SSAify.cpp57
-rw-r--r--src/passes/pass.cpp12
-rw-r--r--src/passes/passes.h2
-rw-r--r--src/shared-constants.h1
-rw-r--r--src/tools/fuzzing.h2
-rw-r--r--src/tools/wasm-emscripten-finalize.cpp25
-rw-r--r--src/tools/wasm-merge.cpp672
-rw-r--r--src/wasm-binary.h8
-rw-r--r--src/wasm-builder.h14
-rw-r--r--src/wasm-emscripten.h2
-rw-r--r--src/wasm-interpreter.h6
-rw-r--r--src/wasm-module-building.h20
-rw-r--r--src/wasm-s-parser.h2
-rw-r--r--src/wasm-stack.h10
-rw-r--r--src/wasm-traversal.h20
-rw-r--r--src/wasm.h10
-rw-r--r--src/wasm/wasm-binary.cpp24
-rw-r--r--src/wasm/wasm-emscripten.cpp149
-rw-r--r--src/wasm/wasm-s-parser.cpp6
-rw-r--r--src/wasm/wasm-validator.cpp18
-rw-r--r--src/wasm/wasm.cpp7
37 files changed, 539 insertions, 849 deletions
diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp
index 71cd92da4..72544d5d7 100644
--- a/src/binaryen-c.cpp
+++ b/src/binaryen-c.cpp
@@ -261,7 +261,7 @@ BinaryenExpressionId BinaryenUnreachableId(void) { return Expression::Id::Unreac
BinaryenExpressionId BinaryenAtomicCmpxchgId(void) { return Expression::Id::AtomicCmpxchgId; }
BinaryenExpressionId BinaryenAtomicRMWId(void) { return Expression::Id::AtomicRMWId; }
BinaryenExpressionId BinaryenAtomicWaitId(void) { return Expression::Id::AtomicWaitId; }
-BinaryenExpressionId BinaryenAtomicWakeId(void) { return Expression::Id::AtomicWakeId; }
+BinaryenExpressionId BinaryenAtomicNotifyId(void) { return Expression::Id::AtomicNotifyId; }
BinaryenExpressionId BinaryenSIMDExtractId(void) { return Expression::Id::SIMDExtractId; }
BinaryenExpressionId BinaryenSIMDReplaceId(void) { return Expression::Id::SIMDReplaceId; }
BinaryenExpressionId BinaryenSIMDShuffleId(void) { return Expression::Id::SIMDShuffleId; }
@@ -1022,11 +1022,11 @@ BinaryenExpressionRef BinaryenAtomicWait(BinaryenModuleRef module, BinaryenExpre
return static_cast<Expression*>(ret);
}
-BinaryenExpressionRef BinaryenAtomicWake(BinaryenModuleRef module, BinaryenExpressionRef ptr, BinaryenExpressionRef wakeCount) {
- auto* ret = Builder(*((Module*)module)).makeAtomicWake((Expression*)ptr, (Expression*)wakeCount, 0);
+BinaryenExpressionRef BinaryenAtomicNotify(BinaryenModuleRef module, BinaryenExpressionRef ptr, BinaryenExpressionRef notifyCount) {
+ auto* ret = Builder(*((Module*)module)).makeAtomicNotify((Expression*)ptr, (Expression*)notifyCount, 0);
if (tracing) {
- traceExpression(ret, "BinaryenAtomicWake", ptr, wakeCount);
+ traceExpression(ret, "BinaryenAtomicNotify", ptr, notifyCount);
}
return static_cast<Expression*>(ret);
@@ -1849,24 +1849,24 @@ BinaryenType BinaryenAtomicWaitGetExpectedType(BinaryenExpressionRef expr) {
assert(expression->is<AtomicWait>());
return static_cast<AtomicWait*>(expression)->expectedType;
}
-// AtomicWake
-BinaryenExpressionRef BinaryenAtomicWakeGetPtr(BinaryenExpressionRef expr) {
+// AtomicNotify
+BinaryenExpressionRef BinaryenAtomicNotifyGetPtr(BinaryenExpressionRef expr) {
if (tracing) {
- std::cout << " BinaryenAtomicWakeGetPtr(expressions[" << expressions[expr] << "]);\n";
+ std::cout << " BinaryenAtomicNotifyGetPtr(expressions[" << expressions[expr] << "]);\n";
}
auto* expression = (Expression*)expr;
- assert(expression->is<AtomicWake>());
- return static_cast<AtomicWake*>(expression)->ptr;
+ assert(expression->is<AtomicNotify>());
+ return static_cast<AtomicNotify*>(expression)->ptr;
}
-BinaryenExpressionRef BinaryenAtomicWakeGetWakeCount(BinaryenExpressionRef expr) {
+BinaryenExpressionRef BinaryenAtomicNotifyGetNotifyCount(BinaryenExpressionRef expr) {
if (tracing) {
- std::cout << " BinaryenAtomicWakeGetWakeCount(expressions[" << expressions[expr] << "]);\n";
+ std::cout << " BinaryenAtomicNotifyGetNotifyCount(expressions[" << expressions[expr] << "]);\n";
}
auto* expression = (Expression*)expr;
- assert(expression->is<AtomicWake>());
- return static_cast<AtomicWake*>(expression)->wakeCount;
+ assert(expression->is<AtomicNotify>());
+ return static_cast<AtomicNotify*>(expression)->notifyCount;
}
// SIMDExtract
BinaryenOp BinaryenSIMDExtractGetOp(BinaryenExpressionRef expr) {
diff --git a/src/binaryen-c.h b/src/binaryen-c.h
index 787bfb242..5bc95e0f8 100644
--- a/src/binaryen-c.h
+++ b/src/binaryen-c.h
@@ -116,7 +116,7 @@ BinaryenExpressionId BinaryenUnreachableId(void);
BinaryenExpressionId BinaryenAtomicCmpxchgId(void);
BinaryenExpressionId BinaryenAtomicRMWId(void);
BinaryenExpressionId BinaryenAtomicWaitId(void);
-BinaryenExpressionId BinaryenAtomicWakeId(void);
+BinaryenExpressionId BinaryenAtomicNotifyId(void);
BinaryenExpressionId BinaryenSIMDExtractId(void);
BinaryenExpressionId BinaryenSIMDReplaceId(void);
BinaryenExpressionId BinaryenSIMDShuffleId(void);
@@ -538,7 +538,7 @@ BinaryenExpressionRef BinaryenAtomicStore(BinaryenModuleRef module, uint32_t byt
BinaryenExpressionRef BinaryenAtomicRMW(BinaryenModuleRef module, BinaryenOp op, BinaryenIndex bytes, BinaryenIndex offset, BinaryenExpressionRef ptr, BinaryenExpressionRef value, BinaryenType type);
BinaryenExpressionRef BinaryenAtomicCmpxchg(BinaryenModuleRef module, BinaryenIndex bytes, BinaryenIndex offset, BinaryenExpressionRef ptr, BinaryenExpressionRef expected, BinaryenExpressionRef replacement, BinaryenType type);
BinaryenExpressionRef BinaryenAtomicWait(BinaryenModuleRef module, BinaryenExpressionRef ptr, BinaryenExpressionRef expected, BinaryenExpressionRef timeout, BinaryenType type);
-BinaryenExpressionRef BinaryenAtomicWake(BinaryenModuleRef module, BinaryenExpressionRef ptr, BinaryenExpressionRef wakeCount);
+BinaryenExpressionRef BinaryenAtomicNotify(BinaryenModuleRef module, BinaryenExpressionRef ptr, BinaryenExpressionRef notifyCount);
BinaryenExpressionRef BinaryenSIMDExtract(BinaryenModuleRef module, BinaryenOp op, BinaryenExpressionRef vec, uint8_t index);
BinaryenExpressionRef BinaryenSIMDReplace(BinaryenModuleRef module, BinaryenOp op, BinaryenExpressionRef vec, uint8_t index, BinaryenExpressionRef value);
BinaryenExpressionRef BinaryenSIMDShuffle(BinaryenModuleRef module, BinaryenExpressionRef left, BinaryenExpressionRef right, const uint8_t mask[16]);
@@ -652,8 +652,8 @@ BinaryenExpressionRef BinaryenAtomicWaitGetExpected(BinaryenExpressionRef expr);
BinaryenExpressionRef BinaryenAtomicWaitGetTimeout(BinaryenExpressionRef expr);
BinaryenType BinaryenAtomicWaitGetExpectedType(BinaryenExpressionRef expr);
-BinaryenExpressionRef BinaryenAtomicWakeGetPtr(BinaryenExpressionRef expr);
-BinaryenExpressionRef BinaryenAtomicWakeGetWakeCount(BinaryenExpressionRef expr);
+BinaryenExpressionRef BinaryenAtomicNotifyGetPtr(BinaryenExpressionRef expr);
+BinaryenExpressionRef BinaryenAtomicNotifyGetNotifyCount(BinaryenExpressionRef expr);
BinaryenOp BinaryenSIMDExtractGetOp(BinaryenExpressionRef expr);
BinaryenExpressionRef BinaryenSIMDExtractGetVec(BinaryenExpressionRef expr);
diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc
index 710f0d829..c9daba100 100644
--- a/src/gen-s-parser.inc
+++ b/src/gen-s-parser.inc
@@ -5,6 +5,9 @@
char op[27] = {'\0'};
strncpy(op, s[0]->c_str(), 26);
switch (op[0]) {
+ case 'a':
+ if (strcmp(op, "atomic.notify") == 0) return makeAtomicNotify(s);
+ goto parse_error;
case 'b': {
switch (op[1]) {
case 'l':
@@ -946,6 +949,9 @@ switch (op[0]) {
default: goto parse_error;
}
}
+ case 'w':
+ if (strcmp(op, "i32.atomic.wait") == 0) return makeAtomicWait(s, i32);
+ goto parse_error;
default: goto parse_error;
}
}
@@ -1235,17 +1241,9 @@ switch (op[0]) {
default: goto parse_error;
}
}
- case 'w': {
- switch (op[5]) {
- case 'a':
- if (strcmp(op, "i32.wait") == 0) return makeAtomicWait(s, i32);
- goto parse_error;
- case 'r':
- if (strcmp(op, "i32.wrap_i64") == 0) return makeUnary(s, UnaryOp::WrapInt64);
- goto parse_error;
- default: goto parse_error;
- }
- }
+ case 'w':
+ if (strcmp(op, "i32.wrap_i64") == 0) return makeUnary(s, UnaryOp::WrapInt64);
+ goto parse_error;
case 'x':
if (strcmp(op, "i32.xor") == 0) return makeBinary(s, BinaryOp::XorInt32);
goto parse_error;
@@ -1594,6 +1592,9 @@ switch (op[0]) {
default: goto parse_error;
}
}
+ case 'w':
+ if (strcmp(op, "i64.atomic.wait") == 0) return makeAtomicWait(s, i64);
+ goto parse_error;
default: goto parse_error;
}
}
@@ -1911,9 +1912,6 @@ switch (op[0]) {
default: goto parse_error;
}
}
- case 'w':
- if (strcmp(op, "i64.wait") == 0) return makeAtomicWait(s, i64);
- goto parse_error;
case 'x':
if (strcmp(op, "i64.xor") == 0) return makeBinary(s, BinaryOp::XorInt64);
goto parse_error;
@@ -2257,9 +2255,6 @@ switch (op[0]) {
default: goto parse_error;
}
}
- case 'w':
- if (strcmp(op, "wake") == 0) return makeAtomicWake(s);
- goto parse_error;
default: goto parse_error;
}
parse_error:
diff --git a/src/ir/ExpressionAnalyzer.cpp b/src/ir/ExpressionAnalyzer.cpp
index fecee0cce..fcbd29665 100644
--- a/src/ir/ExpressionAnalyzer.cpp
+++ b/src/ir/ExpressionAnalyzer.cpp
@@ -171,7 +171,7 @@ void visitImmediates(Expression* curr, T& visitor) {
visitor.visitAddress(curr->offset);
visitor.visitType(curr->expectedType);
}
- void visitAtomicWake(AtomicWake* curr) {
+ void visitAtomicNotify(AtomicNotify* curr) {
visitor.visitAddress(curr->offset);
}
void visitSIMDExtract(SIMDExtract* curr) {
diff --git a/src/ir/ExpressionManipulator.cpp b/src/ir/ExpressionManipulator.cpp
index 32ee442b7..578d35e3f 100644
--- a/src/ir/ExpressionManipulator.cpp
+++ b/src/ir/ExpressionManipulator.cpp
@@ -111,8 +111,8 @@ Expression* flexibleCopy(Expression* original, Module& wasm, CustomCopier custom
Expression* visitAtomicWait(AtomicWait* curr) {
return builder.makeAtomicWait(copy(curr->ptr), copy(curr->expected), copy(curr->timeout), curr->expectedType, curr->offset);
}
- Expression* visitAtomicWake(AtomicWake* curr) {
- return builder.makeAtomicWake(copy(curr->ptr), copy(curr->wakeCount), curr->offset);
+ Expression* visitAtomicNotify(AtomicNotify* curr) {
+ return builder.makeAtomicNotify(copy(curr->ptr), copy(curr->notifyCount), curr->offset);
}
Expression* visitSIMDExtract(SIMDExtract* curr) {
return builder.makeSIMDExtract(curr->op, copy(curr->vec), curr->index);
diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp
index 3f374265c..e4d5180d1 100644
--- a/src/ir/ReFinalize.cpp
+++ b/src/ir/ReFinalize.cpp
@@ -136,7 +136,7 @@ void ReFinalize::visitStore(Store* curr) { curr->finalize(); }
void ReFinalize::visitAtomicRMW(AtomicRMW* curr) { curr->finalize(); }
void ReFinalize::visitAtomicCmpxchg(AtomicCmpxchg* curr) { curr->finalize(); }
void ReFinalize::visitAtomicWait(AtomicWait* curr) { curr->finalize(); }
-void ReFinalize::visitAtomicWake(AtomicWake* curr) { curr->finalize(); }
+void ReFinalize::visitAtomicNotify(AtomicNotify* curr) { curr->finalize(); }
void ReFinalize::visitSIMDExtract(SIMDExtract* curr) { curr->finalize(); }
void ReFinalize::visitSIMDReplace(SIMDReplace* curr) { curr->finalize(); }
void ReFinalize::visitSIMDShuffle(SIMDShuffle* curr) { curr->finalize(); }
diff --git a/src/ir/effects.h b/src/ir/effects.h
index 394bf0116..401232fa1 100644
--- a/src/ir/effects.h
+++ b/src/ir/effects.h
@@ -230,8 +230,8 @@ struct EffectAnalyzer : public PostWalker<EffectAnalyzer> {
isAtomic = true;
if (!ignoreImplicitTraps) implicitTrap = true;
}
- void visitAtomicWake(AtomicWake* curr) {
- // AtomicWake doesn't strictly write memory, but it does modify the waiters
+ void visitAtomicNotify(AtomicNotify* curr) {
+ // AtomicNotify doesn't strictly write memory, but it does modify the waiters
// list associated with the specified address, which we can think of as a
// write.
readsMemory = true;
diff --git a/src/ir/utils.h b/src/ir/utils.h
index db437875d..c91698124 100644
--- a/src/ir/utils.h
+++ b/src/ir/utils.h
@@ -128,7 +128,7 @@ struct ReFinalize : public WalkerPass<PostWalker<ReFinalize, OverriddenVisitor<R
void visitAtomicRMW(AtomicRMW* curr);
void visitAtomicCmpxchg(AtomicCmpxchg* curr);
void visitAtomicWait(AtomicWait* curr);
- void visitAtomicWake(AtomicWake* curr);
+ void visitAtomicNotify(AtomicNotify* curr);
void visitSIMDExtract(SIMDExtract* curr);
void visitSIMDReplace(SIMDReplace* curr);
void visitSIMDShuffle(SIMDShuffle* curr);
@@ -184,7 +184,7 @@ struct ReFinalizeNode : public OverriddenVisitor<ReFinalizeNode> {
void visitAtomicRMW(AtomicRMW* curr) { curr->finalize(); }
void visitAtomicCmpxchg(AtomicCmpxchg* curr) { curr->finalize(); }
void visitAtomicWait(AtomicWait* curr) { curr->finalize(); }
- void visitAtomicWake(AtomicWake* curr) { curr->finalize(); }
+ void visitAtomicNotify(AtomicNotify* curr) { curr->finalize(); }
void visitSIMDExtract(SIMDExtract* curr) { curr->finalize(); }
void visitSIMDReplace(SIMDReplace* curr) { curr->finalize(); }
void visitSIMDShuffle(SIMDShuffle* curr) { curr->finalize(); }
diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js
index aee41255c..1796080fc 100644
--- a/src/js/binaryen.js-post.js
+++ b/src/js/binaryen.js-post.js
@@ -67,7 +67,7 @@ Module['UnreachableId'] = Module['_BinaryenUnreachableId']();
Module['AtomicCmpxchgId'] = Module['_BinaryenAtomicCmpxchgId']();
Module['AtomicRMWId'] = Module['_BinaryenAtomicRMWId']();
Module['AtomicWaitId'] = Module['_BinaryenAtomicWaitId']();
-Module['AtomicWakeId'] = Module['_BinaryenAtomicWakeId']();
+Module['AtomicNotifyId'] = Module['_BinaryenAtomicNotifyId']();
Module['SIMDExtractId'] = Module['_BinaryenSIMDExtractId']();
Module['SIMDReplaceId'] = Module['_BinaryenSIMDReplaceId']();
Module['SIMDShuffleId'] = Module['_BinaryenSIMDShuffleId']();
@@ -1728,8 +1728,8 @@ function wrapModule(module, self) {
self['unreachable'] = function() {
return Module['_BinaryenUnreachable'](module);
};
- self['wake'] = function(ptr, wakeCount) {
- return Module['_BinaryenAtomicWake'](module, ptr, wakeCount);
+ self['notify'] = function(ptr, notifyCount) {
+ return Module['_BinaryenAtomicNotify'](module, ptr, notifyCount);
};
// 'Module' operations
@@ -2208,12 +2208,12 @@ Module['getExpressionInfo'] = function(expr) {
'timeout': Module['_BinaryenAtomicWaitGetTimeout'](expr),
'expectedType': Module['_BinaryenAtomicWaitGetExpectedType'](expr)
};
- case Module['AtomicWakeId']:
+ case Module['AtomicNotifyId']:
return {
'id': id,
'type': type,
- 'ptr': Module['_BinaryenAtomicWakeGetPtr'](expr),
- 'wakeCount': Module['_BinaryenAtomicWakeGetWakeCount'](expr)
+ 'ptr': Module['_BinaryenAtomicNotifyGetPtr'](expr),
+ 'notifyCount': Module['_BinaryenAtomicNotifyGetNotifyCount'](expr)
};
case Module['SIMDExtractId']:
return {
diff --git a/src/mixed_arena.h b/src/mixed_arena.h
index 46487b7fc..5f48f5220 100644
--- a/src/mixed_arena.h
+++ b/src/mixed_arena.h
@@ -277,31 +277,102 @@ public:
// iteration
struct Iterator {
+ using iterator_category = std::random_access_iterator_tag;
+ using value_type = T;
+ using difference_type = std::ptrdiff_t;
+ using pointer = T*;
+ using reference = T&;
+
const SubType* parent;
size_t index;
+ Iterator() : parent(nullptr), index(0) {}
Iterator(const SubType* parent, size_t index) : parent(parent), index(index) {}
+ bool operator==(const Iterator& other) const {
+ return index == other.index && parent == other.parent;
+ }
+
bool operator!=(const Iterator& other) const {
- return index != other.index || parent != other.parent;
+ return !(*this == other);
+ }
+
+ bool operator<(const Iterator& other) const {
+ assert(parent == other.parent);
+ return index < other.index;
+ }
+
+ bool operator>(const Iterator& other) const {
+ return other < *this;
+ }
+
+ bool operator<=(const Iterator& other) const {
+ return !(other < *this);
+ }
+
+ bool operator>=(const Iterator& other) const {
+ return !(*this < other);
}
- void operator++() {
+ Iterator& operator++() {
index++;
+ return *this;
}
- Iterator& operator+=(int off) {
+ Iterator& operator--() {
+ index--;
+ return *this;
+ }
+
+ Iterator operator++(int) {
+ Iterator it = *this;
+ ++*this;
+ return it;
+ }
+
+ Iterator operator--(int) {
+ Iterator it = *this;
+ --*this;
+ return it;
+ }
+
+ Iterator& operator+=(std::ptrdiff_t off) {
index += off;
return *this;
}
- const Iterator operator+(int off) const {
+ Iterator& operator-=(std::ptrdiff_t off) {
+ return *this += -off;
+ }
+
+ Iterator operator+(std::ptrdiff_t off) const {
return Iterator(*this) += off;
}
- T& operator*() {
+ Iterator operator-(std::ptrdiff_t off) const {
+ return *this + -off;
+ }
+
+ std::ptrdiff_t operator-(const Iterator& other) const {
+ assert(parent == other.parent);
+ return index - other.index;
+ }
+
+ friend Iterator operator+(std::ptrdiff_t off, const Iterator& it) {
+ return it + off;
+ }
+
+ T& operator*() const {
return (*parent)[index];
}
+
+ T& operator[](std::ptrdiff_t off) const {
+ return (*parent)[index + off];
+ }
+
+ T* operator->() const {
+ return &(*parent)[index];
+ }
};
Iterator begin() const {
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt
index 2848946f8..2b1b64d84 100644
--- a/src/passes/CMakeLists.txt
+++ b/src/passes/CMakeLists.txt
@@ -12,6 +12,7 @@ SET(passes_SOURCES
DataFlowOpts.cpp
DeadArgumentElimination.cpp
DeadCodeElimination.cpp
+ Directize.cpp
DuplicateFunctionElimination.cpp
ExtractFunction.cpp
Flatten.cpp
diff --git a/src/passes/DeadCodeElimination.cpp b/src/passes/DeadCodeElimination.cpp
index a6b20a7ba..a56c88929 100644
--- a/src/passes/DeadCodeElimination.cpp
+++ b/src/passes/DeadCodeElimination.cpp
@@ -256,7 +256,7 @@ struct DeadCodeElimination : public WalkerPass<PostWalker<DeadCodeElimination>>
case Expression::Id::AtomicCmpxchgId: DELEGATE(AtomicCmpxchg);
case Expression::Id::AtomicRMWId: DELEGATE(AtomicRMW);
case Expression::Id::AtomicWaitId: DELEGATE(AtomicWait);
- case Expression::Id::AtomicWakeId: DELEGATE(AtomicWake);
+ case Expression::Id::AtomicNotifyId: DELEGATE(AtomicNotify);
case Expression::Id::SIMDExtractId: DELEGATE(SIMDExtract);
case Expression::Id::SIMDReplaceId: DELEGATE(SIMDReplace);
case Expression::Id::SIMDShuffleId: DELEGATE(SIMDShuffle);
diff --git a/src/passes/Directize.cpp b/src/passes/Directize.cpp
new file mode 100644
index 000000000..d9400cce7
--- /dev/null
+++ b/src/passes/Directize.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2019 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.
+ */
+
+//
+// Turn indirect calls into direct calls. This is possible if we know
+// the table cannot change, and if we see a constant argument for the
+// indirect call's index.
+//
+
+#include <unordered_map>
+
+#include "wasm.h"
+#include "pass.h"
+#include "wasm-builder.h"
+#include "wasm-traversal.h"
+#include "asm_v_wasm.h"
+
+namespace wasm {
+
+namespace {
+
+struct FlatTable {
+ std::vector<Name> names;
+ bool valid;
+
+ FlatTable(Table& table) {
+ valid = true;
+ for (auto& segment : table.segments) {
+ auto offset = segment.offset;
+ if (!offset->is<Const>()) {
+ // TODO: handle some non-constant segments
+ valid = false;
+ return;
+ }
+ Index start = offset->cast<Const>()->value.geti32();
+ Index end = start + segment.data.size();
+ if (end > names.size()) {
+ names.resize(end);
+ }
+ for (Index i = 0; i < segment.data.size(); i++) {
+ names[start + i] = segment.data[i];
+ }
+ }
+ }
+};
+
+struct FunctionDirectizer : public WalkerPass<PostWalker<FunctionDirectizer>> {
+ bool isFunctionParallel() override { return true; }
+
+ Pass* create() override { return new FunctionDirectizer(flatTable); }
+
+ FunctionDirectizer(FlatTable* flatTable) : flatTable(flatTable) {}
+
+ void visitCallIndirect(CallIndirect* curr) {
+ if (auto* c = curr->target->dynCast<Const>()) {
+ Index index = c->value.geti32();
+ // If the index is invalid, or the type is wrong, we can
+ // emit an unreachable here, since in Binaryen it is ok to
+ // reorder/replace traps when optimizing (but never to
+ // remove them, at least not by default).
+ if (index >= flatTable->names.size()) {
+ replaceWithUnreachable();
+ return;
+ }
+ auto name = flatTable->names[index];
+ if (!name.is()) {
+ replaceWithUnreachable();
+ return;
+ }
+ auto* func = getModule()->getFunction(name);
+ if (getSig(getModule()->getFunctionType(curr->fullType)) !=
+ getSig(func)) {
+ replaceWithUnreachable();
+ return;
+ }
+ // Everything looks good!
+ replaceCurrent(Builder(*getModule()).makeCall(
+ name,
+ curr->operands,
+ curr->type
+ ));
+ }
+ }
+
+private:
+ FlatTable* flatTable;
+
+ void replaceWithUnreachable() {
+ replaceCurrent(Builder(*getModule()).makeUnreachable());
+ }
+};
+
+struct Directize : public Pass {
+ void run(PassRunner* runner, Module* module) override {
+ if (!module->table.exists) return;
+ if (module->table.imported()) return;
+ for (auto& ex : module->exports) {
+ if (ex->kind == ExternalKind::Table) return;
+ }
+ FlatTable flatTable(module->table);
+ if (!flatTable.valid) return;
+ // The table exists and is constant, so this is possible.
+ {
+ PassRunner runner(module);
+ runner.setIsNested(true);
+ runner.add<FunctionDirectizer>(&flatTable);
+ runner.run();
+ }
+ }
+};
+
+} // anonymous namespace
+
+Pass *createDirectizePass() {
+ return new Directize();
+}
+
+} // namespace wasm
+
diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp
index 5e9d9c9ea..6f59fceb7 100644
--- a/src/passes/Precompute.cpp
+++ b/src/passes/Precompute.cpp
@@ -119,7 +119,7 @@ public:
Flow visitAtomicWait(AtomicWait *curr) {
return Flow(NOTPRECOMPUTABLE_FLOW);
}
- Flow visitAtomicWake(AtomicWake *curr) {
+ Flow visitAtomicNotify(AtomicNotify *curr) {
return Flow(NOTPRECOMPUTABLE_FLOW);
}
Flow visitHost(Host *curr) {
diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp
index fd0bf17dc..6d7d512ff 100644
--- a/src/passes/Print.cpp
+++ b/src/passes/Print.cpp
@@ -228,13 +228,13 @@ struct PrintExpressionContents : public Visitor<PrintExpressionContents> {
}
void visitAtomicWait(AtomicWait* curr) {
prepareColor(o);
- o << printType(curr->expectedType) << ".wait";
+ o << printType(curr->expectedType) << ".atomic.wait";
if (curr->offset) {
o << " offset=" << curr->offset;
}
}
- void visitAtomicWake(AtomicWake* curr) {
- printMedium(o, "wake");
+ void visitAtomicNotify(AtomicNotify* curr) {
+ printMedium(o, "atomic.notify");
if (curr->offset) {
o << " offset=" << curr->offset;
}
@@ -904,12 +904,12 @@ struct PrintSExpression : public Visitor<PrintSExpression> {
printFullLine(curr->timeout);
decIndent();
}
- void visitAtomicWake(AtomicWake* curr) {
+ void visitAtomicNotify(AtomicNotify* curr) {
o << '(';
PrintExpressionContents(currFunction, o).visit(curr);
incIndent();
printFullLine(curr->ptr);
- printFullLine(curr->wakeCount);
+ printFullLine(curr->notifyCount);
decIndent();
}
void visitSIMDExtract(SIMDExtract* curr) {
diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp
index 069137ff6..b9c8d5150 100644
--- a/src/passes/RemoveUnusedModuleElements.cpp
+++ b/src/passes/RemoveUnusedModuleElements.cpp
@@ -114,7 +114,7 @@ struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> {
void visitAtomicWait(AtomicWait* curr) {
usesMemory = true;
}
- void visitAtomicWake(AtomicWake* curr) {
+ void visitAtomicNotify(AtomicNotify* curr) {
usesMemory = true;
}
void visitHost(Host* curr) {
diff --git a/src/passes/SSAify.cpp b/src/passes/SSAify.cpp
index 9e6ff2de2..1ed3b976f 100644
--- a/src/passes/SSAify.cpp
+++ b/src/passes/SSAify.cpp
@@ -27,6 +27,26 @@
// TODO: consider adding a "proper" phi node to the AST, that passes
// can utilize
//
+// There is also a "no-merge" variant of this pass. That will ignore
+// sets leading to merges, that is, it only creates new SSA indexes
+// for sets whose gets have just that set, e.g.
+//
+// x = ..
+// f(x, x)
+// x = ..
+// g(x, x)
+// =>
+// x = ..
+// f(x, x)
+// x' = ..
+// g(x', x')
+//
+// This "untangles" local indexes in a way that helps other passes,
+// while not creating copies with overlapping lifetimes that can
+// lead to a code size increase. In particular, the new variables
+// added by ssa-nomerge can be easily removed by the coalesce-locals
+// pass.
+//
#include <iterator>
@@ -49,7 +69,11 @@ static SetLocal IMPOSSIBLE_SET;
struct SSAify : public Pass {
bool isFunctionParallel() override { return true; }
- Pass* create() override { return new SSAify; }
+ Pass* create() override { return new SSAify(allowMerges); }
+
+ SSAify(bool allowMerges) : allowMerges(allowMerges) {}
+
+ bool allowMerges;
Module* module;
Function* func;
@@ -59,21 +83,37 @@ struct SSAify : public Pass {
module = module_;
func = func_;
LocalGraph graph(func);
+ graph.computeInfluences();
+ graph.computeSSAIndexes();
// create new local indexes, one for each set
- createNewIndexes();
+ createNewIndexes(graph);
// we now know the sets for each get, and can compute get indexes and handle phis
computeGetsAndPhis(graph);
// add prepends to function
addPrepends();
}
- void createNewIndexes() {
+ void createNewIndexes(LocalGraph& graph) {
FindAll<SetLocal> sets(func->body);
for (auto* set : sets.list) {
- set->index = addLocal(func->getLocalType(set->index));
+ // Indexes already in SSA form do not need to be modified - there is already
+ // just one set for that index. Otherwise, use a new index, unless merges
+ // are disallowed.
+ if (!graph.isSSA(set->index) && (allowMerges || !hasMerges(set, graph))) {
+ set->index = addLocal(func->getLocalType(set->index));
+ }
}
}
+ bool hasMerges(SetLocal* set, LocalGraph& graph) {
+ for (auto* get : graph.setInfluences[set]) {
+ if (graph.getSetses[get].size() > 1) {
+ return true;
+ }
+ }
+ return false;
+ }
+
void computeGetsAndPhis(LocalGraph& graph) {
FindAll<GetLocal> gets(func->body);
for (auto* get : gets.list) {
@@ -97,6 +137,7 @@ struct SSAify : public Pass {
}
continue;
}
+ if (!allowMerges) continue;
// more than 1 set, need a phi: a new local written to at each of the sets
auto new_ = addLocal(get->type);
auto old = get->index;
@@ -154,8 +195,12 @@ struct SSAify : public Pass {
}
};
-Pass *createSSAifyPass() {
- return new SSAify();
+Pass* createSSAifyPass() {
+ return new SSAify(true);
+}
+
+Pass* createSSAifyNoMergePass() {
+ return new SSAify(false);
}
} // namespace wasm
diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp
index 5d8b8d2c8..fecb644b5 100644
--- a/src/passes/pass.cpp
+++ b/src/passes/pass.cpp
@@ -75,6 +75,7 @@ void PassRegistry::registerPasses() {
registerPass("code-folding", "fold code, merging duplicates", createCodeFoldingPass);
registerPass("const-hoisting", "hoist repeated constants to a local", createConstHoistingPass);
registerPass("dce", "removes unreachable code", createDeadCodeEliminationPass);
+ registerPass("directize", "turns indirect calls into direct ones", createDirectizePass);
registerPass("dfo", "optimizes using the DataFlow SSA IR", createDataFlowOptsPass);
registerPass("duplicate-function-elimination", "removes duplicate functions", createDuplicateFunctionEliminationPass);
registerPass("extract-function", "leaves just one function (useful for debugging)", createExtractFunctionPass);
@@ -135,6 +136,7 @@ void PassRegistry::registerPasses() {
registerPass("souperify-single-use", "emit Souper IR in text form (single-use nodes only)", createSouperifySingleUsePass);
registerPass("spill-pointers", "spill pointers to the C stack (useful for Boehm-style GC)", createSpillPointersPass);
registerPass("ssa", "ssa-ify variables so that they have a single assignment", createSSAifyPass);
+ registerPass("ssa-nomerge", "ssa-ify variables so that they have a single assignment, ignoring merges", createSSAifyNoMergePass);
registerPass("strip", "deprecated; same as strip-debug", createStripDebugPass);
registerPass("strip-debug", "strip debug info (including the names section)", createStripDebugPass);
registerPass("strip-producers", "strip the wasm producers section", createStripProducersPass);
@@ -153,6 +155,11 @@ void PassRunner::addDefaultOptimizationPasses() {
}
void PassRunner::addDefaultFunctionOptimizationPasses() {
+ // Untangling to semi-ssa form is helpful (but best to ignore merges
+ // so as to not introduce new copies).
+ if (options.optimizeLevel >= 3 || options.shrinkLevel >= 1) {
+ add("ssa-nomerge");
+ }
// if we are willing to work very very hard, flatten the IR and do opts
// that depend on flat IR
if (options.optimizeLevel >= 4) {
@@ -204,13 +211,13 @@ void PassRunner::addDefaultFunctionOptimizationPasses() {
add("remove-unused-brs"); // coalesce-locals opens opportunities
add("remove-unused-names"); // remove-unused-brs opens opportunities
add("merge-blocks"); // clean up remove-unused-brs new blocks
- add("optimize-instructions");
// late propagation
if (options.optimizeLevel >= 3 || options.shrinkLevel >= 2) {
add("precompute-propagate");
} else {
add("precompute");
}
+ add("optimize-instructions");
if (options.optimizeLevel >= 2 || options.shrinkLevel >= 1) {
add("rse"); // after all coalesce-locals, and before a final vacuum
}
@@ -222,17 +229,16 @@ void PassRunner::addDefaultGlobalOptimizationPrePasses() {
}
void PassRunner::addDefaultGlobalOptimizationPostPasses() {
- // inlining/dae+optimizing can remove debug annotations
if (options.optimizeLevel >= 2 || options.shrinkLevel >= 1) {
add("dae-optimizing");
}
- // inline when working hard, and when not preserving debug info
if (options.optimizeLevel >= 2 || options.shrinkLevel >= 2) {
add("inlining-optimizing");
}
add("duplicate-function-elimination"); // optimizations show more functions as duplicate
add("remove-unused-module-elements");
add("memory-packing");
+ add("directize"); // may allow more inlining/dae/etc., need --converge for that
// perform Stack IR optimizations here, at the very end of the
// optimization pipeline
if (options.optimizeLevel >= 2 || options.shrinkLevel >= 1) {
diff --git a/src/passes/passes.h b/src/passes/passes.h
index ac7126bd4..fea06e388 100644
--- a/src/passes/passes.h
+++ b/src/passes/passes.h
@@ -31,6 +31,7 @@ Pass* createDAEPass();
Pass* createDAEOptimizingPass();
Pass* createDataFlowOptsPass();
Pass* createDeadCodeEliminationPass();
+Pass* createDirectizePass();
Pass* createDuplicateFunctionEliminationPass();
Pass* createExtractFunctionPass();
Pass* createFlattenPass();
@@ -93,6 +94,7 @@ Pass* createSouperifyPass();
Pass* createSouperifySingleUsePass();
Pass* createSpillPointersPass();
Pass* createSSAifyPass();
+Pass* createSSAifyNoMergePass();
Pass* createTrapModeClamp();
Pass* createTrapModeJS();
Pass* createUnteePass();
diff --git a/src/shared-constants.h b/src/shared-constants.h
index ae7d915ef..52124d891 100644
--- a/src/shared-constants.h
+++ b/src/shared-constants.h
@@ -22,6 +22,7 @@
namespace wasm {
extern Name GROW_WASM_MEMORY,
+ WASM_CALL_CTORS,
MEMORY_BASE,
TABLE_BASE,
GET_TEMP_RET0,
diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h
index f16d0ecad..6aa0764f1 100644
--- a/src/tools/fuzzing.h
+++ b/src/tools/fuzzing.h
@@ -1618,7 +1618,7 @@ private:
} else {
auto* ptr = makePointer();
auto* count = make(i32);
- return builder.makeAtomicWake(ptr, count, logify(get()));
+ return builder.makeAtomicNotify(ptr, count, logify(get()));
}
}
Index bytes;
diff --git a/src/tools/wasm-emscripten-finalize.cpp b/src/tools/wasm-emscripten-finalize.cpp
index 2aa5de46f..180383a90 100644
--- a/src/tools/wasm-emscripten-finalize.cpp
+++ b/src/tools/wasm-emscripten-finalize.cpp
@@ -46,6 +46,7 @@ int main(int argc, const char *argv[]) {
std::string dataSegmentFile;
bool emitBinary = true;
bool debugInfo = false;
+ bool isSideModule = false;
bool legalizeJavaScriptFFI = true;
uint64_t globalBase = INVALID_BASE;
uint64_t initialStackPointer = INVALID_BASE;
@@ -79,7 +80,11 @@ int main(int argc, const char *argv[]) {
[&initialStackPointer](Options*, const std::string&argument ) {
initialStackPointer = std::stoull(argument);
})
-
+ .add("--side-module", "", "Input is an emscripten side module",
+ Options::Arguments::Zero,
+ [&isSideModule](Options *o, const std::string& argument) {
+ isSideModule = true;
+ })
.add("--input-source-map", "-ism", "Consume source map from the specified file",
Options::Arguments::One,
[&inputSourceMapFilename](Options *o, const std::string& argument) { inputSourceMapFilename = argument; })
@@ -130,7 +135,6 @@ int main(int argc, const char *argv[]) {
WasmPrinter::printModule(&wasm, std::cerr);
}
- bool isSideModule = false;
for (const UserSection& section : wasm.userSections) {
if (section.name == BinaryConsts::UserSections::Dylink) {
isSideModule = true;
@@ -166,12 +170,6 @@ int main(int argc, const char *argv[]) {
std::vector<Name> initializerFunctions;
- // The names of standard imports/exports used by lld doesn't quite match that
- // expected by emscripten.
- // TODO(sbc): Unify these
- if (Export* ex = wasm.getExportOrNull("__wasm_call_ctors")) {
- ex->name = "__post_instantiate";
- }
if (wasm.table.imported()) {
if (wasm.table.base != "table") wasm.table.base = Name("table");
}
@@ -182,14 +180,17 @@ int main(int argc, const char *argv[]) {
if (isSideModule) {
generator.replaceStackPointerGlobal();
+ generator.generatePostInstantiateFunction();
} else {
generator.generateRuntimeFunctions();
generator.generateMemoryGrowthFunction();
generator.generateStackInitialization(initialStackPointer);
- // emscripten calls this by default for side libraries so we only need
- // to include in as a static ctor for main module case.
- if (wasm.getExportOrNull("__post_instantiate")) {
- initializerFunctions.push_back("__post_instantiate");
+ // For side modules these gets called via __post_instantiate
+ if (Function* F = generator.generateAssignGOTEntriesFunction()) {
+ initializerFunctions.push_back(F->name);
+ }
+ if (auto* e = wasm.getExportOrNull(WASM_CALL_CTORS)) {
+ initializerFunctions.push_back(e->value);
}
}
diff --git a/src/tools/wasm-merge.cpp b/src/tools/wasm-merge.cpp
deleted file mode 100644
index 6dd522d16..000000000
--- a/src/tools/wasm-merge.cpp
+++ /dev/null
@@ -1,672 +0,0 @@
-/*
- * Copyright 2017 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 WebAssembly merger: loads multiple files, smashes them together,
-// and emits the result.
-//
-// This is *not* a real linker. It just does naive merging.
-//
-
-#include <memory>
-
-#include "parsing.h"
-#include "pass.h"
-#include "shared-constants.h"
-#include "asmjs/shared-constants.h"
-#include "asm_v_wasm.h"
-#include "support/command-line.h"
-#include "support/file.h"
-#include "wasm-io.h"
-#include "wasm-binary.h"
-#include "wasm-builder.h"
-#include "wasm-validator.h"
-#include "ir/module-utils.h"
-
-using namespace wasm;
-
-// Ensure a memory or table is of at least a size
-template<typename T>
-static void ensureSize(T& what, Index size) {
- // ensure the size is sufficient
- while (what.initial * what.kPageSize < size) {
- what.initial = what.initial + 1;
- }
- what.max = std::max(what.initial, what.max);
-}
-
-// A mergeable unit. This class contains basic logic to prepare for merging
-// of two modules.
-struct Mergeable {
- Mergeable(Module& wasm) : wasm(wasm) {
- // scan the module
- findSizes();
- findImports();
- standardizeSegments();
- }
-
- // The module we are working on
- Module& wasm;
-
- // Total sizes of the memory and table data, including things
- // link a bump from the dylink section
- Index totalMemorySize, totalTableSize;
-
- // The names of the imported globals for the memory and table bases
- // (sets, as each may be imported more than once)
- std::set<Name> memoryBaseGlobals, tableBaseGlobals;
-
- // Imported functions and globals provided by the other mergeable
- // are fused together. We track those here, then remove them
- std::map<Name, Name> implementedFunctionImports;
- std::map<Name, Name> implementedGlobalImports;
-
- // setups
-
- // find the memory and table sizes. if there are relocatable sections for them,
- // that is the base size, and a dylink section may increase things further
- void findSizes() {
- totalMemorySize = 0;
- totalTableSize = 0;
- for (auto& segment : wasm.memory.segments) {
- Expression* offset = segment.offset;
- if (offset->is<GetGlobal>()) {
- totalMemorySize = segment.data.size();
- break;
- }
- }
- for (auto& segment : wasm.table.segments) {
- Expression* offset = segment.offset;
- if (offset->is<GetGlobal>()) {
- totalTableSize = segment.data.size();
- break;
- }
- }
- for (auto& section : wasm.userSections) {
- if (section.name == BinaryConsts::UserSections::Dylink) {
- WasmBinaryBuilder builder(wasm, section.data, false);
- totalMemorySize = std::max(totalMemorySize, builder.getU32LEB());
- totalTableSize = std::max(totalTableSize, builder.getU32LEB());
- break; // there can be only one
- }
- }
- // align them
- while (totalMemorySize % 16 != 0) totalMemorySize++;
- while (totalTableSize % 2 != 0) totalTableSize++;
- }
-
- void findImports() {
- ModuleUtils::iterImportedGlobals(wasm, [&](Global* import) {
- if (import->module == ENV && import->base == MEMORY_BASE) {
- memoryBaseGlobals.insert(import->name);
- }
- });
- if (memoryBaseGlobals.size() == 0) {
- // add one
- auto* import = new Global;
- import->name = MEMORY_BASE;
- import->module = ENV;
- import->base = MEMORY_BASE;
- import->type = i32;
- wasm.addGlobal(import);
- memoryBaseGlobals.insert(import->name);
- }
- ModuleUtils::iterImportedGlobals(wasm, [&](Global* import) {
- if (import->module == ENV && import->base == TABLE_BASE) {
- tableBaseGlobals.insert(import->name);
- }
- });
- if (tableBaseGlobals.size() == 0) {
- auto* import = new Global;
- import->name = TABLE_BASE;
- import->module = ENV;
- import->base = TABLE_BASE;
- import->type = i32;
- wasm.addGlobal(import);
- tableBaseGlobals.insert(import->name);
- }
- }
-
- void standardizeSegments() {
- standardizeSegment<Memory, char, Memory::Segment>(wasm, wasm.memory, totalMemorySize, 0, *memoryBaseGlobals.begin());
- // if there are no functions, and we need one, we need to add one as the zero
- if (totalTableSize > 0 && wasm.functions.empty()) {
- auto func = new Function;
- func->name = Name("binaryen$merge-zero");
- func->body = Builder(wasm).makeNop();
- func->type = ensureFunctionType("v", &wasm)->name;
- wasm.addFunction(func);
- }
- Name zero;
- if (totalTableSize > 0) {
- zero = wasm.functions.begin()->get()->name;
- }
- standardizeSegment<Table, Name, Table::Segment>(wasm, wasm.table, totalTableSize, zero, *tableBaseGlobals.begin());
- }
-
- // utilities
-
- Name getNonColliding(Name initial, std::function<bool (Name)> checkIfCollides) {
- if (!checkIfCollides(initial)) {
- return initial;
- }
- int x = 0;
- while (1) {
- auto curr = Name(std::string(initial.str) + '$' + std::to_string(x));
- if (!checkIfCollides(curr)) {
- return curr;
- }
- x++;
- }
- }
-
- // ensure a relocatable segment exists, of the proper size, including
- // the dylink bump applied into it, standardized into the form of
- // not using a dylink section and instead having enough zeros at
- // the end. this makes linking much simpler.ta
- // there may be other non-relocatable segments too.
- template<typename T, typename U, typename Segment>
- void standardizeSegment(Module& wasm, T& what, Index size, U zero, Name globalName) {
- Segment* relocatable = nullptr;
- for (auto& segment : what.segments) {
- Expression* offset = segment.offset;
- if (offset->is<GetGlobal>()) {
- // this is the relocatable one.
- relocatable = &segment;
- break;
- }
- }
- if (!relocatable) {
- // none existing, add one
- what.segments.resize(what.segments.size() + 1);
- relocatable = &what.segments.back();
- relocatable->offset = Builder(wasm).makeGetGlobal(globalName, i32);
- }
- // make sure it is the right size
- while (relocatable->data.size() < size) {
- relocatable->data.push_back(zero);
- }
- ensureSize(what, relocatable->data.size());
- }
-
- // copies a relocatable segment from the input to the output, and
- // copies the non-relocatable ones as well
- template<typename T, typename V>
- void copySegments(T& output, T& input, V updater) {
- for (auto& inputSegment : input.segments) {
- Expression* inputOffset = inputSegment.offset;
- if (inputOffset->is<GetGlobal>()) {
- // this is the relocatable one. find the output's relocatable
- for (auto& segment : output.segments) {
- Expression* offset = segment.offset;
- if (offset->is<GetGlobal>()) {
- // copy our data in
- for (auto item : inputSegment.data) {
- segment.data.push_back(updater(item));
- }
- ensureSize(output, segment.data.size());
- return; // there can be only one
- }
- }
- WASM_UNREACHABLE(); // we must find a relocatable one in the output, as we standardized
- } else {
- // this is a non-relocatable one. just copy it.
- output.segments.push_back(inputSegment);
- }
- }
- }
-};
-
-// A mergeable that is an output, that is, that we merge into. This adds
-// logic to update it for the new data, namely, when an import is provided
-// by the other merged unit, we resolve to access that value directly.
-struct OutputMergeable : public PostWalker<OutputMergeable, Visitor<OutputMergeable>>, public Mergeable {
- OutputMergeable(Module& wasm) : Mergeable(wasm) {}
-
- void visitCall(Call* curr) {
- auto iter = implementedFunctionImports.find(curr->target);
- if (iter != implementedFunctionImports.end()) {
- // this import is now in the module - call it
- replaceCurrent(Builder(*getModule()).makeCall(iter->second, curr->operands, curr->type));
- }
- }
-
- void visitGetGlobal(GetGlobal* curr) {
- auto iter = implementedGlobalImports.find(curr->name);
- if (iter != implementedGlobalImports.end()) {
- // this global is now in the module - get it
- curr->name = iter->second;
- assert(curr->name.is());
- }
- }
-
- void visitModule(Module* curr) {
- // remove imports that are being implemented
- for (auto& pair : implementedFunctionImports) {
- curr->removeFunction(pair.first);
- }
- for (auto& pair : implementedGlobalImports) {
- curr->removeGlobal(pair.first);
- }
- }
-};
-
-// A mergeable that is an input, that is, that we merge into another.
-// This adds logic to disambiguate its names from the other, and to
-// perform all other merging operations.
-struct InputMergeable : public ExpressionStackWalker<InputMergeable, Visitor<InputMergeable>>, public Mergeable {
- InputMergeable(Module& wasm, OutputMergeable& outputMergeable) : Mergeable(wasm), outputMergeable(outputMergeable) {}
-
- // The unit we are being merged into
- OutputMergeable& outputMergeable;
-
- // mappings (after disambiguating with the other mergeable), old name => new name
- std::map<Name, Name> ftNames; // function types
- std::map<Name, Name> eNames; // exports
- std::map<Name, Name> fNames; // functions
- std::map<Name, Name> gNames; // globals
-
- void visitCall(Call* curr) {
- auto iter = implementedFunctionImports.find(curr->target);
- if (iter != implementedFunctionImports.end()) {
- // this import is now in the module - call it
- replaceCurrent(Builder(*getModule()).makeCall(iter->second, curr->operands, curr->type));
- return;
- }
- curr->target = fNames[curr->target];
- assert(curr->target.is());
- }
-
- void visitCallIndirect(CallIndirect* curr) {
- curr->fullType = ftNames[curr->fullType];
- assert(curr->fullType.is());
- }
-
- void visitGetGlobal(GetGlobal* curr) {
- auto iter = implementedGlobalImports.find(curr->name);
- if (iter != implementedGlobalImports.end()) {
- // this import is now in the module - use it
- curr->name = iter->second;
- return;
- }
- curr->name = gNames[curr->name];
- assert(curr->name.is());
- // if this is the memory or table base, add the bump
- if (memoryBaseGlobals.count(curr->name)) {
- addBump(outputMergeable.totalMemorySize);
- } else if (tableBaseGlobals.count(curr->name)) {
- addBump(outputMergeable.totalTableSize);
- }
- }
-
- void visitSetGlobal(SetGlobal* curr) {
- curr->name = gNames[curr->name];
- assert(curr->name.is());
- }
-
- void merge() {
- // find function imports in us that are implemented in the output
- // TODO make maps, avoid N^2
- ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) {
- // per wasm dynamic library rules, we expect to see exports on 'env'
- if (import->module == ENV) {
- // seek an export on the other side that matches
- for (auto& exp : outputMergeable.wasm.exports) {
- if (exp->name == import->base) {
- // fits!
- implementedFunctionImports[import->name] = exp->value;
- break;
- }
- }
- }
- });
- ModuleUtils::iterImportedGlobals(wasm, [&](Global* import) {
- // per wasm dynamic library rules, we expect to see exports on 'env'
- if (import->module == ENV) {
- // seek an export on the other side that matches
- for (auto& exp : outputMergeable.wasm.exports) {
- if (exp->name == import->base) {
- // fits!
- implementedGlobalImports[import->name] = exp->value;
- break;
- }
- }
- }
- });
- // remove the unneeded ones
- for (auto& pair : implementedFunctionImports) {
- wasm.removeFunction(pair.first);
- }
- for (auto& pair : implementedGlobalImports) {
- wasm.removeGlobal(pair.first);
- }
-
- // find new names
- for (auto& curr : wasm.functionTypes) {
- curr->name = ftNames[curr->name] = getNonColliding(curr->name, [&](Name name) -> bool {
- return outputMergeable.wasm.getFunctionTypeOrNull(name);
- });
- }
- ModuleUtils::iterImportedFunctions(wasm, [&](Function* curr) {
- curr->name = fNames[curr->name] = getNonColliding(curr->name, [&](Name name) -> bool {
- return !!outputMergeable.wasm.getFunctionOrNull(name);
- });
- });
- ModuleUtils::iterImportedGlobals(wasm, [&](Global* curr) {
- curr->name = gNames[curr->name] = getNonColliding(curr->name, [&](Name name) -> bool {
- return !!outputMergeable.wasm.getGlobalOrNull(name);
- });
- });
- ModuleUtils::iterDefinedFunctions(wasm, [&](Function* curr) {
- curr->name = fNames[curr->name] = getNonColliding(curr->name, [&](Name name) -> bool {
- return outputMergeable.wasm.getFunctionOrNull(name);
- });
- });
- ModuleUtils::iterDefinedGlobals(wasm, [&](Global* curr) {
- curr->name = gNames[curr->name] = getNonColliding(curr->name, [&](Name name) -> bool {
- return outputMergeable.wasm.getGlobalOrNull(name);
- });
- });
-
- // update global names in input
- {
- auto temp = memoryBaseGlobals;
- memoryBaseGlobals.clear();
- for (auto x : temp) {
- memoryBaseGlobals.insert(gNames[x]);
- }
- }
- {
- auto temp = tableBaseGlobals;
- tableBaseGlobals.clear();
- for (auto x : temp) {
- tableBaseGlobals.insert(gNames[x]);
- }
- }
-
- // find function imports in output that are implemented in the input
- ModuleUtils::iterImportedFunctions(outputMergeable.wasm, [&](Function* import) {
- if (import->module == ENV) {
- for (auto& exp : wasm.exports) {
- if (exp->name == import->base) {
- outputMergeable.implementedFunctionImports[import->name] = fNames[exp->value];
- break;
- }
- }
- }
- });
- ModuleUtils::iterImportedGlobals(outputMergeable.wasm, [&](Global* import) {
- if (import->module == ENV) {
- for (auto& exp : wasm.exports) {
- if (exp->name == import->base) {
- outputMergeable.implementedGlobalImports[import->name] = gNames[exp->value];
- break;
- }
- }
- }
- });
-
- // update the output before bringing anything in. avoid doing so when possible, as in the
- // common case the output module is very large.
- if (outputMergeable.implementedFunctionImports.size() + outputMergeable.implementedGlobalImports.size() > 0) {
- outputMergeable.walkModule(&outputMergeable.wasm);
- }
-
- // memory&table: we place the new memory segments at a higher position. after the existing ones.
- copySegments(outputMergeable.wasm.memory, wasm.memory, [](char x) -> char { return x; });
- copySegments(outputMergeable.wasm.table, wasm.table, [&](Name x) -> Name { return fNames[x]; });
-
- // update the new contents about to be merged in
- walkModule(&wasm);
-
- // handle the dylink post-instantiate. this is special, as if it exists in both, we must in fact call both
- Name POST_INSTANTIATE("__post_instantiate");
- if (fNames.find(POST_INSTANTIATE) != fNames.end() &&
- outputMergeable.wasm.getExportOrNull(POST_INSTANTIATE)) {
- // indeed, both exist. add a call to the second (wasm spec does not give an order requirement)
- auto* func = outputMergeable.wasm.getFunction(outputMergeable.wasm.getExport(POST_INSTANTIATE)->value);
- Builder builder(outputMergeable.wasm);
- func->body = builder.makeSequence(
- builder.makeCall(fNames[POST_INSTANTIATE], {}, none),
- func->body
- );
- }
-
- // copy in the data
- for (auto& curr : wasm.functionTypes) {
- outputMergeable.wasm.addFunctionType(std::move(curr));
- }
- for (auto& curr : wasm.globals) {
- if (curr->imported()) {
- outputMergeable.wasm.addGlobal(curr.release());
- }
- }
- for (auto& curr : wasm.functions) {
- if (curr->imported()) {
- if (curr->type.is()) {
- curr->type = ftNames[curr->type];
- assert(curr->type.is());
- }
- outputMergeable.wasm.addFunction(curr.release());
- }
- }
- for (auto& curr : wasm.exports) {
- if (curr->kind == ExternalKind::Memory || curr->kind == ExternalKind::Table) {
- continue; // wasm has just 1 of each, they must match
- }
- // if an export would collide, do not add the new one, ignore it
- // TODO: warning/error mode?
- if (!outputMergeable.wasm.getExportOrNull(curr->name)) {
- if (curr->kind == ExternalKind::Function) {
- curr->value = fNames[curr->value];
- outputMergeable.wasm.addExport(curr.release());
- } else if (curr->kind == ExternalKind::Global) {
- curr->value = gNames[curr->value];
- outputMergeable.wasm.addExport(curr.release());
- } else {
- WASM_UNREACHABLE();
- }
- }
- }
- // Copy over the remaining non-imports (we have already transferred
- // the imports, and they are nullptrs).
- for (auto& curr : wasm.functions) {
- if (curr) {
- assert(!curr->imported());
- curr->type = ftNames[curr->type];
- assert(curr->type.is());
- outputMergeable.wasm.addFunction(curr.release());
- }
- }
- for (auto& curr : wasm.globals) {
- if (curr) {
- assert(!curr->imported());
- outputMergeable.wasm.addGlobal(curr.release());
- }
- }
- }
-
-private:
- // add an offset to a global.get. we look above, and if there is already an add,
- // we can add into it, avoiding creating a new node
- void addBump(Index bump) {
- if (expressionStack.size() >= 2) {
- auto* parent = expressionStack[expressionStack.size() - 2];
- if (auto* binary = parent->dynCast<Binary>()) {
- if (binary->op == AddInt32) {
- if (auto* num = binary->right->dynCast<Const>()) {
- num->value = num->value.add(Literal(bump));
- return;
- }
- }
- }
- }
- Builder builder(*getModule());
- replaceCurrent(
- builder.makeBinary(
- AddInt32,
- expressionStack.back(),
- builder.makeConst(Literal(int32_t(bump)))
- )
- );
- }
-};
-
-// Finalize the memory/table bases, assinging concrete values into them
-void finalizeBases(Module& wasm, Index memory, Index table) {
- struct FinalizableMergeable : public Mergeable, public PostWalker<FinalizableMergeable, Visitor<FinalizableMergeable>> {
- FinalizableMergeable(Module& wasm, Index memory, Index table) : Mergeable(wasm), memory(memory), table(table) {
- walkModule(&wasm);
- // ensure memory and table sizes suffice, after finalization we have absolute locations now
- for (auto& segment : wasm.memory.segments) {
- ensureSize(wasm.memory, memory + segment.data.size());
- }
- for (auto& segment : wasm.table.segments) {
- ensureSize(wasm.table, table + segment.data.size());
- }
- }
-
- Index memory, table;
-
- void visitGetGlobal(GetGlobal* curr) {
- if (memory != Index(-1) && memoryBaseGlobals.count(curr->name)) {
- finalize(memory);
- } else if (table != Index(-1) && tableBaseGlobals.count(curr->name)) {
- finalize(table);
- }
- }
-
- private:
- void finalize(Index value) {
- replaceCurrent(Builder(*getModule()).makeConst(Literal(int32_t(value))));
- }
- };
- FinalizableMergeable mergeable(wasm, memory, table);
-}
-
-//
-// main
-//
-
-int main(int argc, const char* argv[]) {
- std::vector<std::string> filenames;
- bool emitBinary = true;
- Index finalizeMemoryBase = Index(-1),
- finalizeTableBase = Index(-1);
- bool optimize = false;
- bool verbose = false;
-
- Options options("wasm-merge", "Merge wasm files");
- options
- .add("--output", "-o", "Output file",
- Options::Arguments::One,
- [](Options* o, const std::string& argument) {
- o->extra["output"] = argument;
- Colors::disable();
- })
- .add("--emit-text", "-S", "Emit text instead of binary for the output file",
- Options::Arguments::Zero,
- [&](Options *o, const std::string& argument) { emitBinary = false; })
- .add("--finalize-memory-base", "-fmb", "Finalize the env.__memory_base import",
- Options::Arguments::One,
- [&](Options* o, const std::string& argument) {
- finalizeMemoryBase = atoi(argument.c_str());
- })
- .add("--finalize-table-base", "-ftb", "Finalize the env.__table_base import",
- Options::Arguments::One,
- [&](Options* o, const std::string& argument) {
- finalizeTableBase = atoi(argument.c_str());
- })
- .add("-O", "-O", "Perform merge-time/finalize-time optimizations",
- Options::Arguments::Zero,
- [&](Options* o, const std::string& argument) {
- optimize = true;
- })
- .add("--verbose", "-v", "Verbose output",
- Options::Arguments::Zero,
- [&](Options* o, const std::string& argument) {
- verbose = true;
- })
- .add_positional("INFILES", Options::Arguments::N,
- [&](Options *o, const std::string& argument) {
- filenames.push_back(argument);
- });
- options.parse(argc, argv);
-
- Module output;
- std::vector<std::unique_ptr<Module>> otherModules; // keep all inputs alive, to save copies
- bool first = true;
- for (auto& filename : filenames) {
- ModuleReader reader;
- if (first) {
- // read the first right into output, don't waste time merging into an empty module
- try {
- reader.read(filename, output);
- } catch (ParseException& p) {
- p.dump(std::cerr);
- Fatal() << "error in parsing input";
- }
- first = false;
- } else {
- std::unique_ptr<Module> input = wasm::make_unique<Module>();
- try {
- reader.read(filename, *input);
- } catch (ParseException& p) {
- p.dump(std::cerr);
- Fatal() << "error in parsing input";
- }
- // perform the merge
- OutputMergeable outputMergeable(output);
- InputMergeable inputMergeable(*input, outputMergeable);
- inputMergeable.merge();
- // retain the linked in module as we may depend on parts of it
- otherModules.push_back(std::unique_ptr<Module>(input.release()));
- }
- }
-
- if (verbose) {
- // memory and table are standardized and merged, so it's easy to dump out some stats
- std::cout << "merged total memory size: " << output.memory.segments[0].data.size() << '\n';
- std::cout << "merged total table size: " << output.table.segments[0].data.size() << '\n';
- std::cout << "merged functions: " << output.functions.size() << '\n';
- }
-
- if (finalizeMemoryBase != Index(-1) || finalizeTableBase != Index(-1)) {
- finalizeBases(output, finalizeMemoryBase, finalizeTableBase);
- }
-
- if (optimize) {
- // merge-time/finalize-time optimization
- // it is beneficial to do global optimizations, as well as precomputing to get rid of finalized constants
- PassRunner passRunner(&output);
- passRunner.add("precompute");
- passRunner.add("optimize-instructions"); // things now-constant may be further optimized
- passRunner.addDefaultGlobalOptimizationPostPasses();
- passRunner.run();
- }
-
- if (!WasmValidator().validate(output)) {
- WasmPrinter::printModule(&output);
- Fatal() << "error in validating output";
- }
-
- if (options.extra.count("output") > 0) {
- ModuleWriter writer;
- writer.setDebug(options.debug);
- writer.setBinary(emitBinary);
- writer.write(output, options.extra["output"]);
- }
-}
diff --git a/src/wasm-binary.h b/src/wasm-binary.h
index 32166af00..8b449192a 100644
--- a/src/wasm-binary.h
+++ b/src/wasm-binary.h
@@ -557,7 +557,7 @@ enum ASTNodes {
};
enum AtomicOpcodes {
- AtomicWake = 0x00,
+ AtomicNotify = 0x00,
I32AtomicWait = 0x01,
I64AtomicWait = 0x02,
@@ -962,7 +962,8 @@ public:
void read();
void readUserSection(size_t payloadLen);
- bool more() { return pos < input.size();}
+
+ bool more() { return pos < input.size(); }
uint8_t getInt8();
uint16_t getInt16();
@@ -979,7 +980,6 @@ public:
int64_t getS64LEB();
Type getType();
Type getConcreteType();
- Name getString();
Name getInlineString();
void verifyInt8(int8_t x);
void verifyInt16(int16_t x);
@@ -1108,7 +1108,7 @@ public:
bool maybeVisitAtomicRMW(Expression*& out, uint8_t code);
bool maybeVisitAtomicCmpxchg(Expression*& out, uint8_t code);
bool maybeVisitAtomicWait(Expression*& out, uint8_t code);
- bool maybeVisitAtomicWake(Expression*& out, uint8_t code);
+ bool maybeVisitAtomicNotify(Expression*& out, uint8_t code);
bool maybeVisitConst(Expression*& out, uint8_t code);
bool maybeVisitUnary(Expression*& out, uint8_t code);
bool maybeVisitBinary(Expression*& out, uint8_t code);
diff --git a/src/wasm-builder.h b/src/wasm-builder.h
index eee4e3b79..8c50ff2dc 100644
--- a/src/wasm-builder.h
+++ b/src/wasm-builder.h
@@ -247,13 +247,13 @@ public:
wait->finalize();
return wait;
}
- AtomicWake* makeAtomicWake(Expression* ptr, Expression* wakeCount, Address offset) {
- auto* wake = allocator.alloc<AtomicWake>();
- wake->offset = offset;
- wake->ptr = ptr;
- wake->wakeCount = wakeCount;
- wake->finalize();
- return wake;
+ AtomicNotify* makeAtomicNotify(Expression* ptr, Expression* notifyCount, Address offset) {
+ auto* notify = allocator.alloc<AtomicNotify>();
+ notify->offset = offset;
+ notify->ptr = ptr;
+ notify->notifyCount = notifyCount;
+ notify->finalize();
+ return notify;
}
Store* makeStore(unsigned bytes, uint32_t offset, unsigned align, Expression *ptr, Expression *value, Type type) {
auto* ret = allocator.alloc<Store>();
diff --git a/src/wasm-emscripten.h b/src/wasm-emscripten.h
index acb2994ad..67ba408f5 100644
--- a/src/wasm-emscripten.h
+++ b/src/wasm-emscripten.h
@@ -36,7 +36,9 @@ public:
void generateRuntimeFunctions();
Function* generateMemoryGrowthFunction();
+ Function* generateAssignGOTEntriesFunction();
void generateStackInitialization(Address addr);
+ void generatePostInstantiateFunction();
// Create thunks for use with emscripten Runtime.dynCall. Creates one for each
// signature in the indirect function table.
diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h
index c584f0ea7..ae9494d1c 100644
--- a/src/wasm-interpreter.h
+++ b/src/wasm-interpreter.h
@@ -1124,12 +1124,12 @@ public:
// for now, just assume we are woken up
return Literal(int32_t(0)); // woken up
}
- Flow visitAtomicWake(AtomicWake *curr) {
- NOTE_ENTER("AtomicWake");
+ Flow visitAtomicNotify(AtomicNotify *curr) {
+ NOTE_ENTER("AtomicNotify");
Flow ptr = this->visit(curr->ptr);
if (ptr.breaking()) return ptr;
NOTE_EVAL1(ptr);
- auto count = this->visit(curr->wakeCount);
+ auto count = this->visit(curr->notifyCount);
NOTE_EVAL1(count);
if (count.breaking()) return count;
// TODO: add threads support!
diff --git a/src/wasm-module-building.h b/src/wasm-module-building.h
index e92436952..d1f8f5504 100644
--- a/src/wasm-module-building.h
+++ b/src/wasm-module-building.h
@@ -65,7 +65,7 @@ static std::mutex debug;
// * workers transform functions into nullptrs, and optimize them
// * we keep an atomic count of the number of active workers and
// the number of optimized functions.
-// * after adding a function, the main thread wakes up workers if
+// * after adding a function, the main thread notifys up workers if
// it calculates there is work for them.
// * a lock is used for going to sleep and waking up.
// Locking should be rare, as optimization is
@@ -156,10 +156,10 @@ public:
wasm->addFunction(func);
if (!useWorkers()) return; // we optimize at the end in that case
queueFunction(func);
- // wake workers if needed
- auto wake = availableFuncs.load();
- for (uint32_t i = 0; i < wake; i++) {
- wakeWorker();
+ // notify workers if needed
+ auto notify = availableFuncs.load();
+ for (uint32_t i = 0; i < notify; i++) {
+ notifyWorker();
}
}
@@ -180,7 +180,7 @@ public:
} else {
DEBUG_THREAD("finish()ing");
assert(nextFunction == numFunctions);
- wakeAllWorkers();
+ notifyAllWorkers();
waitUntilAllFinished();
}
// TODO: clear side thread allocators from module allocator, as these threads were transient
@@ -192,14 +192,14 @@ private:
threads.emplace_back(make_unique<std::thread>(workerMain, this));
}
- void wakeWorker() {
- DEBUG_THREAD("wake a worker");
+ void notifyWorker() {
+ DEBUG_THREAD("notify a worker");
std::lock_guard<std::mutex> lock(mutex);
condition.notify_one();
}
- void wakeAllWorkers() {
- DEBUG_THREAD("wake all workers");
+ void notifyAllWorkers() {
+ DEBUG_THREAD("notify all workers");
std::lock_guard<std::mutex> lock(mutex);
condition.notify_all();
}
diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h
index 07de235a2..c80fa8b83 100644
--- a/src/wasm-s-parser.h
+++ b/src/wasm-s-parser.h
@@ -191,7 +191,7 @@ private:
Expression* makeAtomicRMW(Element& s, Type type, uint8_t bytes, const char* extra);
Expression* makeAtomicCmpxchg(Element& s, Type type, uint8_t bytes, const char* extra);
Expression* makeAtomicWait(Element& s, Type type);
- Expression* makeAtomicWake(Element& s);
+ Expression* makeAtomicNotify(Element& s);
Expression* makeSIMDExtract(Element& s, SIMDExtractOp op, size_t lanes);
Expression* makeSIMDReplace(Element& s, SIMDReplaceOp op, size_t lanes);
Expression* makeSIMDShuffle(Element& s);
diff --git a/src/wasm-stack.h b/src/wasm-stack.h
index f0995497a..afef2632b 100644
--- a/src/wasm-stack.h
+++ b/src/wasm-stack.h
@@ -134,7 +134,7 @@ public:
void visitAtomicRMW(AtomicRMW* curr);
void visitAtomicCmpxchg(AtomicCmpxchg* curr);
void visitAtomicWait(AtomicWait* curr);
- void visitAtomicWake(AtomicWake* curr);
+ void visitAtomicNotify(AtomicNotify* curr);
void visitSIMDExtract(SIMDExtract* curr);
void visitSIMDReplace(SIMDReplace* curr);
void visitSIMDShuffle(SIMDShuffle* curr);
@@ -890,15 +890,15 @@ void StackWriter<Mode, Parent>::visitAtomicWait(AtomicWait* curr) {
}
template<StackWriterMode Mode, typename Parent>
-void StackWriter<Mode, Parent>::visitAtomicWake(AtomicWake* curr) {
+void StackWriter<Mode, Parent>::visitAtomicNotify(AtomicNotify* curr) {
visitChild(curr->ptr);
// stop if the rest isn't reachable anyhow
if (curr->ptr->type == unreachable) return;
- visitChild(curr->wakeCount);
- if (curr->wakeCount->type == unreachable) return;
+ visitChild(curr->notifyCount);
+ if (curr->notifyCount->type == unreachable) return;
if (justAddToStack(curr)) return;
- o << int8_t(BinaryConsts::AtomicPrefix) << int8_t(BinaryConsts::AtomicWake);
+ o << int8_t(BinaryConsts::AtomicPrefix) << int8_t(BinaryConsts::AtomicNotify);
emitMemoryAccess(4, 4, 0);
}
diff --git a/src/wasm-traversal.h b/src/wasm-traversal.h
index 541490418..6259bf271 100644
--- a/src/wasm-traversal.h
+++ b/src/wasm-traversal.h
@@ -54,7 +54,7 @@ struct Visitor {
ReturnType visitAtomicRMW(AtomicRMW* curr) { return ReturnType(); }
ReturnType visitAtomicCmpxchg(AtomicCmpxchg* curr) { return ReturnType(); }
ReturnType visitAtomicWait(AtomicWait* curr) { return ReturnType(); }
- ReturnType visitAtomicWake(AtomicWake* curr) { return ReturnType(); }
+ ReturnType visitAtomicNotify(AtomicNotify* curr) { return ReturnType(); }
ReturnType visitSIMDExtract(SIMDExtract* curr) { return ReturnType(); }
ReturnType visitSIMDReplace(SIMDReplace* curr) { return ReturnType(); }
ReturnType visitSIMDShuffle(SIMDShuffle* curr) { return ReturnType(); }
@@ -106,7 +106,7 @@ struct Visitor {
case Expression::Id::AtomicRMWId: DELEGATE(AtomicRMW);
case Expression::Id::AtomicCmpxchgId: DELEGATE(AtomicCmpxchg);
case Expression::Id::AtomicWaitId: DELEGATE(AtomicWait);
- case Expression::Id::AtomicWakeId: DELEGATE(AtomicWake);
+ case Expression::Id::AtomicNotifyId: DELEGATE(AtomicNotify);
case Expression::Id::SIMDExtractId: DELEGATE(SIMDExtract);
case Expression::Id::SIMDReplaceId: DELEGATE(SIMDReplace);
case Expression::Id::SIMDShuffleId: DELEGATE(SIMDShuffle);
@@ -160,7 +160,7 @@ struct OverriddenVisitor {
UNIMPLEMENTED(AtomicRMW);
UNIMPLEMENTED(AtomicCmpxchg);
UNIMPLEMENTED(AtomicWait);
- UNIMPLEMENTED(AtomicWake);
+ UNIMPLEMENTED(AtomicNotify);
UNIMPLEMENTED(SIMDExtract);
UNIMPLEMENTED(SIMDReplace);
UNIMPLEMENTED(SIMDShuffle);
@@ -213,7 +213,7 @@ struct OverriddenVisitor {
case Expression::Id::AtomicRMWId: DELEGATE(AtomicRMW);
case Expression::Id::AtomicCmpxchgId: DELEGATE(AtomicCmpxchg);
case Expression::Id::AtomicWaitId: DELEGATE(AtomicWait);
- case Expression::Id::AtomicWakeId: DELEGATE(AtomicWake);
+ case Expression::Id::AtomicNotifyId: DELEGATE(AtomicNotify);
case Expression::Id::SIMDExtractId: DELEGATE(SIMDExtract);
case Expression::Id::SIMDReplaceId: DELEGATE(SIMDReplace);
case Expression::Id::SIMDShuffleId: DELEGATE(SIMDShuffle);
@@ -265,7 +265,7 @@ struct UnifiedExpressionVisitor : public Visitor<SubType, ReturnType> {
ReturnType visitAtomicRMW(AtomicRMW* curr) { return static_cast<SubType*>(this)->visitExpression(curr); }
ReturnType visitAtomicCmpxchg(AtomicCmpxchg* curr) { return static_cast<SubType*>(this)->visitExpression(curr); }
ReturnType visitAtomicWait(AtomicWait* curr) { return static_cast<SubType*>(this)->visitExpression(curr); }
- ReturnType visitAtomicWake(AtomicWake* curr) { return static_cast<SubType*>(this)->visitExpression(curr); }
+ ReturnType visitAtomicNotify(AtomicNotify* curr) { return static_cast<SubType*>(this)->visitExpression(curr); }
ReturnType visitSIMDExtract(SIMDExtract* curr) { return static_cast<SubType*>(this)->visitExpression(curr); }
ReturnType visitSIMDReplace(SIMDReplace* curr) { return static_cast<SubType*>(this)->visitExpression(curr); }
ReturnType visitSIMDShuffle(SIMDShuffle* curr) { return static_cast<SubType*>(this)->visitExpression(curr); }
@@ -473,7 +473,7 @@ struct Walker : public VisitorType {
static void doVisitAtomicRMW(SubType* self, Expression** currp) { self->visitAtomicRMW((*currp)->cast<AtomicRMW>()); }
static void doVisitAtomicCmpxchg(SubType* self, Expression** currp){ self->visitAtomicCmpxchg((*currp)->cast<AtomicCmpxchg>()); }
static void doVisitAtomicWait(SubType* self, Expression** currp) { self->visitAtomicWait((*currp)->cast<AtomicWait>()); }
- static void doVisitAtomicWake(SubType* self, Expression** currp) { self->visitAtomicWake((*currp)->cast<AtomicWake>()); }
+ static void doVisitAtomicNotify(SubType* self, Expression** currp) { self->visitAtomicNotify((*currp)->cast<AtomicNotify>()); }
static void doVisitSIMDExtract(SubType* self, Expression** currp) { self->visitSIMDExtract((*currp)->cast<SIMDExtract>()); }
static void doVisitSIMDReplace(SubType* self, Expression** currp) { self->visitSIMDReplace((*currp)->cast<SIMDReplace>()); }
static void doVisitSIMDShuffle(SubType* self, Expression** currp) { self->visitSIMDShuffle((*currp)->cast<SIMDShuffle>()); }
@@ -617,10 +617,10 @@ struct PostWalker : public Walker<SubType, VisitorType> {
self->pushTask(SubType::scan, &curr->cast<AtomicWait>()->ptr);
break;
}
- case Expression::Id::AtomicWakeId: {
- self->pushTask(SubType::doVisitAtomicWake, currp);
- self->pushTask(SubType::scan, &curr->cast<AtomicWake>()->wakeCount);
- self->pushTask(SubType::scan, &curr->cast<AtomicWake>()->ptr);
+ case Expression::Id::AtomicNotifyId: {
+ self->pushTask(SubType::doVisitAtomicNotify, currp);
+ self->pushTask(SubType::scan, &curr->cast<AtomicNotify>()->notifyCount);
+ self->pushTask(SubType::scan, &curr->cast<AtomicNotify>()->ptr);
break;
}
case Expression::Id::SIMDExtractId: {
diff --git a/src/wasm.h b/src/wasm.h
index ab9d7c816..12071fdb4 100644
--- a/src/wasm.h
+++ b/src/wasm.h
@@ -206,7 +206,7 @@ public:
AtomicRMWId,
AtomicCmpxchgId,
AtomicWaitId,
- AtomicWakeId,
+ AtomicNotifyId,
SIMDExtractId,
SIMDReplaceId,
SIMDShuffleId,
@@ -512,14 +512,14 @@ class AtomicWait : public SpecificExpression<Expression::AtomicWaitId> {
void finalize();
};
-class AtomicWake : public SpecificExpression<Expression::AtomicWakeId> {
+class AtomicNotify : public SpecificExpression<Expression::AtomicNotifyId> {
public:
- AtomicWake() = default;
- AtomicWake(MixedArena& allocator) : AtomicWake() {}
+ AtomicNotify() = default;
+ AtomicNotify(MixedArena& allocator) : AtomicNotify() {}
Address offset;
Expression* ptr;
- Expression* wakeCount;
+ Expression* notifyCount;
void finalize();
};
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index 7aeb80715..643f69dde 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -394,7 +394,7 @@ void WasmBinaryWriter::writeDataSegments() {
if (combined.data.size() < needed) {
combined.data.resize(needed);
}
- std::copy(segment.data.begin(), segment.data.end(), combined.data.begin() + offset - start);
+ std::copy(segment.data.begin(), segment.data.end(), combined.data.begin() + (offset - start));
}
emit(combined);
break;
@@ -862,14 +862,6 @@ Type WasmBinaryBuilder::getConcreteType() {
return type;
}
-Name WasmBinaryBuilder::getString() {
- if (debug) std::cerr << "<==" << std::endl;
- size_t offset = getInt32();
- Name ret = cashew::IString((&input[0]) + offset, false);
- if (debug) std::cerr << "getString: " << ret << " ==>" << std::endl;
- return ret;
-}
-
Name WasmBinaryBuilder::getInlineString() {
if (debug) std::cerr << "<==" << std::endl;
auto len = getU32LEB();
@@ -1711,7 +1703,7 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
if (maybeVisitAtomicRMW(curr, code)) break;
if (maybeVisitAtomicCmpxchg(curr, code)) break;
if (maybeVisitAtomicWait(curr, code)) break;
- if (maybeVisitAtomicWake(curr, code)) break;
+ if (maybeVisitAtomicNotify(curr, code)) break;
throwError("invalid code after atomic prefix: " + std::to_string(code));
break;
}
@@ -2223,17 +2215,17 @@ bool WasmBinaryBuilder::maybeVisitAtomicWait(Expression*& out, uint8_t code) {
return true;
}
-bool WasmBinaryBuilder::maybeVisitAtomicWake(Expression*& out, uint8_t code) {
- if (code != BinaryConsts::AtomicWake) return false;
- auto* curr = allocator.alloc<AtomicWake>();
- if (debug) std::cerr << "zz node: AtomicWake" << std::endl;
+bool WasmBinaryBuilder::maybeVisitAtomicNotify(Expression*& out, uint8_t code) {
+ if (code != BinaryConsts::AtomicNotify) return false;
+ auto* curr = allocator.alloc<AtomicNotify>();
+ if (debug) std::cerr << "zz node: AtomicNotify" << std::endl;
curr->type = i32;
- curr->wakeCount = popNonVoidExpression();
+ curr->notifyCount = popNonVoidExpression();
curr->ptr = popNonVoidExpression();
Address readAlign;
readMemoryAccess(readAlign, curr->offset);
- if (readAlign != getTypeSize(curr->type)) throwError("Align of AtomicWake must match size");
+ if (readAlign != getTypeSize(curr->type)) throwError("Align of AtomicNotify must match size");
curr->finalize();
out = curr;
return true;
diff --git a/src/wasm/wasm-emscripten.cpp b/src/wasm/wasm-emscripten.cpp
index ea24b945d..404d93ca3 100644
--- a/src/wasm/wasm-emscripten.cpp
+++ b/src/wasm/wasm-emscripten.cpp
@@ -36,7 +36,9 @@ cashew::IString EM_JS_PREFIX("__em_js__");
static Name STACK_SAVE("stackSave"),
STACK_RESTORE("stackRestore"),
STACK_ALLOC("stackAlloc"),
- STACK_INIT("stack$init");
+ STACK_INIT("stack$init"),
+ POST_INSTANTIATE("__post_instantiate"),
+ ASSIGN_GOT_ENTIRES("__assign_got_enties");
void addExportedFunction(Module& wasm, Function* function) {
wasm.addFunction(function);
@@ -47,15 +49,20 @@ void addExportedFunction(Module& wasm, Function* function) {
}
Global* EmscriptenGlueGenerator::getStackPointerGlobal() {
- // Assumption: The first non-imported global is global is __stack_pointer
+ // Assumption: The stack pointer is either imported as __stack_pointer or
+ // its the first non-imported global.
// TODO(sbc): Find a better way to discover the stack pointer. Perhaps the
// linker could export it by name?
for (auto& g : wasm.globals) {
- if (!g->imported()) {
+ if (g->imported()) {
+ if (g->base == "__stack_pointer") {
+ return g.get();
+ }
+ } else {
return g.get();
}
}
- Fatal() << "stack pointer global not found";
+ return nullptr;
}
Expression* EmscriptenGlueGenerator::generateLoadStackPointer() {
@@ -70,6 +77,8 @@ Expression* EmscriptenGlueGenerator::generateLoadStackPointer() {
);
}
Global* stackPointer = getStackPointerGlobal();
+ if (!stackPointer)
+ Fatal() << "stack pointer global not found";
return builder.makeGetGlobal(stackPointer->name, i32);
}
@@ -85,6 +94,8 @@ Expression* EmscriptenGlueGenerator::generateStoreStackPointer(Expression* value
);
}
Global* stackPointer = getStackPointerGlobal();
+ if (!stackPointer)
+ Fatal() << "stack pointer global not found";
return builder.makeSetGlobal(stackPointer->name, value);
}
@@ -143,6 +154,116 @@ void EmscriptenGlueGenerator::generateRuntimeFunctions() {
generateStackRestoreFunction();
}
+static Function* ensureFunctionImport(Module* module, Name name, std::string sig) {
+ // Then see if its already imported
+ ImportInfo info(*module);
+ if (Function* f = info.getImportedFunction(ENV, name)) {
+ return f;
+ }
+ // Failing that create a new function import.
+ auto import = new Function;
+ import->name = name;
+ import->module = ENV;
+ import->base = name;
+ auto* functionType = ensureFunctionType(sig, module);
+ import->type = functionType->name;
+ FunctionTypeUtils::fillFunction(import, functionType);
+ module->addFunction(import);
+ return import;
+}
+
+Function* EmscriptenGlueGenerator::generateAssignGOTEntriesFunction() {
+ std::vector<Global*> got_entries_func;
+ std::vector<Global*> got_entries_mem;
+ for (auto& g : wasm.globals) {
+ if (!g->imported()) {
+ continue;
+ }
+ if (g->module == "GOT.func") {
+ got_entries_func.push_back(g.get());
+ } else if (g->module == "GOT.mem") {
+ got_entries_mem.push_back(g.get());
+ } else {
+ continue;
+ }
+ // Make this an internal, non-imported, global.
+ g->module.clear();
+ g->init = Builder(wasm).makeConst(Literal(0));
+ }
+
+ if (!got_entries_func.size() && !got_entries_mem.size()) {
+ return nullptr;
+ }
+
+ Function* assign_func =
+ builder.makeFunction(ASSIGN_GOT_ENTIRES, std::vector<NameType>{}, none, {});
+ Block* block = builder.makeBlock();
+ assign_func->body = block;
+
+ for (Global* g : got_entries_mem) {
+ Name getter(std::string("g$") + g->base.c_str());
+ ensureFunctionImport(&wasm, getter, "i");
+ Expression* call = builder.makeCall(getter, {}, i32);
+ SetGlobal* set_global = builder.makeSetGlobal(g->name, call);
+ block->list.push_back(set_global);
+ }
+
+ for (Global* g : got_entries_func) {
+ Name getter(std::string("f$") + g->base.c_str());
+ if (auto* f = wasm.getFunctionOrNull(g->base)) {
+ getter.set((getter.c_str() + std::string("$") + getSig(f)).c_str(), false);
+ }
+ ensureFunctionImport(&wasm, getter, "i");
+ Expression* call = builder.makeCall(getter, {}, i32);
+ SetGlobal* set_global = builder.makeSetGlobal(g->name, call);
+ block->list.push_back(set_global);
+ }
+
+ wasm.addFunction(assign_func);
+ return assign_func;
+}
+
+// For emscripten SIDE_MODULE we generate a single exported function called
+// __post_instantiate which calls two functions:
+//
+// - __assign_got_enties
+// - __wasm_call_ctors
+//
+// The former is function we generate here which calls imported g$XXX functions
+// order to assign values to any globals imported from GOT.func or GOT.mem.
+// These globals hold address of functions and globals respectively.
+//
+// The later is the constructor function generaed by lld which performs any
+// fixups on the memory section and calls static constructors.
+void EmscriptenGlueGenerator::generatePostInstantiateFunction() {
+ Builder builder(wasm);
+ Function* post_instantiate =
+ builder.makeFunction(POST_INSTANTIATE, std::vector<NameType>{}, none, {});
+ wasm.addFunction(post_instantiate);
+
+ if (Function* F = generateAssignGOTEntriesFunction()) {
+ // call __assign_got_enties from post_instantiate
+ Expression* call = builder.makeCall(F->name, {}, none);
+ post_instantiate->body = builder.blockify(call);
+ }
+
+ // The names of standard imports/exports used by lld doesn't quite match that
+ // expected by emscripten.
+ // TODO(sbc): Unify these
+ if (auto* e = wasm.getExportOrNull(WASM_CALL_CTORS)) {
+ Expression* call = builder.makeCall(e->value, {}, none);
+ post_instantiate->body = builder.blockify(post_instantiate->body, call);
+ wasm.removeExport(WASM_CALL_CTORS);
+ }
+
+ auto* ex = new Export();
+ ex->value = post_instantiate->name;
+ ex->name = POST_INSTANTIATE;
+ ex->kind = ExternalKind::Function;
+ wasm.addExport(ex);
+ wasm.updateMaps();
+}
+
Function* EmscriptenGlueGenerator::generateMemoryGrowthFunction() {
Name name(GROW_WASM_MEMORY);
std::vector<NameType> params { { NEW_SIZE, i32 } };
@@ -215,24 +336,6 @@ void EmscriptenGlueGenerator::generateDynCallThunks() {
}
}
-static Function* ensureFunctionImport(Module* module, Name name, std::string sig) {
- // Then see if its already imported
- ImportInfo info(*module);
- if (Function* f = info.getImportedFunction(ENV, name)) {
- return f;
- }
- // Failing that create a new function import.
- auto import = new Function;
- import->name = name;
- import->module = ENV;
- import->base = name;
- auto* functionType = ensureFunctionType(sig, module);
- import->type = functionType->name;
- FunctionTypeUtils::fillFunction(import, functionType);
- module->addFunction(import);
- return import;
-}
-
struct RemoveStackPointer : public PostWalker<RemoveStackPointer> {
RemoveStackPointer(Global* stackPointer) : stackPointer(stackPointer) {}
@@ -262,6 +365,8 @@ private:
void EmscriptenGlueGenerator::replaceStackPointerGlobal() {
Global* stackPointer = getStackPointerGlobal();
+ if (!stackPointer)
+ return;
// Replace all uses of stack pointer global
RemoveStackPointer walker(stackPointer);
diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp
index 1067264f7..ddda991cd 100644
--- a/src/wasm/wasm-s-parser.cpp
+++ b/src/wasm/wasm-s-parser.cpp
@@ -1071,11 +1071,11 @@ Expression* SExpressionWasmBuilder::makeAtomicWait(Element& s, Type type) {
return ret;
}
-Expression* SExpressionWasmBuilder::makeAtomicWake(Element& s) {
- auto ret = allocator.alloc<AtomicWake>();
+Expression* SExpressionWasmBuilder::makeAtomicNotify(Element& s) {
+ auto ret = allocator.alloc<AtomicNotify>();
ret->type = i32;
ret->ptr = parseExpression(s[1]);
- ret->wakeCount = parseExpression(s[2]);
+ ret->notifyCount = parseExpression(s[2]);
ret->finalize();
return ret;
}
diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp
index 91e7e7398..6d4490982 100644
--- a/src/wasm/wasm-validator.cpp
+++ b/src/wasm/wasm-validator.cpp
@@ -235,6 +235,7 @@ public:
void visitSwitch(Switch* curr);
void visitCall(Call* curr);
void visitCallIndirect(CallIndirect* curr);
+ void visitConst(Const* curr);
void visitGetLocal(GetLocal* curr);
void visitSetLocal(SetLocal* curr);
void visitGetGlobal(GetGlobal* curr);
@@ -244,7 +245,7 @@ public:
void visitAtomicRMW(AtomicRMW* curr);
void visitAtomicCmpxchg(AtomicCmpxchg* curr);
void visitAtomicWait(AtomicWait* curr);
- void visitAtomicWake(AtomicWake* curr);
+ void visitAtomicNotify(AtomicNotify* curr);
void visitSIMDExtract(SIMDExtract* curr);
void visitSIMDReplace(SIMDReplace* curr);
void visitSIMDShuffle(SIMDShuffle* curr);
@@ -475,6 +476,11 @@ void FunctionValidator::visitCallIndirect(CallIndirect* curr) {
}
}
+void FunctionValidator::visitConst(Const* curr) {
+ shouldBeTrue(getFeatures(curr->type) <= info.features, curr,
+ "all used features should be allowed");
+}
+
void FunctionValidator::visitGetLocal(GetLocal* curr) {
shouldBeTrue(curr->index < getFunction()->getNumLocals(), curr, "local.get index must be small enough");
shouldBeTrue(isConcreteType(curr->type), curr, "local.get must have a valid type - check what you provided when you constructed the node");
@@ -570,12 +576,12 @@ void FunctionValidator::visitAtomicWait(AtomicWait* curr) {
shouldBeEqualOrFirstIsUnreachable(curr->timeout->type, i64, curr, "AtomicWait timeout type must be i64");
}
-void FunctionValidator::visitAtomicWake(AtomicWake* curr) {
+void FunctionValidator::visitAtomicNotify(AtomicNotify* curr) {
shouldBeTrue(info.features.hasAtomics(), curr, "Atomic operation (atomics are disabled)");
shouldBeFalse(!getModule()->memory.shared, curr, "Atomic operation with non-shared memory");
- shouldBeEqualOrFirstIsUnreachable(curr->type, i32, curr, "AtomicWake must have type i32");
- shouldBeEqualOrFirstIsUnreachable(curr->ptr->type, i32, curr, "AtomicWake pointer type must be i32");
- shouldBeEqualOrFirstIsUnreachable(curr->wakeCount->type, i32, curr, "AtomicWake wakeCount type must be i32");
+ shouldBeEqualOrFirstIsUnreachable(curr->type, i32, curr, "AtomicNotify must have type i32");
+ shouldBeEqualOrFirstIsUnreachable(curr->ptr->type, i32, curr, "AtomicNotify pointer type must be i32");
+ shouldBeEqualOrFirstIsUnreachable(curr->notifyCount->type, i32, curr, "AtomicNotify notifyCount type must be i32");
}
void FunctionValidator::visitSIMDExtract(SIMDExtract* curr) {
@@ -1270,6 +1276,8 @@ static void validateExports(Module& module, ValidationInfo& info) {
static void validateGlobals(Module& module, ValidationInfo& info) {
ModuleUtils::iterDefinedGlobals(module, [&](Global* curr) {
+ info.shouldBeTrue(getFeatures(curr->type) <= info.features, curr->name,
+ "all used types should be allowed");
info.shouldBeTrue(curr->init != nullptr, curr->name, "global init must be non-null");
info.shouldBeTrue(curr->init->is<Const>() || curr->init->is<GetGlobal>(), curr->name, "global init must be valid");
if (!info.shouldBeEqual(curr->type, curr->init->type, curr->init, "global init must have correct type") && !info.quiet) {
diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp
index c99bb0994..f7081e49c 100644
--- a/src/wasm/wasm.cpp
+++ b/src/wasm/wasm.cpp
@@ -37,6 +37,7 @@ const char* TargetFeatures = "target_features";
}
Name GROW_WASM_MEMORY("__growWasmMemory"),
+ WASM_CALL_CTORS("__wasm_call_ctors"),
MEMORY_BASE("__memory_base"),
TABLE_BASE("__table_base"),
GET_TEMP_RET0("getTempRet0"),
@@ -105,7 +106,7 @@ const char* getExpressionName(Expression* curr) {
case Expression::Id::AtomicCmpxchgId: return "atomic_cmpxchg";
case Expression::Id::AtomicRMWId: return "atomic_rmw";
case Expression::Id::AtomicWaitId: return "atomic_wait";
- case Expression::Id::AtomicWakeId: return "atomic_wake";
+ case Expression::Id::AtomicNotifyId: return "atomic_notify";
case Expression::Id::SIMDExtractId: return "simd_extract";
case Expression::Id::SIMDReplaceId: return "simd_replace";
case Expression::Id::SIMDShuffleId: return "simd_shuffle";
@@ -420,9 +421,9 @@ void AtomicWait::finalize() {
}
}
-void AtomicWake::finalize() {
+void AtomicNotify::finalize() {
type = i32;
- if (ptr->type == unreachable || wakeCount->type == unreachable) {
+ if (ptr->type == unreachable || notifyCount->type == unreachable) {
type = unreachable;
}
}