diff options
author | Alon Zakai <azakai@google.com> | 2023-10-19 09:11:36 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-19 16:11:36 +0000 |
commit | be6a3393c36ccc1a0cb0d79b116cbe48e169f93b (patch) | |
tree | 8240468a78e62b6d18d747c865557d69f01b4b9f /src | |
parent | f79b5aa26b1fc722853e56b541cd35128786ef6b (diff) | |
download | binaryen-be6a3393c36ccc1a0cb0d79b116cbe48e169f93b.tar.gz binaryen-be6a3393c36ccc1a0cb0d79b116cbe48e169f93b.tar.bz2 binaryen-be6a3393c36ccc1a0cb0d79b116cbe48e169f93b.zip |
RemoveUnusedModuleElements: Make exports skip trampolines (#6026)
If we export a function that just calls another function, we can export that one
instead. Then the one in the middle may be unused,
function foo() {
return bar();
}
export foo; // can be an export of bar
This saves a few bytes in rare cases, but probably more important is that it saves
the trampoline, so if this is on a hot path, we save a call.
Context: emscripten-core/emscripten#20478 (comment)
In general this is not needed as inlining helps us out by inlining foo() into the
caller (since foo is tiny, that always ends up happening). But exports are a case
the inliner cannot handle, so we do it here.
Diffstat (limited to 'src')
-rw-r--r-- | src/passes/RemoveUnusedModuleElements.cpp | 61 |
1 files changed, 61 insertions, 0 deletions
diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index e082f8e15..cf1f265bc 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -601,6 +601,8 @@ struct RemoveUnusedModuleElements : public Pass { : rootAllFunctions(rootAllFunctions) {} void run(Module* module) override { + prepare(module); + std::vector<ModuleElement> roots; // Module start is a root. if (module->start.is()) { @@ -712,6 +714,65 @@ struct RemoveUnusedModuleElements : public Pass { // to a function from an element segment, we may be able to remove // that function, etc.) } + + // Do simple work that prepares the module to be efficiently optimized. + void prepare(Module* module) { + // If a function export is a function that just calls another function, we + // can export that one directly. Doing so might make the function in the + // middle unused: + // + // (export "export" (func $middle)) + // (func $middle + // (call $real) + // ) + // + // => + // + // (export "export" (func $real)) ;; this changed + // (func $middle + // (call $real) + // ) + // + // (Normally this is not needed, as inlining will end up removing such + // silly trampoline functions, but the case of an import being exported does + // not have any code for inlining to work with, so we need to handle it + // directly.) + for (auto& exp : module->exports) { + if (exp->kind != ExternalKind::Function) { + continue; + } + + auto* func = module->getFunction(exp->value); + if (!func->body) { + continue; + } + + auto* call = func->body->dynCast<Call>(); + if (!call) { + continue; + } + + // Don't do this if the type is different, as then we might be + // changing the external interface to the module. + auto* calledFunc = module->getFunction(call->target); + if (calledFunc->type != func->type) { + continue; + } + + // Finally, all the params must simply be forwarded. + auto ok = true; + for (Index i = 0; i < call->operands.size(); i++) { + auto* get = call->operands[i]->dynCast<LocalGet>(); + if (!get || get->index != i) { + ok = false; + break; + } + } + if (ok) { + exp->value = calledFunc->name; + } + } + } }; Pass* createRemoveUnusedModuleElementsPass() { |