microsoft / microsoft/DirectXShaderCompiler
CSE doesn't work properly in some case, producing suboptimal code
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 3.7k
- Forks
- 900
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 44
Description
Steps to Reproduce
Compile Command: "C:\Program Files (x86)\Windows Kits\10\bin\10.0.20348.0\x64\dxc.exe" -T ps_6_2 -Fc Test.d3dasm Test.hlsl -E Main1 or -E Main2
int a;
float2 fixed_uv;
float4 Main1(
float4 SvPosition : SV_POSITION,
float2 uv: TEXCOORD0) : SV_TARGET
{
float Color2 = 0;
[branch]
if (a > 0)
{
Color2 = log2(cos(dot(fixed_uv, fixed_uv)));
}
float Color1 = exp2(cos(dot(fixed_uv, fixed_uv)));
return float4(Color1, Color2, Color1 * Color2, 1);
}
float4 Main2(
float4 SvPosition : SV_POSITION,
float2 uv: TEXCOORD0) : SV_TARGET
{
float Color2 = 0;
float Color1 = exp2(cos(dot(fixed_uv, fixed_uv)));
[branch]
if (a > 0)
{
Color2 = log2(cos(dot(fixed_uv, fixed_uv)));
}
return float4(Color1, Color2, Color1 * Color2, 1);
}
Compiles to
define void @Main1() {
%1 = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 2, i32 0, i32 0, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex)
%2 = call %dx.types.CBufRet.i32 @dx.op.cbufferLoadLegacy.i32(i32 59, %dx.types.Handle %1, i32 0) ; CBufferLoadLegacy(handle,regIndex)
%3 = extractvalue %dx.types.CBufRet.i32 %2, 0
%4 = icmp sgt i32 %3, 0
br i1 %4, label %5, label %12, !dx.controlflow.hints !18
; <label>:5 ; preds = %0
%6 = call %dx.types.CBufRet.f32 @dx.op.cbufferLoadLegacy.f32(i32 59, %dx.types.Handle %1, i32 0) ; CBufferLoadLegacy(handle,regIndex)
%7 = extractvalue %dx.types.CBufRet.f32 %6, 1
%8 = extractvalue %dx.types.CBufRet.f32 %6, 2
%9 = call float @dx.op.dot2.f32(i32 54, float %7, float %8, float %7, float %8) ; Dot2(ax,ay,bx,by)
%10 = call float @dx.op.unary.f32(i32 12, float %9) ; Cos(value)
%11 = call float @dx.op.unary.f32(i32 23, float %10) ; Log(value)
br label %12
; <label>:12 ; preds = %5, %0
%13 = phi float [ %11, %5 ], [ 0.000000e+00, %0 ]
%14 = call %dx.types.CBufRet.f32 @dx.op.cbufferLoadLegacy.f32(i32 59, %dx.types.Handle %1, i32 0) ; CBufferLoadLegacy(handle,regIndex)
%15 = extractvalue %dx.types.CBufRet.f32 %14, 1
%16 = extractvalue %dx.types.CBufRet.f32 %14, 2
%17 = call float @dx.op.dot2.f32(i32 54, float %15, float %16, float %15, float %16) ; Dot2(ax,ay,bx,by)
%18 = call float @dx.op.unary.f32(i32 12, float %17) ; Cos(value)
%19 = call float @dx.op.unary.f32(i32 21, float %18) ; Exp(value)
%20 = fmul fast float %19, %13
call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 0, float %19) ; StoreOutput(outputSigId,rowIndex,colIndex,value)
call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 1, float %13) ; StoreOutput(outputSigId,rowIndex,colIndex,value)
call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 2, float %20) ; StoreOutput(outputSigId,rowIndex,colIndex,value)
call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 3, float 1.000000e+00) ; StoreOutput(outputSigId,rowIndex,colIndex,value)
ret void
}
define void @Main2() {
%1 = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 2, i32 0, i32 0, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex)
%2 = call %dx.types.CBufRet.f32 @dx.op.cbufferLoadLegacy.f32(i32 59, %dx.types.Handle %1, i32 0) ; CBufferLoadLegacy(handle,regIndex)
%3 = extractvalue %dx.types.CBufRet.f32 %2, 1
%4 = extractvalue %dx.types.CBufRet.f32 %2, 2
%5 = call float @dx.op.dot2.f32(i32 54, float %3, float %4, float %3, float %4) ; Dot2(ax,ay,bx,by)
%6 = call float @dx.op.unary.f32(i32 12, float %5) ; Cos(value)
%7 = call float @dx.op.unary.f32(i32 21, float %6) ; Exp(value)
%8 = call %dx.types.CBufRet.i32 @dx.op.cbufferLoadLegacy.i32(i32 59, %dx.types.Handle %1, i32 0) ; CBufferLoadLegacy(handle,regIndex)
%9 = extractvalue %dx.types.CBufRet.i32 %8, 0
%10 = icmp sgt i32 %9, 0
br i1 %10, label %11, label %13, !dx.controlflow.hints !18
; <label>:11 ; preds = %0
%12 = call float @dx.op.unary.f32(i32 23, float %6) ; Log(value)
br label %13
; <label>:13 ; preds = %11, %0
%14 = phi float [ %12, %11 ], [ 0.000000e+00, %0 ]
%15 = fmul fast float %14, %7
call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 0, float %7) ; StoreOutput(outputSigId,rowIndex,colIndex,value)
call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 1, float %14) ; StoreOutput(outputSigId,rowIndex,colIndex,value)
call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 2, float %15) ; StoreOutput(outputSigId,rowIndex,colIndex,value)
call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 3, float 1.000000e+00) ; StoreOutput(outputSigId,rowIndex,colIndex,value)
ret void
}
DXC produces different IL with completely equivalent source code: Main1 is less optimal, as it's likely to load the constant and compute the cosine twice, while Main2 is the desired outcome. The same situation occurs when fixed_uv is replaced with texture sampling.
Another use case
int a;
float2 fixed_uv;
float4 Main3(
float4 SvPosition : SV_POSITION,
float2 uv: TEXCOORD0) : SV_TARGET
{
float Color2 = 0;
[branch]
if (a > 0)
{
Color2 = log2(cos(dot(uv, uv)));
}
float Color1 = exp2(cos(dot(uv, uv)));
return float4(Color1, Color2, Color1 * Color2, 1);
}
Compiles to
define void @Main3() {
%1 = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 2, i32 0, i32 0, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex)
%2 = call float @dx.op.loadInput.f32(i32 4, i32 1, i32 0, i8 0, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis)
%3 = call float @dx.op.loadInput.f32(i32 4, i32 1, i32 0, i8 1, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis)
%4 = call %dx.types.CBufRet.i32 @dx.op.cbufferLoadLegacy.i32(i32 59, %dx.types.Handle %1, i32 0) ; CBufferLoadLegacy(handle,regIndex)
%5 = extractvalue %dx.types.CBufRet.i32 %4, 0
%6 = icmp sgt i32 %5, 0
%7 = call float @dx.op.dot2.f32(i32 54, float %2, float %3, float %2, float %3) ; Dot2(ax,ay,bx,by)
%8 = call float @dx.op.unary.f32(i32 12, float %7) ; Cos(value)
br i1 %6, label %9, label %11, !dx.controlflow.hints !19
; <label>:9 ; preds = %0
%10 = call float @dx.op.unary.f32(i32 23, float %8) ; Log(value)
br label %11
; <label>:11 ; preds = %9, %0
%12 = phi float [ %10, %9 ], [ 0.000000e+00, %0 ]
%13 = call float @dx.op.unary.f32(i32 21, float %8) ; Exp(value)
%14 = fmul fast float %13, %12
call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 0, float %13) ; StoreOutput(outputSigId,rowIndex,colIndex,value)
call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 1, float %12) ; StoreOutput(outputSigId,rowIndex,colIndex,value)
call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 2, float %14) ; StoreOutput(outputSigId,rowIndex,colIndex,value)
call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 3, float 1.000000e+00) ; StoreOutput(outputSigId,rowIndex,colIndex,value)
ret void
}
It appears to suggest that CSE (or perhaps GVN?) is utilized for interpolants, with everything except log2 being promoted outside the block. I am seeking an approach that enables texture samplings and constants to function in a manner similar to interpolants.
Environment
- DXC version Windows SDK 10.0.20348.0
- Host Operating System Windows 10 22H2
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reproducing Main1, Main2, and Main3 with the stated dxc.exe command and compare their emitted LLVM IR. Investigate the compiler optimization path responsible for common subexpression elimination or GVN, using the difference between constant-buffer or texture-derived values and interpolants as the test case. Done means equivalent expressions are optimized consistently without changing shader behavior.
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
- 30/100