diff options
Diffstat (limited to 'src/tools/wasm-ctor-eval.cpp')
-rw-r--r-- | src/tools/wasm-ctor-eval.cpp | 194 |
1 files changed, 126 insertions, 68 deletions
diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index d476bb2cc..fe3d42d09 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -25,6 +25,7 @@ #include <memory> #include "asmjs/shared-constants.h" +#include "ir/find_all.h" #include "ir/gc-type-utils.h" #include "ir/global-utils.h" #include "ir/import-utils.h" @@ -1061,40 +1062,45 @@ EvalCtorOutcome evalCtor(EvallingModuleRunner& instance, params.push_back(Literal::makeZero(type)); } - // We want to handle the form of the global constructor function in LLVM. That - // looks like this: - // - // (func $__wasm_call_ctors - // (call $ctor.1) - // (call $ctor.2) - // (call $ctor.3) - // ) - // - // Some of those ctors may be inlined, however, which would mean that the - // function could have locals, control flow, etc. However, we assume for now - // that it does not have parameters at least (whose values we can't tell). - // And for now we look for a toplevel block and process its children one at a - // time. This allows us to eval some of the $ctor.* functions (or their - // inlined contents) even if not all. - // - // TODO: Support complete partial evalling, that is, evaluate parts of an - // arbitrary function, and not just a sequence in a single toplevel - // block. + // After we successfully eval a line we will store the operations to set up + // the locals here. That is, we need to save the local state in the function, + // which we do by setting up at the entry. We update this list of expressions + // at the same time as applyToModule() - we must only do it after an entire + // atomic "chunk" has been processed succesfully, we do not want partial + // updates from an item in the block that we only partially evalled. When we + // construct the (partially) evalled function, we will create local.sets of + // these expressions at the beginning. + std::vector<Expression*> localExprs; + + // We might have to evaluate multiple functions due to return calls. +start_eval: + while (true) { + // We want to handle the form of the global constructor function in LLVM. + // That looks like this: + // + // (func $__wasm_call_ctors + // (call $ctor.1) + // (call $ctor.2) + // (call $ctor.3) + // ) + // + // Some of those ctors may be inlined, however, which would mean that the + // function could have locals, control flow, etc. However, we assume for now + // that it does not have parameters at least (whose values we can't tell). + // And for now we look for a toplevel block and process its children one at + // a time. This allows us to eval some of the $ctor.* functions (or their + // inlined contents) even if not all. + // + // TODO: Support complete partial evalling, that is, evaluate parts of an + // arbitrary function, and not just a sequence in a single toplevel + // block. + Builder builder(wasm); + auto* block = builder.blockify(func->body); - if (auto* block = func->body->dynCast<Block>()) { // Go through the items in the block and try to execute them. We do all this // in a single function scope for all the executions. EvallingModuleRunner::FunctionScope scope(func, params, instance); - // After we successfully eval a line we will store the operations to set up - // the locals here. That is, we need to save the local state in the - // function, which we do by setting up at the entry. We update this list of - // local.sets at the same time as applyToModule() - we must only do it after - // an entire atomic "chunk" has been processed succesfully, we do not want - // partial updates from an item in the block that we only partially evalled. - std::vector<Expression*> localSets; - - Builder builder(wasm); Literals results; Index successes = 0; @@ -1116,6 +1122,22 @@ EvalCtorOutcome evalCtor(EvallingModuleRunner& instance, break; } + if (flow.breakTo == RETURN_CALL_FLOW) { + // The return-called function is stored in the last value. + func = wasm.getFunction(flow.values.back().getFunc()); + flow.values.pop_back(); + params = std::move(flow.values); + + // Serialize the arguments for the new function and save the module + // state in case we fail to eval the new function. + localExprs.clear(); + for (auto& param : params) { + localExprs.push_back(interface.getSerialization(param)); + } + interface.applyToModule(); + goto start_eval; + } + // So far so good! Serialize the values of locals, and apply to the // module. Note that we must serialize the locals now as doing so may // cause changes that must be applied to the module (e.g. GC data may @@ -1128,11 +1150,9 @@ EvalCtorOutcome evalCtor(EvallingModuleRunner& instance, // of them, and leave it to the optimizer to remove redundant or // unnecessary operations. We just recompute the entire local // serialization sets from scratch each time here, for all locals. - localSets.clear(); + localExprs.clear(); for (Index i = 0; i < func->getNumLocals(); i++) { - auto value = scope.locals[i]; - localSets.push_back( - builder.makeLocalSet(i, interface.getSerialization(value))); + localExprs.push_back(interface.getSerialization(scope.locals[i])); } interface.applyToModule(); successes++; @@ -1144,41 +1164,97 @@ EvalCtorOutcome evalCtor(EvallingModuleRunner& instance, if (flow.breaking()) { // We are returning out of the function (either via a return, or via a // break to |block|, which has the same outcome. That means we don't - // need to execute any more lines, and can consider them to be executed. + // need to execute any more lines, and can consider them to be + // executed. if (!quiet) { std::cout << " ...stopping in block due to break\n"; } // Mark us as having succeeded on the entire block, since we have: we - // are skipping the rest, which means there is no problem there. We must - // set this here so that lower down we realize that we've evalled + // are skipping the rest, which means there is no problem there. We + // must set this here so that lower down we realize that we've evalled // everything. successes = block->list.size(); break; } } - if (successes > 0 && successes < block->list.size()) { - // We managed to eval some but not all. That means we can't just remove - // the entire function, but need to keep parts of it - the parts we have - // not evalled - around. To do so, we create a copy of the function with - // the partially-evalled contents and make the export use that (as the - // function may be used in other places than the export, which we do not - // want to affect). + // If we have not fully evaluated the current function, but we have + // evaluated part of it, have return-called to a different function, or have + // precomputed values for the current return-called function, then we can + // replace the export with a new function that does less work than the + // original. + if ((func->imported() || successes < block->list.size()) && + (successes > 0 || func->name != funcName || + (localExprs.size() && func->getParams() != Type::none))) { + auto originalFuncType = wasm.getFunction(funcName)->type; auto copyName = Names::getValidFunctionName(wasm, funcName); - auto* copyFunc = ModuleUtils::copyFunction(func, wasm, copyName); wasm.getExport(exportName)->value = copyName; + if (func->imported()) { + // We must have return-called this imported function. Generate a new + // function that return-calls the import with the arguments we have + // evalled. + auto copyFunc = builder.makeFunction( + copyName, + originalFuncType, + {}, + builder.makeCall(func->name, localExprs, func->getResults(), true)); + wasm.addFunction(std::move(copyFunc)); + return EvalCtorOutcome(); + } + + // We may have managed to eval some but not all. That means we can't just + // remove the entire function, but need to keep parts of it - the parts we + // have not evalled - around. To do so, we create a copy of the function + // with the partially-evalled contents and make the export use that (as + // the function may be used in other places than the export, which we do + // not want to affect). + auto* copyBody = + builder.blockify(ExpressionManipulator::copy(func->body, wasm)); + // Remove the items we've evalled. - auto* copyBlock = copyFunc->body->cast<Block>(); for (Index i = 0; i < successes; i++) { - copyBlock->list[i] = builder.makeNop(); + copyBody->list[i] = builder.makeNop(); } - // Put the local sets at the front of the block. We know there must be a - // nop in that position (since we've evalled at least one item in the - // block, and replaced it with a nop), so we can overwrite it. - copyBlock->list[0] = builder.makeBlock(localSets); + // Put the local sets at the front of the function body. + auto* setsBlock = builder.makeBlock(); + for (Index i = 0; i < localExprs.size(); ++i) { + setsBlock->list.push_back(builder.makeLocalSet(i, localExprs[i])); + } + copyBody = builder.makeSequence(setsBlock, copyBody, copyBody->type); + + // We may have return-called into a function with different parameter + // types, but we ultimately need to export a function with the original + // signature. If there is a mismatch, shift the local indices to make room + // for the unused parameters. + std::vector<Type> localTypes; + auto originalParams = originalFuncType.getSignature().params; + if (originalParams != func->getParams()) { + // Add locals for the body to use instead of using the params. + for (auto type : func->getParams()) { + localTypes.push_back(type); + } + + // Shift indices in the body so they will refer to the new locals. + auto localShift = originalParams.size(); + if (localShift != 0) { + for (auto* get : FindAll<LocalGet>(copyBody).list) { + get->index += localShift; + } + for (auto* set : FindAll<LocalSet>(copyBody).list) { + set->index += localShift; + } + } + } + + // Add vars from current function. + localTypes.insert(localTypes.end(), func->vars.begin(), func->vars.end()); + + // Create and add the new function. + auto* copyFunc = wasm.addFunction(builder.makeFunction( + copyName, originalFuncType, std::move(localTypes), copyBody)); // Interesting optimizations may be possible both due to removing some but // not all of the code, and due to the locals we just added. @@ -1196,24 +1272,6 @@ EvalCtorOutcome evalCtor(EvallingModuleRunner& instance, return EvalCtorOutcome(); } } - - // Otherwise, we don't recognize a pattern that allows us to do partial - // evalling. So simply call the entire function at once and see if we can - // optimize that. - - Literals results; - try { - results = instance.callFunction(funcName, params); - } catch (FailToEvalException& fail) { - if (!quiet) { - std::cout << " ...stopping since could not eval: " << fail.why << "\n"; - } - return EvalCtorOutcome(); - } - - // Success! Apply the results. - interface.applyToModule(); - return EvalCtorOutcome(results); } // Eval all ctors in a module. |