From 1ac54bb7562d63ab1590b2fa8ed632ff425c7c2b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Jul 2025 12:47:25 -0700 Subject: [PATCH 1/5] fix --- src/ir/branch-hints.h | 118 ++++ src/passes/RemoveUnusedBrs.cpp | 35 +- src/wasm-builder.h | 5 - ...remove-unused-brs_branch-hints-shrink.wast | 180 +++++ .../remove-unused-brs_branch-hints.wast | 666 ++++++++++++++++++ 5 files changed, 993 insertions(+), 11 deletions(-) create mode 100644 src/ir/branch-hints.h create mode 100644 test/lit/passes/remove-unused-brs_branch-hints-shrink.wast create mode 100644 test/lit/passes/remove-unused-brs_branch-hints.wast diff --git a/src/ir/branch-hints.h b/src/ir/branch-hints.h new file mode 100644 index 00000000000..3d93cae9be9 --- /dev/null +++ b/src/ir/branch-hints.h @@ -0,0 +1,118 @@ +/* + * 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) { + if (!likely) { + // We are writing an empty hint. Do not create an empty annotation if one + // did not exist. + if (!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. + 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 from1 and from2 are both likely, then from1 || from2 is even more + // likely. If from1 and from2 are both unlikely, then from1 || from2 is + // slightly more likely, but we assume our hints are nearly certain, so we + // apply it. That is, the math works out the same for |applyAndTo|, so we just + // call that, but we leave the methods separate for clarity and future + // refactoring. + applyAndTo(from1, from2, 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..8812c75a6bc 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" @@ -429,6 +430,7 @@ struct RemoveUnusedBrs : public WalkerPass> { builder.makeSelect(br->condition, curr->condition, zero); } br->finalize(); + BranchHints::copyTo(curr, br, getFunction()); replaceCurrent(Builder(*getModule()).dropIfConcretelyTyped(br)); anotherCycle = true; } @@ -459,6 +461,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 +692,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 +713,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 +1215,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 +1230,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 +1263,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 +1405,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 +1421,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 +1542,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 +1591,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 +1662,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 +1923,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..7f252a19148 --- /dev/null +++ b/test/lit/passes/remove-unused-brs_branch-hints-shrink.wast @@ -0,0 +1,180 @@ +;; 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-mismatch (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-mismatch (param $x i32) (param $y i32) + ;; The hints do not match, so we clear the 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..6f42e0e8269 --- /dev/null +++ b/test/lit/passes/remove-unused-brs_branch-hints.wast @@ -0,0 +1,666 @@ +;; 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. + (@metadata.code.branch_hint "\01") + (if + (local.get $x) + (then + (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) + ) + ) + ) + ) + ) +) From 0215cbcfb4e5b2251f6833389e3667d8a0066074 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Jul 2025 12:58:48 -0700 Subject: [PATCH 2/5] moar --- src/passes/RemoveUnusedBrs.cpp | 3 +- .../remove-unused-brs_branch-hints.wast | 32 ++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 8812c75a6bc..5b505cf1d60 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -397,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) @@ -428,9 +429,9 @@ 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(); - BranchHints::copyTo(curr, br, getFunction()); replaceCurrent(Builder(*getModule()).dropIfConcretelyTyped(br)); anotherCycle = true; } diff --git a/test/lit/passes/remove-unused-brs_branch-hints.wast b/test/lit/passes/remove-unused-brs_branch-hints.wast index 6f42e0e8269..9f6d43606bf 100644 --- a/test/lit/passes/remove-unused-brs_branch-hints.wast +++ b/test/lit/passes/remove-unused-brs_branch-hints.wast @@ -76,7 +76,37 @@ (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. + ;; 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) From 76632f11048669d788566dfaf27dad0234e42900 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Jul 2025 14:52:47 -0700 Subject: [PATCH 3/5] properly apply OR --- src/ir/branch-hints.h | 36 ++++++---- ...remove-unused-brs_branch-hints-shrink.wast | 71 ++++++++++++++++++- .../remove-unused-brs_branch-hints.wast | 28 ++++++++ 3 files changed, 120 insertions(+), 15 deletions(-) diff --git a/src/ir/branch-hints.h b/src/ir/branch-hints.h index 3d93cae9be9..51d885d30b6 100644 --- a/src/ir/branch-hints.h +++ b/src/ir/branch-hints.h @@ -37,12 +37,10 @@ inline std::optional get(Expression* expr, Function* func) { // Set the branch hint for an expression, trampling anything existing before. inline void set(Expression* expr, std::optional likely, Function* func) { - if (!likely) { - // We are writing an empty hint. Do not create an empty annotation if one - // did not exist. - if (!func->codeAnnotations.count(expr)) { - return; - } + // 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; } @@ -88,7 +86,8 @@ inline void applyAndTo(Expression* from1, // 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. + // less likely, so we can once more apply a hint. If the hints differ, than + // 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) { @@ -104,13 +103,22 @@ inline void applyOrTo(Expression* from1, Expression* from2, Expression* to, Function* func) { - // If from1 and from2 are both likely, then from1 || from2 is even more - // likely. If from1 and from2 are both unlikely, then from1 || from2 is - // slightly more likely, but we assume our hints are nearly certain, so we - // apply it. That is, the math works out the same for |applyAndTo|, so we just - // call that, but we leave the methods separate for clarity and future - // refactoring. - applyAndTo(from1, from2, to, 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 diff --git a/test/lit/passes/remove-unused-brs_branch-hints-shrink.wast b/test/lit/passes/remove-unused-brs_branch-hints-shrink.wast index 7f252a19148..21c08e8cd4c 100644 --- a/test/lit/passes/remove-unused-brs_branch-hints-shrink.wast +++ b/test/lit/passes/remove-unused-brs_branch-hints-shrink.wast @@ -144,8 +144,77 @@ ) ) + ;; 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) @@ -160,7 +229,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $join-br_ifs-mismatch (param $x i32) (param $y i32) - ;; The hints do not match, so we clear the hint. + ;; The hints do not match, but we can still use the 1 hint. (block $out (@metadata.code.branch_hint "\00") (br_if $out diff --git a/test/lit/passes/remove-unused-brs_branch-hints.wast b/test/lit/passes/remove-unused-brs_branch-hints.wast index 9f6d43606bf..321fbab8146 100644 --- a/test/lit/passes/remove-unused-brs_branch-hints.wast +++ b/test/lit/passes/remove-unused-brs_branch-hints.wast @@ -119,6 +119,34 @@ ) ) + ;; 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 From 126eb8896cb72275ca41eafc16f5457c91e0677c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Jul 2025 14:55:20 -0700 Subject: [PATCH 4/5] format --- src/ir/branch-hints.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/branch-hints.h b/src/ir/branch-hints.h index 51d885d30b6..5157538f847 100644 --- a/src/ir/branch-hints.h +++ b/src/ir/branch-hints.h @@ -105,7 +105,7 @@ inline void applyOrTo(Expression* from1, 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. + // certain, so we apply it. auto from1Hint = BranchHints::get(from1, func); auto from2Hint = BranchHints::get(from2, func); if ((from1Hint && *from1Hint) || (from2Hint && *from2Hint)) { From a633e50b4698829ebfa8d99992e257730caf6634 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 9 Jul 2025 15:15:36 -0700 Subject: [PATCH 5/5] Update src/ir/branch-hints.h Co-authored-by: Thomas Lively --- src/ir/branch-hints.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/branch-hints.h b/src/ir/branch-hints.h index 5157538f847..a15198f54a8 100644 --- a/src/ir/branch-hints.h +++ b/src/ir/branch-hints.h @@ -86,7 +86,7 @@ inline void applyAndTo(Expression* from1, // 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, than + // 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);