From be6a3393c36ccc1a0cb0d79b116cbe48e169f93b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 19 Oct 2023 09:11:36 -0700 Subject: 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. --- src/passes/RemoveUnusedModuleElements.cpp | 61 +++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) (limited to 'src') 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 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(); + 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(); + if (!get || get->index != i) { + ok = false; + break; + } + } + if (ok) { + exp->value = calledFunc->name; + } + } + } }; Pass* createRemoveUnusedModuleElementsPass() { -- cgit v1.2.3