| /* | |
| * 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. | |
| */ | |
| // | |
| // Hoists repeated constants to a local. A local.get takes 2 bytes | |
| // in most cases, and if a const is larger than that, it may be | |
| // better to store it to a local, then get it from that local. | |
| // | |
| // WARNING: this often shrinks code size, but can *increase* gzip | |
| // size. apparently having the constants in their proper | |
| // places lets them be compressed better, across | |
| // functions, etc. TODO investigate | |
| // TODO: hoisting a zero does not even require an initial set! | |
| // TODO: hoisting a float or double zero is especially beneficial as there | |
| // is no LEB compression for them, and no need for the set, so | |
| // each f32.const is 5 bytes and f64.const is 9 bytes, while it is | |
| // <= 1 byte to declare the local and 2-3 to use it! | |
| // | |
| #include "pass.h" | |
| #include "support/insert_ordered.h" | |
| #include "wasm-binary.h" | |
| #include "wasm-builder.h" | |
| #include "wasm.h" | |
| namespace wasm { | |
| // with fewer uses than this, it is never beneficial to hoist | |
| static const Index MIN_USES = 2; | |
| struct ConstHoisting : public WalkerPass<PostWalker<ConstHoisting>> { | |
| bool isFunctionParallel() override { return true; } | |
| std::unique_ptr<Pass> create() override { | |
| return std::make_unique<ConstHoisting>(); | |
| } | |
| InsertOrderedMap<Literal, std::vector<Expression**>> uses; | |
| void visitConst(Const* curr) { | |
| uses[curr->value].push_back(getCurrentPointer()); | |
| } | |
| void visitFunction(Function* curr) { | |
| std::vector<Expression*> prelude; | |
| for (auto& [value, vec] : uses) { | |
| auto num = vec.size(); | |
| if (worthHoisting(value, num)) { | |
| prelude.push_back(hoist(vec)); | |
| } | |
| } | |
| if (!prelude.empty()) { | |
| Builder builder(*getModule()); | |
| // merge-blocks can optimize this into a single block later in most cases | |
| curr->body = builder.makeSequence(builder.makeBlock(prelude), curr->body); | |
| } | |
| } | |
| private: | |
| bool worthHoisting(Literal value, Index num) { | |
| if (num < MIN_USES) { | |
| return false; | |
| } | |
| // measure the size of the constant | |
| Index size = 0; | |
| TODO_SINGLE_COMPOUND(value.type); | |
| switch (value.type.getBasic()) { | |
| case Type::i32: { | |
| size = getWrittenSize(S32LEB(value.geti32())); | |
| break; | |
| } | |
| case Type::i64: { | |
| size = getWrittenSize(S64LEB(value.geti64())); | |
| break; | |
| } | |
| case Type::f32: | |
| case Type::f64: { | |
| size = value.type.getByteSize(); | |
| break; | |
| } | |
| // not implemented yet | |
| case Type::v128: | |
| return false; | |
| case Type::none: | |
| case Type::unreachable: | |
| WASM_UNREACHABLE("unexpected type"); | |
| } | |
| // compute the benefit, of replacing the uses with | |
| // one use + a set and then a get for each use | |
| // doing the algebra, the criterion here is when | |
| // size > 2(1+num)/(num-1) | |
| // or | |
| // num > (size+2)/(size-2) | |
| auto before = num * size; | |
| auto after = size + 2 /* local.set */ + (2 /* local.get */ * num); | |
| return after < before; | |
| } | |
| template<typename T> Index getWrittenSize(const T& thing) { | |
| BufferWithRandomAccess buffer; | |
| buffer << thing; | |
| return buffer.size(); | |
| } | |
| // replace all the uses with gets, for a local set at the top. returns | |
| // the set. | |
| Expression* hoist(std::vector<Expression**>& vec) { | |
| auto type = (*(vec[0]))->type; | |
| Builder builder(*getModule()); | |
| auto temp = builder.addVar(getFunction(), type); | |
| auto* ret = builder.makeLocalSet(temp, *(vec[0])); | |
| for (auto item : vec) { | |
| *item = builder.makeLocalGet(temp, type); | |
| } | |
| return ret; | |
| } | |
| }; | |
| Pass* createConstHoistingPass() { return new ConstHoisting(); } | |
| } // namespace wasm |