summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md2
-rwxr-xr-xscripts/fuzz_opt.py3
-rw-r--r--src/pass.h8
-rw-r--r--src/passes/RemoveUnusedModuleElements.cpp31
-rw-r--r--src/passes/pass.cpp18
-rw-r--r--src/tools/optimization-options.h4
-rw-r--r--test/lit/passes/gto_and_cfp_in_O.wast28
-rw-r--r--test/lit/passes/remove-unused-module-elements-refs.wast184
-rw-r--r--test/passes/Oz_fuzz-exec_all-features.txt18
-rw-r--r--test/unit/test_passes.py49
10 files changed, 307 insertions, 38 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1fe30bdff..6c7f6f2fb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,8 @@ full changeset diff at the end of each section.
Current Trunk
-------------
+- Add `--closed-world` flag. This enables more optimizations in GC mode as it
+ lets us assume that we can change types inside the module.
- The isorecursive WasmGC type system (i.e. --hybrid) is now the default to
match the spec and the old default equirecursive (i.e. --structural) system
has been removed.
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py
index 2f4fad5c2..98695580d 100755
--- a/scripts/fuzz_opt.py
+++ b/scripts/fuzz_opt.py
@@ -1378,6 +1378,9 @@ def randomize_opt_flags():
# wasm limitation on function body size which is 128K)
if random.random() < 0.5:
ret += ['-fimfs=99999999']
+ # test both closed and open world
+ if random.random() < 0.5:
+ ret += ['--closed-world']
assert ret.count('--flatten') <= 1
return ret
diff --git a/src/pass.h b/src/pass.h
index 120ed2629..02cea7879 100644
--- a/src/pass.h
+++ b/src/pass.h
@@ -184,6 +184,14 @@ struct PassOptions {
// creates it and we know it is all zeros right before the active segments are
// applied.)
bool zeroFilledMemory = false;
+ // Assume code outside of the module does not inspect or interact with GC and
+ // function references, even if they are passed out. The outside may hold on
+ // to them and pass them back in, but not inspect their contents or call them.
+ // By default we do not make that assumption, and assume anything that escapes
+ // to the outside may be inspected in detail, which prevents us from e.g.
+ // changing a type that escapes (so we can't remove or refine fields on an
+ // escaping struct type, for example).
+ bool closedWorld = false;
// Whether to try to preserve debug info through, which are special calls.
bool debugInfo = false;
// Arbitrary string arguments from the commandline, which we forward to
diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp
index 7feda6f05..bf5f84736 100644
--- a/src/passes/RemoveUnusedModuleElements.cpp
+++ b/src/passes/RemoveUnusedModuleElements.cpp
@@ -43,6 +43,8 @@ using ModuleElement = std::pair<ModuleElementKind, Name>;
struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> {
Module* module;
+ bool closedWorld;
+
std::vector<ModuleElement> queue;
std::set<ModuleElement> reachable;
bool usesMemory = false;
@@ -63,13 +65,15 @@ struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> {
// we'll have that type in calledSignatures, and so this contains only
// RefFuncs that we have not seen a call for yet, hence "uncalledRefFuncMap."
//
- // TODO: We assume a closed world in the GC space atm, but eventually should
- // have a flag for that, and when the world is not closed we'd need to
- // check for RefFuncs that flow out to exports or imports
+ // We can only do this when assuming a closed world. TODO: In an open world we
+ // could carefully track which types actually escape out to exports or
+ // imports.
std::unordered_map<HeapType, std::unordered_set<Name>> uncalledRefFuncMap;
- ReachabilityAnalyzer(Module* module, const std::vector<ModuleElement>& roots)
- : module(module) {
+ ReachabilityAnalyzer(Module* module,
+ const std::vector<ModuleElement>& roots,
+ bool closedWorld)
+ : module(module), closedWorld(closedWorld) {
queue = roots;
// Globals used in memory/table init expressions are also roots
for (auto& segment : module->dataSegments) {
@@ -205,6 +209,12 @@ struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> {
void visitMemorySize(MemorySize* curr) { usesMemory = true; }
void visitMemoryGrow(MemoryGrow* curr) { usesMemory = true; }
void visitRefFunc(RefFunc* curr) {
+ if (!closedWorld) {
+ // The world is open, so assume the worst and something (inside or outside
+ // of the module) can call this.
+ maybeAdd(ModuleElement(ModuleElementKind::Function, curr->func));
+ return;
+ }
auto type = curr->type.getHeapType();
if (calledSignatures.count(type)) {
// We must not have a type in both calledSignatures and
@@ -313,12 +323,20 @@ struct RemoveUnusedModuleElements : public Pass {
roots.emplace_back(ModuleElementKind::Function, name);
});
// Compute reachability starting from the root set.
- ReachabilityAnalyzer analyzer(module, roots);
+ auto closedWorld = getPassOptions().closedWorld;
+ ReachabilityAnalyzer analyzer(module, roots, closedWorld);
// RefFuncs that are never called are a special case: We cannot remove the
// function, since then (ref.func $foo) would not validate. But if we know
// it is never called, at least the contents do not matter, so we can
// empty it out.
+ //
+ // We can only do this in a closed world, as otherwise function references
+ // may be called outside of the module (if they escape, which we could in
+ // principle track, see the TODO earlier in this file). So in the case of an
+ // open world we should not have noted anything in uncalledRefFuncMap
+ // earlier and not do any related optimizations there.
+ assert(closedWorld || analyzer.uncalledRefFuncMap.empty());
std::unordered_set<Name> uncalledRefFuncs;
for (auto& [type, targets] : analyzer.uncalledRefFuncMap) {
for (auto target : targets) {
@@ -334,7 +352,6 @@ struct RemoveUnusedModuleElements : public Pass {
assert(analyzer.uncalledRefFuncMap.count(type) == 0);
}
#endif
-
// Remove unreachable elements.
module->removeFunctions([&](Function* curr) {
if (analyzer.reachable.count(
diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp
index 9daf8e1d3..ee4e2ed21 100644
--- a/src/passes/pass.cpp
+++ b/src/passes/pass.cpp
@@ -594,18 +594,24 @@ void PassRunner::addDefaultGlobalOptimizationPrePasses() {
addIfNoDWARFIssues("once-reduction");
}
if (wasm->features.hasGC() && options.optimizeLevel >= 2) {
- addIfNoDWARFIssues("type-refining");
- addIfNoDWARFIssues("signature-pruning");
- addIfNoDWARFIssues("signature-refining");
+ if (options.closedWorld) {
+ addIfNoDWARFIssues("type-refining");
+ addIfNoDWARFIssues("signature-pruning");
+ addIfNoDWARFIssues("signature-refining");
+ }
addIfNoDWARFIssues("global-refining");
// Global type optimization can remove fields that are not needed, which can
// remove ref.funcs that were once assigned to vtables but are no longer
// needed, which can allow more code to be removed globally. After those,
// constant field propagation can be more effective.
- addIfNoDWARFIssues("gto");
+ if (options.closedWorld) {
+ addIfNoDWARFIssues("gto");
+ }
addIfNoDWARFIssues("remove-unused-module-elements");
- addIfNoDWARFIssues("cfp");
- addIfNoDWARFIssues("gsi");
+ if (options.closedWorld) {
+ addIfNoDWARFIssues("cfp");
+ addIfNoDWARFIssues("gsi");
+ }
}
// TODO: generate-global-effects here, right before function passes, then
// discard in addDefaultGlobalOptimizationPostPasses? the benefit seems
diff --git a/src/tools/optimization-options.h b/src/tools/optimization-options.h
index a1a21b596..ff9bb973d 100644
--- a/src/tools/optimization-options.h
+++ b/src/tools/optimization-options.h
@@ -250,8 +250,8 @@ struct OptimizationOptions : public ToolOptions {
"contents or call them.",
OptimizationOptionsCategory,
Options::Arguments::Zero,
- [](Options*, const std::string&) {
- // TODO: Implement this.
+ [this](Options*, const std::string&) {
+ passOptions.closedWorld = true;
})
.add("--zero-filled-memory",
"-uim",
diff --git a/test/lit/passes/gto_and_cfp_in_O.wast b/test/lit/passes/gto_and_cfp_in_O.wast
index a36bbc4ea..9d385d0f9 100644
--- a/test/lit/passes/gto_and_cfp_in_O.wast
+++ b/test/lit/passes/gto_and_cfp_in_O.wast
@@ -1,18 +1,37 @@
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
-;; RUN: foreach %s %t wasm-opt -O -all --nominal -S -o - | filecheck %s
+;; RUN: foreach %s %t wasm-opt -O -all --nominal --closed-world -S -o - | filecheck %s
+;; RUN: foreach %s %t wasm-opt -O -all --nominal -S -o - | filecheck %s --check-prefix OPEN_WORLD
;; Test that -O, with nominal typing + GC enabled, will run global type
-;; optimization in conjunction with constant field propagation etc.
+;; optimization in conjunction with constant field propagation etc. But, in an
+;; open world we do not run them.
(module
+ ;; OPEN_WORLD: (type $struct (struct (field (mut funcref)) (field (mut i32))))
(type $struct (struct_subtype (field (mut funcref)) (field (mut i32)) data))
+ ;; OPEN_WORLD: (type $none_=>_none (func))
+
+ ;; OPEN_WORLD: (type $none_=>_i32 (func (result i32)))
+
+ ;; OPEN_WORLD: (global $glob (ref $struct) (struct.new $struct
+ ;; OPEN_WORLD-NEXT: (ref.func $by-ref)
+ ;; OPEN_WORLD-NEXT: (i32.const 100)
+ ;; OPEN_WORLD-NEXT: ))
(global $glob (ref $struct) (struct.new $struct
(ref.func $by-ref)
(i32.const 100)
))
+ ;; OPEN_WORLD: (export "main" (func $main))
+
+ ;; OPEN_WORLD: (func $by-ref (type $none_=>_none) (; has Stack IR ;)
+ ;; OPEN_WORLD-NEXT: (struct.set $struct 1
+ ;; OPEN_WORLD-NEXT: (global.get $glob)
+ ;; OPEN_WORLD-NEXT: (i32.const 200)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: )
(func $by-ref
;; This function is kept alive by the reference in $glob. After we remove
;; the field that the funcref is written to, we remove the funcref, which
@@ -33,6 +52,11 @@
;; CHECK: (func $main (type $none_=>_i32) (; has Stack IR ;) (result i32)
;; CHECK-NEXT: (i32.const 100)
;; CHECK-NEXT: )
+ ;; OPEN_WORLD: (func $main (type $none_=>_i32) (; has Stack IR ;) (result i32)
+ ;; OPEN_WORLD-NEXT: (struct.get $struct 1
+ ;; OPEN_WORLD-NEXT: (global.get $glob)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: )
(func $main (export "main") (result i32)
;; After all the above optimizations, we can infer that $main should simply
;; return 100.
diff --git a/test/lit/passes/remove-unused-module-elements-refs.wast b/test/lit/passes/remove-unused-module-elements-refs.wast
index 1e23099b9..8e4ed70a7 100644
--- a/test/lit/passes/remove-unused-module-elements-refs.wast
+++ b/test/lit/passes/remove-unused-module-elements-refs.wast
@@ -1,12 +1,22 @@
;; 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 --nominal -all -S -o - | filecheck %s
+;; RUN: foreach %s %t wasm-opt --remove-unused-module-elements --closed-world --nominal -all -S -o - | filecheck %s
+;; RUN: foreach %s %t wasm-opt --remove-unused-module-elements --nominal -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 $A (func))
+ ;; OPEN_WORLD: (type $A (func))
(type $A (func))
+
;; CHECK: (type $ref?|$A|_=>_none (func (param (ref null $A))))
;; CHECK: (type $B (func))
+ ;; OPEN_WORLD: (type $ref?|$A|_=>_none (func (param (ref null $A))))
+
+ ;; OPEN_WORLD: (type $B (func))
(type $B (func))
;; CHECK: (elem declare func $target-A $target-B)
@@ -30,6 +40,27 @@
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
+ ;; OPEN_WORLD: (elem declare func $target-A $target-B)
+
+ ;; OPEN_WORLD: (export "foo" (func $foo))
+
+ ;; OPEN_WORLD: (func $foo (type $ref?|$A|_=>_none) (param $A (ref null $A))
+ ;; OPEN_WORLD-NEXT: (drop
+ ;; OPEN_WORLD-NEXT: (ref.func $target-A)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: (drop
+ ;; OPEN_WORLD-NEXT: (ref.func $target-B)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: (call_ref $A
+ ;; OPEN_WORLD-NEXT: (local.get $A)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: (block ;; (replaces something unreachable we can't emit)
+ ;; OPEN_WORLD-NEXT: (drop
+ ;; OPEN_WORLD-NEXT: (unreachable)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: (unreachable)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: )
(func $foo (export "foo") (param $A (ref null $A))
;; This export has two RefFuncs, and one CallRef.
(drop
@@ -51,6 +82,9 @@
;; CHECK: (func $target-A (type $A)
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
+ ;; OPEN_WORLD: (func $target-A (type $A)
+ ;; OPEN_WORLD-NEXT: (nop)
+ ;; OPEN_WORLD-NEXT: )
(func $target-A (type $A)
;; This function is reachable from the export "foo": there is a RefFunc and
;; a CallRef for it there.
@@ -64,6 +98,9 @@
;; CHECK: (func $target-B (type $B)
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
+ ;; OPEN_WORLD: (func $target-B (type $B)
+ ;; OPEN_WORLD-NEXT: (nop)
+ ;; OPEN_WORLD-NEXT: )
(func $target-B (type $B)
;; This function is not reachable. We have a RefFunc in "foo" but no
;; suitable CallRef.
@@ -71,33 +108,51 @@
;; Note that we cannot remove the function, as the RefFunc must refer to
;; something in order to validate. But we can clear out the body of this
;; function with an unreachable.
+ ;;
+ ;; As mentioned above, in an open world we cannot optimize here, so the
+ ;; function body will remain empty as a nop, and not turn into an
+ ;; unreachable.
)
)
;; As above, but reverse the order inside $foo, so we see the CallRef first.
(module
;; CHECK: (type $A (func))
+ ;; OPEN_WORLD: (type $A (func))
(type $A (func))
(type $B (func))
+ ;; CHECK: (type $ref?|$A|_=>_none (func (param (ref null $A))))
+
;; CHECK: (elem declare func $target-A)
;; CHECK: (export "foo" (func $foo))
- ;; CHECK: (func $foo (type $A)
- ;; CHECK-NEXT: (block ;; (replaces something unreachable we can't emit)
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.null nofunc)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (unreachable)
+ ;; CHECK: (func $foo (type $ref?|$A|_=>_none) (param $A (ref null $A))
+ ;; CHECK-NEXT: (call_ref $A
+ ;; CHECK-NEXT: (local.get $A)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.func $target-A)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
- (func $foo (export "foo")
+ ;; OPEN_WORLD: (type $ref?|$A|_=>_none (func (param (ref null $A))))
+
+ ;; OPEN_WORLD: (elem declare func $target-A)
+
+ ;; OPEN_WORLD: (export "foo" (func $foo))
+
+ ;; OPEN_WORLD: (func $foo (type $ref?|$A|_=>_none) (param $A (ref null $A))
+ ;; OPEN_WORLD-NEXT: (call_ref $A
+ ;; OPEN_WORLD-NEXT: (local.get $A)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: (drop
+ ;; OPEN_WORLD-NEXT: (ref.func $target-A)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: )
+ (func $foo (export "foo") (param $A (ref null $A))
(call_ref $A
- (ref.null $A)
+ (local.get $A)
)
(drop
(ref.func $target-A)
@@ -105,8 +160,11 @@
)
;; CHECK: (func $target-A (type $A)
- ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
+ ;; OPEN_WORLD: (func $target-A (type $A)
+ ;; OPEN_WORLD-NEXT: (nop)
+ ;; OPEN_WORLD-NEXT: )
(func $target-A (type $A)
;; This function is reachable.
)
@@ -119,6 +177,7 @@
;; As above, but interleave CallRefs with RefFuncs.
(module
;; CHECK: (type $A (func))
+ ;; OPEN_WORLD: (type $A (func))
(type $A (func))
(type $B (func))
@@ -142,6 +201,26 @@
;; CHECK-NEXT: (ref.func $target-A-2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
+ ;; OPEN_WORLD: (type $ref?|$A|_=>_none (func (param (ref null $A))))
+
+ ;; OPEN_WORLD: (elem declare func $target-A-1 $target-A-2)
+
+ ;; OPEN_WORLD: (export "foo" (func $foo))
+
+ ;; OPEN_WORLD: (func $foo (type $ref?|$A|_=>_none) (param $A (ref null $A))
+ ;; OPEN_WORLD-NEXT: (call_ref $A
+ ;; OPEN_WORLD-NEXT: (local.get $A)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: (drop
+ ;; OPEN_WORLD-NEXT: (ref.func $target-A-1)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: (call_ref $A
+ ;; OPEN_WORLD-NEXT: (local.get $A)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: (drop
+ ;; OPEN_WORLD-NEXT: (ref.func $target-A-2)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: )
(func $foo (export "foo") (param $A (ref null $A))
(call_ref $A
(local.get $A)
@@ -160,6 +239,9 @@
;; CHECK: (func $target-A-1 (type $A)
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
+ ;; OPEN_WORLD: (func $target-A-1 (type $A)
+ ;; OPEN_WORLD-NEXT: (nop)
+ ;; OPEN_WORLD-NEXT: )
(func $target-A-1 (type $A)
;; This function is reachable.
)
@@ -167,6 +249,9 @@
;; CHECK: (func $target-A-2 (type $A)
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
+ ;; OPEN_WORLD: (func $target-A-2 (type $A)
+ ;; OPEN_WORLD-NEXT: (nop)
+ ;; OPEN_WORLD-NEXT: )
(func $target-A-2 (type $A)
;; This function is reachable.
)
@@ -180,6 +265,7 @@
;; same.
(module
;; CHECK: (type $A (func))
+ ;; OPEN_WORLD: (type $A (func))
(type $A (func))
(type $B (func))
@@ -203,6 +289,26 @@
;; CHECK-NEXT: (local.get $A)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
+ ;; OPEN_WORLD: (type $ref?|$A|_=>_none (func (param (ref null $A))))
+
+ ;; OPEN_WORLD: (elem declare func $target-A-1 $target-A-2)
+
+ ;; OPEN_WORLD: (export "foo" (func $foo))
+
+ ;; OPEN_WORLD: (func $foo (type $ref?|$A|_=>_none) (param $A (ref null $A))
+ ;; OPEN_WORLD-NEXT: (drop
+ ;; OPEN_WORLD-NEXT: (ref.func $target-A-1)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: (call_ref $A
+ ;; OPEN_WORLD-NEXT: (local.get $A)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: (drop
+ ;; OPEN_WORLD-NEXT: (ref.func $target-A-2)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: (call_ref $A
+ ;; OPEN_WORLD-NEXT: (local.get $A)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: )
(func $foo (export "foo") (param $A (ref null $A))
(drop
(ref.func $target-A-1)
@@ -221,6 +327,9 @@
;; CHECK: (func $target-A-1 (type $A)
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
+ ;; OPEN_WORLD: (func $target-A-1 (type $A)
+ ;; OPEN_WORLD-NEXT: (nop)
+ ;; OPEN_WORLD-NEXT: )
(func $target-A-1 (type $A)
;; This function is reachable.
)
@@ -228,6 +337,9 @@
;; CHECK: (func $target-A-2 (type $A)
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
+ ;; OPEN_WORLD: (func $target-A-2 (type $A)
+ ;; OPEN_WORLD-NEXT: (nop)
+ ;; OPEN_WORLD-NEXT: )
(func $target-A-2 (type $A)
;; This function is reachable.
)
@@ -241,15 +353,20 @@
;; but for now other imports do not (until we add a flag for closed-world).
(module
;; CHECK: (type $A (func))
+ ;; OPEN_WORLD: (type $A (func))
(type $A (func))
;; CHECK: (type $funcref_=>_none (func (param funcref)))
;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $call-without-effects (param funcref)))
+ ;; OPEN_WORLD: (type $funcref_=>_none (func (param funcref)))
+
+ ;; OPEN_WORLD: (import "binaryen-intrinsics" "call.without.effects" (func $call-without-effects (param funcref)))
(import "binaryen-intrinsics" "call.without.effects"
(func $call-without-effects (param funcref)))
;; CHECK: (import "other" "import" (func $other-import (param funcref)))
+ ;; OPEN_WORLD: (import "other" "import" (func $other-import (param funcref)))
(import "other" "import"
(func $other-import (param funcref)))
@@ -265,6 +382,18 @@
;; CHECK-NEXT: (ref.func $target-drop)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
+ ;; OPEN_WORLD: (elem declare func $target-drop $target-keep)
+
+ ;; OPEN_WORLD: (export "foo" (func $foo))
+
+ ;; OPEN_WORLD: (func $foo (type $A)
+ ;; OPEN_WORLD-NEXT: (call $call-without-effects
+ ;; OPEN_WORLD-NEXT: (ref.func $target-keep)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: (call $other-import
+ ;; OPEN_WORLD-NEXT: (ref.func $target-drop)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: )
(func $foo (export "foo")
;; Calling the intrinsic with a reference is considered a call of the
;; reference, so we will not remove $target-keep.
@@ -280,13 +409,20 @@
;; CHECK: (func $target-keep (type $A)
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
+ ;; OPEN_WORLD: (func $target-keep (type $A)
+ ;; OPEN_WORLD-NEXT: (nop)
+ ;; OPEN_WORLD-NEXT: )
(func $target-keep (type $A)
)
;; CHECK: (func $target-drop (type $A)
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
+ ;; OPEN_WORLD: (func $target-drop (type $A)
+ ;; OPEN_WORLD-NEXT: (nop)
+ ;; OPEN_WORLD-NEXT: )
(func $target-drop (type $A)
+ ;; In a closed world we can turn this body into unreachable.
)
)
@@ -294,6 +430,7 @@
;; function being called.
(module
;; CHECK: (type $A (func))
+ ;; OPEN_WORLD: (type $A (func))
(type $A (func))
;; CHECK: (type $funcref_=>_none (func (param funcref)))
@@ -301,10 +438,16 @@
;; CHECK: (type $ref?|$A|_=>_none (func (param (ref null $A))))
;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $call-without-effects (param funcref)))
+ ;; OPEN_WORLD: (type $funcref_=>_none (func (param funcref)))
+
+ ;; OPEN_WORLD: (type $ref?|$A|_=>_none (func (param (ref null $A))))
+
+ ;; OPEN_WORLD: (import "binaryen-intrinsics" "call.without.effects" (func $call-without-effects (param funcref)))
(import "binaryen-intrinsics" "call.without.effects"
(func $call-without-effects (param funcref)))
;; CHECK: (import "other" "import" (func $other-import (param funcref)))
+ ;; OPEN_WORLD: (import "other" "import" (func $other-import (param funcref)))
(import "other" "import"
(func $other-import (param funcref)))
@@ -323,6 +466,21 @@
;; CHECK-NEXT: (ref.func $target-keep-2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
+ ;; OPEN_WORLD: (elem declare func $target-keep $target-keep-2)
+
+ ;; OPEN_WORLD: (export "foo" (func $foo))
+
+ ;; OPEN_WORLD: (func $foo (type $ref?|$A|_=>_none) (param $A (ref null $A))
+ ;; OPEN_WORLD-NEXT: (call $call-without-effects
+ ;; OPEN_WORLD-NEXT: (local.get $A)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: (drop
+ ;; OPEN_WORLD-NEXT: (ref.func $target-keep)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: (call $other-import
+ ;; OPEN_WORLD-NEXT: (ref.func $target-keep-2)
+ ;; OPEN_WORLD-NEXT: )
+ ;; OPEN_WORLD-NEXT: )
(func $foo (export "foo") (param $A (ref null $A))
;; Call the intrinsic without a RefFunc. All we infer here is the type,
;; which means we must assume anything with type $A (and a reference) can be
@@ -341,12 +499,18 @@
;; CHECK: (func $target-keep (type $A)
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
+ ;; OPEN_WORLD: (func $target-keep (type $A)
+ ;; OPEN_WORLD-NEXT: (nop)
+ ;; OPEN_WORLD-NEXT: )
(func $target-keep (type $A)
)
;; CHECK: (func $target-keep-2 (type $A)
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
+ ;; OPEN_WORLD: (func $target-keep-2 (type $A)
+ ;; OPEN_WORLD-NEXT: (nop)
+ ;; OPEN_WORLD-NEXT: )
(func $target-keep-2 (type $A)
)
)
diff --git a/test/passes/Oz_fuzz-exec_all-features.txt b/test/passes/Oz_fuzz-exec_all-features.txt
index aa8d3d6c9..f67fc31c3 100644
--- a/test/passes/Oz_fuzz-exec_all-features.txt
+++ b/test/passes/Oz_fuzz-exec_all-features.txt
@@ -69,17 +69,13 @@
[fuzz-exec] calling static-br_on_cast_fail
[LoggingExternalInterface logging -2]
(module
- (rec
- (type $bytes (array (mut i8)))
- (type $struct (struct (field (mut i32))))
- (type $void_func (func))
- (type $extendedstruct (struct ))
- (type $int_func (func (result i32)))
- (type $i32_=>_none (func (param i32)))
- (type $none_=>_ref|$struct| (func (result (ref $struct))))
- (type $anyref_=>_none (func (param anyref)))
- (type $none_=>_none (func))
- )
+ (type $bytes (array (mut i8)))
+ (type $void_func (func))
+ (type $struct (struct (field (mut i32))))
+ (type $i32_=>_none (func (param i32)))
+ (type $extendedstruct (struct (field (mut i32)) (field f64)))
+ (type $anyref_=>_none (func (param anyref)))
+ (type $int_func (func (result i32)))
(import "fuzzing-support" "log-i32" (func $log (param i32)))
(export "structs" (func $0))
(export "arrays" (func $1))
diff --git a/test/unit/test_passes.py b/test/unit/test_passes.py
new file mode 100644
index 000000000..2fe7f6263
--- /dev/null
+++ b/test/unit/test_passes.py
@@ -0,0 +1,49 @@
+import os
+import re
+import subprocess
+from scripts.test import shared
+from . import utils
+
+
+class PassesTest(utils.BinaryenTestCase):
+ # Given some arguments, return the passes that were run.
+ def get_passes_run(self, args):
+ os.environ['BINARYEN_PASS_DEBUG'] = '1'
+ try:
+ hello_wat = self.input_path('hello_world.wat')
+ log = shared.run_process(shared.WASM_OPT + [hello_wat] + args,
+ stderr=subprocess.PIPE).stderr
+ print(log)
+ passes = re.findall(r'running pass: ([\w-]+)\.\.\.', log)
+ return passes
+ finally:
+ del os.environ['BINARYEN_PASS_DEBUG']
+
+ def test_O2(self):
+ args = ['-O2', '-all']
+ for nominal in ['--nominal', False]:
+ for closed_world in ['--closed-world', False]:
+ curr_args = args[:]
+ if nominal:
+ curr_args.append(nominal)
+ if closed_world:
+ curr_args.append(closed_world)
+ passes = self.get_passes_run(curr_args)
+
+ # dce always runs
+ self.assertIn('dce', passes)
+
+ # some passes only run in closed world
+ CLOSED_WORLD_PASSES = [
+ 'type-refining',
+ 'signature-pruning',
+ 'signature-refining',
+ 'gto',
+ 'cfp',
+ 'gsi',
+ ]
+ for pass_ in CLOSED_WORLD_PASSES:
+ if closed_world:
+ self.assertIn(pass_, passes)
+ else:
+ self.assertNotIn(pass_, passes)