diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt
index ff94a3905e8f..d56601bf620c 100644
--- a/libyul/CMakeLists.txt
+++ b/libyul/CMakeLists.txt
@@ -106,6 +106,8 @@ add_library(yul
optimiser/CircularReferencesPruner.h
optimiser/CommonSubexpressionEliminator.cpp
optimiser/CommonSubexpressionEliminator.h
+ optimiser/ConditionalBranchFlattener.cpp
+ optimiser/ConditionalBranchFlattener.h
optimiser/ConditionalSimplifier.cpp
optimiser/ConditionalSimplifier.h
optimiser/ConditionalUnsimplifier.cpp
diff --git a/libyul/optimiser/ConditionalBranchFlattener.cpp b/libyul/optimiser/ConditionalBranchFlattener.cpp
new file mode 100644
index 000000000000..40e49defe97b
--- /dev/null
+++ b/libyul/optimiser/ConditionalBranchFlattener.cpp
@@ -0,0 +1,154 @@
+/*
+ This file is part of solidity.
+
+ solidity is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ solidity is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with solidity. If not, see .
+*/
+// SPDX-License-Identifier: GPL-3.0
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+using namespace solidity;
+using namespace solidity::yul;
+using namespace solidity::util;
+
+void ConditionalBranchFlattener::run(OptimiserStepContext& _context, Block& _ast)
+{
+ ConditionalBranchFlattener{_context}(_ast);
+}
+
+void ConditionalBranchFlattener::operator()(Block& _block)
+{
+ iterateReplacing(
+ _block.statements,
+ [&](Statement& _s) -> std::optional>
+ {
+ visit(_s);
+
+ if (!std::holds_alternative(_s))
+ return {};
+
+ If& ifStatement = std::get(_s);
+ std::vector& bodyStatements = ifStatement.body.statements;
+
+ if (bodyStatements.size() != 1)
+ return {};
+
+ Statement& bodyStatement = bodyStatements.front();
+ if (!std::holds_alternative(bodyStatement))
+ return {};
+
+ Assignment& assignment = std::get(bodyStatement);
+ if (assignment.variableNames.size() != 1)
+ return {};
+
+ SideEffectsCollector sideEffectsCollector(m_context.dialect);
+ sideEffectsCollector.visit(*assignment.value);
+ if (!sideEffectsCollector.movable())
+ return {};
+
+ langutil::DebugData::ConstPtr debugData = ifStatement.debugData;
+ YulName conditionName = m_context.dispenser.newName({YulName("condition")});
+
+ auto normalizedCondition = std::make_unique(
+ createBuiltinCall(
+ debugData,
+ "iszero",
+ make_vector(
+ createBuiltinCall(
+ debugData,
+ "iszero",
+ make_vector(std::move(*ifStatement.condition))
+ )
+ )
+ )
+ );
+
+ std::vector transformed;
+ transformed.reserve(2);
+
+ transformed.push_back(VariableDeclaration{
+ debugData,
+ {NameWithDebugData{debugData, conditionName}},
+ std::move(normalizedCondition)
+ });
+
+ YulName targetName = assignment.variableNames[0].name;
+ std::unique_ptr rhs = std::move(assignment.value);
+
+ Expression mask = createBuiltinCall(
+ debugData,
+ "sub",
+ make_vector(
+ m_context.dialect.zeroLiteral(),
+ Identifier{debugData, conditionName}
+ )
+ );
+
+ Expression diff = createBuiltinCall(
+ debugData,
+ "xor",
+ make_vector(
+ Identifier{debugData, targetName},
+ std::move(*rhs)
+ )
+ );
+
+ Expression maskedDiff = createBuiltinCall(
+ debugData,
+ "and",
+ make_vector(
+ std::move(mask),
+ std::move(diff)
+ )
+ );
+
+ Expression finalExpr = createBuiltinCall(
+ debugData,
+ "xor",
+ make_vector(
+ Identifier{debugData, targetName},
+ std::move(maskedDiff)
+ )
+ );
+
+ transformed.push_back(Assignment{
+ debugData,
+ assignment.variableNames,
+ std::make_unique(std::move(finalExpr))
+ });
+
+ return transformed;
+ }
+ );
+}
+
+FunctionCall ConditionalBranchFlattener::createBuiltinCall(
+ langutil::DebugData::ConstPtr _debugData,
+ std::string const& _name,
+ std::vector _arguments
+)
+{
+ auto handle = m_context.dialect.findBuiltin(_name);
+ yulAssert(handle.has_value(), "Builtin not found: " + _name);
+ return FunctionCall{
+ std::move(_debugData),
+ {BuiltinName{nullptr, *handle}},
+ std::move(_arguments)
+ };
+}
diff --git a/libyul/optimiser/ConditionalBranchFlattener.h b/libyul/optimiser/ConditionalBranchFlattener.h
new file mode 100644
index 000000000000..eb72108600af
--- /dev/null
+++ b/libyul/optimiser/ConditionalBranchFlattener.h
@@ -0,0 +1,69 @@
+/*
+ This file is part of solidity.
+
+ solidity is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ solidity is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with solidity. If not, see .
+*/
+// SPDX-License-Identifier: GPL-3.
+#pragma once
+
+#include
+#include
+#include
+
+namespace solidity::yul
+{
+
+/**
+ * Replaces conditional execution with branchless bitwise operations.
+ *
+ * Rewrites:
+ * if c { x := a }
+ * to:
+ * let condition := iszero(iszero(c))
+ * x := xor(x, and(sub(0, condition), xor(a, x)))
+ *
+ * This transformation is applied if:
+ * - The body of the `if` statement contains a single assignment.
+ * - All RHS expressions in the assignment are movable.
+ * - There are no control flow statements in the body.
+ *
+ * The logic `xor(x, and(sub(0, condition), xor(a, x)))` effectively implements
+ * `condition ? a : x` using bitwise operations, relying on `condition` being 0 or 1.
+ * `sub(0, condition)` generates a mask of all ones if condition is 1, and all zeros if condition
+ * is 0.
+ *
+ * Prerequisites: Disambiguator
+ */
+class ConditionalBranchFlattener: public ASTModifier
+{
+public:
+ static constexpr char const* name = "ConditionalBranchFlattener";
+ static void run(OptimiserStepContext& _context, Block& _ast);
+
+ using ASTModifier::operator();
+ void operator()(Block& _block) override;
+
+private:
+ ConditionalBranchFlattener(OptimiserStepContext& _context): m_context(_context) {}
+
+ FunctionCall createBuiltinCall(
+ langutil::DebugData::ConstPtr _debugData,
+ std::string const& _name,
+ std::vector _arguments
+ );
+
+ OptimiserStepContext& m_context;
+};
+
+}
diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp
index 17698d74e2a0..7e001b1565d0 100644
--- a/libyul/optimiser/Suite.cpp
+++ b/libyul/optimiser/Suite.cpp
@@ -26,6 +26,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -232,6 +233,7 @@ std::map> const& OptimiserSuite::all
BlockFlattener,
CircularReferencesPruner,
CommonSubexpressionEliminator,
+ ConditionalBranchFlattener,
ConditionalSimplifier,
ConditionalUnsimplifier,
ControlFlowSimplifier,
@@ -273,6 +275,7 @@ std::map const& OptimiserSuite::stepNameToAbbreviationMap()
{BlockFlattener::name, 'f'},
{CircularReferencesPruner::name, 'l'},
{CommonSubexpressionEliminator::name, 'c'},
+ {ConditionalBranchFlattener::name, 'B'},
{ConditionalSimplifier::name, 'C'},
{ConditionalUnsimplifier::name, 'U'},
{ControlFlowSimplifier::name, 'n'},
diff --git a/test/libyul/YulOptimizerTestCommon.cpp b/test/libyul/YulOptimizerTestCommon.cpp
index cd54318c19ef..7a4148b3a21b 100644
--- a/test/libyul/YulOptimizerTestCommon.cpp
+++ b/test/libyul/YulOptimizerTestCommon.cpp
@@ -27,6 +27,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -142,6 +143,12 @@ YulOptimizerTestCommon::YulOptimizerTestCommon(std::shared_ptr