Abstract 1 Introduction 2 Background 3 Motivation 4 Detailed Design 5 Implementation 6 Evaluation 7 Limitation and Future Work 8 Related Work 9 Conclusion References

Eliminate Branches by Melding IR Instructions

Yuze Li000Equal contribution. ORCID Virginia Tech, Blacksburg, VA, USA    Srinivasan Ramachandra Sharma000Equal contribution. ORCID Virginia Tech, Blacksburg, VA, USA    Charitha Saumya ORCID Intel Corporation, Santa Clara, CA, USA    Ali R. Butt ORCID Virginia Tech, Blacksburg, VA, USA    Kirshanthan Sundararajah ORCID Virginia Tech, Blacksburg, VA, USA
Abstract

Branch mispredictions cause catastrophic performance penalties in modern processors, leading to performance loss. While hardware predictors and profile-guided techniques exist, data-dependent branches with irregular access patterns remain challenging. Traditional if-conversion eliminates branches via software predication but faces limitations on architectures like x86. It often fails on paths containing memory instructions or incurs excessive instruction overhead by fully speculating large branch bodies. This paper presents MERIT (MElding IR InsTructions), a compiler transformation that eliminates branches by aligning and melding similar operations from divergent paths at the IR instruction level. By observing that divergent paths often perform structurally similar operations with different operands, MERIT adapts sequence alignment to discover merging opportunities and employs safe operand-level guarding to ensure semantic correctness without hardware predication. Implemented as an LLVM pass and evaluated on 102 programs from four benchmark suites, MERIT achieves a 1.52× geometric mean speedup on 24 branch-heavy microbenchmarks (peak 32× on toUpper) and reduces branch mispredictions by 48.9% on average. On more realistic workloads – SPECrate 2017 (16 benchmarks), SQLite TPC-H (22 queries), and CPython pyperformance (40 benchmarks) – MERIT with profile-guided function selection achieves a positive geomean (1.01×) across all three suites, demonstrating consistent improvement without regressions.

Keywords and phrases:
Control-flow Transformation, Branch Elimination
Copyright and License:
[Uncaptioned image] © Yuze Li, Srinivasan Ramachandra Sharma, Charitha Saumya, Ali R. Butt,
and Kirshanthan Sundararajah; licensed under Creative Commons License CC-BY 4.0
2012 ACM Subject Classification:
Software and its engineering Compilers
; Software and its engineering Software performance
Related Version:
Previous Version: https://arxiv.org/abs/2512.22390
Supplementary Material:
Software  (Artifact): https://zenodo.org/records/19598511
Funding:
This work is sponsored in part by the NSF under the grants: CSR-2106634 and CSR-2312785.
Supplementary Material:
Software  (ECOOP 2026 Artifact Evaluation approved artifact): https://doi.org/10.4230/DARTS.12.1.1
Editors:
Robbert Krebbers and Alexandra Silva

1 Introduction

Modern superscalar processors rely on speculative execution to maintain high instruction throughput. Conditional branches introduce a critical control-flow hazard, breaking this linear flow and forcing the processor to guess the correct execution path. Mispredicting the branch paths can cause catastrophic performance penalties (up to 18% instructions-per-cycle (IPC) loss [22]).

Hardware branch prediction has evolved significantly [39, 25, 36, 37, 38], and profile-guided approaches can eliminate specific problematic branches [20, 41]. However, data-dependent branches with irregular patterns remain challenging due to the trade-off between prediction accuracy and silicon area.

A complementary software-based approach, if-conversion, can eliminate the control-flow hazard altogether without any hardware changes [3, 4, 30, 2, 21, 27], albeit the difficulty for compilers to identify which branches are destined to mispredict. Instead of generating a conditional jump, the compiler produces a single, straight-line sequence of predicated instructions. If-conversion operates at the Machine level IR on a branch: when all instructions that are control dependent on a branch are predicated using the same condition as the branch, that branch can legally be removed. However, this transformation suffers from two fundamental limitations on x86: First, x86 lacks the hardware predication support. If-conversion resorts to speculation-based approaches that execute both paths and select between the final results. However, it cannot handle branches containing unsafe memory operations (loads from potentially invalid addresses or conditional stores), leaving many “convertible” branches untransformed. Second, on x86, even when transformation succeeds, the overhead of duplicating all operations from both paths can outweigh the benefit of branch elimination, particularly for structurally-similar branches containing substantial computations.

This paper introduces MERIT (MElding IR InsTructions), a compiler transformation that differs from traditional if-conversion by operating at the target-independent IR level rather than the target-dependent lower levels, to solve the above challenges. We observe that divergent control-flow paths often perform structurally similar operations differing primarily in their operands. Instead of speculatively executing entire paths, MERIT aligns instruction sequences from both branches by identifying which operations are identical across paths. These aligned instructions are melded into a single instruction with conditional operand selection, while unaligned instructions are melded after they are safely (i.e., preserving correctness) matched with extraneous instructions inserted by MERIT.

This instruction-level approach achieves three key advantages over traditional if-conversion. (1) Safe handling of memory operations through control-flow analysis in IR: Unlike if-conversion in late machine-IR, MERIT performs IR-level analysis in the middle-end to reason about memory access. The availability of the target-independent IR level semantics enables MERIT to transform branches that are traditionally skipped by if-conversion. During melding, conditional loads and stores become unconditional by getting guarded by the branch condition to select the address to guarantee correct results. (2) Reduced static instruction overhead through IR instruction melding: By merging structurally similar instructions rather than duplicating entire paths, MERIT produces fewer operations. When both branches compute x = a + offset with different offsets, traditional if-conversion generates four operations (two additions, two selects) while MERIT generates two (one select, one addition). This reduces code size, improves instruction cache utilization, and minimizes overhead that can negate branch elimination benefits. (3) Target-independent IR-level transformation: The transformation is architecture-independent - the transformation is applicable irrespective of underlying architecture. MERIT produces branchless select-based code that unlocks downstream optimizations. Straight-line code enables better instruction scheduling, register allocation, and vectorization – opportunities inhibited by control-flow boundaries. In order to see the performance benefit, this work focuses on x86 architecture.
Our key contributions are as follows:

  • MERIT: A novel compiler transformation that completely eliminates branches through instruction-level alignment and melding through semantic analysis, addressing the fundamental limitations of traditional if-conversion.

  • Full Alignment Using Extraneous Instructions: MERIT adapts the Smith-Waterman algorithm [40] to discover locally optimal alignments across divergent paths, inserting safe extraneous instructions to fill unmergeable gaps and achieve full instruction sequence alignment.

  • Correctness and Safety Guarantee: MERIT ensures the data flow after inserting extraneous instructions will never corrupt the original program data flow. It uses semantic analysis to ensure safe memory access that traditional if-conversion cannot employ.

  • We implement MERIT as an LLVM pass and evaluate on 102 benchmarks from four benchmark suites. On 24 branch-heavy microbenchmarks, MERIT achieves a 1.52× geometric mean speedup (peak 32× on toUpper) over pure hardware speculation, reducing mispredictions by 48.9% on average. On realistic workloads (SPECrate 2017, SQLite TPC-H, CPython pyperformance), MERIT with profile-guided optimization (PGO) function selection achieves a 1.01× geomean across all three suites with no regressions, whereas naive transformation without PGO can regress substantially on non-branch-heavy code.

In the rest of the paper we first present necessary background for this work (Section 2). Then, we motivate our instruction-level approach with key examples in Section 3 and illustrate the MERIT transformation design in Section 4. We describe our implementation as an LLVM pass and its integration with PGO in Section 5, followed with a comprehensive evaluation in Section 6. Finally, we discuss limitations and future work in Section 7, mention related research in Section 8, and conclude in Section 9.

2 Background

2.1 Control-flow Hazard and Branch Prediction

Modern processors use deep pipelines and speculative execution to achieve high instructions-per-cycle (IPC). When encountering a conditional branch, the processor must predict its direction and speculatively fetch instructions from the predicted path. A correct prediction maintains pipeline flow, but a misprediction forces a complete pipeline flush and restart, a penalty that increases with pipeline depth. Researchers have spent decades on branch prediction design to increase prediction accuracy. State-of-the-art hardware predictors are typically TAGE-like [39, 36, 37, 38], perceptron-based [16, 18], or a combination of those [17, 38]. Both types of predictors have distinct advantages: TAGE exploits the limited predictor storage very efficiently, whereas perceptron-based predictors can easily combine different sorts of input information. Recent researchers leverage PGO to increase prediction accuracy [20, 41, 46]. However, achieving high prediction accuracy often trades off with high space area on the chip. Plus, redesigning hardware to accommodate new compiler hints is non-scalable as it requires specialized ISA to accommodate software hints, hindering realistic in-production deployment.

2.2 If-conversion

If-conversion is a well-established compiler technique that eliminates branches by converting control-flow into data flow [3, 27, 4, 30, 2, 21]. The goal is to eliminate control dependencies that can limit the exposure of instruction-level parallelism (ILP) and avoid performance penalties from branch mispredictions. Instead of a conditional jump, the compiler generates a single, straight-line sequence of predicated instructions. The transformation is crucial for enabling other optimizations like vectorization and software pipelining, and it is especially critical for specialized architectures, such as digital signal processors (DSPs). Newest research applies the conversion early [27] to prevent the compiler from making optimizations that would harm performance. On architectures like ARM, this uses predicates to conditionally commit instruction results. The hardware executes these instructions, but only allows the results to be committed if their governing predicate is true; otherwise, the instruction is nullified.

However, x86 lacks hardware predication. If-conversion must instead execute both paths unconditionally and select the result with cmov, which requires both paths to be free of faulting operations. The compiler therefore skips branches containing memory operations, leaving numerous potentially optimizable code regions untransformed. Secondly, if-conversion transforms naively: it must fully execute both paths before selection. This “execute everything, select results after” model can introduce excessive instruction overhead, especially for branches with large bodies, often degrading performance despite eliminating mispredictions. To mitigate this, various approaches have been proposed, such as using PGO to selectively apply the transformation [47, 44]. Other hybrid techniques, like Wish Branches [21], defer the decision to runtime by encoding both the branch and its if-converted version in the binary. Despite these mitigations, the inefficient full-speculation model is largely retained. In contrast, MERIT moves beyond this branch-level decision by operating at the IR instruction level, melding similar operations to reduce redundant work rather than fully speculating both paths.

2.3 Sequence Alignment

The Smith-Waterman algorithm [40], originally developed for biological sequence alignment, uses dynamic programming to find optimal local alignments between two sequences. Given two sequences of lengths n and m, the algorithm constructs an (n+1)×(m+1) scoring matrix M where M[i,j] represents the optimal alignment score for the prefixes of length i and j. The recurrence relation is:

M[i,j]=max{M[i1,j1]+score(ai,bj)(pair elements)M[i1,j]gap_penalty(gap in sequence 2)M[i,j1]gap_penalty(gap in sequence 1)0(no negative scores) (1)

where the score function returns a positive bonus for matching elements and zero (or a penalty) for mismatches. The gap penalty discourages unaligned elements. After computing M, backtracking from the maximum score yields the optimal local alignment that maximizes similarity while minimizing gaps.

MERIT applies the Smith-Waterman algorithm to align structurally similar instruction sequences across divergent control-flow paths. In this setting, the two sequences are the instruction lists It and If from the true and false branches, respectively. The score function returns a positive bonus (match_bonus) if two instructions form a compatible pair (Definition 1), and zero otherwise. Each gap corresponds to an instruction present in only one path, which necessitates an extraneous instruction insertion during the melding step. The resulting alignment score is subsequently used as a profitability filter (Section 4.4) to decide whether to proceed with the transformation.

3 Motivation

In this section, we use two examples to demonstrate how MERIT eliminates branches by melding (and adding extraneous) instructions on both paths.

3.1 How MERIT Eliminates Branches

The primary intuition behind MERIT transformation is to insert extraneous (i.e., dummy) instructions so that both divergent paths execute the same sequence of operations, enabling the explicit branch to be eliminated. Consider the function to_upper in Listing 1 111Only for illustration purposes. Standard compiler optimizations apply after MERIT’s transformation., which converts all lowercase letters in a given string of length SIZE to uppercase ones. The if-conditional within the for-loop is executed repeatedly while iterating through each character of the string. This branch is highly unpredictable because of its data dependency on the characters of the input string. Experimenting to_upper on x86 shows that around 27% of all dynamic branches in the function are mispredicted and it leads to an extremely low IPC of 0.48.

1void to_upper(char *str) {
2 for(int i=0; i<SIZE; i++) {
3 if(str[i]>=’a’ & str[i]<=’z’)
4 str[i] = str[i] + ’A’ -’a’;
5 }
6}
8void to_upper_branchless(char *str){
9 for(int i=0; i<SIZE; i++){
10 bool cond = (str[i]>=’a’) & (str[i]<=’z’);
11 unsigned int diff = cond ? ’A’-’a’ : 0;
12 str[i] += diff;
13 }
14}
Listing 1: Motivating Example.

The function to_upper_branchless is semantically the same as to_upper. However, the if-statement within the loop is eliminated, and the loop body is straight line code without any branches (here the ternary operator would be translated to cmov instructions in x86 assembly). This version has nearly zero branch mispredictions and yields a 32× speedup compared to the original version. In this version, irrespective of the value cond, which holds a value representing the outcome of the branch in the original version, the computation of adding a constant value diff to the character of the string is executed. This does not affect the correctness, as when the branch is not taken, the transformation only adds a zero to the character. MERIT automatically recognizes the branched code and transforms it into the branchless version. However, traditional if-conversion does not perform this transformation.
Caveat: The illustrative str[i] += diff write in Listing 1 is not, in general, safe – it performs a store on the path where the original program did not. Section 4.3 shows that MERIT does not write to the original address in this case; instead, it uses safe address substitution so that the conditional store on the not-taken path targets a compiler-allocated SAFE_ADDR, which is non-aliasing with any program memory. The full transformed code (Figure 2(c)) makes this explicit with t4_t8 = cond ? &text[i] : SAFE_ADDR.

3.2 How MERIT Differs from if-conversion

Figure 1 shows a case where both branches perform structurally identical computations differing only in their operands. On commodity architectures that rely on cmov (x86), if-conversion uses a speculative “compute everything, select results” method, calculates the outcomes for both the if and else paths entirely, and then uses (cmov) to select the correct final results. While this strategy eliminates branch misprediction penalties, due to the complete linearization on the control-flow, forcibly enabling if-conversion introduces excessive ALU instructions by performing every operation twice when only one outcome is needed. Plus, due to lack of hardware speculation support on x86, if-conversion does not transform code regions containing memory operations.

On the contrary, MERIT uses instruction-level alignment to recognize the structural similarity between the branches. Instead of duplicating the computations, it performs operand-level merging. This “select operands, compute once” method first uses select instructions to choose the correct constant based on the branch condition, and then executes each arithmetic operation a single time with the selected operand. MERIT achieves a significant reduction in total operations by merging the computation at the instruction level, rather than merely selecting between fully-computed results, leading to better performance.

Figure 1: The given example shows MERIT can save 33% operations compared to if-conversion by recognizing that both paths perform the same operations with different constants, enabling instruction-level merging rather than result-level selection.

4 Detailed Design

This section describes the compiler transformation MERIT for statically merging divergent control-flow paths. Unlike traditional if-conversion, which computes both paths entirely and selects the result, MERIT performs instruction-level melding: it aligns structurally similar operations and executes each operation once with multiplexed operands, minimizing redundant work while eliminating the branch.

The transformation must preserve semantic equivalence: both the original and the transformed code must produce identical results for all inputs. Moreover, the transformation should not introduce any new behavior, such as speculative execution of merged paths triggering runtime exceptions (e.g., memory faults, division-by-zero, etc.). We describe the transformation algorithm (Section 4.2), prove its correctness (Section 4.7), and explain how design choices meet these requirements.

4.1 Overview

The MERIT transformation adapts instruction melding from DARM [35], a GPU optimization that merges control-divergent paths. While DARM targets GPU warp divergence and tolerates partial merging, MERIT optimizes for x86 branch misprediction elimination with the following distinct requirements. First, complete branch elimination is paramount: partial merging that leaves residual branches defeats the purpose, as even a single remaining branch incurs the full misprediction penalty (15-17 cycles). Second, the transformation must minimize instruction overhead because excessive extraneous instructions or select chains create dependency bottlenecks that limit out-of-order execution. Third, memory speculation must be safe: speculative loads to invalid addresses are non-fatal on GPUs with forgiving memory models but catastrophic on x86 systems where page faults abort execution.

The key insight of MERIT is inserting extraneous instructions into both sides of a conditional branch such that the instruction sequences become identical, enabling complete alignment (formally defined in Section 4.2) and branch elimination. The transformation operates on diamond-shaped control-flow regions: if-then-else (two-sided) or if-then (one-sided) patterns where both paths converge to a common merge point.

4.2 MERIT Transformation

Consider a program with an if-then-else branch with two basic blocks Bt and Bf (i.e., diamond-shaped control-flow), and It and If are the instruction sequences of those basic blocks, respectively.

Definition 1.

Compatible Pair: Let (a,b) be a pair of instructions. It is a compatible pair if and only if the operation of a and b is identical (e.g., operation of a and b are iadd). It is not necessary for instructions a and b to have identical operands for the pair (a,b) to be compatible.

Definition 2.

Instruction Alignment: Let It={i1t<<int} and If={i1f<<imf} be the complete ordered sequences of instructions in basic blocks Bt and Bf respectively. An instruction alignment is an ordered sequence of pairs A={(a1,b1)<<(ak,bk)} such that ajIt{ψ}, bjIf{ψ}. The sequence length k satisfies max(n,m)kn+m, and j[1,k], (aj,bj)(ψ,ψ). If ajψ and bjψ, then (aj,bj) is a compatible pair for merging. Here, ψ denotes the absence of an instruction (i.e., empty slot).

Definition 3.

Unaligned Instruction: Let (ai,bi)A be a pair in an instruction alignment A such that ai=ψ or bi=ψ. Let i be the valid instruction in the pair (ai,bi), then i is called an unaligned instruction.

Definition 4.

Complete Alignment: An instruction alignment A is called complete if it does not contain any unaligned instructions (i.e., for all pairs (aj,bj)A, ajψ and bjψ). This implies that the lengths of the instruction sequences in the aligned basic blocks must be equal (n=m).

Definition 5.

Extraneous Instruction: Let It contain an unaligned instruction i. An instruction i′′ is inserted into If such that i and i′′ are compatible and form a pair in the complete alignment A. i′′ is defined as an extraneous instruction. While functionally similar to speculative predication, MERIT generates i′′ using semantic safety analysis to ensure it does not trigger exceptions (e.g., division-by-zero, memory traps) when executed on the path where it was originally absent.

If the instruction alignment for Bt and Bf is complete, we can fully merge Bt and Bf into a single basic block, eliminating the conditional branch. The first step of MERIT is to transform the alignment A into a complete alignment A.

After computing the complete alignment A, MERIT generates the merged code by processing each aligned pair (it,if)A according to the following rules:

Case 1: Identical operations and operands.

If it and if have the same operation and all operands match (k:operandk(it)=operandk(if)), emit a single instruction inew with no select. This is the ideal case requiring no additional overhead.

Case 2: Identical operations, differing operands.

If it and if have the same operation but operands differ, emit select instructions for each differing operand, then emit inew using the selected operands: operandk=select(cond,operandk(it),operandk(if)), inew=operation(operand1,operand2,).

Case 3: Gaps (unaligned instructions).

If (it,ψ) or (ψ,if) is a gap in the alignment, insert an extraneous instruction (Section 4.3) to complete the pair before applying Case 2.

Select minimization

Minimizing select instructions is critical: each select creates a data dependency that serializes execution, and excessive select chains extend the critical path, limiting the processor’s ability to exploit instruction-level parallelism through out-of-order execution. On Ice Lake, a select (cmov) instruction has 1-cycle latency but cannot be issued until the condition is resolved, creating a dependency bottleneck when cascaded [12, 1].

The select operations can be minimized if both sides of the conditional branch have similar def-use chains. More precisely, let it=op(ot1,ot2) and if=op(of1,of2) be two aligned instructions in the alignment A. Merging it and if does not require additional select operations if ot1=of1 and ot2=of2 or (ot1,of1) and (ot2,of2) are also produced by aligned instructions in A. For example, consider two aligned instructions it: r1 = t1 + 4 and if: r2 = t2 + 4. If t1 and t2 are the results of a previously aligned and merged instruction pair (producing a single value tnew), and the constant operand 4 is identical, MERIT generates a single merged instruction r_new = t_new + 4. No additional select instructions are required because the operands are either shared constants or flow directly from the already-merged data dependency chain.

4.3 Handling Gaps: Extraneous Instruction Construction

Extraneous instructions are essential for completing the alignment when gaps exist. Their construction must ensure safety – no exceptions on either execution path.

Handling ALU operations

There is flexibility in setting operands for extraneous ALU instructions. We can use safe constant values depending on the semantics of the instruction (e.g., 0 for add and 1 for div instructions to avoid division-by-zero). Alternatively, we can preserve def-use chains to minimize select operations. In MERIT, we perform a mix of both: we preserve def-use chains (which minimizes selects) when the extraneous instruction cannot fail (overflow, underflow, division by zero, or undefined behavior); otherwise we fall back to safe constants.

Handling memory operations

Memory operations present the greatest challenge for production-safe branch elimination. Speculatively executing a load from an address that would not be accessed in the original branched code risks triggering a page fault or segmentation violation if the address is invalid, immediately crashing the program.

MERIT ensures safety through safe address substitution. For each extraneous memory operation Mext, we construct:

addrsafe(Mext)=select(cond,addr(M),SAFE_ADDR) (2)

where SAFE_ADDR is a compiler-allocated memory location per module guaranteed to:

  • Reside in valid, mapped address space (i.e., no page faults)

  • Never overlaps with program memory (pProgramMem:SAFE_ADDRp)

  • For loads: contain well-defined values (i.e., zero-initialized)

  • For stores: a separate compiler-allocated location that no original program code references, so writes to it have no observable effect

This ensures that speculative loads and stores complete without any runtime exceptions. In principle, loads and stores could share a single SAFE_ADDR: since the final select at the merge point always discards the speculated value, the content of SAFE_ADDR is irrelevant; the only requirement is that it be initialized (so a speculative load before any store yields a defined value rather than undef, which LLVM is free to exploit as undefined behavior). In practice, our implementation allocates separate load and store locations. Sharing a single address would cause LLVM’s alias analysis to treat load and store as conflicting, preventing reordering and inhibiting optimization of the surrounding code. Note that accessing SAFE_ADDR does not create too much cache pollution, as it is only an 8-byte zero-initialized global variable per module, far below the L1D cache sizes on modern CPUs. However, one of the potential costs is the memory (bandwidth) pressure from accessing the dummy addresses.

We apply the following criteria when aligning memory operations:

  • If two aligned memory operations access the same address at compile time, we merge them into a single memory operation (no select needed).

  • If two aligned memory operations access different memory locations, we merge them and select the address for the merged instruction conditionally.

  • If there is an unaligned memory operation, we insert an extraneous memory instruction to access SAFE_ADDR.

  • Races on SAFE_ADDR are benign because all values read from or written to it are discarded by select. No original computation depends on SAFE_ADDR’s content.

4.4 Transformation Decision: Filtering Heuristics

While the Smith-Waterman algorithm (Section 2.3) computes the optimal alignment given two instruction sequences, not all alignments are profitable to transform. The alignment score is used as a profitability filter to decide whether to proceed with the transformation.

To quantify the similarity of def-use chains from both paths, MERIT computes:

Score=num_matches×match_bonusnum_gaps×gap_penalty (3)

Each matched pair contributes positively (shared computation that need only execute once), while each gap contributes negatively (an extraneous instruction that adds overhead). If the score falls below a threshold (default 0.2, exposed as a compilation flag), MERIT rejects the transformation. The threshold represents a trade-off: too high misses profitable opportunities; too low transforms branches where instruction overhead exceeds the misprediction savings.

Scope and limitations.

The scoring filter applies only to if-then-else branches; if-then branches (canonicalized to diamonds with an empty else) score non-positively by construction – every instruction is a gap, so the score is strictly negative whenever the then-arm is non-empty – and instead rely on the profile-guided filter (Section 5.2). Even on if-then-else, the structural score (i) ignores runtime factors such as misprediction rate and memory-access patterns and (ii) collapses the alignment to match/gap counts, ignoring path-structure asymmetry (instruction latencies, critical-path depth, path-length ratios); addressing both requires a principled cost model that we leave to future work (Section 7), with MERIT-PGO serving as the pragmatic substitute.

4.5 MERIT Transformation Example

Now we explain how MERIT transformation works in action using our running example (Listing 1). Figure 2 shows how to_upper function is transformed at each stage. Figure 2(a) shows to_upper function with an empty else section inserted. This is an extra canonicalization step of MERIT that converts if-then to if-then-else form, which allows it to merge if-then branches. Also, instructions are shown on separate lines (Lines 4-6) for better readability. Figure 2(b) shows the code after extraneous code insertion (Section 4.3). Here, the else path is empty; therefore, all the instructions are unaligned. The else path contains inserted extraneous instructions. For example, the load operation in Line 4 is repeated with a location (mem) from the safe global memory space in Line 9 after the extraneous code insertion. MERIT also tries to preserve def-use chains and minimize select operations required for merging. For example, variable t7 at Line 11 uses t6 at Line 10, similar to variable t3 using t2 as its first operand. t7 uses 0 as its second operand to avoid any overflow/underflow bugs. This example also demonstrates how store instructions are handled during extraneous code insertion. On the if path, there is a store (Line 7) of value t3 to text[i]. On the else path, the same store is performed (Line 12), but the stored location is mem (the safe address) and the stored value is t7. Figure 2(c) shows the code after the melding step (Section 4.2)222This sequence of steps is to illustrate the melding process. Standard optimizations (Constant propagation, DCE, CSE) run after MERIT to clean up redundant code.. Notice extra select instructions (shown as a ternary operator) are inserted to select operands if input operands do not match. The transformed program is much faster to execute than the original one (Section 3).

1// ...
2if ((text[i] >= ’a’)
3 & (text[i] <= ’z’)){
4 t1 = text[i] - ’a’;
5 t2 = t1 + ’A’;
6 text[i] = t2;
7} else {
8}
(a) Empty else insertion.
1// ...
2if ((text[i] >= ’a’)
3 & (text[i] <= ’z’)){
4 t1 = text[i];
5 t2 = t1 - ’a’;
6 t3 = t2 + ’A’;
7 text[i] = t3;
8} else {
9 t5 = *mem;
10 t6 = t5 - 0;
11 t7 = t6 + 0;
12 *mem = t7;
13}
(b) Extraneous code insertion.
1// ...
2is_lower = (text[i] >= ’a’)
3 & (text[i] <= ’z’);
4t1_t5 = is_lower ? text[i] : *mem;
5s1 = is_lower ? 0 : ’a’;
6t2_t6 = t1_t5 - s1;
7s2 = is_lower ? 0 : ’A’;
8t3_t7 = t2_t6 + s2;
9t4_t8 = is_lower ? &text[i] : mem;
10*t4_t8 = t3_t7;
(c) Code after the merging.
Figure 2: MERIT transformation example.

4.6 Complete Transformation Algorithm

We describe the overall MERIT transformation for a program in Algorithm 1. MERIT transformation iterates through all the functions in a program. For each function, it collects all the valid branches for applying the MERIT transformation. The structural validity of a branch is determined by the two paths of the branch having straight-line control-flow converging at a basic block. In other words, control-flow regions of if-then-else (i.e., two-sided branches) or if-then (i.e., one-sided branches).

Then, for each valid branch, it computes the instruction alignment (Section 2.3). For two-sided branches, if the alignment score falls below the threshold (Section 4.4), the transformation is rejected. Otherwise, it inserts extraneous instructions to complete the alignment (Section 4.3) and merges the two blocks (Section 4.2). One-sided branches (if-then) bypass this threshold check, as discussed in Section 4.4. A whole basic block of extraneous instructions will be added. MERIT repeatedly performs these steps until there are no changes to the function. This is a fixed-point computation: each merge strictly decreases the number of eligible diamond regions in F (the merged branch is eliminated, no new branches are introduced, and simplify(F) does not undo prior merges), so the outer Repeat loop terminates in at most a number of iterations linear in the original number of valid branches. Each merge may, however, expose new diamond regions (e.g., a nested branch becomes eligible after its enclosing branch is eliminated), which is precisely why iteration is required. As a defensive measure, our implementation additionally enforces a hard iteration cap.

Algorithm 1 MERIT Transformation Algorithm.

4.7 Correctness and Safety Verification

The MERIT transformation satisfies two critical properties. First, semantic preservation: the transformed program produces identical outputs to the original program for all inputs. Second, crash-freedom: speculative execution does not trigger exceptions that the original program would avoid.

Notation.

We use denotational style semantics for reasoning. Let denote the semantic function of a program, mapping an input state σ (program-visible variables, memory, I/O) to an output state. A branch construct B has condition cond, a true path Pt, and a false path Pf, whose instruction sequences are It and If (Section 4.2). We denote by VO the set of SSA values defined by original instructions in ItIf, and by VE the values defined by the extraneous instructions that MERIT inserts to complete the alignment. The subset of VO that is live at the merge point is what flows into downstream code. For a branch B, the original semantics is

B(σ)={Pt(σ)if σcond,Pf(σ)otherwise. (4)

After transformation, both paths are augmented into Pt and Pf and linearized into a single straight-line code P that (i) computes cond exactly as the original one does, (ii) executes the merged instruction sequence, and (iii) at the merge point of the branch, every live-out variable x gets selected as follows:

xP=select(cond,xPt,xPf). (5)
Sufficient conditions.

All of the following conditions are enforced by design as in Sections 4.2 and 4.3:

  1. (C1)

    Condition preservation: cond and its data dependencies are not modified; no extraneous instruction is inserted into or before the computation of cond.

  2. (C2)

    Selection: The select statement in Equation 5 holds for every live-out variable x.

  3. (C3)

    Safe memory substitution: For every memory operation in P whose pre-merge counterpart was either an extraneous instruction or an aligned memory operation at a distinct address, the address is select(cond,addrt,addrf) where addrt is the original address on the Pt-side (or SAFE_ADDR if Pt’s slot was a gap), and for addrf, correspondingly. For stores, the stored value is select(cond,v,vdummy), so the not-taken-path targets SAFE_ADDR (via the address select) and writes a well-defined dummy value (via the value select); the specific choice of vdummy is an optimization addressed in Section 4.3, not a correctness requirement. SAFE_ADDR is a compiler-allocated location in a valid mapped space that does not alias with any original program memory.

  4. (C4)

    Non-interference: No original value transitively depends on any extraneous value; formally, letting deps(u) be the transitive dependence closure of u, we have uVO:deps(u)VE=.

Note that Condition (C4) alone is necessary but not sufficient for correctness: even if no original value depends on an extraneous value, a speculatively issued load from a bad pointer would still result in a fault, and a speculative store to the wrong address would still corrupt the state of memory. The Condition (C3) is what closes that fault/corruption gap, while Conditions (C1) and (C2) ensure that the merge point yields the taken path’s values rather than the speculated ones. The termination of the fixed-point iteration across merge steps is argued separately in Section 4.6.

Semantic preservation.

By the Condition (C1), P evaluates cond on σ to the same truth value as the original. By the Condition (C4), each extraneous instruction’s result is consumed only by subsequent extraneous instructions or by select instructions that discard it on the taken path. Therefore, every value in VO computed along Pt (resp. Pf) is unaffected by any value in VE. Hence, the projection of Pt onto VO equals Pt, i.e., Pt|VO=Pt (resp., Pf|VO=Pf). By the Condition (C3), every memory write that only occurs under speculation happens to SAFE_ADDR, which is disjoint from the original program memory. Hence, the memory component of σ is unchanged. Therefore, xPt=xPt and xPf=xPf for all live-out variables x. Substituting into Equation 5 yields xP=select(cond,xPt,xPf). By the semantics of select, this equals xPt when σcond and xPf otherwise, which is the same case split as Equation 4. Thus, P(σ)=B(σ) for all σ.

Crash-freedom via safe address substitution.

Memory operations pose the primary safety challenge, and we treat loads and stores separately.

Loads. Consider a load L in path Pt originally guarded by cond: in the original program, L executes only when cond holds. Under speculation, the merged load executes unconditionally, risking a fault if its address were invalid when ¬cond holds. By selecting the address, the Condition (C3) prevents this scenario: the merged instruction is a single load at select(cond,addr(L),SAFE_ADDR), so when ¬cond holds, the load reads from SAFE_ADDR, a valid, zero-initialized location. The value produced on the ¬cond path is in VE and is consumed only by subsequent extraneous instructions or discarded by a downstream select; by the Condition (C4), no original value in VO depends on it.

Stores. A store poses the additional risk of an observable write in the not-taken path. Consider a store S writing v to addr(S) on Pt originally guarded by cond. After merging, MERIT emits a single store whose address is select(cond,addr(S),SAFE_ADDR) and whose value is select(cond,v,vdummy). For crash-freedom, vdummy needs only to be a well-defined value (i.e., not undef); which specific well-defined value is chosen is an optimization issue addressed in Section 4.3 (MERIT uses an identity constant or a def-use-preserving choice to minimize downstream selects), not a correctness requirement. When cond holds, the store S writes v to addr(S) exactly as the original. On the other hand, when ¬cond holds, it writes vdummy to SAFE_ADDR. By Condition (C3), SAFE_ADDR does not alias with any original program memory, so the write on the not-taken path has no observable effect and no subsequent original loads can read vdummy.

MERIT does not reorder existing memory operations across the branch boundary; it only introduces accesses to SAFE_ADDR, whose non-aliasing property (i.e., Condition (C3)) ensures that no memory-ordering property of the original program is violated.

Example of a merge step.

In Figure 2, we illustrate the invariance of the original dataflow. Before merging (Figure 2(b)), the taken path contributes VO={values in t1,t2,t3,text[i]} and the gap-filled extraneous path contributes VE={values in t5,t6,t7,mem}, where the symbol mem in the extraneous slots denotes SAFE_ADDR (distinct from any original program memory) and the extraneous ALU ops use def-use-preserving operands (0/+0). The merge (Figure 2(c)) proceeds aligned pair by pair and, critically, each aligned memory pair becomes one memory instruction with a select-chosen address (not two memory instructions with a select-chosen value):

t1_5 =load(select(cond,&text[i],mem)) (one load, address selected)
s1 =select(cond,‘a’, 0) (operand selected)
t2_6 =t1_5s1 (single arithmetic op)
s2 =select(cond,‘A’, 0) (operand selected)
t3_7 =t2_6+s2 (single arithmetic op)
t4_8 =select(cond,&text[i],mem) (store address selected)
store (t3_7,t4_8) (one store)

Evaluating on σcond, every select returns its cond-branch argument: t1_5 loads from text[i] (equal to original t1), s1=‘a’, t2_6=t1‘a’ (equal to t2), s2=‘A’, t3_7=t2+‘A’ (equal to t3), and the store writes t3 to text[i], identical to the original path. On σ⊧̸cond, every memory access routes to SAFE_ADDR, the arithmetic instructions run on extraneous values in VE, and the store updates only SAFE_ADDR; no value in VO is affected (Condition (C4)) and no fault can occur (Condition (C3)). Because each merge step preserves Conditions (C1)(C4), so does their composition across the fixed-point iteration of Algorithm 1.

5 Implementation

5.1 Compiler Integration

We implement MERIT as an LLVM pass in LLVM-14, integrated into the standard pipeline and enabled by default at optimization levels above -O0. It is scheduled after early canonicalization passes (e.g., SimplifyCFG, SROA, Mem2Reg) but before the inlining pipeline and later optimization stages. This position is intentional: early passes canonicalize the control-flow graph and eliminate redundant code, producing simpler IR that reduces the cost of MERIT’s instruction alignment. Running before inlining also keeps function bodies compact, limiting the number of branch sites MERIT must analyze per function. By running before backend optimizations, MERIT also avoids potential interference with later-stage alias analysis or memory layout changes.

5.2 Enabling Profile-guided Optimization

While MERIT can achieve high performance by instruction-level merging on control-flow patterns, indiscriminately applying the transformation across all branches in a program can lead to poor performance. The overhead introduced by select instructions and operand multiplexing may exceed the benefit of branch elimination in cases where branch prediction accuracy is already high or when the merged instruction sequence is significantly longer than the original branching code. We address this challenge by enhancing the application of MERIT in the fashion of a Profile-guided Optimization (PGO): selectively transform based on observed empirical performance.

MERIT provides fine-grained filtering mechanisms accessible through compiler command-line options, either filtering by source file names, function names, or line numbers to enable selective application of the transformation. For instance, developers can specify function-level inclusion or exclusion lists using -include-func-names=func1,func2 to selectively enable MERIT only for high-value targets (e.g., hot functions, or functions whose branches have the highest misprediction rate), or -exclude-func-names=func3,func4 to blacklist problematic functions (e.g., functions known to cause heavy overhead). Additionally, MERIT supports file-level filtering via -exclude-file-names=utils.c,legacy.c to exclude entire source files, and line-level precision through -json-include-lines=profile.json where the JSON file maps source filenames to arrays of line numbers that should undergo transformation. This multi-granularity filtering infrastructure enables developers to iteratively refine their optimization strategy by excluding transformation sites that exhibit poor performance characteristics while retaining those that demonstrate clear benefits.

6 Evaluation

6.1 Experimentation Setup

Hardware and OS

All tests are run on a x86 server with Intel Xeon Silver 4314 CPU, featuring 32 cores, and 192 GB of RAM. The microarchitecture is Intel Sunny Cove (Ice Lake) with TAGE-based branch predictor. The system runs Ubuntu 22.04.5 LTS based on kernel version 6.8.0.

Workloads

We evaluate MERIT on a total of 102 benchmarks from four benchmark suites:

  • 24 branch-heavy microbenchmarks that we self-curated to span eight algorithmic categories: sorting (bubbleSort, shakerSort, shellSort, heapSort, qsort, bitonic), searching (binSearch), graph (prim, krushkal, pageRank, karger, a-star), array (arrayUnion, arrayIntersection, arrayMerge), dynamic programming (kadane, smithw), string manipulation (toUpper), partitioning/merging (dutchFlag, merge, ccomp), image processing (dialation, erosion), and simulation (lbm). A program is admitted to the suite if it satisfies two criteria: (i) it contains at least one data-dependent branch in a hot loop, and (ii) the branch direction is not statically predictable. No candidate programs were excluded after curation; the suite is the full set that met both criteria.

  • 16 CPU SPEC2017 [8] benchmarks compiled in rate mode (SPEC’s throughput-oriented configuration that runs multiple copies in parallel). We intentionally exclude all Fortran workloads because LLVM-14 lacks the flang-new code generation capabilities required to lower Fortran to LLVM IR.

  • 40 pyperformance [42] benchmarks invoked from MERIT-optimized Python interpreter.

  • 22 TPC-H [43] online analytical processing (OLAP) queries processed by MERIT-optimized SQLite [13] engine.

Comparison targets

We compile the benchmarks into four configurations for evaluation:

  • MERIT: transformation applied with backend optimization disabled, isolating MERIT’s direct effect from downstream pass interference (analysis-only, not production).

  • MERIT-O2: transformation applied with the full -O2 backend pipeline enabled – the primary production-ready configuration.

  • MERIT-PGO: profile-guided selective transformation; we profile execution, identify functions exhibiting performance degradation, and exclude them from MERIT (methodology detailed in Section 6.3).

  • if-conv: LLVM’s early if-conversion pass [27], which operates in machine-level IR with -O2 enabled and uses target scheduling-model cycle costs to guide transformation, but cannot speculate memory accesses.

All modes are normalized to their corresponding baseline compiled with the same backend optimization level (-O2). Specifically, MERIT is compared to baseline with backend optimizations disabled. MERIT-O2, MERIT-PGO, and if-conv are compared to baseline with backend enabled. This ensures fair comparison by isolating the effect of each transformation from backend optimization benefits. Note that all modes with O2 (including baselines) already include SimplifyCFG’s basic conversion for simple, safe control flow to hyperblock [23]. This represents the state-of-the-art for conservative (memory-safe) branch elimination. We report the median across 5 independent runs, with standard deviation <2% for all measurements.

6.2 Compilation Statistics

Table 1: Compile-time overhead (OH), number of transformation sites (#Sites), and code size change (ΔSize) for SPECrate 2017, SQLite, and Python.
Benchmark Mode OH (%) #Sites ΔSize (%)
500.perlbench_r O2-baseline 0.0 1089 0.0
O2-ifconv 4.9 1137 0.0
O2-MERIT 93.7 7175 3.4
507.cactuBSSN_r O2-baseline 0.0 275 0.0
O2-ifconv -10.8 332 N/A
O2-MERIT 2.7 2063 N/A
508.namd_r O2-baseline 0.0 510 0.0
O2-ifconv -4.2 510 0.0
O2-MERIT 15.7 1549 1.4
510.parest_r O2-baseline 0.0 2743 0.0
O2-ifconv -2.7 2761 0.0
O2-MERIT 3.4 4513 1.8
520.omnetpp_r O2-baseline 0.0 764 0.0
O2-ifconv 1.4 785 0.0
O2-MERIT 12.5 2045 0.8
523.xalancbmk_r O2-baseline 0.0 1440 0.0
O2-ifconv -11.3 1471 0.0
O2-MERIT -5.3 2931 0.5
531.deepsjeng_r O2-baseline 0.0 88 0.0
O2-ifconv 0.0 95 0.2
O2-MERIT 16.0 486 5.1
538.imagick_r O2-baseline 0.0 1524 0.0
O2-ifconv -4.6 1576 0.0
O2-MERIT 17.8 3833 2.2
557.xz_r O2-baseline 0.0 90 0.0
O2-ifconv 0.4 94 0.0
O2-MERIT 28.2 453 2.5
Python 3.10 O2-baseline 0.0 1144 0.0
O2-ifconv 5.3 1363 0.1
O2-MERIT 14.4 3665 2.2
SQLite 3 O2-baseline 0.0 402 0.0
O2-ifconv 2.9 430 0.1
O2-MERIT 20.5 1708 4.2

Table 1 reports compile-time overhead, number of transformed sites, and code-size changes for the -O2 baseline, if-conv, and MERIT. The baseline already performs a small amount of conservative branch simplification via SimplifyCFG (e.g., FoldTwoEntryPHINode); if-conv adds only a modest number of additional conversions (e.g., +48 on 500.perlbench_r), reflecting its conservativeness on x86 due to unsafe memory speculation. MERIT consistently discovers many more opportunities (e.g., 7175 vs. 1089 on 500.perlbench_r, and 3665 vs. 1144 on Python), enabled by IR-level analysis and safe transformation of conditional memory operations. Compile-time overhead tracks these site counts: if-conv stays within a few percent of baseline, while MERIT ranges from near-zero on small benchmarks up to 93.7% on 500.perlbench_r, where the O(n2) Smith-Waterman alignment cost is amplified by the 7175 transformation sites. In practice the cost is bounded by the branch-arm instruction count, which is typically small (median <15 instructions per arm on SPECrate), and selective application via PGO or a cost model (Section 7) keeps overhead manageable. Negative overheads in a few rows (e.g., 11.3% if-conv on 523.xalancbmk_r, 10.8% on 507.cactuBSSN_r) reflect downstream-pass speedups when branch elimination yields simpler IR for subsequent passes. Text-size changes remain small (typically 2.5%), with the largest in this table being 5.1% (531.deepsjeng_r) and 4.2% (SQLite).

6.3 Microbenchmarks

Backend optimization impact on microbenchmarks

Figure 3(a) shows the runtime performance of MERIT on the 24 microbenchmarks (categories listed in Section 6.1). MERIT achieves up to 32× speedup over hardware speculation in toUpper, with a 1.52× geometric mean (geomean) speedup across all benchmarks. MERIT-O2 achieves better performance than MERIT in toUpper, ccomp, dutchFlag, and bubbleSort but underperforms MERIT on others (geomean 1.42×). Notice that in arrayMerge, heapSort, a-star, and prim, MERIT-O2 incurs performance degradation, but not in MERIT. Note that MERIT (backend disabled) serves only to isolate the pass’s direct effects for diagnostic purposes. For real-world deployment, MERIT-O2 or MERIT-PGO would be used. This reveals systematic interference patterns where downstream optimization passes undo or degrade MERIT’s transformations. We identify three distinct interference patterns that account for the observed regressions.

Pattern 1: Select-to-branch reversion.

Downstream passes convert MERIT’s selects back into conditional branches when their cost heuristic scores the chain as “expensive”. On prim, MERIT eliminates a single branch guarding three conjunctive conditions; downstream reversion turns the resulting select chain into three separate conditional jumps, each independently mispredictable. prim drops from a 7% gain (MERIT) to a 37% loss (MERIT-O2) – a 44 percentage-point swing.

Pattern 2: Loop unrolling amplification.

In heapSort and arrayMerge, MERIT converts conditional swaps into straight-line select-based code, enlarging the loop body. The loop unroller – calibrated for branched bodies – applies its standard unroll factor to the now-larger body, and the resulting i-cache pressure negates the misprediction savings.

Pattern 3: Register-pressure misjudgement.

MERIT’s selects keep operands from both paths simultaneously live. In a-star, the select chain transforming a priority-queue update under a multi-pointer-dereference branch elevates pressure enough that the allocator spills to the stack; the spill loads/stores in the inner loop negate MERIT’s misprediction savings, yielding a net MERIT-O2 slowdown.

Takeaway: Since MERIT operates at the early LLVM-IR level, specific downstream interference patterns can negate or reverse MERIT’s performance gains. These patterns are systematic and predictable, motivating the need for MERIT-aware backend coordination.

(a) MERIT achieves performance geomean of 1.52× (MERIT-O2: 1.42×) compared to if-conversion’s 1.05×.
(b) MERIT achieves average 48.9% branch-miss reduction compared to if-conversion’s 6.8%.
(c) MERIT achieves 25.9% IPC improvement (MERIT-O2: 38.9%) compared to if-conversion’s 1.9% on average.
(d) MERIT-O2 incurs 17.5% runtime instruction overhead (MERIT: 4.3%) compared to if-conversion’s 0.9% on average.
Figure 3: Performance, branch-miss reduction, IPC change, and runtime instruction overhead of MERIT and early if-conversion on the microbenchmarks, compared to hardware speculation (no transformation).

Early if-conversion shows much less performance improvement (only 1.05× geomean). This is because it cannot transform control-flow regions where unsafe memory operations exist. For example, toUpper has str[i] within the branch (Listing 1) that might cause invalid memory access on x86. However, such a problem is avoided in MERIT by safely guarding memory access during IR instruction melding.

We noticed a rare case in dutchFlag where both MERIT and if-conversion show improvement, but if-conversion beats MERIT. This algorithm’s loop contains a highly unpredictable, three-way branch that guards only simple counter increments. Early if-conversion excels here by transforming this into computationally trivial cmov instructions, eliminating the misprediction penalties. This specific workload is ideal for if-conversion because it lacks the complex, unsafe memory operations that MERIT is designed to handle. Furthermore, as a later-stage pass, the extremely low register pressure and the simple data dependency chains mean that backend optimizations (register allocation, instruction scheduling) work well with early if-conversion’s output, whereas MERIT’s IR-level selects may undergo additional lowering transformations that introduce relative inefficiencies.

Understanding Performance Through IPC and Instruction Overhead

The performance results in Figure 3(a) are driven by two competing factors: misprediction elimination (beneficial) vs instruction overhead and ILP disruption (harmful). Figures 3(c) and 3(d) quantify these effects.

IPC Analysis.

(Figure 3(c)): MERIT improves IPC by 25.9% on average (and MERIT-O2 by 38.9%) because microbenchmarks are dominated by highly unpredictable branches. The baseline suffers frequent pipeline flushes (15-17 cycles per misprediction), occurring every 3-10 iterations. MERIT eliminates these devastating penalties by trading speculative parallelism for deterministic sequential execution. While MERIT’s select chains create dependency serialization that disturbs out-of-order execution, the avoided flush penalty dominates, resulting in net IPC improvement.

Instruction Overhead.

(Figure 3(d)): MERIT-O2 incurs 17.5% average instruction overhead by executing extraneous instructions from both branch paths, while MERIT (backend-disabled) incurs only 4.3%. However, this overhead does not correlate with performance loss. Workloads like qsort, arrayMerge, and heapSort all see performance gains despite 10-50% instruction increase, because the cost of extraneous instructions is negligible compared to frequent, severe misprediction penalties.

CPU SPEC2017 Performance

Figure 4(a) shows SPECrate 2017’s performance transformed by MERIT’s pass. Unfortunately, blindly applying MERIT on most benchmarks shows performance degradation (blue bars). This is because most benchmarks are not branch-heavy, thus MERIT produces excessive runtime instructions, giving geomean of 0.96× compared to no transformation. However, when we apply PGO to identify and exclude those functions that cause most performance degradation, all benchmarks show either the same or better performance (green bars). Take 505.mcf_r as an example, naive MERIT generates 16% degradation but shows 3% runtime improvement when we identify and exclude those bad functions. If-conversion, showing the same behavior we have seen in microbenchmarks, barely shows any performance differences, indicating its conservativeness when producing branchless code.

PGO Methodology

For SPEC2017 benchmarks, we strictly separate training and testing phases to avoid overfitting. The PGO profiling runs use the training input datasets provided by SPEC (smaller, faster datasets designed for validation), while the final performance measurements use the full reference input datasets. This ensures our transformation decisions generalize beyond the profiling workload. For microbenchmarks, which lack separate training inputs, we profile and measure using the same inputs – a limitation we acknowledge, though the results remain valid for demonstrating MERIT’s effectiveness on branch-heavy kernels.

(a) MERIT achieves performance geomean of 0.96×, MERIT-PGO of 1.01×, compared to if-conversion’s 0.99×.
(b) MERIT achieves average 6.9%, MERIT-PGO achieves 3.5% branch-miss reduction compared to if-conversion’s 0.4%.
(c) MERIT shows near-neutral 0.4% IPC change on average; MERIT-PGO achieves +1.0% IPC improvement, compared to if-conversion’s 0.1%.
(d) MERIT incurs 2.7% runtime instruction overhead, MERIT-PGO incurs 0.6% overhead compared to if-conversion’s near-zero (0.0%) overhead on average.
Figure 4: Performance, branch-miss reduction, IPC change, and runtime instruction overhead of MERIT and early if-conversion on SPECrate 2017.

Misprediction Reduction

We evaluate how well MERIT eliminates branch mispredictions compared to if-conversion. On average, MERIT reduces 48.9% branches mispredictions on the microbenchmarks (Figure 3(b)) and 6.9% on SPECrate 2017 (Figure 4(b)), respectively. Compared to early if-conversion, MERIT reduces 42.1% more mispredictions in our microbenchmarks and 7.3% more in SPECrate 2017. For the microbenchmarks, the overall trend of misprediction reduction matches the performance improvement in Figure 3(a). However, for a more realistic SPECrate benchmark, misprediction reductions do not directly correlate to performance improvement. For example, in 505.mcf_r, although MERIT and MERIT-PGO reduce nearly the same amount of mispredictions (22.4% vs. 22.1%), MERIT-PGO gives 3% positive performance compared to MERIT which gives 16% performance degradation.

In 538.imagick_r, MERIT reduces 45% mispredictions yet causes an 8% slowdown and a 3.7% IPC drop. The root cause is critical-path extension outweighing misprediction savings: ImageMagick’s hot loop performs pixel transformations with conditional color-space conversions (RGBYUV), and MERIT forces both conversion paths to execute speculatively, adding 12 FP ops and a 6-instruction select-dependency chain per iteration, raising live FP values from 9 to 18. The mispredicted branch incurred a 15-cycle pipeline flush 9% of the time (avg 1.4 cycles/iter), but MERIT’s select chain adds 4–5 cycles to the critical path on every iteration; over billions of iterations the per-iteration FP serialization dominates the occasional flush savings. This exposes a limitation of structural filtering: without operation-latency awareness (FP is 3–5× slower than integer), MERIT cannot distinguish cheap branches from those guarding heavy computation. The cost model (Section 7) must incorporate operation type and critical-path depth, not just instruction count.

IPC and Instruction Overhead in SPEC2017

IPC Impact: Unlike microbenchmarks, MERIT shows near-neutral average IPC on SPEC workloads (0.4%), with per-benchmark outcomes ranging from 14.4% (544.nab_r) to +3.3% (505.mcf_r). SPEC’s branches are mostly predictable (80–95% accuracy vs. 50–70% in microbenchmarks), so the baseline rarely suffers pipeline flushes; MERIT’s select-chain serialization and misprediction elimination roughly cancel on average. The serialization disrupts ILP on every iteration even when the original branch would have predicted correctly. Under MERIT-PGO, IPC becomes net positive (+1.0%) as the most harmful functions (e.g., 544.nab_r, 14.4% IPC) are excluded. Instruction Overhead: Naive MERIT incurs 2.7% average instruction overhead on SPEC, while MERIT-PGO reduces this to 0.6% by excluding transformation on functions where overhead dominates benefit.

Takeaway: For complex workloads, solely reducing mispredictions is insufficient for performance gains. Critical path extension and operation latency must be considered. An intelligent cost model is essential to avoid transformations that introduce per-iteration overhead exceeding occasional misprediction penalties.

6.4 Case Study: SQLite

Our first case study, a MERIT-compiled SQLite engine running 22 TPC-H queries, clearly demonstrates the necessity of profile-guided selective transformation. For the TPC-H queries, which lack separate training datasets, we use the same inputs for both profiling and measurement. However, the function-level exclusion decisions generalize well across query shapes. For instance, excluding a poorly-performing hash table function benefits all queries that use that code path.

As shown in Figure 5, naively applying MERIT (blue bars) results in significant performance degradation on many queries, particularly 7, 8, 9, and 21, leading to a geomean of 0.85×. This is again due to the instruction overhead from transforming branches that were not performance bottlenecks.

However, the MERIT-PGO (green bars) results are transformative. By using PGO to identify and exclude problematic functions, we completely mitigate all slowdowns and achieve a positive geomean performance of 1.01×. Furthermore, MERIT-PGO achieves speedups on several queries (e.g., 5, 6, 11, 15, and 18) by targeting only the high-value, misprediction-prone branches. This selective transformation reinforces our takeaway: for complex real-world applications, MERIT must be paired with an intelligent cost model or PGO filtering to achieve performance gains. If-conversion barely affects performance, likely because it cannot transform the performance-critical blocks.

Figure 5: TPC-H performance with SQLite. MERIT achieves performance geomean of 0.85×, MERIT-PGO of 1.01×, compared to if-conversion’s 1.0×.

6.5 Case Study: Python

In our second case study, we evaluate MERIT on a large real-world application: the CPython interpreter (v3.10) with 40 benchmarks from the pyperformance suite [42]. Shown in Figure 6, applying MERIT indiscriminately results in a 1.01× geomean speedup, with some workloads like unpickle_list (peak 1.11×) and nbody seeing benefits, while others do not. In comparison, the standard if-conv pass is more volatile, showing gains on some workloads (e.g., regex_effbot) but notable losses on others (e.g., tomli_loads, bpe_tokeniser, crypto_pyaes), for a 0.99× geomean.

We do not report MERIT-PGO performance for the Python suite because its function-level filtering is too coarse-grained for a complex runtime like the interpreter, where diverse workloads from the pyperformance suite stress different parts of the code. Excluding a function to benefit one workload (e.g., nbody) could harm another (e.g., json_loads) that relies on the same function’s transformation. However, this points to a practical strategy for specialized environments: one could use MERIT with PGO to create a highly-tuned interpreter optimized only for their specific workload (e.g., a JSON serialization service), transforming only the hot, misprediction-prone functions on the application’s critical path to achieve speedups that would be washed out in a general-purpose benchmark.

Figure 6: Pyperformance performance with Python. MERIT achieves 1.01× geomean performance compared to if-conversion’s 0.99×.

6.6 Phase Ordering Study: MERIT vs. Inlining

MERIT is positioned before the inlining in the LLVM pipeline (Section 5) for three reasons, with the overarching principle that pre-inline placement bounds MERIT’s worst-case regressions while post-inline placement does not (substantiated quantitatively below). Note that in LLVM’s -O2 pipeline, the inliner does not run in isolation: it is flanked by SimplifyCFG invocations on both sides as part of the module-simplification cluster, so placing MERIT before or after the inliner places it before or after this whole cluster. First, transforming branches inside callees into straight-line code gives the inliner simpler, branchless function bodies to reason about, leading to better inlining decisions and cleaner input for downstream passes. Second, MERIT’s path-alignment step is quadratic in the instruction count of branch arms; post-inlining functions absorb many helpers, and the branch arms inside them are significantly larger, inflating alignment costs. Third, SimplifyCFG’s FoldTwoEntryPHINode fold collapses simple diamonds into select chains on its own and competes with MERIT for the same branches; running MERIT before the inliner-flanking SimplifyCFG invocations preserves diamond structures that would otherwise be folded away before MERIT could exploit them.

To quantify how these arguments play out in practice, we compare two placements: pre (MERIT before the inliner, the default used throughout the paper) and post (MERIT after the inliner). Both placements bypass the scoring filter of Section 4.4 so that only the pipeline position – not per-branch profitability decisions – drives the difference. This is why the geomeans reported below are lower than Section 6.3’s MERIT-O2 geomean of 1.42×, where the scoring filter selectively transforms only profitable branches. To probe the quadratic-alignment argument at realistic branch-arm sizes, we additionally measure compile time on the sqlite3.c amalgamation (8.9 MB single translation unit).

We first show the runtime performance of pre (geomean 1.17×) and post (geomean 1.16×) over the -O2 baseline on the 22 microbenchmarks in Figure 7. The two geomeans are essentially tied, but the per-benchmark distribution is not symmetric. Pre wins decisively on benchmarks whose callees contain divergent diamonds that the intermediate SimplifyCFG consumes before post ever sees them. smithw is the clearest case: pre runs before the inliner and merges all structurally-identical update branches inside the callee that is subsequently inlined, delivering a 1.30× speedup, while post arrives after SimplifyCFG has already collapsed those branches into a select chain and regresses to 0.99×. dutchFlag shows the same pattern (1.23×1.00×). These two post-inline worst cases erase MERIT’s entire speedup on those workloads. Post, in turn, wins on benchmarks whose original branch arms were calls to structurally-similar helpers: the call-rejection filter of pre (Section 4.4) drops such branches unconditionally, whereas post sees two straight-line bodies after inlining and merges them. arrayUnion and arrayMerge exemplify this, gaining roughly 1.10× under post versus near-baseline under pre. The asymmetry is what drives the placement decision: post’s wins cluster in a narrow 1.10× band, while its losses collapse speedups as large as 1.30× all the way back to baseline.

Figure 7: Pre- vs. post-inline MERIT speedup over the -O2 baseline on the 22 microbenchmarks. Geomean 1.17× (pre) vs. 1.16× (post); the two worst-case regressions under post-inline (smithw 1.30×0.99×, dutchFlag 1.23×1.00×) both erase the speedup entirely.

Moving to compile-time (Table 2): pre adds 28% user-time and 4.1% peak resident set size (RSS, the compiler process’s peak memory footprint) for 2,328 merges, while post adds 51% user-time and 8.9% peak RSS for 2,716 merges. That is, post produces 17% more merges than pre, but at 23 percentage points more compile time and 4.8 percentage points more peak memory. The average branch arm post-inline is much larger than pre-inline because inlined helpers push additional instructions into each arm, and since MERIT’s path-alignment step is O(n2) in branch-arm instruction count, the cost is material at realistic branch-arm sizes.

Table 2: Compile-time cost of pre- vs. post-inline MERIT placement on sqlite3.c.
Mode Compile user-time Max RSS # Merges
base 21.92 s 239,284 kB 0
pre 28.05 s (+28.0%) 249,068 kB (+4.1%) 2,328
post 33.12 s (+51.1%) 260,652 kB (+8.9%) 2,716

Takeaway: Pre- and post-inline MERIT show similar geomean performance but benefit different code patterns: pre-inline is good at merging divergent diamonds inside small callees (e.g., smithw, dutchFlag) before SimplifyCFG collapses them, while post-inline is good at merging branch arms that originally were calls to structurally-similar helpers (e.g., arrayUnion, arrayMerge). We pick pre-inline as the default because its worst-case regressions are bounded while post-inline’s are not, and because its compile-time cost on realistic translation units is strictly smaller.

7 Limitation and Future Work

We identified two key limitations that constrain MERIT’s effectiveness: unavailability of a static cost model for selective transformation and a lack of coordination between MERIT and backend optimization passes.

7.1 Need for a Cost Model

Our evaluation demonstrates that forcibly applying MERIT transformation can degrade performance on complex workloads, especially when branches are mostly predictable (Section 6). This motivates the need for selective application. While MERIT-PGO addresses this limitation by using runtime profile data to filter transformations based on empirical performance, a more principled approach would use compile-time cost-benefit analysis to predict transformation profitability, specially for use cases where empirical performance data may not be readily available.

Such a cost model would need to balance the branch cost (misprediction probability and penalty) against the speculation overhead (instruction count, memory operations, critical path extension, asymmetry penalty). However, building an accurate static cost model faces fundamental challenges. First, estimating misprediction probability without runtime data is imprecise: real misprediction rates depend on input data patterns unknowable at compile time. Second, MERIT operates at the target-independent IR level, where hardware-specific factors such as execution port contention, store buffer capacity, and pipeline depth are not yet known. Third, the model would need to predict downstream backend interference (discussed below), requiring whole-pipeline cost analysis that remains an open research challenge.

Our evaluation further reveals additional complexities that a cost model must address. The imagick_r case study (Section 6) demonstrates that operation latency characteristics matter: floating-point operations impose significantly higher critical path extension than integer operations, causing per-iteration overhead to outweigh occasional misprediction savings. Similarly, memory operation patterns vary widely in their speculation cost: condition loads are essentially free, while different-base loads risk cache pollution. Understanding when these factors dominate the cost-benefit trade-off, and how they interact with workload characteristics (CPU-bound vs memory-bound), requires deeper analysis of microarchitectural behavior than simple instruction counting can provide.

Preliminary exploration of static cost modeling suggests that even sophisticated heuristics struggle to match PGO’s accuracy. This gap motivates continued reliance on profile-guided approaches for production deployment. Promising future directions include hybrid cost models that combine static analysis with runtime profiling, machine learning (TRACED [11]), or LLM Compiler [10], and that can learn complex cost patterns from large codebases, or adaptive systems that can dynamically adjust transformation decisions based on observed performance. However, the design space for such systems remains largely unexplored.

Orthogonal to the cost-model question, PGO integration itself can be automated end-to-end: the current function-level include/exclude lists are produced manually by the developer after inspecting profiles, but the same decisions can be derived automatically from LLVM’s standard profiling infrastructure (instrumentation or sample-based). Full automation (compile, instrument, run, derive per-function transform decisions, recompile) would remove the remaining developer burden and is the natural next deployment step.

7.2 Lack of MERIT-aware Backend Coordination

The second limitation is the lack of coordination between MERIT and backend optimization passes. As quantified in Section 6.3, three backend interference patterns: select-to-branch reversion, loop unrolling amplification, and register pressure spills, can individually cause regressions of 4-41% even when the IR-level transformation successfully eliminates misprediction-prone branches. These patterns are not bugs in the backend; they arise because backend passes apply cost heuristics calibrated for branched code to MERIT’s branchless output, lacking awareness that the select chains originated from deliberate branch elimination rather than unoptimized source code.

A natural mitigation is to tag MERIT-generated select instructions with LLVM metadata that communicates transformation provenance to downstream passes. Concretely, MERIT can attach a !MERIT.select metadata node to every select it produces (e.g., number of merged instructions, whether the select guards a memory operation). Backend passes that currently revert selects to branches – notably code generator’s cmov-vs-branch lowering heuristic – can then query this metadata before applying their default cost model. When the metadata indicates that the select originated from MERIT’s deliberate branch elimination, these passes can either preserve the select unconditionally, or apply a threshold to partially reverse the MERIT transformation [5, 45], preventing the backend interferences that account for the regressions. The interaction between early IR-level transformations and late backend optimizations remains an important area for future compiler research.

8 Related Work

8.1 Branch Prediction

Hardware predictors

Several hardware techniques have been proposed for branch prediction, including Fetch Directed Instruction Prefetching (FDIP) [31, 15], TAGE-like predictors [39, 36, 37, 38], and perceptron-based predictors [16, 18]. Despite their aim for high accuracy, they can struggle with noisy histories and are often overwhelmed by the large branch footprints of data center applications, leading to frequent capacity-induced misses.

Hybrid (profile-guided) predictors

Hybrid approaches leverage software PGO, widely used in data centers [29, 28, 9, 6, 19], to augment hardware prediction: Whisper [20] injects Boolean prediction hints into the binary, and BranchNet [46] trains ML models served by a small on-chip inference engine. These techniques solve prediction problems offline that are intractable for hardware to learn at runtime.

8.2 Compiler Techniques for Branch Elimination

Predication and if-conversion

If-conversion is a classical compiler technique for eliminating branches by converting control dependences into data dependences [3, 4, 30, 21, 27]. A large body of work has studied forming compiler regions that enable aggressive predication, including hyperblocks [24] and superblocks [14]. Because predication interacts tightly with scheduling, reverse if-conversion [45] was proposed to convert scheduled predicated code back into control flow.

Control-flow restructuring and code duplication

Orthogonal to predication, many compiler optimizations reduce executed branches by restructuring and duplicating code. Mueller and Whalley propose avoiding conditional branches via code replication [26]. Bodík et al. show that branch outcomes can be inferred along interprocedural paths and eliminate correlated branches by interprocedural analysis plus code restructuring [7].

Structural code merging and relation to MERIT

Closer in spirit, HyBF [34] fuses sibling branches and Rocha et al. [32, 33] merge whole functions via sequence alignment (later refined to SSA form), but all target code-size reduction rather than misprediction elimination, and none provides safe memory speculation. MERIT instead melds structurally-similar operations at a target-independent IR to eliminate the control-flow hazard while bounding instruction overhead.

9 Conclusion

This paper introduces MERIT, a novel compiler transformation that eliminates branches on x86 by melding structurally similar operation sequences. Unlike traditional if-conversion, MERIT applies at the target-independent level. Its “select operands, compute once” strategy uses semantic analysis for safe operand-level guarding, enabling it to safely transform branches with conditional memory operations that other methods must skip. We also demonstrate how MERIT can be easily integrated with PGO by allowing selective transformation. On 24 branch-heavy microbenchmarks, MERIT achieves a 1.52× geometric mean speedup (up to 32×) with 48.9% fewer branch mispredictions; on realistic workloads (SPECrate 2017, SQLite TPC-H, CPython pyperformance), MERIT with PGO achieves a 1.01× geomean across all three suites without regressions. Our analysis reveals that MERIT’s effectiveness is currently constrained by the lack of a cost model and by “MERIT-blind” backend optimizations that can undo its work, highlighting these as critical areas for future research.

References

  • [1] Andreas Abel and Jan Reineke. uops.info: Characterizing latency, throughput, and port usage of instructions on intel microarchitectures. In Iris Bahar, Maurice Herlihy, Emmett Witchel, and Alvin R. Lebeck, editors, Proceedings of the Twenty-Fourth International Conference on Architectural Support for Programming Languages and Operating Systems, ASPLOS 2019, Providence, RI, USA, April 13-17, 2019, pages 673–686. ACM, 2019. doi:10.1145/3297858.3304062.
  • [2] KA Jordan Alexander and Kim Nikolai. Ir-level versus machine-level if-conversion for predicated architectures. In Proceedings of the 10th Workshop on Optimizations for DSP and Embedded Systems (ODES-10), pages 3–10, 2012. doi:10.1145/2443608.2443611.
  • [3] John R. Allen, Ken Kennedy, Carrie Porterfield, and Joe D. Warren. Conversion of control dependence to data dependence. In John R. Wright, Larry Landweber, Alan J. Demers, and Tim Teitelbaum, editors, Conference Record of the Tenth Annual ACM Symposium on Principles of Programming Languages, Austin, Texas, USA, January 1983, pages 177–189. ACM Press, 1983. doi:10.1145/567067.567085.
  • [4] David I. August, Wen-mei W. Hwu, and Scott A. Mahlke. A framework for balancing control flow and predication. In Mark Smotherman and Tom Conte, editors, Proceedings of the Thirtieth Annual IEEE/ACM International Symposium on Microarchitecture, MICRO 30, Research Triangle Park, North Carolina, USA, December 1-3, 1997, pages 92–103. IEEE, ACM/IEEE Computer Society, 1997. doi:10.1109/MICRO.1997.645801.
  • [5] David I. August, Wen-mei W. Hwu, and Scott A. Mahlke. The partial reverse if-conversion framework for balancing control flow and predication. Int. J. Parallel Program., 27(5):381–423, 1999. doi:10.1023/A:1018787007582.
  • [6] Grant Ayers, Nayana Prasad Nagendra, David I. August, Hyoun Kyu Cho, Svilen Kanev, Christos Kozyrakis, Trivikram Krishnamurthy, Heiner Litz, Tipp Moseley, and Parthasarathy Ranganathan. Asmdb: understanding and mitigating front-end stalls in warehouse-scale computers. In Srilatha Bobbie Manne, Hillery C. Hunter, and Erik R. Altman, editors, Proceedings of the 46th International Symposium on Computer Architecture, ISCA 2019, Phoenix, AZ, USA, June 22-26, 2019, pages 462–473. ACM, 2019. doi:10.1145/3307650.3322234.
  • [7] Rastislav Bodík, Rajiv Gupta, and Mary Lou Soffa. Interprocedural conditional branch elimination. In Marina C. Chen, Ron K. Cytron, and A. Michael Berman, editors, Proceedings of the ACM SIGPLAN ’97 Conference on Programming Language Design and Implementation (PLDI), Las Vegas, Nevada, USA, June 15-18, 1997, pages 146–158. ACM, 1997. doi:10.1145/258915.258929.
  • [8] James Bucek, Klaus-Dieter Lange, and Jóakim v. Kistowski. Spec cpu2017: Next-generation compute benchmark. In Companion of the 2018 ACM/SPEC International Conference on Performance Engineering, pages 41–42, 2018. doi:10.1145/3185768.3185771.
  • [9] Dehao Chen, Xinliang David Li, and Tipp Moseley. Autofdo: automatic feedback-directed optimization for warehouse-scale applications. In Björn Franke, Youfeng Wu, and Fabrice Rastello, editors, Proceedings of the 2016 International Symposium on Code Generation and Optimization, CGO 2016, Barcelona, Spain, March 12-18, 2016, pages 12–23. ACM, 2016. doi:10.1145/2854038.2854044.
  • [10] Chris Cummins, Volker Seeker, Dejan Grubisic, Baptiste Rozière, Jonas Gehring, Gabriel Synnaeve, and Hugh Leather. LLM compiler: Foundation language models for compiler optimization. In Daniel Kluss, Sara Achour, and Jens Palsberg, editors, Proceedings of the 34th ACM SIGPLAN International Conference on Compiler Construction, CC 2025, Las Vegas, NV, USA, March 1-2, 2025, pages 141–153. ACM, 2025. doi:10.1145/3708493.3712691.
  • [11] Yangruibo Ding, Benjamin Steenhoek, Kexin Pei, Gail E. Kaiser, Wei Le, and Baishakhi Ray. TRACED: execution-aware pre-training for source code. In Proceedings of the 46th IEEE/ACM International Conference on Software Engineering, ICSE 2024, Lisbon, Portugal, April 14-20, 2024, pages 36:1–36:12. ACM, 2024. doi:10.1145/3597503.3608140.
  • [12] Agner Fog. The microarchitecture of intel, amd and via cpus: An optimization guide for assembly programmers and compiler makers. Software optimization resources, 2016. URL: https://www.agner.org/optimize/microarchitecture.pdf.
  • [13] Kevin P. Gaffney, Martin Prammer, Laurence C. Brasfield, D. Richard Hipp, Dan R. Kennedy, and Jignesh M. Patel. Sqlite: Past, present, and future. Proc. VLDB Endow., 15(12):3535–3547, 2022. doi:10.14778/3554821.3554842.
  • [14] Wen-mei W. Hwu, Scott A. Mahlke, William Y. Chen, Pohua P. Chang, Nancy J. Warter, Roger A. Bringmann, Roland G. Ouellette, Richard E. Hank, Tokuzo Kiyohara, Grant E. Haab, John G. Holm, and Daniel M. Lavery. The superblock: An effective technique for VLIW and superscalar compilation. J. Supercomput., 7(1-2):229–248, 1993. doi:10.1007/BF01205185.
  • [15] Yasuo Ishii, Jaekyu Lee, Krishnendra Nathella, and Dam Sunwoo. Rebasing instruction prefetching: An industry perspective. IEEE Comput. Archit. Lett., 19(2):147–150, 2020. doi:10.1109/LCA.2020.3035068.
  • [16] Daniel A Jiménez. Multiperspective perceptron predictor. In 5th JILP Workshop on Computer Architecture Competitions (JWAC-5): Championship Branch Prediction (CBP-5), 2016. URL: https://jilp.org/cbp2016/paper/DanielJimenez1.pdf.
  • [17] Daniel A. Jiménez. Multiperspective perceptron predictor with TAGE. In 5th JILP Workshop on Computer Architecture Competitions (JWAC-5): Championship Branch Prediction (CBP-5), 2016. URL: https://jilp.org/cbp2016/paper/DanielJimenez2.pdf.
  • [18] Daniel A. Jiménez and Calvin Lin. Dynamic branch prediction with perceptrons. In Proceedings of the Seventh International Symposium on High-Performance Computer Architecture (HPCA’01), Nuevo Leone, Mexico, January 20-24, 2001, pages 197–206. IEEE Computer Society, 2001. doi:10.1109/HPCA.2001.903263.
  • [19] Tanvir Ahmed Khan, Nathan Brown, Akshitha Sriraman, Niranjan K. Soundararajan, Rakesh Kumar, Joseph Devietti, Sreenivas Subramoney, Gilles A. Pokam, Heiner Litz, and Baris Kasikci. Twig: Profile-guided BTB prefetching for data center applications. In MICRO ’21: 54th Annual IEEE/ACM International Symposium on Microarchitecture, Virtual Event, Greece, October 18-22, 2021, pages 816–829. ACM, 2021. doi:10.1145/3466752.3480124.
  • [20] Tanvir Ahmed Khan, Muhammed Ugur, Krishnendra Nathella, Dam Sunwoo, Heiner Litz, Daniel A. Jiménez, and Baris Kasikci. Whisper: Profile-guided branch misprediction elimination for data center applications. In 55th IEEE/ACM International Symposium on Microarchitecture, MICRO 2022, Chicago, IL, USA, October 1-5, 2022, pages 19–34. IEEE, IEEE, 2022. doi:10.1109/MICRO56248.2022.00017.
  • [21] Hyesoon Kim, Onur Mutlu, Yale N. Patt, and Jared Stark. Wish branches: Enabling adaptive and aggressive predicated execution. IEEE Micro, 26(1):48–58, 2006. doi:10.1109/MM.2006.27.
  • [22] Chit-Kwan Lin and Stephen J. Tarsa. Branch prediction is not a solved problem: Measurements, opportunities, and future directions. CoRR, abs/1906.08170, 2019. doi:10.48550/arXiv.1906.08170.
  • [23] LLVM Project. llvm::simplifycfgpass class reference, 2025. LLVM Doxygen documentation. Accessed: 2026-02-13. URL: https://llvm.org/doxygen/classllvm_1_1SimplifyCFGPass.html.
  • [24] Scott A. Mahlke, David C. Lin, William Y. Chen, Richard E. Hank, and Roger A. Bringmann. Effective compiler support for predicated execution using the hyperblock. In Wen-mei W. Hwu, editor, Proceedings of the 25th Annual International Symposium on Microarchitecture, Portland, Oregon, USA, November 1992, pages 45–54. ACM / IEEE Computer Society, 1992. doi:10.1109/MICRO.1992.696999.
  • [25] Pierre Michaud. An alternative tage-like conditional branch predictor. ACM Trans. Archit. Code Optim., 15(3):30:1–30:23, 2018. doi:10.1145/3226098.
  • [26] Frank Mueller and David B. Whalley. Avoiding conditional branches by code replication. In David W. Wall, editor, Proceedings of the ACM SIGPLAN’95 Conference on Programming Language Design and Implementation (PLDI), La Jolla, California, USA, June 18-21, 1995, pages 56–66. ACM, 1995. doi:10.1145/207110.207116.
  • [27] Dorit Nuzman, Ayal Zaks, and Ziv Ben-Zion. If-convert as early as you must. In Proceedings of the 33rd ACM SIGPLAN International Conference on Compiler Construction, CC 2024, pages 26–38, New York, NY, USA, 2024. Association for Computing Machinery. doi:10.1145/3640537.3641562.
  • [28] Maksim Panchenko, Rafael Auler, Bill Nell, and Guilherme Ottoni. BOLT: A practical binary optimizer for data centers and beyond. In Mahmut Taylan Kandemir, Alexandra Jimborean, and Tipp Moseley, editors, IEEE/ACM International Symposium on Code Generation and Optimization, CGO 2019, Washington, DC, USA, February 16-20, 2019, pages 2–14. IEEE, IEEE, 2019. doi:10.1109/CGO.2019.8661201.
  • [29] Maksim Panchenko, Rafael Auler, Laith Sakka, and Guilherme Ottoni. Lightning BOLT: powerful, fast, and scalable binary optimization. In Aaron Smith, Delphine Demange, and Rajiv Gupta, editors, CC ’21: 30th ACM SIGPLAN International Conference on Compiler Construction, Virtual Event, Republic of Korea, March 2-3, 2021, pages 119–130. ACM, 2021. doi:10.1145/3446804.3446843.
  • [30] Joseph CH Park and Mike Schlansker. On predicated execution. Hewlett-Packard Laboratories Palo Alto, California, 1991. URL: https://shiftleft.com/mirrors/www.hpl.hp.com/techreports/91/HPL-91-58.pdf.
  • [31] Glenn Reinman, Brad Calder, and Todd M. Austin. Fetch directed instruction prefetching. In Ronny Ronen, Matthew K. Farrens, and Ilan Y. Spillinger, editors, Proceedings of the 32nd Annual IEEE/ACM International Symposium on Microarchitecture, MICRO 32, Haifa, Israel, November 16-18, 1999, pages 16–27. IEEE, ACM/IEEE Computer Society, 1999. doi:10.1109/MICRO.1999.809439.
  • [32] Rodrigo C. O. Rocha, Pavlos Petoumenos, Zheng Wang, Murray Cole, and Hugh Leather. Function merging by sequence alignment. In 2019 IEEE/ACM International Symposium on Code Generation and Optimization (CGO), pages 149–163. IEEE, 2019. doi:10.1109/CGO.2019.8661174.
  • [33] Rodrigo C. O. Rocha, Pavlos Petoumenos, Zheng Wang, Murray Cole, and Hugh Leather. Effective function merging in the SSA form. In Proceedings of the 41st ACM SIGPLAN Conference on Programming Language Design and Implementation, pages 854–868, 2020. doi:10.1145/3385412.3386030.
  • [34] Rodrigo C. O. Rocha, Charitha Saumya, Kirshanthan Sundararajah, Pavlos Petoumenos, Milind Kulkarni, and Michael F. P. O’Boyle. Hybf: A hybrid branch fusion strategy for code size reduction. In Clark Verbrugge, Ondrej Lhoták, and Xipeng Shen, editors, Proceedings of the 32nd ACM SIGPLAN International Conference on Compiler Construction, CC 2023, Montréal, QC, Canada, February 25-26, 2023, pages 156–167. ACM, 2023. doi:10.1145/3578360.3580267.
  • [35] Charitha Saumya, Kirshanthan Sundararajah, and Milind Kulkarni. DARM: control-flow melding for SIMT thread divergence reduction. In Jae W. Lee, Sebastian Hack, and Tatiana Shpeisman, editors, IEEE/ACM International Symposium on Code Generation and Optimization, CGO 2022, Seoul, Korea, Republic of, April 2-6, 2022, pages 1–13. IEEE, IEEE, 2022. doi:10.1109/CGO53902.2022.9741285.
  • [36] André Seznec. A 64 kbytes isl-tage branch predictor. In JWAC-2: Championship Branch Prediction, 2011. URL: https://jilp.org/jwac-2/program/cbp3_03_seznec.pdf.
  • [37] André Seznec. Tage-sc-l branch predictors. In JILP-Championship Branch Prediction, 2014. URL: https://jilp.org/cbp2014/paper/AndreSeznec.pdf.
  • [38] André Seznec. Tage-sc-l branch predictors again. In 5th JILP Workshop on Computer Architecture Competitions (JWAC-5): Championship Branch Prediction (CBP-5), 2016. URL: https://jilp.org/cbp2016/paper/AndreSeznecLimited.pdf.
  • [39] André Seznec and Pierre Michaud. De-aliased hybrid branch predictors. PhD thesis, INRIA, 1999. doi:10.1145/2155620.2155635.
  • [40] Temple F Smith, Michael S Waterman, et al. Identification of common molecular subsequences. Journal of molecular biology, 147(1):195–197, 1981. doi:10.1016/0022-2836(81)90087-5.
  • [41] Shixin Song, Tanvir Ahmed Khan, Sara Mahdizadeh-Shahri, Akshitha Sriraman, Niranjan K. Soundararajan, Sreenivas Subramoney, Daniel A. Jiménez, Heiner Litz, and Baris Kasikci. Thermometer: profile-guided btb replacement for data center applications. In Valentina Salapura, Mohamed Zahran, Fred Chong, and Lingjia Tang, editors, ISCA ’22: The 49th Annual International Symposium on Computer Architecture, New York, New York, USA, June 18 - 22, 2022, pages 742–756. ACM, 2022. doi:10.1145/3470496.3527430.
  • [42] Victor Stinner. The python performance benchmark suite — python performance benchmark suite 1.0.6 documentation. URL: https://pyperformance.readthedocs.io/.
  • [43] Transaction Processing Performance Council (TPC). TPC benchmarktm A: standard specification. In Jim Gray, editor, The Benchmark Handbook for Database and Transaction Systems (2nd Edition), Revision 3.0.1. Morgan Kaufmann, May 1993. URL: https://www.tpc.org/tpc_documents_current_versions/pdf/tpc-h_v2.17.1.pdf.
  • [44] Li Wang, Hong An, Yongqing Ren, and Yaobin Wang. Profile guided optimization for dataflow predication. In 13th Asia-Pacific Computer Systems Architecture Conference, ACSAC 2008, Hsinchu, China, August 4-6, 2008, pages 1–8. IEEE, IEEE Computer Society, 2008. doi:10.1109/APCSAC.2008.4625471.
  • [45] Nancy J. Warter, Scott A. Mahlke, Wen-Mei W. Hwu, and B. Ramakrishna Rau. Reverse if-conversion. In Proceedings of the ACM SIGPLAN 1993 Conference on Programming Language Design and Implementation (PLDI), pages 290–299, 1993. doi:10.1145/155090.155118.
  • [46] Siavash Zangeneh, Lizy K. John, and Andreas Gerstlauer. Branchnet: A convolutional neural network to predict hard-to-predict branches. In Proceedings of the 53rd Annual IEEE/ACM International Symposium on Microarchitecture (MICRO), pages 118–130. IEEE, 2020. doi:10.1109/MICRO50266.2020.00020.
  • [47] Eric J Zimmerman. Profile-directed If-Conversion in Superscalar Microprocessors. PhD thesis, University of Illinois at Urbana-Champaign, 2005. URL: https://llvm.org/pubs/2005-07-ZimmermanMSThesis.pdf.