From 5d9589d23a43c70ca657ee1d3cdea0b6deb1178d Mon Sep 17 00:00:00 2001 From: Govind Yadav Date: Sat, 5 Sep 2026 11:13:43 +0530 Subject: [PATCH] Add option to remove empty function exports --- src/passes/RemoveUnusedModuleElements.cpp | 29 +++++++++++++++ ...e-unused-module-elements-empty-exports.wat | 36 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 test/lit/passes/remove-unused-module-elements-empty-exports.wat diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index ee2079b31d6..25e3f5ef31c 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -35,6 +35,10 @@ // reference forces us to keep something in the IR to be referred to, but only // a use actually makes us keep its contents as well. // +// The remove-unused-module-elements-consider-empty-exports-unused pass +// argument treats exports of empty functions as unused. This is useful for +// runtimes where such exports are optional. +// #include #include @@ -887,6 +891,31 @@ struct RemoveUnusedModuleElements : public Pass { void run(Module* module) override { prepare(module); + // Some runtimes treat missing exports as optional. For those runtimes, + // allow exported functions that do nothing to be handled like other + // unused module elements. Remove the exports before finding roots so that + // an otherwise-unreferenced function can be removed as well. + if (!rootAllFunctions && + hasArgument( + "remove-unused-module-elements-consider-empty-exports-unused")) { + module->removeExports([&](Export* curr) { + if (curr->kind != ExternalKind::Function) { + return false; + } + auto* func = module->getFunction(*curr->getInternalName()); + if (func->imported()) { + return false; + } + if (func->body->is()) { + return true; + } + if (auto* block = func->body->dynCast()) { + return block->list.empty(); + } + return false; + }); + } + std::vector roots; // Module start is a root. if (module->start.is()) { diff --git a/test/lit/passes/remove-unused-module-elements-empty-exports.wat b/test/lit/passes/remove-unused-module-elements-empty-exports.wat new file mode 100644 index 00000000000..8b34a51500d --- /dev/null +++ b/test/lit/passes/remove-unused-module-elements-empty-exports.wat @@ -0,0 +1,36 @@ +;; RUN: wasm-opt %s --remove-unused-module-elements -S -o - | filecheck %s --check-prefix=DEFAULT +;; RUN: wasm-opt %s --remove-unused-module-elements --pass-arg=remove-unused-module-elements-consider-empty-exports-unused -S -o - | filecheck %s --check-prefix=OPTION + +(module + (import "env" "noop" (func $imported)) + + (func $empty) + (func $empty-used) + (func $call-empty + (call $empty-used) + ) + (func $nonempty + (drop + (i32.const 0) + ) + ) + + (export "empty" (func $empty)) + (export "empty-used" (func $empty-used)) + (export "call-empty" (func $call-empty)) + (export "nonempty" (func $nonempty)) + (export "imported" (func $imported)) +) + +;; DEFAULT: (export "empty" (func $empty)) +;; DEFAULT-NEXT: (export "empty-used" (func $empty-used)) +;; DEFAULT: (func $empty +;; DEFAULT: (func $empty-used + +;; OPTION-NOT: (export "empty" +;; OPTION-NOT: (export "empty-used" +;; OPTION: (export "call-empty" (func $call-empty)) +;; OPTION-NEXT: (export "nonempty" (func $nonempty)) +;; OPTION-NEXT: (export "imported" (func $imported)) +;; OPTION-NOT: (func $empty{{[[:space:]]*$}} +;; OPTION: (func $empty-used