Wrong branch executed for short-circuit || condition in SPIR-V-loaded shader

This fragment shader renders incorrectly when created via glShaderBinary:

#version 460 core

layout(location = 0) in vec2 vUv;
layout(location = 0) out vec4 fragColor;


void main()
{
  fragColor = vec4(0.0);

  bool outside =
    vUv.x > 0.75 || vUv.x < 0.25;

  if ( outside )
    fragColor = vec4(0.0, 1.0, 0.0, 1.0);

  if ( !outside )
    fragColor = vec4(0.0, 0.0, 1.0, 1.0);
}

Swapping vUv.x comparison order moves green part to the right. Rewriting the same condition to use && chain (vUv.x > 0.25 && vUv.x < 0.75)gets really weird and produces black area.

Compiling the same shader via glShaderSource produces expected output regardless of used chain or comparison ordering:

Both glsl/spv paths render correctly on AMD hardware and under Mesa3D drivers on the same NVIDIA hardware.

That’s how I build spv binaries:

glslang -G -S vert shaders/shader.vert -o shaders/shader.vert.spv
glslang -G -S frag shaders/shader.frag -o shaders/shader.frag.spv
spirv-opt --ssa-rewrite shaders/shader.frag.spv -o shaders/shader.frag.spv

Using other spirv-opt optimization flags (or not using spirv-opt at all) renders correctly. Using --print-all for attached fragment shader snippet gives the following difference.

Before --ssa-rewrite:

%32 = OpPhi %bool %24 %5 %31 %26
OpStore %outside %32
%33 = OpLoad %bool %outside // <-- gets optimized out
OpSelectionMerge %35 None
OpBranchConditional %33 %34 %35
%34 = OpLabel
OpStore %fragColor %37
OpBranch %35
%35 = OpLabel
%38 = OpLoad %bool %outside // <-- gets optimized out
%39 = OpLogicalNot %bool %38
OpSelectionMerge %41 None
OpBranchConditional %39 %40 %41

After --ssa-rewrite:

%32 = OpPhi %bool %24 %5 %31 %26
OpStore %outside %32
OpSelectionMerge %35 None
OpBranchConditional %32 %34 %35
%34 = OpLabel
OpStore %fragColor %37
OpBranch %35
%35 = OpLabel
%39 = OpLogicalNot %bool %32
OpSelectionMerge %41 None
OpBranchConditional %39 %40 %41

Even though I’m more familiar with x86 asm, reading through SPIR-V spec I don’t see the optimization spirv-opt did here is suspicious or invalid. Given other drivers render the same SPV correctly I don’t think it’s spirv-opt fault.

In my production code this bug produces artifacts in innocent snippets like if ( uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0 ) because it mostly seems to evaluate only the last expression in the chain. The awkward shader I attached as a sample is the simplest reproducible form with the least amount of used spirv-opt flags.

glslang version: 11:16.3.0
spirv-opt version: v2026.2.rc2-1-g2ec8457a (initially I had old v2018 SPIR-V tools, there bug was triggered by -O flag)

I encountered this bug using relatively old NVIDIA driver somewhere from February 2026. Updating the driver didn’t fix the issue.

MRE sources & binaries:

nvidia-driver-bug-src.zip (47.0 KB)

nvidia-driver-bug-binaries.zip (416.2 KB)

Might be related:

Tested on several machines with Win 10 x64 under these drivers:
NVIDIA 610.88 (RTX 4070Ti, RTX 5060Ti)
AMD 27.20.14044.2 (integrated GPU on a laptop)
Mesa3D 26.1.4 (RTX 4070Ti)