summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/asm2wasm.h2
-rw-r--r--src/ir/abstract.h156
-rw-r--r--src/ir/load-utils.h2
-rw-r--r--src/parsing.h2
-rw-r--r--src/passes/OptimizeInstructions.cpp136
-rw-r--r--src/passes/SafeHeap.cpp4
-rw-r--r--src/wasm-js.cpp10
-rw-r--r--src/wasm-type.h3
-rw-r--r--src/wasm.h4
-rw-r--r--src/wasm/literal.cpp4
-rw-r--r--src/wasm/wasm-s-parser.cpp2
-rw-r--r--src/wasm/wasm-type.cpp24
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