diff options
Diffstat (limited to 'src')
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; } } |