summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2023-10-19 09:11:36 -0700
committerGitHub <noreply@github.com>2023-10-19 16:11:36 +0000
commitbe6a3393c36ccc1a0cb0d79b116cbe48e169f93b (patch)
tree8240468a78e62b6d18d747c865557d69f01b4b9f /src
parentf79b5aa26b1fc722853e56b541cd35128786ef6b (diff)
downloadbinaryen-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.cpp61
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() {