Repository navigation
JIT: STRESS_OPT_REPEAT silent miscompile produces wrong-direction branch into divide-by-zero #129386
Description
Note
This issue was filed by GitHub Copilot CLI on @AndyAyersMS's machine.
The repro is verified locally on a current main (
untime-a build with both PR #129348 and PR #129136 applied).
Summary
Under DOTNET_JitEnableOptRepeat=1 (or DOTNET_JitStress=1/2/DOTNET_JitStressModeNames=STRESS_OPT_REPEAT) the JIT silently miscompiles a uint-comparison branch, taking the wrong direction so control flow lands on a % 0 that throws DivideByZeroException. Just one extra optimization pass (DOTNET_JitOptRepeatCount=2) is sufficient -- so this is an idempotency bug: the second pass over already-optimized IR produces wrong code.
Same general shape as the recently-filed #129288 (wrong-direction branch into a divide-by-zero path) but a different mechanism. Surfaced by an experimental in-development fuzzer (ReifyCs).
Minimal repro env
DOTNET_TieredCompilation=0 DOTNET_JitEnableOptRepeat=1 DOTNET_JitOptRepeat=Fn30000002142 DOTNET_JitOptRepeatCount=2
Run the program below under corerun -- it throws
Unhandled exception. System.DivideByZeroException: Attempted to divide by zero. at Program.Fn30000002142(UInt32 p0, UInt32 p1) at Program.Main()
Without JitOptRepeat, or under MinOpts, the program returns 0 with no exception.
Why it's a JIT bug
The very first statement of the function is
csharp v6 = (0u < p0) ? 1 : 0; if (v6 != 0) goto b1; goto b3;
With Input_p0 = 0u, v6 == 0 and the branch must fall through to b3. The buggy code takes goto b1 instead, and eventually reaches block b2 which contains v19 = v5 % p0; -- a divide by zero because p0 == 0. Block b2 is unreachable under the source semantics; with DOTNET_JitOptRepeat disabled, every config (tier0_min, fullopts, tier1_pgo, runtime_async, stress2, stressregs) prints 00000000 and exits 0.
Stress flag bisection (windows.x64 Checked, runtime-a)
| Setting | Result |
|---|---|
| default | PASS |
DOTNET_JitStress=1 |
CRASH |
DOTNET_JitStress=2 |
CRASH |
DOTNET_JitStressModeNames=STRESS_OPT_REPEAT |
CRASH |
DOTNET_JitOptRepeatCount=2 (with JitEnableOptRepeat=1 + JitOptRepeat=Fn30000002142) |
CRASH |
| All other individual STRESS_* modes tried (REVERSE_FLAG, MERGED_RETURNS, OPT_BOOLS_GC, OPT_BOOLS_COMPARE_CHAIN_COST, BB_PROFILE, DO_WHILE_LOOPS, REGS, REMORPH_TREES, MAKE_CSE, CLONE_EXPR, FOLD, DOWNWARDS_COUNTED_LOOPS, STRENGTH_REDUCTION, SPLIT_TREES_RANDOMLY, EMITTER) | PASS |
So this is specifically a JitOptRepeat idempotency bug.
Environment
- HEAD of
mainas of 2026-06-14 (Checked CoreCLR, x64 Windows), plus PR JIT: don't claim rotates set ZF on xarch #129348 (rotate ZF) and PR JIT: fix FitsIn<int32_t> assert in BitOperations.Rotate{Left,Right} const-fold #129136 (FitsIn const-fold + gtNewIconNode invariant) -- bug reproduces with those fixes in. - Tracing shows the rotate path from JIT: stale ZF after rotate causes wrong-direction branch in if (BitOperations.RotateRight(x, n) == 0) pattern #129288 is NOT involved here; this is something else in the optimization pipeline.
- Discovered using ReifyCs; seed
30000002142, profileintrinsic.
Repro source
Program.cs (243 lines, autogenerated)
// Generated by ReifyCs. Do not edit by hand.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
public static class Program
{
private static volatile uint Input_p0 = unchecked((uint)0x00000000);
private static volatile uint Input_p1 = unchecked((uint)0xFFFFFFFF);
private static volatile uint Sink;
private static int[] __trace = new int[64];
private static int __traceLen = 0;
[MethodImpl(MethodImplOptions.NoInlining)]
public static uint Fn30000002142(uint p0, uint p1)
{
unchecked
{
uint v1 = unchecked((uint)0x00000000), v2 = unchecked((uint)0x00000000), v3 = unchecked((uint)0x00000000), v4 = unchecked((uint)0x00000000), v5 = unchecked((uint)0x00000000), v7 = unchecked((uint)0x00000000), v9 = unchecked((uint)0x00000000), v11 = unchecked((uint)0x00000000), v13 = unchecked((uint)0x00000000), v14 = unchecked((uint)0x00000000), v16 = unchecked((uint)0x00000000), v18 = unchecked((uint)0x00000000), v19 = unchecked((uint)0x00000000), v20 = unchecked((uint)0x00000000), v22 = unchecked((uint)0x00000000), v23 = unchecked((uint)0x00000000), v24 = unchecked((uint)0x00000000), v25 = unchecked((uint)0x00000000), v27 = unchecked((uint)0x00000000), v28 = unchecked((uint)0x00000000), v29 = unchecked((uint)0x00000000), v31 = unchecked((uint)0x00000000), v32 = unchecked((uint)0x00000000), v33 = unchecked((uint)0x00000000), v35 = unchecked((uint)0x00000000), v37 = unchecked((uint)0x00000000), v39 = unchecked((uint)0x00000000), v40 = unchecked((uint)0x00000000), v41 = unchecked((uint)0x00000000), v42 = unchecked((uint)0x00000000), v43 = unchecked((uint)0x00000000), v44 = unchecked((uint)0x00000000), v47 = unchecked((uint)0x00000000), v49 = unchecked((uint)0x00000000), v50 = unchecked((uint)0x00000000), v51 = unchecked((uint)0x00000000), v52 = unchecked((uint)0x00000000), v54 = unchecked((uint)0x00000000), v55 = unchecked((uint)0x00000000), v56 = unchecked((uint)0x00000000), v57 = unchecked((uint)0x00000000), v58 = unchecked((uint)0x00000000), v61 = unchecked((uint)0x00000000), v62 = unchecked((uint)0x00000000), v63 = unchecked((uint)0x00000000), v65 = unchecked((uint)0x00000000), v66 = unchecked((uint)0x00000000), v67 = unchecked((uint)0x00000000), v68 = unchecked((uint)0x00000000), v69 = unchecked((uint)0x00000000), v71 = unchecked((uint)0x00000000), v72 = unchecked((uint)0x00000000), v73 = unchecked((uint)0x00000000), v74 = unchecked((uint)0x00000000), v75 = unchecked((uint)0x00000000), v78 = unchecked((uint)0x00000000), v80 = unchecked((uint)0x00000000), v82 = unchecked((uint)0x00000000), v83 = unchecked((uint)0x00000000), v84 = unchecked((uint)0x00000000), v85 = unchecked((uint)0x00000000), v86 = unchecked((uint)0x00000000), v87 = unchecked((uint)0x00000000), v88 = unchecked((uint)0x00000000), v89 = unchecked((uint)0x00000000), v91 = unchecked((uint)0x00000000), v93 = unchecked((uint)0x00000000), v94 = unchecked((uint)0x00000000), v96 = unchecked((uint)0x00000000), v97 = unchecked((uint)0x00000000), v99 = unchecked((uint)0x00000000), v100 = unchecked((uint)0x00000000), v101 = unchecked((uint)0x00000000), v102 = unchecked((uint)0x00000000), v103 = unchecked((uint)0x00000000), v104 = unchecked((uint)0x00000000), v105 = unchecked((uint)0x00000000), v107 = unchecked((uint)0x00000000), v108 = unchecked((uint)0x00000000), v109 = unchecked((uint)0x00000000), v111 = unchecked((uint)0x00000000), v112 = unchecked((uint)0x00000000), v113 = unchecked((uint)0x00000000), v114 = unchecked((uint)0x00000000), v115 = unchecked((uint)0x00000000), v116 = unchecked((uint)0x00000000), v117 = unchecked((uint)0x00000000), v119 = unchecked((uint)0x00000000), v120 = unchecked((uint)0x00000000), v121 = unchecked((uint)0x00000000), v123 = unchecked((uint)0x00000000), v124 = unchecked((uint)0x00000000);
int v6 = unchecked((int)0x00000000), v8 = unchecked((int)0x00000000), v10 = unchecked((int)0x00000000), v12 = unchecked((int)0x00000000), v15 = unchecked((int)0x00000000), v17 = unchecked((int)0x00000000), v21 = unchecked((int)0x00000000), v26 = unchecked((int)0x00000000), v30 = unchecked((int)0x00000000), v34 = unchecked((int)0x00000000), v36 = unchecked((int)0x00000000), v38 = unchecked((int)0x00000000), v45 = unchecked((int)0x00000000), v46 = unchecked((int)0x00000000), v48 = unchecked((int)0x00000000), v53 = unchecked((int)0x00000000), v59 = unchecked((int)0x00000000), v60 = unchecked((int)0x00000000), v64 = unchecked((int)0x00000000), v70 = unchecked((int)0x00000000), v76 = unchecked((int)0x00000000), v77 = unchecked((int)0x00000000), v79 = unchecked((int)0x00000000), v81 = unchecked((int)0x00000000), v90 = unchecked((int)0x00000000), v92 = unchecked((int)0x00000000), v95 = unchecked((int)0x00000000), v98 = unchecked((int)0x00000000), v106 = unchecked((int)0x00000000), v110 = unchecked((int)0x00000000), v118 = unchecked((int)0x00000000), v122 = unchecked((int)0x00000000);
b0:
__trace[__traceLen++] = 0;
v1 = (unchecked((uint)0x00000000) - p1);
v2 = ~p1;
v3 = unchecked((uint)0x0003B742) % p1;
v4 = unchecked((uint)0x000EBA68) - unchecked((uint)0x00020256);
v5 = v4 + unchecked((uint)0xFFF392DE);
v6 = (unchecked((uint)0x00000000) < p0) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
if (v6 != unchecked((int)0x00000000)) goto b1;
goto b3;
b1:
__trace[__traceLen++] = 1;
v7 = unchecked((uint)0x000003E8) * unchecked((uint)0x000003E8);
v8 = BitOperations.PopCount(unchecked((uint)0xFFFFFFFE));
v9 = (uint)(v8);
v10 = (BitOperations.IsPow2(v7) ? 1 : 0);
v11 = (uint)(v10);
v12 = (unchecked((uint)0x00035FCF) > p1) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
v13 = (uint)(v12);
v14 = v13 - unchecked((uint)0x000D3403);
v15 = (unchecked((uint)0xFFFFFC18) < unchecked((uint)0x000E4C4C)) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
v16 = (uint)(v15);
v17 = (v14 > v4) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
if (v17 != unchecked((int)0x00000000)) goto b2;
goto b4;
b2:
__trace[__traceLen++] = 2;
v18 = unchecked((uint)0xFFF14FB8) - v7;
v19 = v5 % p0;
v20 = unchecked((uint)0x00000007) * v4;
v21 = (p0 <= v11) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
if (v21 != unchecked((int)0x00000000)) goto b6;
goto b11;
b3:
__trace[__traceLen++] = 3;
v22 = v14 - unchecked((uint)0x000CBA13);
v23 = v11 | unchecked((uint)0xFFFC3D1A);
v24 = v4 & v22;
v25 = unchecked((uint)0x00000000) & v1;
v26 = (v24 > p0) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
if (v26 != unchecked((int)0x00000000)) goto b10;
goto b12;
b4:
__trace[__traceLen++] = 4;
v27 = ~v1;
v28 = v23 & v23;
v29 = v13 + v2;
v30 = (v25 >= unchecked((uint)0xFFF454BF)) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
v31 = (uint)(v30);
v32 = unchecked((uint)0xFFFAFC10) * unchecked((uint)0x0009156D);
goto b5;
b5:
__trace[__traceLen++] = 5;
v33 = BitOperations.RotateLeft(unchecked((uint)0x00000001), (int)(unchecked((uint)0x00000003)));
v34 = (BitOperations.IsPow2(unchecked((uint)0x00088E9E)) ? 1 : 0);
v35 = (uint)(v34);
v36 = (v20 < v3) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
v37 = (uint)(v36);
v38 = (v4 > v37) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
if (v38 != unchecked((int)0x00000000)) goto b9;
goto b17;
b6:
__trace[__traceLen++] = 6;
v39 = v28 - v2;
v40 = v4 - v13;
v41 = ~v29;
v42 = unchecked((uint)0x0006EE48) & unchecked((uint)0x0007BA65);
v43 = unchecked((uint)0xFFF9D5DE) - v29;
v44 = ~v3;
v45 = (v42 <= v44) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
if (v45 != unchecked((int)0x00000000)) goto b7;
goto b8;
b7:
__trace[__traceLen++] = 7;
v46 = (BitOperations.IsPow2(unchecked((uint)0x00000001)) ? 1 : 0);
v47 = (uint)(v46);
v48 = (v9 >= unchecked((uint)0xFFFC9099)) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
v49 = (uint)(v48);
v50 = Math.Clamp(unchecked((uint)0xFFF8528F), unchecked((uint)0x00000005), v47);
v51 = v5 | v1;
v52 = v14 & unchecked((uint)0x000E9218);
v53 = (v2 >= v3) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
if (v53 != unchecked((int)0x00000000)) goto b14;
goto b11;
b8:
__trace[__traceLen++] = 8;
v54 = v7 + unchecked((uint)0xFFF26B1D);
v55 = v25 % unchecked((uint)0x00012C0B);
v56 = v43 + v43;
v57 = ~v29;
v58 = v52 * v3;
v59 = (v52 == unchecked((uint)0x0000000D)) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
if (v59 != unchecked((int)0x00000000)) goto b19;
goto b13;
b9:
__trace[__traceLen++] = 9;
v60 = (v4 < v1) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
v61 = (uint)(v60);
v62 = (unchecked((uint)0x00000000) - v24);
v63 = v9 - v22;
v64 = BitOperations.TrailingZeroCount(unchecked((uint)0xFFFFFFFE));
v65 = (uint)(v64);
return v65;
b10:
__trace[__traceLen++] = 10;
v66 = v51 >>> (int)(v61);
v67 = v58 - unchecked((uint)0x0004A8B9);
v68 = Math.Max(unchecked((uint)0xFFFA3E09), unchecked((uint)0xFFFFFFFD));
v69 = v33 - v50;
v70 = BitOperations.PopCount(unchecked((uint)0xFFFBC8AA));
v71 = (uint)(v70);
v72 = v4 | unchecked((uint)0x00004D11);
goto b18;
b11:
__trace[__traceLen++] = 11;
v73 = v51 + unchecked((uint)0x000003E8);
v74 = BitOperations.RotateRight(v39, (int)(v42));
v75 = unchecked((uint)0xFFFAF3CA) + v58;
v76 = (v68 == v19) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
if (v76 != unchecked((int)0x00000000)) goto b13;
goto b0;
b12:
__trace[__traceLen++] = 12;
v77 = (v4 < v23) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
v78 = (uint)(v77);
v79 = (v35 <= unchecked((uint)0x000B9492)) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
v80 = (uint)(v79);
v81 = BitOperations.TrailingZeroCount(unchecked((uint)0xFFFFFFFF));
v82 = (uint)(v81);
return v82;
b13:
__trace[__traceLen++] = 13;
v83 = ~v23;
v84 = v25 + v1;
v85 = Math.Max(unchecked((uint)0x80000000), p1);
v86 = unchecked((uint)0xFFFF5264) % v84;
v87 = p0 & unchecked((uint)0xFFFFFFF9);
v88 = ~p0;
goto b2;
b14:
__trace[__traceLen++] = 14;
v89 = v24 | v50;
v90 = (v75 != v67) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
v91 = (uint)(v90);
v92 = (unchecked((uint)0xFFFE36FE) >= unchecked((uint)0xFFFC820F)) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
v93 = (uint)(v92);
v94 = v68 + unchecked((uint)0xFFFFE835);
v95 = (v5 != v47) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
v96 = (uint)(v95);
v97 = v56 | unchecked((uint)0xFFF13A97);
v98 = (v41 < v73) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
if (v98 != unchecked((int)0x00000000)) goto b15;
goto b16;
b15:
__trace[__traceLen++] = 15;
v99 = (unchecked((uint)0x00000000) - v44);
v100 = (unchecked((uint)0x00000000) - v54);
v101 = unchecked((uint)0x0008CF57) ^ v78;
v102 = v93 >> (int)(unchecked((uint)0xFFFEE4CC));
v103 = unchecked((uint)0x0000000B) + unchecked((uint)0xFFF57D10);
goto b18;
b16:
__trace[__traceLen++] = 16;
v104 = p1 & unchecked((uint)0xFFF40D10);
v105 = unchecked((uint)0x0000000B) % unchecked((uint)0x0000A288);
v106 = (v67 < unchecked((uint)0xFFF41EEF)) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
v107 = (uint)(v106);
return v107;
b17:
__trace[__traceLen++] = 17;
v108 = v11 >> (int)(v23);
v109 = v75 >> (int)(v41);
v110 = (v1 < v37) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
v111 = (uint)(v110);
v112 = v62 + v65;
return v112;
b18:
__trace[__traceLen++] = 18;
v113 = v102 | unchecked((uint)0xFFF905A6);
v114 = Math.Clamp(unchecked((uint)0x00000001), unchecked((uint)0x00000001), unchecked((uint)0x00000000));
v115 = unchecked((uint)0x7FFFFFFE) + unchecked((uint)0x00002928);
v116 = v7 - unchecked((uint)0xFFFA72BA);
v117 = v115 - v32;
return v117;
b19:
__trace[__traceLen++] = 19;
v118 = (v83 <= unchecked((uint)0x000BF458)) ? unchecked((int)0x00000001) : unchecked((int)0x00000000);
v119 = (uint)(v118);
v120 = unchecked((uint)0x0000001F) * v93;
v121 = ~v75;
v122 = BitOperations.Log2(v24);
v123 = (uint)(v122);
v124 = v63 + v3;
return v124;
}
}
public static int Main()
{
bool __doWarmup = System.Environment.GetEnvironmentVariable("REIFYCS_SKIP_WARMUP") == null;
if (__doWarmup)
{
for (int __w = 0; __w < 200; __w++)
{
Sink = Fn30000002142(Input_p0, Input_p1);
__traceLen = 0; // discard warmup traces
}
System.Threading.Thread.Sleep(75); // Tier0 -> Instrumented Tier0
for (int __s = 0; __s < 100; __s++)
{
Sink = Fn30000002142(Input_p0, Input_p1);
__traceLen = 0; // discard settle traces
}
System.Threading.Thread.Sleep(125); // Instrumented Tier0 -> Tier1
}
uint result = Fn30000002142(Input_p0, Input_p1);
Console.WriteLine($"{(uint)result:X8}");
var sb = new System.Text.StringBuilder();
for (int i = 0; i < __traceLen; i++) {
if (i > 0) sb.Append(' ');
sb.Append(__trace[i]);
}
Console.WriteLine("TRACE " + sb.ToString());
return 0;
}
}
Activity
dotnet-policy-service commentedon Jun 14, 2026
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.
AndyAyersMS commentedon Jun 14, 2026
Clarifying scope: this repro requires
DOTNET_JitOptRepeat=Fn30000002142(orSTRESS_OPT_REPEAT/JitStress=1/JitStress=2) to be set; default FullOpts produces the correct output00000000. So no customer-facing FullOpts/Tier1 code path hits this organically -- it's specifically the second optimization iteration that mis-transforms the already-optimized IR. The bug is still a real correctness defect in the optimizer's idempotency (and useful to root-cause because the offending pass might cause issues elsewhere later), just not a release-blocker for end users.AndyAyersMS commentedon Jun 14, 2026
Reduced repro (246 -> 122 lines, ~16KB -> ~4.4KB) using line-level delta-debugging with MinOpts as the source-of-truth oracle:
DOTNET_JitMinOpts=100000000\nTRACE 12\nexit 000000000\nTRACE 12\nexit 0DOTNET_JitOptRepeat=Fn30000002142DivideByZeroExceptionatv19 = v5 % p0The remaining function still has noise (uninitialized intermediate uses, many
__tracewrites), but the control-flow shape is clear: under the source semantics the path isb0 -> b3 -> b12 -> return; blockb2(containingv19 = v5 % p0;withp0 == 0) is unreachable. Opt-repeat makes the JIT takeb0 -> b1instead and eventually lands onb2.Reduced Program.cs (122 lines)
JIT: fix opt-repeat divide-by-zero miscompile (dotnet#129386)
JIT: fix opt-repeat divide-by-zero miscompile (#129386) (#129533)
JIT: fix opt-repeat divide-by-zero miscompile (#129386) (#129533)