/* * Copyright 2015 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. */ // // Misc optimizations that are useful for and/or are only valid for // emscripten output. // #include #include #include #include #include namespace wasm { struct PostEmscripten : public WalkerPass> { bool isFunctionParallel() override { return true; } Pass* create() override { return new PostEmscripten; } // When we have a Load from a local value (typically a GetLocal) plus a constant offset, // we may be able to fold it in. // The semantics of the Add are to wrap, while wasm offset semantics purposefully do // not wrap. So this is not always safe to do. For example, a load may depend on // wrapping via // (2^32 - 10) + 100 => wrap and load from address 90 // Without wrapping, we get something too large, and an error. *However*, for // asm2wasm output coming from Emscripten, we allocate the lowest 1024 for mapped // globals. Mapped globals are simple types (i32, float or double), always // accessed directly by a single constant. Therefore if we see (..) + K where // K is less then 1024, then if it wraps, it wraps into [0, 1024) which is at best // a mapped global, but it can't be because they are accessed directly (at worst, // it's 0 or an unused section of memory that was reserved for mapped globlas). // Thus it is ok to optimize such small constants into Load offsets. #define SAFE_MAX 1024 void optimizeMemoryAccess(Expression*& ptr, Address& offset) { while (1) { auto* add = ptr->dynCast(); if (!add) break; if (add->op != AddInt32) break; auto* left = add->left->dynCast(); auto* right = add->right->dynCast(); // note: in optimized code, we shouldn't see an add of two constants, so don't worry about that much // (precompute would optimize that) if (left) { auto value = left->value.geti32(); if (value >= 0 && value < SAFE_MAX) { offset = offset + value; ptr = add->right; continue; } } if (right) { auto value = right->value.geti32(); if (value >= 0 && value < SAFE_MAX) { offset = offset + value; ptr = add->left; continue; } } break; } // finally ptr may be a const, but it isn't worth folding that in (we still have a const); in fact, // it's better to do the opposite for gzip purposes as well as for readability. auto* last = ptr->dynCast(); if (last) { last->value = Literal(int32_t(last->value.geti32() + offset)); offset = 0; } } void visitLoad(Load* curr) { optimizeMemoryAccess(curr->ptr, curr->offset); } void visitStore(Store* curr) { optimizeMemoryAccess(curr->ptr, curr->offset); } void visitCallImport(CallImport* curr) { // special asm.js imports can be optimized auto* import = getModule()->getImport(curr->target); if (import->module == GLOBAL_MATH) { if (import->base == POW) { if (auto* exponent = curr->operands[1]->dynCast()) { if (exponent->value == Literal(double(2.0))) { // This is just a square operation, do a multiply Localizer localizer(curr->operands[0], getFunction(), getModule()); Builder builder(*getModule()); replaceCurrent(builder.makeBinary(MulFloat64, localizer.expr, builder.makeGetLocal(localizer.index, localizer.expr->type))); } else if (exponent->value == Literal(double(0.5))) { // This is just a square root operation replaceCurrent(Builder(*getModule()).makeUnary(SqrtFloat64, curr->operands[0])); } } } } } }; Pass *createPostEmscriptenPass() { return new PostEmscripten(); } } // namespace wasm