diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/asm2wasm.h | 2 | ||||
-rw-r--r-- | src/ir/abstract.h | 156 | ||||
-rw-r--r-- | src/ir/load-utils.h | 2 | ||||
-rw-r--r-- | src/parsing.h | 2 | ||||
-rw-r--r-- | src/passes/OptimizeInstructions.cpp | 136 | ||||
-rw-r--r-- | src/passes/SafeHeap.cpp | 4 | ||||
-rw-r--r-- | src/wasm-js.cpp | 10 | ||||
-rw-r--r-- | src/wasm-type.h | 3 | ||||
-rw-r--r-- | src/wasm.h | 4 | ||||
-rw-r--r-- | src/wasm/literal.cpp | 4 | ||||
-rw-r--r-- | src/wasm/wasm-s-parser.cpp | 2 | ||||
-rw-r--r-- | src/wasm/wasm-type.cpp | 24 |
12 files changed, 312 insertions, 37 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h index d395cec60..fdbafc413 100644 --- a/src/asm2wasm.h +++ b/src/asm2wasm.h @@ -1719,7 +1719,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { ret->right = process(ast[3]); ret->op = parseAsmBinaryOp(ast[1]->getIString(), ast[2], ast[3], ret->left, ret->right); ret->finalize(); - if (ret->op == BinaryOp::RemSInt32 && isTypeFloat(ret->type)) { + if (ret->op == BinaryOp::RemSInt32 && isFloatType(ret->type)) { // WebAssembly does not have floating-point remainder, we have to emit a call to a special import of ours CallImport *call = allocator.alloc<CallImport>(); call->target = F64_REM; diff --git a/src/ir/abstract.h b/src/ir/abstract.h new file mode 100644 index 000000000..3744cb42d --- /dev/null +++ b/src/ir/abstract.h @@ -0,0 +1,156 @@ +/* + * Copyright 2018 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. + */ + +// Abstracts out operations from specific opcodes. + +#ifndef wasm_ir_abstract_h +#define wasm_ir_abstract_h + +#include <wasm.h> + +namespace wasm { + +namespace Abstract { + +enum Op { + // Unary + Neg, + // Binary + Add, Sub, Mul, DivU, DivS, Rem, RemU, RemS, + Shl, ShrU, ShrS, + And, Or, Xor, + // Relational + Eq, Ne, +}; + +// Provide a wasm type and an abstract op and get the concrete one. For example, you can +// provide i32 and Add and receive the specific opcode for a 32-bit addition, +// AddInt32. +// If the op does not exist, it returns Invalid. +inline UnaryOp getUnary(Type type, Op op) { + switch (type) { + case i32: { + switch (op) { + default: return InvalidUnary; + } + break; + } + case i64: { + switch (op) { + default: return InvalidUnary; + } + break; + } + case f32: { + switch (op) { + case Neg: return NegFloat32; + default: return InvalidUnary; + } + break; + } + case f64: { + switch (op) { + case Neg: return NegFloat64; + default: return InvalidUnary; + } + break; + } + default: return InvalidUnary; + } + WASM_UNREACHABLE(); +} + +inline BinaryOp getBinary(Type type, Op op) { + switch (type) { + case i32: { + switch (op) { + case Add: return AddInt32; + case Sub: return SubInt32; + case Mul: return MulInt32; + case DivU: return DivUInt32; + case DivS: return DivSInt32; + case RemU: return RemUInt32; + case RemS: return RemSInt32; + case Shl: return ShlInt32; + case ShrU: return ShrUInt32; + case ShrS: return ShrSInt32; + case And: return AndInt32; + case Or: return OrInt32; + case Xor: return XorInt32; + case Eq: return EqInt32; + case Ne: return NeInt32; + default: return InvalidBinary; + } + break; + } + case i64: { + switch (op) { + case Add: return AddInt64; + case Sub: return SubInt64; + case Mul: return MulInt64; + case DivU: return DivUInt64; + case DivS: return DivSInt64; + case RemU: return RemUInt64; + case RemS: return RemSInt64; + case Shl: return ShlInt64; + case ShrU: return ShrUInt64; + case ShrS: return ShrSInt64; + case And: return AndInt64; + case Or: return OrInt64; + case Xor: return XorInt64; + case Eq: return EqInt64; + case Ne: return NeInt64; + default: return InvalidBinary; + } + break; + } + case f32: { + switch (op) { + case Add: return AddFloat32; + case Sub: return SubFloat32; + case Mul: return MulFloat32; + case DivU: return DivFloat32; + case DivS: return DivFloat32; + case Eq: return EqFloat32; + case Ne: return NeFloat32; + default: return InvalidBinary; + } + break; + } + case f64: { + switch (op) { + case Add: return AddFloat64; + case Sub: return SubFloat64; + case Mul: return MulFloat64; + case DivU: return DivFloat64; + case DivS: return DivFloat64; + case Eq: return EqFloat64; + case Ne: return NeFloat64; + default: return InvalidBinary; + } + break; + } + default: return InvalidBinary; + } + WASM_UNREACHABLE(); +} + +} // namespace Abstract + +} // namespace wasm + +#endif // wasm_ir_abstract_h + diff --git a/src/ir/load-utils.h b/src/ir/load-utils.h index 41b3c69ef..8df4bcde8 100644 --- a/src/ir/load-utils.h +++ b/src/ir/load-utils.h @@ -29,7 +29,7 @@ namespace LoadUtils { inline bool isSignRelevant(Load* load) { auto type = load->type; if (load->type == unreachable) return false; - return !isTypeFloat(type) && load->bytes < getTypeSize(type); + return !isFloatType(type) && load->bytes < getTypeSize(type); } // check if a load can be signed (which some opts want to do) diff --git a/src/parsing.h b/src/parsing.h index cb6ee8c4b..06797cf54 100644 --- a/src/parsing.h +++ b/src/parsing.h @@ -80,7 +80,7 @@ inline Expression* parseConst(cashew::IString s, Type type, MixedArena& allocato const char *str = s.str; auto ret = allocator.alloc<Const>(); ret->type = type; - if (isTypeFloat(type)) { + if (isFloatType(type)) { if (s == _INFINITY) { switch (type) { case f32: ret->value = Literal(std::numeric_limits<float>::infinity()); break; diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 62c29b184..70e339241 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -24,6 +24,7 @@ #include <pass.h> #include <wasm-s-parser.h> #include <support/threads.h> +#include <ir/abstract.h> #include <ir/utils.h> #include <ir/cost.h> #include <ir/effects.h> @@ -541,9 +542,11 @@ struct OptimizeInstructions : public WalkerPass<PostWalker<OptimizeInstructions, } } } - return optimizeAddedConstants(binary); + auto* ret = optimizeAddedConstants(binary); + if (ret) return ret; } else if (binary->op == SubInt32) { - return optimizeAddedConstants(binary); + auto* ret = optimizeAddedConstants(binary); + if (ret) return ret; } // a bunch of operations on a constant right side can be simplified if (auto* right = binary->right->dynCast<Const>()) { @@ -567,19 +570,9 @@ struct OptimizeInstructions : public WalkerPass<PostWalker<OptimizeInstructions, } } } - // some math operations have trivial results TODO: many more - if (right->value == Literal(int32_t(0))) { - if (binary->op == ShlInt32 || binary->op == ShrUInt32 || binary->op == ShrSInt32 || binary->op == OrInt32) { - return binary->left; - } else if ((binary->op == MulInt32 || binary->op == AndInt32) && - !EffectAnalyzer(getPassOptions(), binary->left).hasSideEffects()) { - return binary->right; - } - } else if (right->value == Literal(int32_t(1))) { - if (binary->op == MulInt32) { - return binary->left; - } - } + // some math operations have trivial results + Expression* ret = optimizeWithConstantOnRight(binary); + if (ret) return ret; // the square of some operations can be merged if (auto* left = binary->left->dynCast<Binary>()) { if (left->op == binary->op) { @@ -645,6 +638,10 @@ struct OptimizeInstructions : public WalkerPass<PostWalker<OptimizeInstructions, if (binary->op == AndInt32 || binary->op == OrInt32) { return conditionalizeExpensiveOnBitwise(binary); } + // relation/comparisons allow for math optimizations + if (binary->isRelational()) { + return optimizeRelational(binary); + } } else if (auto* unary = curr->dynCast<Unary>()) { // de-morgan's laws if (unary->op == EqZInt32) { @@ -1098,6 +1095,115 @@ private: } return false; } + + // optimize trivial math operations, given that the right side of a binary + // is a constant + // TODO: templatize on type? + Expression* optimizeWithConstantOnRight(Binary* binary) { + auto type = binary->right->type; + auto* right = binary->right->cast<Const>(); + if (isIntegerType(type)) { + // operations on zero + if (right->value == LiteralUtils::makeLiteralFromInt32(0, type)) { + if (binary->op == Abstract::getBinary(type, Abstract::Shl) || + binary->op == Abstract::getBinary(type, Abstract::ShrU) || + binary->op == Abstract::getBinary(type, Abstract::ShrS) || + binary->op == Abstract::getBinary(type, Abstract::Or)) { + return binary->left; + } else if ((binary->op == Abstract::getBinary(type, Abstract::Mul) || + binary->op == Abstract::getBinary(type, Abstract::And)) && + !EffectAnalyzer(getPassOptions(), binary->left).hasSideEffects()) { + return binary->right; + } + } + // wasm binary encoding uses signed LEBs, which slightly favor negative + // numbers: -64 is more efficient than +64 etc. we therefore prefer + // x - -64 over x + 64 + if (binary->op == Abstract::getBinary(type, Abstract::Add) || + binary->op == Abstract::getBinary(type, Abstract::Sub)) { + auto value = right->value.getInteger(); + if (value == 0x40 || + value == 0x2000 || + value == 0x100000 || + value == 0x8000000 || + value == 0x400000000LL || + value == 0x20000000000LL || + value == 0x1000000000000LL || + value == 0x80000000000000LL || + value == 0x4000000000000000LL) { + right->value = right->value.neg(); + if (binary->op == Abstract::getBinary(type, Abstract::Add)) { + binary->op = Abstract::getBinary(type, Abstract::Sub); + } else { + binary->op = Abstract::getBinary(type, Abstract::Add); + } + } + } + } + // note that this is correct even on floats with a NaN on the left, + // as a NaN would skip the computation and just return the NaN, + // and that is precisely what we do here. but, the same with -1 + // (change to a negation) would be incorrect for that reason. + if (right->value == LiteralUtils::makeLiteralFromInt32(1, type)) { + if (binary->op == Abstract::getBinary(type, Abstract::Mul) || + binary->op == Abstract::getBinary(type, Abstract::DivS) || + binary->op == Abstract::getBinary(type, Abstract::DivU)) { + return binary->left; + } + } + return nullptr; + } + + // integer math, even on 2s complement, allows stuff like + // x + 5 == 7 + // => + // x == 2 + // TODO: templatize on type? + Expression* optimizeRelational(Binary* binary) { + // TODO: inequalities can also work, if the constants do not overflow + auto type = binary->right->type; + if (binary->op ==Abstract::getBinary(type, Abstract::Eq) || + binary->op ==Abstract::getBinary(type, Abstract::Ne)) { + if (isIntegerType(binary->left->type)) { + if (auto* left = binary->left->dynCast<Binary>()) { + if (left->op == Abstract::getBinary(type, Abstract::Add) || + left->op == Abstract::getBinary(type, Abstract::Sub)) { + if (auto* leftConst = left->right->dynCast<Const>()) { + if (auto* rightConst = binary->right->dynCast<Const>()) { + return combineRelationalConstants(binary, left, leftConst, nullptr, rightConst); + } else if (auto* rightBinary = binary->right->dynCast<Binary>()) { + if (rightBinary->op == Abstract::getBinary(type, Abstract::Add) || + rightBinary->op == Abstract::getBinary(type, Abstract::Sub)) { + if (auto* rightConst = rightBinary->right->dynCast<Const>()) { + return combineRelationalConstants(binary, left, leftConst, rightBinary, rightConst); + } + } + } + } + } + } + } + } + return nullptr; + } + + // given a relational binary with a const on both sides, combine the constants + // left is also a binary, and has a constant; right may be just a constant, in which + // case right is nullptr + Expression* combineRelationalConstants(Binary* binary, Binary* left, Const* leftConst, Binary* right, Const* rightConst) { + auto type = binary->right->type; + // we fold constants to the right + Literal extra = leftConst->value; + if (left->op == Abstract::getBinary(type, Abstract::Sub)) { + extra = extra.neg(); + } + if (right && right->op == Abstract::getBinary(type, Abstract::Sub)) { + extra = extra.neg(); + } + rightConst->value = rightConst->value.sub(extra); + binary->left = left->left; + return binary; + } }; Pass *createOptimizeInstructionsPass() { diff --git a/src/passes/SafeHeap.cpp b/src/passes/SafeHeap.cpp index b095b741d..325516f4d 100644 --- a/src/passes/SafeHeap.cpp +++ b/src/passes/SafeHeap.cpp @@ -38,7 +38,7 @@ static Name getLoadName(Load* curr) { std::string ret = "SAFE_HEAP_LOAD_"; ret += printType(curr->type); ret += "_" + std::to_string(curr->bytes) + "_"; - if (!isTypeFloat(curr->type) && !curr->signed_) { + if (!isFloatType(curr->type) && !curr->signed_) { ret += "U_"; } if (curr->isAtomic) { @@ -160,7 +160,7 @@ struct SafeHeap : public Pass { if (bytes > getTypeSize(type)) continue; for (auto signed_ : { true, false }) { load.signed_ = signed_; - if (isTypeFloat(type) && signed_) continue; + if (isFloatType(type) && signed_) continue; for (Index align : { 1, 2, 4, 8 }) { load.align = align; if (align > bytes) continue; diff --git a/src/wasm-js.cpp b/src/wasm-js.cpp index a2f09504d..5bcd7e133 100644 --- a/src/wasm-js.cpp +++ b/src/wasm-js.cpp @@ -369,8 +369,8 @@ extern "C" void EMSCRIPTEN_KEEPALIVE instantiate() { } HEAP32[0] = save0; HEAP32[1] = save1; return ret; - }, (uint32_t)addr, load->bytes, isTypeFloat(load->type), load->signed_, &out64); - if (!isTypeFloat(load->type)) { + }, (uint32_t)addr, load->bytes, isFloatType(load->type), load->signed_, &out64); + if (!isFloatType(load->type)) { if (load->type == i64) { if (load->bytes == 8) { return Literal(out64); @@ -391,7 +391,7 @@ extern "C" void EMSCRIPTEN_KEEPALIVE instantiate() { abort(); } // nicely aligned - if (!isTypeFloat(load->type)) { + if (!isFloatType(load->type)) { int64_t ret; if (load->bytes == 1) { if (load->signed_) { @@ -463,11 +463,11 @@ extern "C" void EMSCRIPTEN_KEEPALIVE instantiate() { Module["info"].parent["HEAPU8"][addr + i] = HEAPU8[i]; } HEAP32[0] = save0; HEAP32[1] = save1; - }, (uint32_t)addr, store_->bytes, isTypeFloat(store_->valueType), isTypeFloat(store_->valueType) ? value.getFloat() : (double)value.getInteger()); + }, (uint32_t)addr, store_->bytes, isFloatType(store_->valueType), isFloatType(store_->valueType) ? value.getFloat() : (double)value.getInteger()); return; } // nicely aligned - if (!isTypeFloat(store_->valueType)) { + if (!isFloatType(store_->valueType)) { if (store_->bytes == 1) { EM_ASM_INT({ Module['info'].parent['HEAP8'][$0] = $1 }, addr, (uint32_t)value.getInteger()); } else if (store_->bytes == 2) { diff --git a/src/wasm-type.h b/src/wasm-type.h index e7da782d7..6b2618c8c 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -32,10 +32,11 @@ enum Type { const char* printType(Type type); unsigned getTypeSize(Type type); -bool isTypeFloat(Type type); Type getType(unsigned size, bool float_); Type getReachableType(Type a, Type b); bool isConcreteType(Type type); +bool isFloatType(Type type); +bool isIntegerType(Type type); } // namespace wasm diff --git a/src/wasm.h b/src/wasm.h index 9d8648308..6277d32fc 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -109,6 +109,8 @@ enum UnaryOp { // Extend signed subword-sized integer. This differs from e.g. ExtendSInt32 // because the input integer is in an i64 value insetad of an i32 value. ExtendS8Int32, ExtendS16Int32, ExtendS8Int64, ExtendS16Int64, ExtendS32Int64, + + InvalidUnary }; enum BinaryOp { @@ -135,6 +137,8 @@ enum BinaryOp { // relational ops EqFloat64, NeFloat64, // int or float LtFloat64, LeFloat64, GtFloat64, GeFloat64, // float + + InvalidBinary }; enum HostOp { diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index dca3d64b4..a6dcd17f0 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -249,8 +249,8 @@ Literal Literal::convertUToF64() const { Literal Literal::neg() const { switch (type) { - case Type::i32: return Literal(i32 ^ 0x80000000); - case Type::i64: return Literal(int64_t(i64 ^ 0x8000000000000000ULL)); + case Type::i32: return Literal(-uint32_t(i32)); + case Type::i64: return Literal(-uint64_t(i64)); case Type::f32: return Literal(i32 ^ 0x80000000).castToF32(); case Type::f64: return Literal(int64_t(i64 ^ 0x8000000000000000ULL)).castToF64(); default: WASM_UNREACHABLE(); diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp index a185e02c3..5635282f7 100644 --- a/src/wasm/wasm-s-parser.cpp +++ b/src/wasm/wasm-s-parser.cpp @@ -760,7 +760,7 @@ Expression* SExpressionWasmBuilder::makeExpression(Element& s) { case 'r': { if (op[1] == 'e') { if (op[2] == 'm') return makeBinary(s, op[4] == 'u' ? BINARY_INT(RemU) : BINARY_INT(RemS), type); - if (op[2] == 'i') return makeUnary(s, isTypeFloat(type) ? (type == f32 ? UnaryOp::ReinterpretInt32 : UnaryOp::ReinterpretInt64) : (type == i32 ? UnaryOp::ReinterpretFloat32 : UnaryOp::ReinterpretFloat64), type); + if (op[2] == 'i') return makeUnary(s, isFloatType(type) ? (type == f32 ? UnaryOp::ReinterpretInt32 : UnaryOp::ReinterpretInt64) : (type == i32 ? UnaryOp::ReinterpretFloat32 : UnaryOp::ReinterpretFloat64), type); } if (op[1] == 'o' && op[2] == 't') { return makeBinary(s, op[3] == 'l' ? BINARY_INT(RotL) : BINARY_INT(RotR), type); diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 4b159b6e2..b030e87ae 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -44,14 +44,6 @@ unsigned getTypeSize(Type type) { } } -bool isTypeFloat(Type type) { - switch (type) { - case f32: - case f64: return true; - default: return false; - } -} - Type getType(unsigned size, bool float_) { if (size < 4) return Type::i32; if (size == 4) return float_ ? Type::f32 : Type::i32; @@ -67,4 +59,20 @@ bool isConcreteType(Type type) { return type != none && type != unreachable; } +bool isIntegerType(Type type) { + switch (type) { + case i32: + case i64: return true; + default: return false; + } +} + +bool isFloatType(Type type) { + switch (type) { + case f32: + case f64: return true; + default: return false; + } +} + } // namespace wasm |