diff --git a/src/ir/branch-hints.h b/src/ir/branch-hints.h new file mode 100644 index 00000000000..a15198f54a8 --- /dev/null +++ b/src/ir/branch-hints.h @@ -0,0 +1,126 @@ +/* + * Copyright 2025 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef wasm_ir_branch_hint_h +#define wasm_ir_branch_hint_h + +#include "wasm.h" + +// +// Branch hint utilities to get them, set, flip, etc. +// + +namespace wasm::BranchHints { + +// Get the branch hint for an expression. +inline std::optional get(Expression* expr, Function* func) { + auto iter = func->codeAnnotations.find(expr); + if (iter == func->codeAnnotations.end()) { + // No annotations at all. + return {}; + } + return iter->second.branchLikely; +} + +// Set the branch hint for an expression, trampling anything existing before. +inline void set(Expression* expr, std::optional likely, Function* func) { + // When we are writing an empty hint, do not create an empty annotation if one + // did not exist. + if (!likely && !func->codeAnnotations.count(expr)) { + return; + } + func->codeAnnotations[expr].branchLikely = likely; +} + +// Clear the branch hint for an expression. +inline void clear(Expression* expr, Function* func) { + // Do not create an empty annotation if one did not exist. + auto iter = func->codeAnnotations.find(expr); + if (iter == func->codeAnnotations.end()) { + return; + } + iter->second.branchLikely = {}; +} + +// Copy the branch hint for an expression to another, trampling anything +// existing before for the latter. +inline void copyTo(Expression* from, Expression* to, Function* func) { + auto fromLikely = get(from, func); + set(to, fromLikely, func); +} + +// Flip the branch hint for an expression (if it exists). +inline void flip(Expression* expr, Function* func) { + if (auto likely = get(expr, func)) { + set(expr, !*likely, func); + } +} + +// Copy the branch hint for an expression to another, flipping it while we do +// so. +inline void copyFlippedTo(Expression* from, Expression* to, Function* func) { + copyTo(from, to, func); + flip(to, func); +} + +// Given two expressions to read from, apply the AND hint to a target. That is, +// the target will be true when both inputs are true. |to| may be equal to +// |from1| or |from2|. The hint of |to| is trampled. +inline void applyAndTo(Expression* from1, + Expression* from2, + Expression* to, + Function* func) { + // If from1 and from2 are both likely, then from1 && from2 is slightly less + // likely, but we assume our hints are nearly certain, so we apply it. And, + // converse, if from1 and from2 and both unlikely, then from1 && from2 is even + // less likely, so we can once more apply a hint. If the hints differ, then + // one is unlikely or unknown, and we can't say anything about from1 && from2. + auto from1Hint = BranchHints::get(from1, func); + auto from2Hint = BranchHints::get(from2, func); + if (from1Hint == from2Hint) { + set(to, from1Hint, func); + } else { + // The hints do not even match. + BranchHints::clear(to, func); + } +} + +// As |applyAndTo|, but now the condition on |to| the OR of |from1| and |from2|. +inline void applyOrTo(Expression* from1, + Expression* from2, + Expression* to, + Function* func) { + // If one is likely then so is the from1 || from2. If both are unlikely then + // from1 || from2 is slightly more likely, but we assume our hints are nearly + // certain, so we apply it. + auto from1Hint = BranchHints::get(from1, func); + auto from2Hint = BranchHints::get(from2, func); + if ((from1Hint && *from1Hint) || (from2Hint && *from2Hint)) { + set(to, true, func); + } else if (from1Hint && from2Hint) { + // We ruled out that either one is present and true, so if both are present, + // both must be false. + assert(!*from1Hint && !*from2Hint); + set(to, false, func); + } else { + // We don't know. + BranchHints::clear(to, func); + } +} + +} // namespace wasm::BranchHints + +#endif // wasm_ir_branch_hint_h diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 64c68f2354e..5b505cf1d60 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -18,6 +18,7 @@ // Removes branches for which we go to where they go anyhow // +#include "ir/branch-hints.h" #include "ir/branch-utils.h" #include "ir/cost.h" #include "ir/drop.h" @@ -396,6 +397,7 @@ struct RemoveUnusedBrs : public WalkerPass> { curr->condition, br->value, getPassOptions(), *getModule())) { if (!br->condition) { br->condition = curr->condition; + BranchHints::copyTo(curr, br, getFunction()); } else { // In this case we can replace // if (condition1) br_if (condition2) @@ -427,6 +429,7 @@ struct RemoveUnusedBrs : public WalkerPass> { // That keeps the order of the two conditions as it was originally. br->condition = builder.makeSelect(br->condition, curr->condition, zero); + BranchHints::applyAndTo(curr, br, br, getFunction()); } br->finalize(); replaceCurrent(Builder(*getModule()).dropIfConcretelyTyped(br)); @@ -459,6 +462,7 @@ struct RemoveUnusedBrs : public WalkerPass> { Builder builder(*getModule()); curr->condition = builder.makeSelect( child->condition, curr->condition, builder.makeConst(int32_t(0))); + BranchHints::applyAndTo(curr, child, curr, getFunction()); curr->ifTrue = child->ifTrue; } } @@ -689,6 +693,7 @@ struct RemoveUnusedBrs : public WalkerPass> { brIf->condition = builder.makeUnary(EqZInt32, brIf->condition); last->name = brIf->name; brIf->name = loop->name; + BranchHints::flip(brIf, getFunction()); return true; } else { // there are elements in the middle, @@ -709,6 +714,7 @@ struct RemoveUnusedBrs : public WalkerPass> { builder.makeIf(brIf->condition, builder.makeBreak(brIf->name), stealSlice(builder, block, i + 1, list.size())); + BranchHints::copyTo(brIf, list[i], getFunction()); block->finalize(); return true; } @@ -1210,6 +1216,7 @@ struct RemoveUnusedBrs : public WalkerPass> { // we are an if-else where the ifTrue is a break without a // condition, so we can do this ifTrueBreak->condition = iff->condition; + BranchHints::copyTo(iff, ifTrueBreak, getFunction()); ifTrueBreak->finalize(); list[i] = Builder(*getModule()).dropIfConcretelyTyped(ifTrueBreak); ExpressionManipulator::spliceIntoBlock(curr, i + 1, iff->ifFalse); @@ -1224,6 +1231,7 @@ struct RemoveUnusedBrs : public WalkerPass> { *getModule())) { ifFalseBreak->condition = Builder(*getModule()).makeUnary(EqZInt32, iff->condition); + BranchHints::copyFlippedTo(iff, ifFalseBreak, getFunction()); ifFalseBreak->finalize(); list[i] = Builder(*getModule()).dropIfConcretelyTyped(ifFalseBreak); ExpressionManipulator::spliceIntoBlock(curr, i + 1, iff->ifTrue); @@ -1256,7 +1264,9 @@ struct RemoveUnusedBrs : public WalkerPass> { Builder builder(*getModule()); br1->condition = builder.makeBinary(OrInt32, br1->condition, br2->condition); + BranchHints::applyOrTo(br1, br2, br1, getFunction()); ExpressionManipulator::nop(br2); + BranchHints::clear(br2, getFunction()); } } } else { @@ -1396,9 +1406,12 @@ struct RemoveUnusedBrs : public WalkerPass> { // no other breaks to that name, so we can do this if (!drop) { assert(!br->value); - replaceCurrent(builder.makeIf( - builder.makeUnary(EqZInt32, br->condition), curr)); + auto* iff = builder.makeIf( + builder.makeUnary(EqZInt32, br->condition), curr); + replaceCurrent(iff); + BranchHints::copyFlippedTo(br, iff, getFunction()); ExpressionManipulator::nop(br); + BranchHints::clear(br, getFunction()); curr->finalize(curr->type); } else { // To use an if, the value must have no side effects, as in the @@ -1409,8 +1422,9 @@ struct RemoveUnusedBrs : public WalkerPass> { if (EffectAnalyzer::canReorder( passOptions, *getModule(), br->condition, br->value)) { ExpressionManipulator::nop(list[0]); - replaceCurrent( - builder.makeIf(br->condition, br->value, curr)); + auto* iff = builder.makeIf(br->condition, br->value, curr); + BranchHints::copyTo(br, iff, getFunction()); + replaceCurrent(iff); } } else { // The value has side effects, so it must always execute. We @@ -1529,6 +1543,14 @@ struct RemoveUnusedBrs : public WalkerPass> { optimizeSetIf(getCurrentPointer()); } + // Flip an if's condition with an eqz, and flip its arms. + void flip(If* iff) { + std::swap(iff->ifTrue, iff->ifFalse); + iff->condition = + Builder(*getModule()).makeUnary(EqZInt32, iff->condition); + BranchHints::flip(iff, getFunction()); + } + void optimizeSetIf(Expression** currp) { if (optimizeSetIfWithBrArm(currp)) { return; @@ -1570,9 +1592,10 @@ struct RemoveUnusedBrs : public WalkerPass> { // Wonderful, do it! Builder builder(*getModule()); if (flipCondition) { - builder.flip(iff); + flip(iff); } br->condition = iff->condition; + BranchHints::copyTo(iff, br, getFunction()); br->finalize(); set->value = two; auto* block = builder.makeSequence(br, set); @@ -1640,7 +1663,7 @@ struct RemoveUnusedBrs : public WalkerPass> { Builder builder(*getModule()); LocalGet* get = iff->ifTrue->dynCast(); if (get && get->index == set->index) { - builder.flip(iff); + flip(iff); } else { get = iff->ifFalse->dynCast(); if (get && get->index != set->index) { @@ -1901,6 +1924,7 @@ struct RemoveUnusedBrs : public WalkerPass> { curr->type = Type::unreachable; block->list.push_back(curr); block->finalize(); + BranchHints::clear(curr, getFunction()); // The type changed, so refinalize. refinalize = true; } else { diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 00612a4020b..dc877bd0223 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1481,11 +1481,6 @@ class Builder { return makeDrop(curr); } - void flip(If* iff) { - std::swap(iff->ifTrue, iff->ifFalse); - iff->condition = makeUnary(EqZInt32, iff->condition); - } - // Returns a replacement with the precise same type, and with minimal contents // as best we can. As a replacement, this may reuse the input node. template Expression* replaceWithIdenticalType(T* curr) { diff --git a/test/lit/passes/remove-unused-brs_branch-hints-shrink.wast b/test/lit/passes/remove-unused-brs_branch-hints-shrink.wast new file mode 100644 index 00000000000..21c08e8cd4c --- /dev/null +++ b/test/lit/passes/remove-unused-brs_branch-hints-shrink.wast @@ -0,0 +1,249 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --remove-unused-brs -all --shrink-level=1 -S -o - \ +;; RUN: | filecheck %s + +(module + ;; CHECK: (import "a" "b" (func $none (type $1))) + (import "a" "b" (func $none)) + + ;; CHECK: (func $join-br_ifs (type $0) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $join-br_ifs (param $x i32) (param $y i32) + ;; The br_ifs will be joined into a single one. The hint should propagate, + ;; as it matches. + (block $out + (@metadata.code.branch_hint "\01") + (br_if $out + (local.get $x) + ) + (@metadata.code.branch_hint "\01") + (br_if $out + (local.get $y) + ) + ;; Extra code so that the entire testcase does not get optimized out as + ;; trivial. + (call $none) + (br_if $out + (local.get $y) + ) + ) + ) + + ;; CHECK: (func $join-br_ifs-0 (type $0) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $join-br_ifs-0 (param $x i32) (param $y i32) + ;; The hints once more match, but now are 0. We still propagate. + (block $out + (@metadata.code.branch_hint "\00") + (br_if $out + (local.get $x) + ) + (@metadata.code.branch_hint "\00") + (br_if $out + (local.get $y) + ) + + (call $none) + (br_if $out + (local.get $y) + ) + ) + ) + + ;; CHECK: (func $join-br_ifs-no (type $0) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $join-br_ifs-no (param $x i32) (param $y i32) + ;; One is missing a hint, so we clear the hint. + (block $out + (@metadata.code.branch_hint "\00") + (br_if $out + (local.get $x) + ) + (br_if $out + (local.get $y) + ) + + (call $none) + (br_if $out + (local.get $y) + ) + ) + ) + + ;; CHECK: (func $join-br_ifs-no-flip (type $0) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $join-br_ifs-no-flip (param $x i32) (param $y i32) + ;; The other one is missing the hint, so we clear the hint. + (block $out + (br_if $out + (local.get $x) + ) + (@metadata.code.branch_hint "\00") + (br_if $out + (local.get $y) + ) + + (call $none) + (br_if $out + (local.get $y) + ) + ) + ) + + ;; CHECK: (func $join-br_ifs-yes-one (type $0) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $join-br_ifs-yes-one (param $x i32) (param $y i32) + ;; One has a 1 hint, so we can use that. + (block $out + (@metadata.code.branch_hint "\01") + (br_if $out + (local.get $x) + ) + (br_if $out + (local.get $y) + ) + + (call $none) + (br_if $out + (local.get $y) + ) + ) + ) + + ;; CHECK: (func $join-br_ifs-yes-other (type $0) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $join-br_ifs-yes-other (param $x i32) (param $y i32) + ;; As above, but the other has the 1, which we can still use. + (block $out + (br_if $out + (local.get $x) + ) + (@metadata.code.branch_hint "\01") + (br_if $out + (local.get $y) + ) + + (call $none) + (br_if $out + (local.get $y) + ) + ) + ) + + ;; CHECK: (func $join-br_ifs-mismatch (type $0) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $join-br_ifs-mismatch (param $x i32) (param $y i32) + ;; The hints do not match, but we can still use the 1 hint. + (block $out + (@metadata.code.branch_hint "\00") + (br_if $out + (local.get $x) + ) + (@metadata.code.branch_hint "\01") + (br_if $out + (local.get $y) + ) + + (call $none) + (br_if $out + (local.get $y) + ) + ) + ) +) diff --git a/test/lit/passes/remove-unused-brs_branch-hints.wast b/test/lit/passes/remove-unused-brs_branch-hints.wast new file mode 100644 index 00000000000..321fbab8146 --- /dev/null +++ b/test/lit/passes/remove-unused-brs_branch-hints.wast @@ -0,0 +1,724 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --remove-unused-brs -all -S -o - \ +;; RUN: | filecheck %s + +(module + ;; CHECK: (import "a" "b" (func $i32 (type $3) (result i32))) + (import "a" "b" (func $i32 (result i32))) + ;; CHECK: (import "a" "b" (func $none (type $2))) + (import "a" "b" (func $none)) + + ;; CHECK: (tag $e (type $2)) + (tag $e) + + ;; CHECK: (func $if-br (type $1) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-br (param $x i32) (param $y i32) + (block $out + ;; This nop prevents the entire testcase from being trivial. + (nop) + ;; The if-br will turn into a br_if. The branch hint should then go on the + ;; br_if, and remain 01. + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (br $out) + ) + ) + ) + ) + + ;; CHECK: (func $if-br_0 (type $1) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-br_0 (param $x i32) (param $y i32) + (block $out + (nop) + ;; As above, but a hint of 0. + (@metadata.code.branch_hint "\00") + (if + (local.get $x) + (then + (br $out) + ) + ) + ) + ) + + ;; CHECK: (func $if-br_if (type $1) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-br_if (param $x i32) (param $y i32) + (block $out + (nop) + ;; As above, but the br has a condition. We can merge conditions (using a + ;; select), and then move the hint to the br_if, as the br_if has the + ;; same hint as the if. + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (@metadata.code.branch_hint "\01") + (br_if $out + (local.get $y) + ) + ) + ) + ) + ) + + ;; CHECK: (func $if-br_if-no (type $1) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-br_if-no (param $x i32) (param $y i32) + (block $out + (nop) + ;; As above, but the br lacks a hint, so we emit no hint. + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (br_if $out + (local.get $y) + ) + ) + ) + ) + ) + + ;; CHECK: (func $if-br_if-no-2 (type $1) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-br_if-no-2 (param $x i32) (param $y i32) + (block $out + (nop) + ;; As above, but now the if lacks a hint, so we emit no hint. + (if + (local.get $x) + (then + (@metadata.code.branch_hint "\01") + (br_if $out + (local.get $y) + ) + ) + ) + ) + ) + + ;; CHECK: (func $if-if-1* (type $1) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-if-1* (param $x i32) (param $y i32) + ;; Both ifs have a hint of 1, so after we merge the ifs the combined + ;; condition remains likely. + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (@metadata.code.branch_hint "\01") + (if + (local.get $y) + (then + (call $none) + ) + ) + ) + ) + ;; The outer if still has a hint of 1, but the inner is 0. We emit no hint. + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (@metadata.code.branch_hint "\00") + (if + (local.get $y) + (then + (call $none) + ) + ) + ) + ) + ;; The outer if still has a hint of 1, but the inner has none. We emit no + ;; hint. + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (if + (local.get $y) + (then + (call $none) + ) + ) + ) + ) + ) + + ;; CHECK: (func $if-if-0* (type $1) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-if-0* (param $x i32) (param $y i32) + ;; As above, but now the outer if has hints of 0. + + ;; The hints do not match, so we emit no hint. + (@metadata.code.branch_hint "\00") + (if + (local.get $x) + (then + (@metadata.code.branch_hint "\01") + (if + (local.get $y) + (then + (call $none) + ) + ) + ) + ) + ;; The hints match, so the combined condition is unlikely. + (@metadata.code.branch_hint "\00") + (if + (local.get $x) + (then + (@metadata.code.branch_hint "\00") + (if + (local.get $y) + (then + (call $none) + ) + ) + ) + ) + ;; Inner lacks a hint, so we emit nothing. + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (if + (local.get $y) + (then + (call $none) + ) + ) + ) + ) + ) + + ;; CHECK: (func $if-if-?* (type $1) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-if-?* (param $x i32) (param $y i32) + ;; As above, but now the outer if has no hint. We emit no hints here. + + (if + (local.get $x) + (then + (@metadata.code.branch_hint "\01") + (if + (local.get $y) + (then + (call $none) + ) + ) + ) + ) + (if + (local.get $x) + (then + (@metadata.code.branch_hint "\00") + (if + (local.get $y) + (then + (call $none) + ) + ) + ) + ) + (if + (local.get $x) + (then + (if + (local.get $y) + (then + (call $none) + ) + ) + ) + ) + ) + + ;; CHECK: (func $loop-br_if-flip (type $0) (param $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (block $block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $loop + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-br_if-flip (param $x i32) + (block $block + (loop $loop + ;; This br_if's condition will flip when it is turned from a break out + ;; of the loop to a continue inside it. The hint should flip too. + (@metadata.code.branch_hint "\00") + (br_if $block + (local.get $x) + ) + (br $loop) + ) + ) + ) + + ;; CHECK: (func $loop-br_if-flip-reverse (type $0) (param $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (block $block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_if $loop + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-br_if-flip-reverse (param $x i32) + ;; As above, with a hint of 1, that should flip to 0. + (block $block + (loop $loop + (@metadata.code.branch_hint "\01") + (br_if $block + (local.get $x) + ) + (br $loop) + ) + ) + ) + + ;; CHECK: (func $loop-br_if-if (type $0) (param $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (block $block + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-br_if-if (param $x i32) + (loop $loop + (block $block + ;; This br_if will turn into an if with the same condition. The hint can + ;; be copied over. + (@metadata.code.branch_hint "\00") + (br_if $block + (local.get $x) + ) + ;; Extra code so simpler optimizations do not kick in. + (drop (i32.const 42)) + (br $loop) + ) + ) + ) + + ;; CHECK: (func $throw-if-br_if-0 (type $0) (param $x i32) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch_all $catch) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_if $catch + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-if-br_if-0 (param $x i32) + (block $catch + (try_table (catch_all $catch) + ;; This if can turn into a br_if. The branch hint should be copied. + (@metadata.code.branch_hint "\00") + (if + (local.get $x) + (then + (throw $e) + ) + ) + ) + ) + ) + + ;; CHECK: (func $throw-if-br_if-1 (type $0) (param $x i32) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch_all $catch) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $catch + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-if-br_if-1 (param $x i32) + ;; As above, but the hint is 1. + (block $catch + (try_table (catch_all $catch) + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (throw $e) + ) + ) + ) + ) + ) + + ;; CHECK: (func $throw-if-br_if-no (type $0) (param $x i32) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch_all $catch) + ;; CHECK-NEXT: (br_if $catch + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-if-br_if-no (param $x i32) + ;; As above, but there is no branch hint, so we should emit none. + (block $catch + (try_table (catch_all $catch) + (if + (local.get $x) + (then + (throw $e) + ) + ) + ) + ) + ) + + ;; CHECK: (func $loop-if-br (type $0) (param $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $loop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-if-br (param $x i32) + ;; This if with a br arm can turn into a br_if. The hint should be copied. + (loop $loop + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (br $loop) + ) + (else + ;; This call, and the one below, are needed for the pattern that is + ;; recognized here. + (call $none) + ) + ) + (call $none) + ) + ) + + ;; CHECK: (func $loop-if-br-reverse (type $0) (param $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_if $loop + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop-if-br-reverse (param $x i32) + ;; As above, with arms flipped. Now the condition flips. + (loop $loop + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (call $none) + ) + (else + (br $loop) + ) + ) + (call $none) + ) + ) + + ;; CHECK: (func $restructure-if (type $0) (param $x i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (block $block + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $restructure-if (param $x i32) + (block $block + ;; We will emit an if with flipped condition, which should get a flipped + ;; hint. + (@metadata.code.branch_hint "\01") + (br_if $block + (local.get $x) + ) + (call $none) + ) + ) + + ;; CHECK: (func $restructure-if-value (type $4) (param $x i32) (result i32) + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (block $value (result i32) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $restructure-if-value (param $x i32) (result i32) + ;; We will emit an if with the same condition, which should get the same + ;; hint. + (block $value (result i32) + (drop + (@metadata.code.branch_hint "\01") + (br_if $value + (i32.const 0) + (local.get $x) + ) + ) + (unreachable) + ) + ) + + ;; CHECK: (func $set-if-br-arm (type $0) (param $x i32) + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (@metadata.code.branch_hint "\00") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set-if-br-arm (param $x i32) + (local $temp i32) + ;; The if will turn into a br_if, with the same hint. + (block $out + (local.set $temp + (@metadata.code.branch_hint "\00") + (if (result i32) + (local.get $x) + (then + (br $out) + ) + (else + (i32.const 0) + ) + ) + ) + ) + ) + + ;; CHECK: (func $set-if-br-arm-flip (type $0) (param $x i32) + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (@metadata.code.branch_hint "\01") + ;; CHECK-NEXT: (br_if $out + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set-if-br-arm-flip (param $x i32) + (local $temp i32) + ;; As above, but with arms reversed. + ;; The if will turn into a flipped br_if, with a flipped hint. + (block $out + (local.set $temp + (@metadata.code.branch_hint "\00") + (if (result i32) + (local.get $x) + (then + (i32.const 0) + ) + (else + (br $out) + ) + ) + ) + ) + ) +)