From ceb338140a7e14d03103f6c31278b9bfca21c4d0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 13 Nov 2025 10:48:22 -0800 Subject: [PATCH 1/6] test --- .../passes/remove-unused-brs-intrinsics.wast | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 test/lit/passes/remove-unused-brs-intrinsics.wast diff --git a/test/lit/passes/remove-unused-brs-intrinsics.wast b/test/lit/passes/remove-unused-brs-intrinsics.wast new file mode 100644 index 00000000000..2cda3653408 --- /dev/null +++ b/test/lit/passes/remove-unused-brs-intrinsics.wast @@ -0,0 +1,69 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --remove-unused-brs --shrink-level=0 -all -S -o - | filecheck %s --check-prefix=SHRINK0 +;; RUN: wasm-opt %s --remove-unused-brs --shrink-level=1 -all -S -o - | filecheck %s --check-prefix=SHRINK1 +;; RUN: wasm-opt %s --remove-unused-brs --shrink-level=2 -all -S -o - | filecheck %s --check-prefix=SHRINK2 + +(module + ;; SHRINK0: (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (type $0) (param funcref) (result i32))) + ;; SHRINK1: (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (type $0) (param funcref) (result i32))) + ;; SHRINK2: (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (type $0) (param funcref) (result i32))) + (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (param funcref) (result i32))) + + ;; SHRINK0: (func $no-selectify (type $1) (param $x i32) (result i32) + ;; SHRINK0-NEXT: (if (result i32) + ;; SHRINK0-NEXT: (local.get $x) + ;; SHRINK0-NEXT: (then + ;; SHRINK0-NEXT: (call $call.without.effects + ;; SHRINK0-NEXT: (ref.func $ret) + ;; SHRINK0-NEXT: ) + ;; SHRINK0-NEXT: ) + ;; SHRINK0-NEXT: (else + ;; SHRINK0-NEXT: (i32.const 42) + ;; SHRINK0-NEXT: ) + ;; SHRINK0-NEXT: ) + ;; SHRINK0-NEXT: ) + ;; SHRINK1: (func $no-selectify (type $1) (param $x i32) (result i32) + ;; SHRINK1-NEXT: (select + ;; SHRINK1-NEXT: (call $call.without.effects + ;; SHRINK1-NEXT: (ref.func $ret) + ;; SHRINK1-NEXT: ) + ;; SHRINK1-NEXT: (i32.const 42) + ;; SHRINK1-NEXT: (local.get $x) + ;; SHRINK1-NEXT: ) + ;; SHRINK1-NEXT: ) + ;; SHRINK2: (func $no-selectify (type $1) (param $x i32) (result i32) + ;; SHRINK2-NEXT: (select + ;; SHRINK2-NEXT: (call $call.without.effects + ;; SHRINK2-NEXT: (ref.func $ret) + ;; SHRINK2-NEXT: ) + ;; SHRINK2-NEXT: (i32.const 42) + ;; SHRINK2-NEXT: (local.get $x) + ;; SHRINK2-NEXT: ) + ;; SHRINK2-NEXT: ) + (func $no-selectify (param $x i32) (result i32) + ;; The call to $ret has no effects, so we can selectify here in theory. + ;; The cost of a call prevents it, however, when not shrinking at level 2. + (if (result i32) + (local.get $x) + (then + (call $call.without.effects (ref.func $ret)) + ) + (else + (i32.const 42) + ) + ) + ) + + ;; SHRINK0: (func $ret (type $2) (result i32) + ;; SHRINK0-NEXT: (i32.const 1337) + ;; SHRINK0-NEXT: ) + ;; SHRINK1: (func $ret (type $2) (result i32) + ;; SHRINK1-NEXT: (i32.const 1337) + ;; SHRINK1-NEXT: ) + ;; SHRINK2: (func $ret (type $2) (result i32) + ;; SHRINK2-NEXT: (i32.const 1337) + ;; SHRINK2-NEXT: ) + (func $ret (result i32) + (i32.const 1337) + ) +) From 1ce1b17cec31e9fa853b7f4ad98f53bdd27997c4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 13 Nov 2025 10:50:35 -0800 Subject: [PATCH 2/6] fix --- src/ir/cost.h | 16 +++++++++++----- .../passes/remove-unused-brs-intrinsics.wast | 17 +++++++++++------ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/ir/cost.h b/src/ir/cost.h index 6f15731f1aa..ecba03f48b5 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -47,6 +47,12 @@ struct CostAnalyzer : public OverriddenVisitor { // cost due to shortening the time to the next collection. static const CostType AllocationCost = 100; + // Calls can have unpredictable, unknown cost. Model it as a large number. + // TODO: For calls to functions in this module, we could in principle scan + // them. However, call effects generally mean the cost is a moot point, + // except for call.without.effects, which is rare. + static const CostType CallCost = 100; + CostType maybeVisit(Expression* curr) { return curr ? visit(curr) : 0; } CostType visitBlock(Block* curr) { @@ -68,23 +74,23 @@ struct CostAnalyzer : public OverriddenVisitor { return 2 + visit(curr->condition) + maybeVisit(curr->value); } CostType visitCall(Call* curr) { - // XXX this does not take into account if the call is to an import, which - // may be costlier in general - CostType ret = 4; + CostType ret = CallCost; for (auto* child : curr->operands) { ret += visit(child); } return ret; } CostType visitCallIndirect(CallIndirect* curr) { - CostType ret = 6 + visit(curr->target); + // Model indirect calls as more expensive than call_refs. + CostType ret = CallCost + 2 + visit(curr->target); for (auto* child : curr->operands) { ret += visit(child); } return ret; } CostType visitCallRef(CallRef* curr) { - CostType ret = 5 + visit(curr->target); + // Model call_refs as more expensive than direct calls. + CostType ret = CallCost + 1 + visit(curr->target); for (auto* child : curr->operands) { ret += visit(child); } diff --git a/test/lit/passes/remove-unused-brs-intrinsics.wast b/test/lit/passes/remove-unused-brs-intrinsics.wast index 2cda3653408..bb9b2256059 100644 --- a/test/lit/passes/remove-unused-brs-intrinsics.wast +++ b/test/lit/passes/remove-unused-brs-intrinsics.wast @@ -23,12 +23,16 @@ ;; SHRINK0-NEXT: ) ;; SHRINK0-NEXT: ) ;; SHRINK1: (func $no-selectify (type $1) (param $x i32) (result i32) - ;; SHRINK1-NEXT: (select - ;; SHRINK1-NEXT: (call $call.without.effects - ;; SHRINK1-NEXT: (ref.func $ret) - ;; SHRINK1-NEXT: ) - ;; SHRINK1-NEXT: (i32.const 42) + ;; SHRINK1-NEXT: (if (result i32) ;; SHRINK1-NEXT: (local.get $x) + ;; SHRINK1-NEXT: (then + ;; SHRINK1-NEXT: (call $call.without.effects + ;; SHRINK1-NEXT: (ref.func $ret) + ;; SHRINK1-NEXT: ) + ;; SHRINK1-NEXT: ) + ;; SHRINK1-NEXT: (else + ;; SHRINK1-NEXT: (i32.const 42) + ;; SHRINK1-NEXT: ) ;; SHRINK1-NEXT: ) ;; SHRINK1-NEXT: ) ;; SHRINK2: (func $no-selectify (type $1) (param $x i32) (result i32) @@ -42,7 +46,8 @@ ;; SHRINK2-NEXT: ) (func $no-selectify (param $x i32) (result i32) ;; The call to $ret has no effects, so we can selectify here in theory. - ;; The cost of a call prevents it, however, when not shrinking at level 2. + ;; The cost of a call prevents it, however, when not shrinking at level 2 + ;; (2 means "shrink at all costs", and this does shrink). (if (result i32) (local.get $x) (then From 8dd40e606c0ee13425ea921ea733b5b3dc1d2349 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 13 Nov 2025 11:14:43 -0800 Subject: [PATCH 3/6] update.test --- CHANGELOG.md | 6 + test/lit/passes/monomorphize-benefit.wast | 295 +++++++++++++++------- 2 files changed, 209 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 074acfba2fa..994aa33a583 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,12 @@ Current Trunk - The --mod-asyncify-never-unwind and --mod-asyncify-always-and-only-unwind passed were deleted. They only existed to support the lazy code loading support in emscripten that was removed. (#7893) + - The cost modeling of calls was increased (see #8044 for background). Calls + now have a high cost, which is normally not noticeable (as calls have effects + that usually make their cost moot), but you may notice this when using + call.without.effects (calls will no longer be assumed to be cheap enough to + run unconditionally) or monomorphize (which inputs a cost factor as a + number). v124 ---- diff --git a/test/lit/passes/monomorphize-benefit.wast b/test/lit/passes/monomorphize-benefit.wast index a5c5e689f39..ca929a47b39 100644 --- a/test/lit/passes/monomorphize-benefit.wast +++ b/test/lit/passes/monomorphize-benefit.wast @@ -986,70 +986,113 @@ (import "a" "c" (func $import2 (param (ref $A)))) ;; DEFAULT: (func $target-long (type $2) (param $0 anyref) (result (ref $A)) - ;; DEFAULT-NEXT: (call $import) - ;; DEFAULT-NEXT: (call $import) - ;; DEFAULT-NEXT: (call $import) - ;; DEFAULT-NEXT: (call $import) - ;; DEFAULT-NEXT: (call $import) - ;; DEFAULT-NEXT: (call $import) + ;; DEFAULT-NEXT: (local $1 i32) + ;; DEFAULT-NEXT: (local.set $1 + ;; DEFAULT-NEXT: (i32.const 1000) + ;; DEFAULT-NEXT: ) + ;; DEFAULT-NEXT: (loop $loop + ;; DEFAULT-NEXT: (br_if $loop + ;; DEFAULT-NEXT: (local.tee $1 + ;; DEFAULT-NEXT: (i32.add + ;; DEFAULT-NEXT: (local.get $1) + ;; DEFAULT-NEXT: (local.get $1) + ;; DEFAULT-NEXT: ) + ;; DEFAULT-NEXT: ) + ;; DEFAULT-NEXT: ) + ;; DEFAULT-NEXT: ) ;; DEFAULT-NEXT: (ref.cast (ref $A) ;; DEFAULT-NEXT: (local.get $0) ;; DEFAULT-NEXT: ) ;; DEFAULT-NEXT: ) ;; ZERO___: (func $target-long (type $2) (param $0 anyref) (result (ref $A)) - ;; ZERO___-NEXT: (call $import) - ;; ZERO___-NEXT: (call $import) - ;; ZERO___-NEXT: (call $import) - ;; ZERO___-NEXT: (call $import) - ;; ZERO___-NEXT: (call $import) - ;; ZERO___-NEXT: (call $import) + ;; ZERO___-NEXT: (local $1 i32) + ;; ZERO___-NEXT: (local.set $1 + ;; ZERO___-NEXT: (i32.const 1000) + ;; ZERO___-NEXT: ) + ;; ZERO___-NEXT: (loop $loop + ;; ZERO___-NEXT: (br_if $loop + ;; ZERO___-NEXT: (local.tee $1 + ;; ZERO___-NEXT: (i32.add + ;; ZERO___-NEXT: (local.get $1) + ;; ZERO___-NEXT: (local.get $1) + ;; ZERO___-NEXT: ) + ;; ZERO___-NEXT: ) + ;; ZERO___-NEXT: ) + ;; ZERO___-NEXT: ) ;; ZERO___-NEXT: (ref.cast (ref $A) ;; ZERO___-NEXT: (local.get $0) ;; ZERO___-NEXT: ) ;; ZERO___-NEXT: ) ;; THIRD__: (func $target-long (type $2) (param $0 anyref) (result (ref $A)) - ;; THIRD__-NEXT: (call $import) - ;; THIRD__-NEXT: (call $import) - ;; THIRD__-NEXT: (call $import) - ;; THIRD__-NEXT: (call $import) - ;; THIRD__-NEXT: (call $import) - ;; THIRD__-NEXT: (call $import) + ;; THIRD__-NEXT: (local $1 i32) + ;; THIRD__-NEXT: (local.set $1 + ;; THIRD__-NEXT: (i32.const 1000) + ;; THIRD__-NEXT: ) + ;; THIRD__-NEXT: (loop $loop + ;; THIRD__-NEXT: (br_if $loop + ;; THIRD__-NEXT: (local.tee $1 + ;; THIRD__-NEXT: (i32.add + ;; THIRD__-NEXT: (local.get $1) + ;; THIRD__-NEXT: (local.get $1) + ;; THIRD__-NEXT: ) + ;; THIRD__-NEXT: ) + ;; THIRD__-NEXT: ) + ;; THIRD__-NEXT: ) ;; THIRD__-NEXT: (ref.cast (ref $A) ;; THIRD__-NEXT: (local.get $0) ;; THIRD__-NEXT: ) ;; THIRD__-NEXT: ) ;; TWOTRDS: (func $target-long (type $2) (param $0 anyref) (result (ref $A)) - ;; TWOTRDS-NEXT: (call $import) - ;; TWOTRDS-NEXT: (call $import) - ;; TWOTRDS-NEXT: (call $import) - ;; TWOTRDS-NEXT: (call $import) - ;; TWOTRDS-NEXT: (call $import) - ;; TWOTRDS-NEXT: (call $import) + ;; TWOTRDS-NEXT: (local $1 i32) + ;; TWOTRDS-NEXT: (local.set $1 + ;; TWOTRDS-NEXT: (i32.const 1000) + ;; TWOTRDS-NEXT: ) + ;; TWOTRDS-NEXT: (loop $loop + ;; TWOTRDS-NEXT: (br_if $loop + ;; TWOTRDS-NEXT: (local.tee $1 + ;; TWOTRDS-NEXT: (i32.add + ;; TWOTRDS-NEXT: (local.get $1) + ;; TWOTRDS-NEXT: (local.get $1) + ;; TWOTRDS-NEXT: ) + ;; TWOTRDS-NEXT: ) + ;; TWOTRDS-NEXT: ) + ;; TWOTRDS-NEXT: ) ;; TWOTRDS-NEXT: (ref.cast (ref $A) ;; TWOTRDS-NEXT: (local.get $0) ;; TWOTRDS-NEXT: ) ;; TWOTRDS-NEXT: ) ;; HUNDRED: (func $target-long (type $1) (param $0 anyref) (result (ref $A)) - ;; HUNDRED-NEXT: (call $import) - ;; HUNDRED-NEXT: (call $import) - ;; HUNDRED-NEXT: (call $import) - ;; HUNDRED-NEXT: (call $import) - ;; HUNDRED-NEXT: (call $import) - ;; HUNDRED-NEXT: (call $import) + ;; HUNDRED-NEXT: (local $1 i32) + ;; HUNDRED-NEXT: (local.set $1 + ;; HUNDRED-NEXT: (i32.const 1000) + ;; HUNDRED-NEXT: ) + ;; HUNDRED-NEXT: (loop $loop + ;; HUNDRED-NEXT: (br_if $loop + ;; HUNDRED-NEXT: (local.tee $1 + ;; HUNDRED-NEXT: (i32.add + ;; HUNDRED-NEXT: (local.get $1) + ;; HUNDRED-NEXT: (local.get $1) + ;; HUNDRED-NEXT: ) + ;; HUNDRED-NEXT: ) + ;; HUNDRED-NEXT: ) + ;; HUNDRED-NEXT: ) ;; HUNDRED-NEXT: (ref.cast (ref $A) ;; HUNDRED-NEXT: (local.get $0) ;; HUNDRED-NEXT: ) ;; HUNDRED-NEXT: ) (func $target-long (param $any anyref) (result (ref $A)) + (local $temp i32) ;; This function does a cast, aside from a lot of other work. The other work ;; causes us to only optimize when we are ok with getting a very small % of ;; improvement. - (call $import) - (call $import) - (call $import) - (call $import) - (call $import) - (call $import) + + ;; A silly loop that might in theory end after a long time. + (local.set $temp (i32.const 1000)) + (loop $loop + (local.set $temp (i32.add (local.get $temp) (local.get $temp))) + (br_if $loop (local.get $temp)) + ) + (ref.cast (ref $A) (local.get $any) ) @@ -1514,12 +1557,20 @@ ;; DEFAULT-NEXT: ) ;; ZERO___: (func $target-long_6 (type $4) (param $0 anyref) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) +;; ZERO___-NEXT: (local $1 i32) +;; ZERO___-NEXT: (local.set $1 +;; ZERO___-NEXT: (i32.const 1000) +;; ZERO___-NEXT: ) +;; ZERO___-NEXT: (loop $loop +;; ZERO___-NEXT: (br_if $loop +;; ZERO___-NEXT: (local.tee $1 +;; ZERO___-NEXT: (i32.add +;; ZERO___-NEXT: (local.get $1) +;; ZERO___-NEXT: (local.get $1) +;; ZERO___-NEXT: ) +;; ZERO___-NEXT: ) +;; ZERO___-NEXT: ) +;; ZERO___-NEXT: ) ;; ZERO___-NEXT: ) ;; ZERO___: (func $target-long_7 (type $5) (param $0 i32) (result (ref $A)) @@ -1529,47 +1580,77 @@ ;; ZERO___-NEXT: (local.get $0) ;; ZERO___-NEXT: ) ;; ZERO___-NEXT: ) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) +;; ZERO___-NEXT: (local.set $0 +;; ZERO___-NEXT: (i32.const 1000) +;; ZERO___-NEXT: ) +;; ZERO___-NEXT: (loop $loop +;; ZERO___-NEXT: (br_if $loop +;; ZERO___-NEXT: (local.tee $0 +;; ZERO___-NEXT: (i32.add +;; ZERO___-NEXT: (local.get $0) +;; ZERO___-NEXT: (local.get $0) +;; ZERO___-NEXT: ) +;; ZERO___-NEXT: ) +;; ZERO___-NEXT: ) +;; ZERO___-NEXT: ) ;; ZERO___-NEXT: (local.get $1) ;; ZERO___-NEXT: ) ;; ZERO___: (func $target-long_8 (type $6) (param $0 i32) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) +;; ZERO___-NEXT: (local.set $0 +;; ZERO___-NEXT: (i32.const 1000) +;; ZERO___-NEXT: ) +;; ZERO___-NEXT: (loop $loop +;; ZERO___-NEXT: (br_if $loop +;; ZERO___-NEXT: (local.tee $0 +;; ZERO___-NEXT: (i32.add +;; ZERO___-NEXT: (local.get $0) +;; ZERO___-NEXT: (local.get $0) +;; ZERO___-NEXT: ) +;; ZERO___-NEXT: ) +;; ZERO___-NEXT: ) +;; ZERO___-NEXT: ) ;; ZERO___-NEXT: ) ;; ZERO___: (func $target-long_9 (type $7) (result (ref $A)) -;; ZERO___-NEXT: (local $0 (ref (exact $A))) +;; ZERO___-NEXT: (local $0 i32) +;; ZERO___-NEXT: (local $1 (ref (exact $A))) ;; ZERO___-NEXT: (local.set $0 +;; ZERO___-NEXT: (i32.const 1000) +;; ZERO___-NEXT: ) +;; ZERO___-NEXT: (loop $loop +;; ZERO___-NEXT: (br_if $loop +;; ZERO___-NEXT: (local.tee $0 +;; ZERO___-NEXT: (i32.add +;; ZERO___-NEXT: (local.get $0) +;; ZERO___-NEXT: (local.get $0) +;; ZERO___-NEXT: ) +;; ZERO___-NEXT: ) +;; ZERO___-NEXT: ) +;; ZERO___-NEXT: ) +;; ZERO___-NEXT: (local.set $1 ;; ZERO___-NEXT: (struct.new $A ;; ZERO___-NEXT: (i32.const 42) ;; ZERO___-NEXT: ) ;; ZERO___-NEXT: ) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (local.get $0) +;; ZERO___-NEXT: (local.get $1) ;; ZERO___-NEXT: ) ;; ZERO___: (func $target-long_10 (type $1) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) -;; ZERO___-NEXT: (call $import) +;; ZERO___-NEXT: (local $0 i32) +;; ZERO___-NEXT: (local.set $0 +;; ZERO___-NEXT: (i32.const 1000) +;; ZERO___-NEXT: ) +;; ZERO___-NEXT: (loop $loop +;; ZERO___-NEXT: (br_if $loop +;; ZERO___-NEXT: (local.tee $0 +;; ZERO___-NEXT: (i32.add +;; ZERO___-NEXT: (local.get $0) +;; ZERO___-NEXT: (local.get $0) +;; ZERO___-NEXT: ) +;; ZERO___-NEXT: ) +;; ZERO___-NEXT: ) +;; ZERO___-NEXT: ) ;; ZERO___-NEXT: ) ;; ZERO___: (func $target-short_11 (type $4) (param $0 anyref) @@ -1597,21 +1678,36 @@ ;; ZERO___-NEXT: ) ;; THIRD__: (func $target-long_6 (type $4) (param $0 i32) -;; THIRD__-NEXT: (call $import) -;; THIRD__-NEXT: (call $import) -;; THIRD__-NEXT: (call $import) -;; THIRD__-NEXT: (call $import) -;; THIRD__-NEXT: (call $import) -;; THIRD__-NEXT: (call $import) +;; THIRD__-NEXT: (local.set $0 +;; THIRD__-NEXT: (i32.const 1000) +;; THIRD__-NEXT: ) +;; THIRD__-NEXT: (loop $loop +;; THIRD__-NEXT: (br_if $loop +;; THIRD__-NEXT: (local.tee $0 +;; THIRD__-NEXT: (i32.add +;; THIRD__-NEXT: (local.get $0) +;; THIRD__-NEXT: (local.get $0) +;; THIRD__-NEXT: ) +;; THIRD__-NEXT: ) +;; THIRD__-NEXT: ) +;; THIRD__-NEXT: ) ;; THIRD__-NEXT: ) ;; THIRD__: (func $target-long_7 (type $1) -;; THIRD__-NEXT: (call $import) -;; THIRD__-NEXT: (call $import) -;; THIRD__-NEXT: (call $import) -;; THIRD__-NEXT: (call $import) -;; THIRD__-NEXT: (call $import) -;; THIRD__-NEXT: (call $import) +;; THIRD__-NEXT: (local $0 i32) +;; THIRD__-NEXT: (local.set $0 +;; THIRD__-NEXT: (i32.const 1000) +;; THIRD__-NEXT: ) +;; THIRD__-NEXT: (loop $loop +;; THIRD__-NEXT: (br_if $loop +;; THIRD__-NEXT: (local.tee $0 +;; THIRD__-NEXT: (i32.add +;; THIRD__-NEXT: (local.get $0) +;; THIRD__-NEXT: (local.get $0) +;; THIRD__-NEXT: ) +;; THIRD__-NEXT: ) +;; THIRD__-NEXT: ) +;; THIRD__-NEXT: ) ;; THIRD__-NEXT: ) ;; THIRD__: (func $target-short_8 (type $6) (param $0 anyref) @@ -1627,21 +1723,36 @@ ;; THIRD__-NEXT: ) ;; TWOTRDS: (func $target-long_6 (type $4) (param $0 i32) -;; TWOTRDS-NEXT: (call $import) -;; TWOTRDS-NEXT: (call $import) -;; TWOTRDS-NEXT: (call $import) -;; TWOTRDS-NEXT: (call $import) -;; TWOTRDS-NEXT: (call $import) -;; TWOTRDS-NEXT: (call $import) +;; TWOTRDS-NEXT: (local.set $0 +;; TWOTRDS-NEXT: (i32.const 1000) +;; TWOTRDS-NEXT: ) +;; TWOTRDS-NEXT: (loop $loop +;; TWOTRDS-NEXT: (br_if $loop +;; TWOTRDS-NEXT: (local.tee $0 +;; TWOTRDS-NEXT: (i32.add +;; TWOTRDS-NEXT: (local.get $0) +;; TWOTRDS-NEXT: (local.get $0) +;; TWOTRDS-NEXT: ) +;; TWOTRDS-NEXT: ) +;; TWOTRDS-NEXT: ) +;; TWOTRDS-NEXT: ) ;; TWOTRDS-NEXT: ) ;; TWOTRDS: (func $target-long_7 (type $1) -;; TWOTRDS-NEXT: (call $import) -;; TWOTRDS-NEXT: (call $import) -;; TWOTRDS-NEXT: (call $import) -;; TWOTRDS-NEXT: (call $import) -;; TWOTRDS-NEXT: (call $import) -;; TWOTRDS-NEXT: (call $import) +;; TWOTRDS-NEXT: (local $0 i32) +;; TWOTRDS-NEXT: (local.set $0 +;; TWOTRDS-NEXT: (i32.const 1000) +;; TWOTRDS-NEXT: ) +;; TWOTRDS-NEXT: (loop $loop +;; TWOTRDS-NEXT: (br_if $loop +;; TWOTRDS-NEXT: (local.tee $0 +;; TWOTRDS-NEXT: (i32.add +;; TWOTRDS-NEXT: (local.get $0) +;; TWOTRDS-NEXT: (local.get $0) +;; TWOTRDS-NEXT: ) +;; TWOTRDS-NEXT: ) +;; TWOTRDS-NEXT: ) +;; TWOTRDS-NEXT: ) ;; TWOTRDS-NEXT: ) ;; TWOTRDS: (func $target-short_8 (type $6) (param $0 anyref) From 5ee174b63bc2a06918e837ad06bd1e9d4945b49e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 13 Nov 2025 11:21:31 -0800 Subject: [PATCH 4/6] test --- .../passes/remove-unused-brs-intrinsics.wast | 60 ++++++++++++++++--- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/test/lit/passes/remove-unused-brs-intrinsics.wast b/test/lit/passes/remove-unused-brs-intrinsics.wast index bb9b2256059..29e3be6e434 100644 --- a/test/lit/passes/remove-unused-brs-intrinsics.wast +++ b/test/lit/passes/remove-unused-brs-intrinsics.wast @@ -4,12 +4,12 @@ ;; RUN: wasm-opt %s --remove-unused-brs --shrink-level=2 -all -S -o - | filecheck %s --check-prefix=SHRINK2 (module - ;; SHRINK0: (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (type $0) (param funcref) (result i32))) - ;; SHRINK1: (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (type $0) (param funcref) (result i32))) - ;; SHRINK2: (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (type $0) (param funcref) (result i32))) + ;; SHRINK0: (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (type $1) (param funcref) (result i32))) + ;; SHRINK1: (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (type $1) (param funcref) (result i32))) + ;; SHRINK2: (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (type $1) (param funcref) (result i32))) (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (param funcref) (result i32))) - ;; SHRINK0: (func $no-selectify (type $1) (param $x i32) (result i32) + ;; SHRINK0: (func $if-call-no-effects (type $0) (param $x i32) (result i32) ;; SHRINK0-NEXT: (if (result i32) ;; SHRINK0-NEXT: (local.get $x) ;; SHRINK0-NEXT: (then @@ -22,7 +22,7 @@ ;; SHRINK0-NEXT: ) ;; SHRINK0-NEXT: ) ;; SHRINK0-NEXT: ) - ;; SHRINK1: (func $no-selectify (type $1) (param $x i32) (result i32) + ;; SHRINK1: (func $if-call-no-effects (type $0) (param $x i32) (result i32) ;; SHRINK1-NEXT: (if (result i32) ;; SHRINK1-NEXT: (local.get $x) ;; SHRINK1-NEXT: (then @@ -35,7 +35,7 @@ ;; SHRINK1-NEXT: ) ;; SHRINK1-NEXT: ) ;; SHRINK1-NEXT: ) - ;; SHRINK2: (func $no-selectify (type $1) (param $x i32) (result i32) + ;; SHRINK2: (func $if-call-no-effects (type $0) (param $x i32) (result i32) ;; SHRINK2-NEXT: (select ;; SHRINK2-NEXT: (call $call.without.effects ;; SHRINK2-NEXT: (ref.func $ret) @@ -44,7 +44,7 @@ ;; SHRINK2-NEXT: (local.get $x) ;; SHRINK2-NEXT: ) ;; SHRINK2-NEXT: ) - (func $no-selectify (param $x i32) (result i32) + (func $if-call-no-effects (param $x i32) (result i32) ;; The call to $ret has no effects, so we can selectify here in theory. ;; The cost of a call prevents it, however, when not shrinking at level 2 ;; (2 means "shrink at all costs", and this does shrink). @@ -59,6 +59,52 @@ ) ) + ;; SHRINK0: (func $if-call-effects (type $0) (param $x i32) (result i32) + ;; SHRINK0-NEXT: (if (result i32) + ;; SHRINK0-NEXT: (local.get $x) + ;; SHRINK0-NEXT: (then + ;; SHRINK0-NEXT: (call $ret) + ;; SHRINK0-NEXT: ) + ;; SHRINK0-NEXT: (else + ;; SHRINK0-NEXT: (i32.const 42) + ;; SHRINK0-NEXT: ) + ;; SHRINK0-NEXT: ) + ;; SHRINK0-NEXT: ) + ;; SHRINK1: (func $if-call-effects (type $0) (param $x i32) (result i32) + ;; SHRINK1-NEXT: (if (result i32) + ;; SHRINK1-NEXT: (local.get $x) + ;; SHRINK1-NEXT: (then + ;; SHRINK1-NEXT: (call $ret) + ;; SHRINK1-NEXT: ) + ;; SHRINK1-NEXT: (else + ;; SHRINK1-NEXT: (i32.const 42) + ;; SHRINK1-NEXT: ) + ;; SHRINK1-NEXT: ) + ;; SHRINK1-NEXT: ) + ;; SHRINK2: (func $if-call-effects (type $0) (param $x i32) (result i32) + ;; SHRINK2-NEXT: (if (result i32) + ;; SHRINK2-NEXT: (local.get $x) + ;; SHRINK2-NEXT: (then + ;; SHRINK2-NEXT: (call $ret) + ;; SHRINK2-NEXT: ) + ;; SHRINK2-NEXT: (else + ;; SHRINK2-NEXT: (i32.const 42) + ;; SHRINK2-NEXT: ) + ;; SHRINK2-NEXT: ) + ;; SHRINK2-NEXT: ) + (func $if-call-effects (param $x i32) (result i32) + ;; As above, but a normal call. The effects stop selectifying even in 2. + (if (result i32) + (local.get $x) + (then + (call $ret) + ) + (else + (i32.const 42) + ) + ) + ) + ;; SHRINK0: (func $ret (type $2) (result i32) ;; SHRINK0-NEXT: (i32.const 1337) ;; SHRINK0-NEXT: ) From 5a9cdb9a8b1da3a0d7e0284771c3002f292c8595 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 13 Nov 2025 11:25:47 -0800 Subject: [PATCH 5/6] changelog --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 994aa33a583..9d76e192370 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,12 +22,12 @@ Current Trunk - The --mod-asyncify-never-unwind and --mod-asyncify-always-and-only-unwind passed were deleted. They only existed to support the lazy code loading support in emscripten that was removed. (#7893) - - The cost modeling of calls was increased (see #8044 for background). Calls - now have a high cost, which is normally not noticeable (as calls have effects - that usually make their cost moot), but you may notice this when using + - The cost modeling of calls was increased to a high number. That cost is + usually not something you can notice (as calls have effects that make + removing/replacing them impossible), but you may notice this when using call.without.effects (calls will no longer be assumed to be cheap enough to run unconditionally) or monomorphize (which inputs a cost factor as a - number). + number). (#8047) v124 ---- From 9f25473aba6a34321b5329829a59308a03311c12 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 13 Nov 2025 14:37:23 -0800 Subject: [PATCH 6/6] feedback --- src/ir/cost.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ir/cost.h b/src/ir/cost.h index ecba03f48b5..175cf722e2e 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -81,8 +81,8 @@ struct CostAnalyzer : public OverriddenVisitor { return ret; } CostType visitCallIndirect(CallIndirect* curr) { - // Model indirect calls as more expensive than call_refs. - CostType ret = CallCost + 2 + visit(curr->target); + // Model indirect calls as more expensive than direct ones. + CostType ret = CallCost + 1 + visit(curr->target); for (auto* child : curr->operands) { ret += visit(child); }