diff options
author | Alon Zakai <alonzakai@gmail.com> | 2016-06-26 11:00:02 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-06-26 11:00:02 -0700 |
commit | 45b358706c86415c5982f9e777fa9e19a33b27a3 (patch) | |
tree | d1caa4180c8d0f4a76319fd11f8b18f9f446e6c3 /src | |
parent | c410d93d3af9813f889b4011f964d4becf43bc27 (diff) | |
parent | 87f3020cf4e666a6eb6620106e48ee042cd2f666 (diff) | |
download | binaryen-45b358706c86415c5982f9e777fa9e19a33b27a3.tar.gz binaryen-45b358706c86415c5982f9e777fa9e19a33b27a3.tar.bz2 binaryen-45b358706c86415c5982f9e777fa9e19a33b27a3.zip |
Merge pull request #602 from WebAssembly/dsl-nice
Use a DSL in OptimizeInstructions
Diffstat (limited to 'src')
-rw-r--r-- | src/asm2wasm.h | 1 | ||||
-rw-r--r-- | src/asmjs/shared-constants.cpp | 35 | ||||
-rw-r--r-- | src/asmjs/shared-constants.h | 35 | ||||
-rw-r--r-- | src/ast_utils.h | 126 | ||||
-rw-r--r-- | src/parsing.h | 5 | ||||
-rw-r--r-- | src/passes/OptimizeInstructions.cpp | 186 | ||||
-rw-r--r-- | src/passes/OptimizeInstructions.wast | 152 | ||||
-rw-r--r-- | src/passes/OptimizeInstructions.wast.processed | 152 | ||||
-rw-r--r-- | src/shared-constants.h | 56 | ||||
-rw-r--r-- | src/shell-interface.h | 1 | ||||
-rw-r--r-- | src/wasm-binary.h | 22 | ||||
-rw-r--r-- | src/wasm-builder.h | 48 | ||||
-rw-r--r-- | src/wasm-linker.cpp | 9 | ||||
-rw-r--r-- | src/wasm-s-parser.h | 3 | ||||
-rw-r--r-- | src/wasm.cpp | 55 |
15 files changed, 736 insertions, 150 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h index c4cc766aa..2233a10ae 100644 --- a/src/asm2wasm.h +++ b/src/asm2wasm.h @@ -25,6 +25,7 @@ #include "wasm.h" #include "emscripten-optimizer/optimizer.h" #include "mixed_arena.h" +#include "shared-constants.h" #include "asmjs/shared-constants.h" #include "asm_v_wasm.h" #include "passes/passes.h" diff --git a/src/asmjs/shared-constants.cpp b/src/asmjs/shared-constants.cpp index bc1c0fa3d..b2321a209 100644 --- a/src/asmjs/shared-constants.cpp +++ b/src/asmjs/shared-constants.cpp @@ -47,35 +47,9 @@ cashew::IString GLOBAL("global"), SQRT("sqrt"), I32_TEMP("asm2wasm_i32_temp"), DEBUGGER("debugger"), - GROW_WASM_MEMORY("__growWasmMemory"), - NEW_SIZE("newSize"), - MODULE("module"), - START("start"), - FUNC("func"), - PARAM("param"), - RESULT("result"), - MEMORY("memory"), - SEGMENT("segment"), - EXPORT("export"), - IMPORT("import"), - TABLE("table"), - LOCAL("local"), - TYPE("type"), - CALL("call"), - CALL_IMPORT("call_import"), - CALL_INDIRECT("call_indirect"), - BLOCK("block"), - BR_IF("br_if"), - THEN("then"), - ELSE("else"), - NEG_INFINITY("-infinity"), - NEG_NAN("-nan"), - CASE("case"), - BR("br"), USE_ASM("use asm"), BUFFER("buffer"), ENV("env"), - FAKE_RETURN("fake_return_waka123"), MATH_IMUL("Math_imul"), MATH_CLZ32("Math_clz32"), MATH_CTZ32("Math_ctz32"), @@ -87,13 +61,6 @@ cashew::IString GLOBAL("global"), MATH_NEAREST("Math_NEAREST"), MATH_SQRT("Math_sqrt"), MATH_MIN("Math_max"), - MATH_MAX("Math_min"), - ASSERT_RETURN("assert_return"), - ASSERT_TRAP("assert_trap"), - ASSERT_INVALID("assert_invalid"), - SPECTEST("spectest"), - PRINT("print"), - INVOKE("invoke"), - EXIT("exit"); + MATH_MAX("Math_min"); } diff --git a/src/asmjs/shared-constants.h b/src/asmjs/shared-constants.h index 2f6c608ba..fb32340ab 100644 --- a/src/asmjs/shared-constants.h +++ b/src/asmjs/shared-constants.h @@ -50,35 +50,9 @@ extern cashew::IString GLOBAL, SQRT, I32_TEMP, DEBUGGER, - GROW_WASM_MEMORY, - NEW_SIZE, - MODULE, - START, - FUNC, - PARAM, - RESULT, - MEMORY, - SEGMENT, - EXPORT, - IMPORT, - TABLE, - LOCAL, - TYPE, - CALL, - CALL_IMPORT, - CALL_INDIRECT, - BLOCK, - BR_IF, - THEN, - ELSE, - NEG_INFINITY, - NEG_NAN, - CASE, - BR, USE_ASM, BUFFER, ENV, - FAKE_RETURN, MATH_IMUL, MATH_CLZ32, MATH_CTZ32, @@ -90,14 +64,7 @@ extern cashew::IString GLOBAL, MATH_NEAREST, MATH_SQRT, MATH_MIN, - MATH_MAX, - ASSERT_RETURN, - ASSERT_TRAP, - ASSERT_INVALID, - SPECTEST, - PRINT, - INVOKE, - EXIT; + MATH_MAX; } diff --git a/src/ast_utils.h b/src/ast_utils.h index 8952114bc..77bfaf1f3 100644 --- a/src/ast_utils.h +++ b/src/ast_utils.h @@ -20,6 +20,7 @@ #include "support/hash.h" #include "wasm.h" #include "wasm-traversal.h" +#include "wasm-builder.h" namespace wasm { @@ -210,6 +211,117 @@ struct ExpressionManipulator { new (output) OutputType(allocator); return output; } + + template<typename T> + static Expression* flexibleCopy(Expression* original, Module& wasm, T& custom) { + struct Copier : public Visitor<Copier, Expression*> { + Module& wasm; + T& custom; + + Builder builder; + + Copier(Module& wasm, T& custom) : wasm(wasm), custom(custom), builder(wasm) {} + + Expression* copy(Expression* curr) { + if (!curr) return nullptr; + auto* ret = custom.copy(curr); + if (ret) return ret; + return Visitor<Copier, Expression*>::visit(curr); + } + + Expression* visitBlock(Block *curr) { + auto* ret = builder.makeBlock(); + for (Index i = 0; i < curr->list.size(); i++) { + ret->list.push_back(copy(curr->list[i])); + } + ret->name = curr->name; + ret->finalize(curr->type); + return ret; + } + Expression* visitIf(If *curr) { + return builder.makeIf(copy(curr->condition), copy(curr->ifTrue), copy(curr->ifFalse)); + } + Expression* visitLoop(Loop *curr) { + return builder.makeLoop(curr->out, curr->in, copy(curr->body)); + } + Expression* visitBreak(Break *curr) { + return builder.makeBreak(curr->name, copy(curr->value), copy(curr->condition)); + } + Expression* visitSwitch(Switch *curr) { + return builder.makeSwitch(curr->targets, curr->default_, copy(curr->condition), copy(curr->value)); + } + Expression* visitCall(Call *curr) { + auto* ret = builder.makeCall(curr->target, {}, curr->type); + for (Index i = 0; i < curr->operands.size(); i++) { + ret->operands.push_back(copy(curr->operands[i])); + } + return ret; + } + Expression* visitCallImport(CallImport *curr) { + auto* ret = builder.makeCallImport(curr->target, {}, curr->type); + for (Index i = 0; i < curr->operands.size(); i++) { + ret->operands.push_back(copy(curr->operands[i])); + } + return ret; + } + Expression* visitCallIndirect(CallIndirect *curr) { + auto* ret = builder.makeCallIndirect(curr->fullType, curr->target, {}, curr->type); + for (Index i = 0; i < curr->operands.size(); i++) { + ret->operands.push_back(copy(curr->operands[i])); + } + return ret; + } + Expression* visitGetLocal(GetLocal *curr) { + return builder.makeGetLocal(curr->index, curr->type); + } + Expression* visitSetLocal(SetLocal *curr) { + return builder.makeSetLocal(curr->index, copy(curr->value)); + } + Expression* visitLoad(Load *curr) { + return builder.makeLoad(curr->bytes, curr->signed_, curr->offset, curr->align, copy(curr->ptr), curr->type); + } + Expression* visitStore(Store *curr) { + return builder.makeStore(curr->bytes, curr->offset, curr->align, copy(curr->ptr), copy(curr->value)); + } + Expression* visitConst(Const *curr) { + return builder.makeConst(curr->value); + } + Expression* visitUnary(Unary *curr) { + return builder.makeUnary(curr->op, copy(curr->value)); + } + Expression* visitBinary(Binary *curr) { + return builder.makeBinary(curr->op, copy(curr->left), copy(curr->right)); + } + Expression* visitSelect(Select *curr) { + return builder.makeSelect(copy(curr->condition), copy(curr->ifTrue), copy(curr->ifFalse)); + } + Expression* visitReturn(Return *curr) { + return builder.makeReturn(copy(curr->value)); + } + Expression* visitHost(Host *curr) { + assert(curr->operands.size() == 0); + return builder.makeHost(curr->op, curr->nameOperand, {}); + } + Expression* visitNop(Nop *curr) { + return builder.makeNop(); + } + Expression* visitUnreachable(Unreachable *curr) { + return builder.makeUnreachable(); + } + }; + + Copier copier(wasm, custom); + return copier.copy(original); + } + + static Expression* copy(Expression* original, Module& wasm) { + struct Copier { + Expression* copy(Expression* curr) { + return nullptr; + } + } copier; + return flexibleCopy(original, wasm, copier); + } }; struct ExpressionAnalyzer { @@ -242,7 +354,8 @@ struct ExpressionAnalyzer { return func->result != none; } - static bool equal(Expression* left, Expression* right) { + template<typename T> + static bool flexibleEqual(Expression* left, Expression* right, T& comparer) { std::vector<Name> nameStack; std::map<Name, std::vector<Name>> rightNames; // for each name on the left, the stack of names on the right (a stack, since names are scoped and can nest duplicatively Nop popNameMarker; @@ -284,6 +397,8 @@ struct ExpressionAnalyzer { popName(); continue; } + if (comparer.compare(left, right)) continue; // comparison hook, before all the rest + // continue with normal structural comparison if (left->_id != right->_id) return false; #define PUSH(clazz, what) \ leftStack.push_back(left->cast<clazz>()->what); \ @@ -426,6 +541,15 @@ struct ExpressionAnalyzer { return true; } + static bool equal(Expression* left, Expression* right) { + struct Comparer { + bool compare(Expression* left, Expression* right) { + return false; + } + } comparer; + return flexibleEqual(left, right, comparer); + } + // hash an expression, ignoring superficial details like specific internal names static uint32_t hash(Expression* curr) { uint32_t digest = 0; diff --git a/src/parsing.h b/src/parsing.h index 6745ad7cd..f5df77bad 100644 --- a/src/parsing.h +++ b/src/parsing.h @@ -21,6 +21,7 @@ #include <sstream> #include <string> +#include "shared-constants.h" #include "asmjs/shared-constants.h" #include "mixed_arena.h" #include "support/utilities.h" @@ -34,7 +35,7 @@ inline Expression* parseConst(cashew::IString s, WasmType type, MixedArena& allo auto ret = allocator.alloc<Const>(); ret->type = type; if (isWasmTypeFloat(type)) { - if (s == INFINITY_) { + if (s == _INFINITY) { switch (type) { case f32: ret->value = Literal(std::numeric_limits<float>::infinity()); break; case f64: ret->value = Literal(std::numeric_limits<double>::infinity()); break; @@ -52,7 +53,7 @@ inline Expression* parseConst(cashew::IString s, WasmType type, MixedArena& allo //std::cerr << "make constant " << str << " ==> " << ret->value << '\n'; return ret; } - if (s == NAN_) { + if (s == _NAN) { switch (type) { case f32: ret->value = Literal(float(std::nan(""))); break; case f64: ret->value = Literal(double(std::nan(""))); break; diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index efa9409a7..9fa059327 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -22,66 +22,152 @@ #include <wasm.h> #include <pass.h> +#include <wasm-s-parser.h> namespace wasm { -struct OptimizeInstructions : public WalkerPass<PostWalker<OptimizeInstructions, Visitor<OptimizeInstructions>>> { - bool isFunctionParallel() override { return true; } +Name I32_EXPR = "i32.expr", + I64_EXPR = "i64.expr", + F32_EXPR = "f32.expr", + F64_EXPR = "f64.expr", + ANY_EXPR = "any.expr"; - Pass* create() override { return new OptimizeInstructions; } +// A pattern +struct Pattern { + Expression* input; + Expression* output; + + Pattern(Expression* input, Expression* output) : input(input), output(output) {} +}; + +// Database of patterns +struct PatternDatabase { + Module wasm; + + char* input; + + std::map<Expression::Id, std::vector<Pattern>> patternMap; // root expression id => list of all patterns for it TODO optimize more + + PatternDatabase() { + // TODO: do this on first use, with a lock, to avoid startup pause + // generate module + input = strdup( + #include "OptimizeInstructions.wast.processed" + ); + SExpressionParser parser(input); + Element& root = *parser.root; + SExpressionWasmBuilder builder(wasm, *root[0]); + // parse module form + auto* func = wasm.getFunction("patterns"); + auto* body = func->body->cast<Block>(); + for (auto* item : body->list) { + auto* pair = item->cast<Block>(); + patternMap[pair->list[0]->_id].emplace_back(pair->list[0], pair->list[1]); + } + } + + ~PatternDatabase() { + free(input); + }; +}; + +static PatternDatabase database; + +// Check for matches and apply them +struct Match { + Module& wasm; + Pattern& pattern; + + Match(Module& wasm, Pattern& pattern) : wasm(wasm), pattern(pattern) {} + + std::vector<Expression*> wildcards; // id in i32.any(id) etc. => the expression it represents in this match - void visitIf(If* curr) { - // flip branches to get rid of an i32.eqz - if (curr->ifFalse) { - auto condition = curr->condition->dynCast<Unary>(); - if (condition && condition->op == EqZInt32 && condition->value->type == i32) { - curr->condition = condition->value; - std::swap(curr->ifTrue, curr->ifFalse); + // Comparing/checking + + // Check if we can match to this pattern, updating ourselves with the info if so + bool check(Expression* seen) { + // compare seen to the pattern input, doing a special operation for our "wildcards" + assert(wildcards.size() == 0); + return ExpressionAnalyzer::flexibleEqual(pattern.input, seen, *this); + } + + bool compare(Expression* subInput, Expression* subSeen) { + CallImport* call = subInput->dynCast<CallImport>(); + if (!call || call->operands.size() != 1 || call->operands[0]->type != i32 || !call->operands[0]->is<Const>()) return false; + Index index = call->operands[0]->cast<Const>()->value.geti32(); + // handle our special functions + auto checkMatch = [&](WasmType type) { + if (type != none && subSeen->type != type) return false; + while (index >= wildcards.size()) { + wildcards.push_back(nullptr); } + if (!wildcards[index]) { + // new wildcard + wildcards[index] = subSeen; // NB: no need to copy + return true; + } else { + // We are seeing this index for a second or later time, check it matches + return ExpressionAnalyzer::equal(subSeen, wildcards[index]); + }; + }; + if (call->target == I32_EXPR) { + if (checkMatch(i32)) return true; + } else if (call->target == I64_EXPR) { + if (checkMatch(i64)) return true; + } else if (call->target == F32_EXPR) { + if (checkMatch(f32)) return true; + } else if (call->target == F64_EXPR) { + if (checkMatch(f64)) return true; + } else if (call->target == ANY_EXPR) { + if (checkMatch(none)) return true; } + return false; + } + + // Applying/copying + + // Apply the match, generate an output expression from the matched input, performing substitutions as necessary + Expression* apply() { + return ExpressionManipulator::flexibleCopy(pattern.output, wasm, *this); } - void visitUnary(Unary* curr) { - if (curr->op == EqZInt32) { - // fold comparisons that flow into an EqZ - auto* child = curr->value->dynCast<Binary>(); - if (child && (child->type == i32 || child->type == i64)) { - switch (child->op) { - case EqInt32: child->op = NeInt32; break; - case NeInt32: child->op = EqInt32; break; - case LtSInt32: child->op = GeSInt32; break; - case LtUInt32: child->op = GeUInt32; break; - case LeSInt32: child->op = GtSInt32; break; - case LeUInt32: child->op = GtUInt32; break; - case GtSInt32: child->op = LeSInt32; break; - case GtUInt32: child->op = LeUInt32; break; - case GeSInt32: child->op = LtSInt32; break; - case GeUInt32: child->op = LtUInt32; break; - case EqInt64: child->op = NeInt64; break; - case NeInt64: child->op = EqInt64; break; - case LtSInt64: child->op = GeSInt64; break; - case LtUInt64: child->op = GeUInt64; break; - case LeSInt64: child->op = GtSInt64; break; - case LeUInt64: child->op = GtUInt64; break; - case GtSInt64: child->op = LeSInt64; break; - case GtUInt64: child->op = LeUInt64; break; - case GeSInt64: child->op = LtSInt64; break; - case GeUInt64: child->op = LtUInt64; break; - case EqFloat32: child->op = NeFloat32; break; - case NeFloat32: child->op = EqFloat32; break; - case LtFloat32: child->op = GeFloat32; break; - case LeFloat32: child->op = GtFloat32; break; - case GtFloat32: child->op = LeFloat32; break; - case GeFloat32: child->op = LtFloat32; break; - case EqFloat64: child->op = NeFloat64; break; - case NeFloat64: child->op = EqFloat64; break; - case LtFloat64: child->op = GeFloat64; break; - case LeFloat64: child->op = GtFloat64; break; - case GtFloat64: child->op = LeFloat64; break; - case GeFloat64: child->op = LtFloat64; break; - default: return; + + // When copying a wildcard, perform the substitution. + // TODO: we can reuse nodes, not copying a wildcard when it appears just once, and we can reuse other individual nodes when they are discarded anyhow. + Expression* copy(Expression* curr) { + CallImport* call = curr->dynCast<CallImport>(); + if (!call || call->operands.size() != 1 || call->operands[0]->type != i32 || !call->operands[0]->is<Const>()) return nullptr; + Index index = call->operands[0]->cast<Const>()->value.geti32(); + // handle our special functions + if (call->target == I32_EXPR || call->target == I64_EXPR || call->target == F32_EXPR || call->target == F64_EXPR || call->target == ANY_EXPR) { + return ExpressionManipulator::copy(wildcards.at(index), wasm); + } + return nullptr; + } +}; + +// Main pass class +struct OptimizeInstructions : public WalkerPass<PostWalker<OptimizeInstructions, UnifiedExpressionVisitor<OptimizeInstructions>>> { + bool isFunctionParallel() override { return true; } + + Pass* create() override { return new OptimizeInstructions; } + + void visitExpression(Expression* curr) { + // we may be able to apply multiple patterns, one may open opportunities that look deeper NB: patterns must not have cycles + while (1) { + auto iter = database.patternMap.find(curr->_id); + if (iter == database.patternMap.end()) return; + auto& patterns = iter->second; + bool more = false; + for (auto& pattern : patterns) { + Match match(*getModule(), pattern); + if (match.check(curr)) { + curr = match.apply(); + replaceCurrent(curr); + more = true; + break; // exit pattern for loop, return to main while loop } - replaceCurrent(child); } + if (!more) break; } } }; diff --git a/src/passes/OptimizeInstructions.wast b/src/passes/OptimizeInstructions.wast new file mode 100644 index 000000000..83459ad14 --- /dev/null +++ b/src/passes/OptimizeInstructions.wast @@ -0,0 +1,152 @@ + +;; This file contains patterns for OptimizeInstructions. Basically, we use a DSL for the patterns, +;; and the DSL is just wasm itself, plus some functions with special meanings +;; +;; This file is converted into OptimizeInstructions.wast.processed by +;; scripts/process_optimize_instructions.py +;; which makes it importable by C++. Then we just #include it there, avoiding the need to ship +;; a data file on the side. + +(module + ;; "expr" represents an arbitrary expression. The input is an id, so the same expression + ;; can appear more than once. The type (i32 in i32.expr, etc.) is the return type, as this + ;; needs to have the right type for where it is placed. + (import $i32.expr "dsl" "i32.expr" (param i32) (result i32)) + (import $i64.expr "dsl" "i64.expr" (param i32) (result i64)) + (import $f32.expr "dsl" "f32.expr" (param i32) (result f32)) + (import $f64.expr "dsl" "f64.expr" (param i32) (result f64)) + (import $any.expr "dsl" "any.expr" (param i32) (result i32)) ;; ignorable return type + + ;; main function. each block here is a pattern pair of input => output + (func $patterns + (block + ;; flip if-else arms to get rid of an eqz + (if + (i32.eqz + (call_import $i32.expr (i32.const 0)) + ) + (call_import $any.expr (i32.const 1)) + (call_import $any.expr (i32.const 2)) + ) + (if + (call_import $i32.expr (i32.const 0)) + (call_import $any.expr (i32.const 2)) + (call_import $any.expr (i32.const 1)) + ) + ) + ;; De Morgans Laws + (block + (i32.eqz (i32.eq (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1)))) + (i32.ne (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1))) + ) + (block + (i32.eqz (i32.ne (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1)))) + (i32.eq (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1))) + ) + (block + (i32.eqz (i32.lt_s (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1)))) + (i32.ge_s (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1))) + ) + (block + (i32.eqz (i32.lt_u (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1)))) + (i32.ge_u (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1))) + ) + (block + (i32.eqz (i32.le_s (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1)))) + (i32.gt_s (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1))) + ) + (block + (i32.eqz (i32.le_u (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1)))) + (i32.gt_u (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1))) + ) + (block + (i32.eqz (i32.gt_s (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1)))) + (i32.le_s (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1))) + ) + (block + (i32.eqz (i32.gt_u (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1)))) + (i32.le_u (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1))) + ) + (block + (i32.eqz (i32.ge_s (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1)))) + (i32.lt_s (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1))) + ) + (block + (i32.eqz (i32.ge_u (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1)))) + (i32.lt_u (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1))) + ) + (block + (i32.eqz (i64.eq (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1)))) + (i64.ne (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1))) + ) + (block + (i32.eqz (i64.ne (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1)))) + (i64.eq (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1))) + ) + (block + (i32.eqz (i64.lt_s (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1)))) + (i64.ge_s (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1))) + ) + (block + (i32.eqz (i64.lt_u (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1)))) + (i64.ge_u (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1))) + ) + (block + (i32.eqz (i64.le_s (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1)))) + (i64.gt_s (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1))) + ) + (block + (i32.eqz (i64.le_u (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1)))) + (i64.gt_u (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1))) + ) + (block + (i32.eqz (i64.gt_s (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1)))) + (i64.le_s (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1))) + ) + (block + (i32.eqz (i64.gt_u (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1)))) + (i64.le_u (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1))) + ) + (block + (i32.eqz (i64.ge_s (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1)))) + (i64.lt_s (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1))) + ) + (block + (i32.eqz (i64.ge_u (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1)))) + (i64.lt_u (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1))) + ) + (block + (i32.eqz (f32.eq (call_import $f32.expr (i32.const 0)) (call_import $f32.expr (i32.const 1)))) + (f32.ne (call_import $f32.expr (i32.const 0)) (call_import $f32.expr (i32.const 1))) + ) + (block + (i32.eqz (f32.ne (call_import $f32.expr (i32.const 0)) (call_import $f32.expr (i32.const 1)))) + (f32.eq (call_import $f32.expr (i32.const 0)) (call_import $f32.expr (i32.const 1))) + ) + (block + (i32.eqz (f32.lt (call_import $f32.expr (i32.const 0)) (call_import $f32.expr (i32.const 1)))) + (f32.ge (call_import $f32.expr (i32.const 0)) (call_import $f32.expr (i32.const 1))) + ) + (block + (i32.eqz (f32.le (call_import $f32.expr (i32.const 0)) (call_import $f32.expr (i32.const 1)))) + (f32.gt (call_import $f32.expr (i32.const 0)) (call_import $f32.expr (i32.const 1))) + ) + (block + (i32.eqz (f64.eq (call_import $f64.expr (i32.const 0)) (call_import $f64.expr (i32.const 1)))) + (f64.ne (call_import $f64.expr (i32.const 0)) (call_import $f64.expr (i32.const 1))) + ) + (block + (i32.eqz (f64.ne (call_import $f64.expr (i32.const 0)) (call_import $f64.expr (i32.const 1)))) + (f64.eq (call_import $f64.expr (i32.const 0)) (call_import $f64.expr (i32.const 1))) + ) + (block + (i32.eqz (f64.lt (call_import $f64.expr (i32.const 0)) (call_import $f64.expr (i32.const 1)))) + (f64.ge (call_import $f64.expr (i32.const 0)) (call_import $f64.expr (i32.const 1))) + ) + (block + (i32.eqz (f64.le (call_import $f64.expr (i32.const 0)) (call_import $f64.expr (i32.const 1)))) + (f64.gt (call_import $f64.expr (i32.const 0)) (call_import $f64.expr (i32.const 1))) + ) + ) +) + diff --git a/src/passes/OptimizeInstructions.wast.processed b/src/passes/OptimizeInstructions.wast.processed new file mode 100644 index 000000000..6bd251c95 --- /dev/null +++ b/src/passes/OptimizeInstructions.wast.processed @@ -0,0 +1,152 @@ +"\n" +";; This file contains patterns for OptimizeInstructions. Basically, we use a DSL for the patterns,\n" +";; and the DSL is just wasm itself, plus some functions with special meanings\n" +";;\n" +";; This file is converted into OptimizeInstructions.wast.processed by\n" +";; scripts/process_optimize_instructions.py\n" +";; which makes it importable by C++. Then we just #include it there, avoiding the need to ship\n" +";; a data file on the side.\n" +"\n" +"(module\n" +";; \"expr\" represents an arbitrary expression. The input is an id, so the same expression\n" +";; can appear more than once. The type (i32 in i32.expr, etc.) is the return type, as this\n" +";; needs to have the right type for where it is placed.\n" +"(import $i32.expr \"dsl\" \"i32.expr\" (param i32) (result i32))\n" +"(import $i64.expr \"dsl\" \"i64.expr\" (param i32) (result i64))\n" +"(import $f32.expr \"dsl\" \"f32.expr\" (param i32) (result f32))\n" +"(import $f64.expr \"dsl\" \"f64.expr\" (param i32) (result f64))\n" +"(import $any.expr \"dsl\" \"any.expr\" (param i32) (result i32)) ;; ignorable return type\n" +"\n" +";; main function. each block here is a pattern pair of input => output\n" +"(func $patterns\n" +"(block\n" +";; flip if-else arms to get rid of an eqz\n" +"(if\n" +"(i32.eqz\n" +"(call_import $i32.expr (i32.const 0))\n" +")\n" +"(call_import $any.expr (i32.const 1))\n" +"(call_import $any.expr (i32.const 2))\n" +")\n" +"(if\n" +"(call_import $i32.expr (i32.const 0))\n" +"(call_import $any.expr (i32.const 2))\n" +"(call_import $any.expr (i32.const 1))\n" +")\n" +")\n" +";; De Morgans Laws\n" +"(block\n" +"(i32.eqz (i32.eq (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1))))\n" +"(i32.ne (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (i32.ne (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1))))\n" +"(i32.eq (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (i32.lt_s (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1))))\n" +"(i32.ge_s (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (i32.lt_u (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1))))\n" +"(i32.ge_u (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (i32.le_s (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1))))\n" +"(i32.gt_s (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (i32.le_u (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1))))\n" +"(i32.gt_u (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (i32.gt_s (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1))))\n" +"(i32.le_s (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (i32.gt_u (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1))))\n" +"(i32.le_u (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (i32.ge_s (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1))))\n" +"(i32.lt_s (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (i32.ge_u (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1))))\n" +"(i32.lt_u (call_import $i32.expr (i32.const 0)) (call_import $i32.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (i64.eq (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1))))\n" +"(i64.ne (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (i64.ne (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1))))\n" +"(i64.eq (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (i64.lt_s (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1))))\n" +"(i64.ge_s (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (i64.lt_u (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1))))\n" +"(i64.ge_u (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (i64.le_s (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1))))\n" +"(i64.gt_s (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (i64.le_u (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1))))\n" +"(i64.gt_u (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (i64.gt_s (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1))))\n" +"(i64.le_s (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (i64.gt_u (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1))))\n" +"(i64.le_u (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (i64.ge_s (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1))))\n" +"(i64.lt_s (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (i64.ge_u (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1))))\n" +"(i64.lt_u (call_import $i64.expr (i32.const 0)) (call_import $i64.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (f32.eq (call_import $f32.expr (i32.const 0)) (call_import $f32.expr (i32.const 1))))\n" +"(f32.ne (call_import $f32.expr (i32.const 0)) (call_import $f32.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (f32.ne (call_import $f32.expr (i32.const 0)) (call_import $f32.expr (i32.const 1))))\n" +"(f32.eq (call_import $f32.expr (i32.const 0)) (call_import $f32.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (f32.lt (call_import $f32.expr (i32.const 0)) (call_import $f32.expr (i32.const 1))))\n" +"(f32.ge (call_import $f32.expr (i32.const 0)) (call_import $f32.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (f32.le (call_import $f32.expr (i32.const 0)) (call_import $f32.expr (i32.const 1))))\n" +"(f32.gt (call_import $f32.expr (i32.const 0)) (call_import $f32.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (f64.eq (call_import $f64.expr (i32.const 0)) (call_import $f64.expr (i32.const 1))))\n" +"(f64.ne (call_import $f64.expr (i32.const 0)) (call_import $f64.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (f64.ne (call_import $f64.expr (i32.const 0)) (call_import $f64.expr (i32.const 1))))\n" +"(f64.eq (call_import $f64.expr (i32.const 0)) (call_import $f64.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (f64.lt (call_import $f64.expr (i32.const 0)) (call_import $f64.expr (i32.const 1))))\n" +"(f64.ge (call_import $f64.expr (i32.const 0)) (call_import $f64.expr (i32.const 1)))\n" +")\n" +"(block\n" +"(i32.eqz (f64.le (call_import $f64.expr (i32.const 0)) (call_import $f64.expr (i32.const 1))))\n" +"(f64.gt (call_import $f64.expr (i32.const 0)) (call_import $f64.expr (i32.const 1)))\n" +")\n" +")\n" +")\n" +"\n" diff --git a/src/shared-constants.h b/src/shared-constants.h new file mode 100644 index 000000000..fb94ff11b --- /dev/null +++ b/src/shared-constants.h @@ -0,0 +1,56 @@ +/* + * Copyright 2016 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. + */ + +namespace wasm { + +extern Name GROW_WASM_MEMORY, + NEW_SIZE, + MODULE, + START, + FUNC, + PARAM, + RESULT, + MEMORY, + SEGMENT, + EXPORT, + IMPORT, + TABLE, + LOCAL, + TYPE, + CALL, + CALL_IMPORT, + CALL_INDIRECT, + BLOCK, + BR_IF, + THEN, + ELSE, + _NAN, + _INFINITY, + NEG_INFINITY, + NEG_NAN, + CASE, + BR, + FAKE_RETURN, + ASSERT_RETURN, + ASSERT_TRAP, + ASSERT_INVALID, + SPECTEST, + PRINT, + INVOKE, + EXIT; + +} // namespace wasm + diff --git a/src/shell-interface.h b/src/shell-interface.h index 62fc2432a..f6c12e8ca 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -21,6 +21,7 @@ #ifndef wasm_shell_interface_h #define wasm_shell_interface_h +#include "shared-constants.h" #include "asmjs/shared-constants.h" #include "wasm.h" #include "wasm-interpreter.h" diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 5cb56f770..a76bf7fa5 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -238,16 +238,16 @@ enum Meta { }; namespace Section { - auto Memory = "memory"; - auto Signatures = "type"; - auto ImportTable = "import"; - auto FunctionSignatures = "function"; - auto Functions = "code"; - auto ExportTable = "export"; - auto DataSegments = "data"; - auto FunctionTable = "table"; - auto Names = "name"; - auto Start = "start"; + extern const char* Memory; + extern const char* Signatures; + extern const char* ImportTable; + extern const char* FunctionSignatures; + extern const char* Functions; + extern const char* ExportTable; + extern const char* DataSegments; + extern const char* FunctionTable; + extern const char* Names; + extern const char* Start; }; enum FunctionEntry { @@ -445,7 +445,7 @@ enum TypeForms { } // namespace BinaryConsts -int8_t binaryWasmType(WasmType type) { +inline int8_t binaryWasmType(WasmType type) { switch (type) { case none: return 0; case i32: return 1; diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 2eb051251..501124999 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -93,13 +93,23 @@ public: ret->finalize(); return ret; } - // Switch - // CallBase - // Call - // Also do a version which takes a sig? - CallImport* makeCallImport(Name target, const std::vector<Expression*>& args) { + template<typename T> + Switch* makeSwitch(T& list, Name default_, Expression* condition, Expression* value = nullptr) { + auto* ret = wasm.allocator.alloc<Switch>(); + ret->targets.set(list); + ret->default_ = default_; ret->value = value; ret->condition = condition; + return ret; + } + Call* makeCall(Name target, const std::vector<Expression*>& args, WasmType type) { + auto* call = wasm.allocator.alloc<Call>(); + call->type = type; // not all functions may exist yet, so type must be provided + call->target = target; + call->operands.set(args); + return call; + } + CallImport* makeCallImport(Name target, const std::vector<Expression*>& args, WasmType type) { auto* call = wasm.allocator.alloc<CallImport>(); - call->type = wasm.getImport(target)->type->result; + call->type = type; // similar to makeCall, for consistency call->target = target; call->operands.set(args); return call; @@ -112,6 +122,14 @@ public: call->operands.set(args); return call; } + CallIndirect* makeCallIndirect(Name fullType, Expression* target, const std::vector<Expression*>& args, WasmType type) { + auto* call = wasm.allocator.alloc<CallIndirect>(); + call->fullType = fullType; + call->type = type; + call->target = target; + call->operands.set(args); + return call; + } // FunctionType GetLocal* makeGetLocal(Index index, WasmType type) { auto* ret = wasm.allocator.alloc<GetLocal>(); @@ -126,7 +144,12 @@ public: ret->type = value->type; return ret; } - // Load + Load* makeLoad(unsigned bytes, bool signed_, uint32_t offset, unsigned align, Expression *ptr, WasmType type) { + auto* ret = wasm.allocator.alloc<Load>(); + ret->bytes = bytes; ret->signed_ = signed_; ret->offset = offset; ret->align = align; ret->ptr = ptr; + ret->type = type; + return ret; + } Store* makeStore(unsigned bytes, uint32_t offset, unsigned align, Expression *ptr, Expression *value) { auto* ret = wasm.allocator.alloc<Store>(); ret->bytes = bytes; ret->offset = offset; ret->align = align; ret->ptr = ptr; ret->value = value; @@ -152,7 +175,12 @@ public: ret->finalize(); return ret; } - // Select + Select* makeSelect(Expression* condition, Expression *ifTrue, Expression *ifFalse) { + auto* ret = wasm.allocator.alloc<Select>(); + ret->condition = condition; ret->ifTrue = ifTrue; ret->ifFalse = ifFalse; + ret->finalize(); + return ret; + } Return* makeReturn(Expression *value) { auto* ret = wasm.allocator.alloc<Return>(); ret->value = value; @@ -165,7 +193,9 @@ public: ret->operands.set(operands); return ret; } - // Unreachable + Unreachable* makeUnreachable() { + return wasm.allocator.alloc<Unreachable>(); + } // Additional utility functions for building on top of nodes diff --git a/src/wasm-linker.cpp b/src/wasm-linker.cpp index e89c324b4..34dbc7f9c 100644 --- a/src/wasm-linker.cpp +++ b/src/wasm-linker.cpp @@ -25,13 +25,6 @@ using namespace wasm; cashew::IString EMSCRIPTEN_ASM_CONST("emscripten_asm_const"); -namespace wasm { -// These are defined (not just declared) in shared-constants.h, so we can't just -// include that header. TODO: Move the definitions into a cpp file. -extern cashew::IString ENV; -extern cashew::IString MEMORY; -} - void Linker::placeStackPointer(Address stackAllocation) { // ensure this is the first allocation @@ -403,7 +396,7 @@ Function* Linker::getImportThunk(Name name, const FunctionType* funcType) { for (Index i = 0; i < funcType->params.size(); ++i) { args.push_back(wasmBuilder.makeGetLocal(i, funcType->params[i])); } - Expression* call = wasmBuilder.makeCallImport(name, args); + Expression* call = wasmBuilder.makeCallImport(name, args, funcType->result); f->body = call; out.wasm.addFunction(f); return f; diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h index 0893a5bba..b31b727b7 100644 --- a/src/wasm-s-parser.h +++ b/src/wasm-s-parser.h @@ -28,6 +28,7 @@ #include "wasm.h" #include "wasm-binary.h" +#include "shared-constants.h" #include "asmjs/shared-constants.h" #include "mixed_arena.h" #include "parsing.h" @@ -41,7 +42,7 @@ using namespace cashew; // Globals -int unhex(char c) { +inline int unhex(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; diff --git a/src/wasm.cpp b/src/wasm.cpp index dd06a8d14..6732f9ca4 100644 --- a/src/wasm.cpp +++ b/src/wasm.cpp @@ -20,9 +20,64 @@ namespace wasm { +// shared constants + Name WASM("wasm"), RETURN_FLOW("*return:)*"); +namespace BinaryConsts { +namespace Section { + const char* Memory = "memory"; + const char* Signatures = "type"; + const char* ImportTable = "import"; + const char* FunctionSignatures = "function"; + const char* Functions = "code"; + const char* ExportTable = "export"; + const char* DataSegments = "data"; + const char* FunctionTable = "table"; + const char* Names = "name"; + const char* Start = "start"; +}; +}; + +Name GROW_WASM_MEMORY("__growWasmMemory"), + NEW_SIZE("newSize"), + MODULE("module"), + START("start"), + FUNC("func"), + PARAM("param"), + RESULT("result"), + MEMORY("memory"), + SEGMENT("segment"), + EXPORT("export"), + IMPORT("import"), + TABLE("table"), + LOCAL("local"), + TYPE("type"), + CALL("call"), + CALL_IMPORT("call_import"), + CALL_INDIRECT("call_indirect"), + BLOCK("block"), + BR_IF("br_if"), + THEN("then"), + ELSE("else"), + _NAN("NaN"), + _INFINITY("Infinity"), + NEG_INFINITY("-infinity"), + NEG_NAN("-nan"), + CASE("case"), + BR("br"), + FAKE_RETURN("fake_return_waka123"), + ASSERT_RETURN("assert_return"), + ASSERT_TRAP("assert_trap"), + ASSERT_INVALID("assert_invalid"), + SPECTEST("spectest"), + PRINT("print"), + INVOKE("invoke"), + EXIT("exit"); + +// core AST type checking + struct TypeSeeker : public PostWalker<TypeSeeker, Visitor<TypeSeeker>> { Expression* target; // look for this one Name targetName; |