From b93eb75a54078260d71d274d1d83f757babf8782 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 18 Aug 2025 16:23:40 -0700 Subject: [PATCH 1/4] fix --- src/passes/RemoveUnusedModuleElements.cpp | 11 ++ .../remove-unused-module-elements-cont.wast | 136 ++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 test/lit/passes/remove-unused-module-elements-cont.wast diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index 52e9c6d3ddc..efbc145ce34 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -190,6 +190,17 @@ struct Noter : public PostWalker> { auto type = curr->ref->type.getHeapType(); noteStructField(StructField{type, curr->index}); } + + void visitContNew(ContNew* curr) { + // The function reference that is passed in here will be called, just as if + // we were a call_ref, except at a potentially later time. + // Ignore unreachable code. + if (!curr->func->type.isRef()) { + return; + } + + noteCallRef(curr->func->type.getHeapType()); + } }; // Analyze a module to find what things are referenced and what things are used. diff --git a/test/lit/passes/remove-unused-module-elements-cont.wast b/test/lit/passes/remove-unused-module-elements-cont.wast new file mode 100644 index 00000000000..e5b1cb0a043 --- /dev/null +++ b/test/lit/passes/remove-unused-module-elements-cont.wast @@ -0,0 +1,136 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt --remove-unused-module-elements --closed-world -all -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt --remove-unused-module-elements -all -S -o - | filecheck %s --check-prefix OPEN_WORLD + +;; Test both open world (default) and closed world. In a closed world we can do +;; more with function refs, as we assume nothing calls them on the outside, so +;; if no calls exist to the right type, the function is not reached. + +(module + ;; CHECK: (type $func (func)) + ;; OPEN_WORLD: (type $func (func)) + (type $func (func)) + ;; CHECK: (type $cont (cont $func)) + ;; OPEN_WORLD: (type $cont (cont $func)) + (type $cont (cont $func)) + + ;; CHECK: (elem declare func $func) + + ;; CHECK: (tag $tag (type $func)) + ;; OPEN_WORLD: (elem declare func $func) + + ;; OPEN_WORLD: (tag $tag (type $func)) + (tag $tag (type $func)) + + ;; CHECK: (export "run" (func $run)) + + ;; CHECK: (func $func (type $func) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; OPEN_WORLD: (export "run" (func $run)) + + ;; OPEN_WORLD: (func $func (type $func) + ;; OPEN_WORLD-NEXT: (nop) + ;; OPEN_WORLD-NEXT: ) + (func $func + ;; This function is only ever referred to by a ref.func that is passed into + ;; a cont.new. We should not modify this to unreachable, which we would do if + ;; we didn't realize it will execute. + (nop) + ) + + ;; CHECK: (func $run (type $func) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block (result (ref $cont)) + ;; CHECK-NEXT: (resume $cont (on $tag $block) + ;; CHECK-NEXT: (cont.new $cont + ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPEN_WORLD: (func $run (type $func) + ;; OPEN_WORLD-NEXT: (drop + ;; OPEN_WORLD-NEXT: (block $block (result (ref $cont)) + ;; OPEN_WORLD-NEXT: (resume $cont (on $tag $block) + ;; OPEN_WORLD-NEXT: (cont.new $cont + ;; OPEN_WORLD-NEXT: (ref.func $func) + ;; OPEN_WORLD-NEXT: ) + ;; OPEN_WORLD-NEXT: ) + ;; OPEN_WORLD-NEXT: (return) + ;; OPEN_WORLD-NEXT: ) + ;; OPEN_WORLD-NEXT: ) + ;; OPEN_WORLD-NEXT: ) + (func $run (export "run") + ;; No changes are expected here. + (drop + (block $block (result (ref $cont)) + (resume $cont (on $tag $block) + (cont.new $cont + (ref.func $func) + ) + ) + (return) + ) + ) + ) +) + +(module + ;; CHECK: (type $func (func)) + ;; OPEN_WORLD: (type $func (func)) + (type $func (func)) + ;; CHECK: (type $cont (cont $func)) + ;; OPEN_WORLD: (type $cont (cont $func)) + (type $cont (cont $func)) + + ;; CHECK: (tag $tag (type $func)) + ;; OPEN_WORLD: (tag $tag (type $func)) + (tag $tag (type $func)) + + ;; CHECK: (export "run" (func $run)) + + ;; CHECK: (func $run (type $func) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block (result (ref $cont)) + ;; CHECK-NEXT: (resume $cont (on $tag $block) + ;; CHECK-NEXT: (cont.new $cont + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPEN_WORLD: (export "run" (func $run)) + + ;; OPEN_WORLD: (func $run (type $func) + ;; OPEN_WORLD-NEXT: (drop + ;; OPEN_WORLD-NEXT: (block $block (result (ref $cont)) + ;; OPEN_WORLD-NEXT: (resume $cont (on $tag $block) + ;; OPEN_WORLD-NEXT: (cont.new $cont + ;; OPEN_WORLD-NEXT: (ref.null nofunc) + ;; OPEN_WORLD-NEXT: ) + ;; OPEN_WORLD-NEXT: ) + ;; OPEN_WORLD-NEXT: (return) + ;; OPEN_WORLD-NEXT: ) + ;; OPEN_WORLD-NEXT: ) + ;; OPEN_WORLD-NEXT: ) + (func $run (export "run") + ;; We should not error on a null function reference in the cont.new. + (drop + (block $block (result (ref $cont)) + (resume $cont (on $tag $block) + (cont.new $cont + (ref.null $func) + ) + ) + (return) + ) + ) + ) +) + From 16510992ab9d34db5302dbf0cbb03141263c308b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 18 Aug 2025 16:24:11 -0700 Subject: [PATCH 2/4] fix --- scripts/test/fuzzing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 1afbb898833..7f951ce94cc 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -119,6 +119,7 @@ 'cont_export.wast', 'cont_export_throw.wast', 'type-merging-cont.wast', + 'remove-unused-module-elements-cont.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', From 32183650d0c11efa6257694684f89bf8896f3ba5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 18 Aug 2025 16:25:52 -0700 Subject: [PATCH 3/4] fix --- src/passes/RemoveUnusedModuleElements.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index efbc145ce34..6be12de05f4 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -194,7 +194,6 @@ struct Noter : public PostWalker> { void visitContNew(ContNew* curr) { // The function reference that is passed in here will be called, just as if // we were a call_ref, except at a potentially later time. - // Ignore unreachable code. if (!curr->func->type.isRef()) { return; } From 2e05281a8dc6a792fec83935f9a2c0706736c612 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 18 Aug 2025 16:26:25 -0700 Subject: [PATCH 4/4] fix --- test/lit/passes/remove-unused-module-elements-cont.wast | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lit/passes/remove-unused-module-elements-cont.wast b/test/lit/passes/remove-unused-module-elements-cont.wast index e5b1cb0a043..3e10fad9490 100644 --- a/test/lit/passes/remove-unused-module-elements-cont.wast +++ b/test/lit/passes/remove-unused-module-elements-cont.wast @@ -36,7 +36,7 @@ (func $func ;; This function is only ever referred to by a ref.func that is passed into ;; a cont.new. We should not modify this to unreachable, which we would do if - ;; we didn't realize it will execute. + ;; we didn't realize it will execute (in closed world). (nop) )