microsoft / microsoft/DirectXShaderCompiler

[SPIR-V] Nested static 'for' loops with unroll translate to 'while( true )' loops with actual branches

Open
#4,486 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

spirv
Dominant language
C++
Stars
3.7k
Forks
900
Avg merge
2d 11h
Merged PRs (30d)
44

Description

Hi! We're using DXC version 1.6.2104.52. We target mobile devices, such as Adreno and Mali GPUs.
Consider the following code:

float4 color = (float4)0;
float4 colors[4];
float luminances[4];
[unroll]
for ( int j = 0; j < (4 - 1); j++ )
{
	[unroll]
	for ( int k = 0; k < (4 - j - 1); k++ )
	{
		[flatten]
		if ( luminances[k] > luminances[k + 1] )
		{
			float tmpLum = luminances[k];
			luminances[k] = luminances[k + 1];
			luminances[k + 1] = tmpLum;

			float4 tmpColor = colors[k];
			colors[k] = colors[k + 1];
			colors[k + 1] = tmpColor;
		}
	}
}

All of the iteration bounds are known at compile time, so we expect this loop to be fully unrolled. But, when viewing shader disassembly, the final code looks like

  while(true) {
    int _117 = Phi(0, _118);
    bool _120 = _117 < 3;
    [[Unroll]]
    if(!_120) break;

    while(true) {
      int _124 = Phi(0, _125);
      int _127 = 3 - _117;
      bool _128 = _124 < _127;
      [[Unroll]]
      if(!_128) break;
      float* _131 = &_74[_124];
      float _132 = *_131;
      int _125 = _124 + 1;
      float* _133 = &_74[_125];
      float _134 = *_133;
      bool _135 = _132 > _134;
      [[Flatten]]
      if(_135) {
        float _138 = *_131;
        float _139 = *_133;
        *_131 = _139;
        *_133 = _138;
        float4* _140 = &_73[_124];
        float4 _141 = *_140;
        float4* _142 = &_73[_125];
        float4 _143 = *_142;
        *_140 = _143;
        *_142 = _141;
      }

    }

    int _118 = _117 + 1;
  }

When doing Mali GPU captures, this produces actual branches on GPU with warp divergence up to 30%. Running SPIR-V binaries through malioc shows that the resulting shader is disproportionally bottlenecked by Load/Store unit operations (1.8 cycles arithmetics, 15 cycles worst case Load/Store unit operations).

If we unroll the shader manually, like this

void boxMedianOneStep( int j, inout float4 colors[4], inout float luminances[4] )
{
	[unroll]
	for ( int k = 0; k < (4 - j - 1); k++ )
	{
		[flatten]
		if ( luminances[k] > luminances[k + 1] )
		{
			float tmpLum = luminances[k];
			luminances[k] = luminances[k + 1];
			luminances[k + 1] = tmpLum;

			float4 tmpColor = colors[k];
			colors[k] = colors[k + 1];
			colors[k + 1] = tmpColor;
		}
	}
}

boxMedianOneStep( 0, colors, luminances );
boxMedianOneStep( 1, colors, luminances );
boxMedianOneStep( 2, colors, luminances );

the resulting code is perfectly flat, L/S bottlenecks are gone and there is zero warp divergence. The unrolled shader is up to 8x faster than the original one. Our legacy GLES toolchain (FXC->HLSLcc) handles this case with no problem. The new DXC/SPV-Opt toolchain does not, and even running additional loop unroll passes on final SPV code does not help at all.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the supplied nested HLSL reproducer and compare its generated SPIR-V disassembly with the manually unrolled version. Run the example through DXC 1.6.2104.52 and inspect the loop-unrolling and SPIR-V lowering behavior; done means compile-time-bounded nested loops produce flat code without the shown loop branches or divergence.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.