Skip to content

Commit c9a1d76

Browse files
ShabbyXAngle LUCI CQ
authored andcommitted
Vulkan: Respect short-circuit more safely
Previously, if the LHS of `&&`/`||` or true/false expression of `?:` didn't have side effects, non-short-circuiting SPIR-V instructions were generated. This is not completely safe, as an expression without side effect might still be invalid to execute, such as loading from an array with an out-of-bounds index. Bug: angleproject:465831139 Change-Id: I5c865919d7a36523ba25ea50151a9451aef46e36 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7228207 Commit-Queue: Yuxin Hu <[email protected]> Auto-Submit: Shahbaz Youssefi <[email protected]> Reviewed-by: Yuxin Hu <[email protected]> Commit-Queue: Shahbaz Youssefi <[email protected]>
1 parent a326ff5 commit c9a1d76

File tree

5 files changed

+159
-27
lines changed

5 files changed

+159
-27
lines changed

src/compiler/translator/spirv/OutputSPIRV.cpp

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2341,6 +2341,60 @@ void OutputSPIRVTraverser::visitArrayLength(TIntermUnary *node)
23412341
nodeDataInitRValue(&mNodeData.back(), castResultId, intTypeId);
23422342
}
23432343

2344+
// If an expression is short-circuited, it must not be executed. However, in some cases there is
2345+
// nothing to execute, such as constants, variables etc. Notably, hasSideEffects() is not
2346+
// a sufficient check, because it could include read-only operations that are out of bounds, despite
2347+
// not having any side effects.
2348+
bool IsSafeToExecuteInShortCircuit(TIntermTyped *node)
2349+
{
2350+
// Constants and symbols are safe to execute.
2351+
if (node->getAsConstantUnion() || node->getAsSymbolNode())
2352+
{
2353+
return true;
2354+
}
2355+
2356+
// Swizzle is safe if the operand is safe.
2357+
{
2358+
TIntermSwizzle *asSwizzle = node->getAsSwizzleNode();
2359+
if (asSwizzle)
2360+
{
2361+
return IsSafeToExecuteInShortCircuit(asSwizzle->getOperand());
2362+
}
2363+
}
2364+
2365+
// Indexing a struct or interface block is safe to execute, as long as no array index is in the
2366+
// access chain.
2367+
{
2368+
TIntermBinary *asBinary = node->getAsBinaryNode();
2369+
if (asBinary != nullptr)
2370+
{
2371+
return (asBinary->getOp() == EOpIndexDirectInterfaceBlock ||
2372+
asBinary->getOp() == EOpIndexDirectStruct) &&
2373+
IsSafeToExecuteInShortCircuit(asBinary->getLeft());
2374+
}
2375+
}
2376+
2377+
// Constructors are safe as long as every member is safe.
2378+
{
2379+
TIntermAggregate *asAggregate = node->getAsAggregate();
2380+
if (asAggregate != nullptr && asAggregate->getOp() == EOpConstruct)
2381+
{
2382+
for (TIntermNode *component : *asAggregate->getSequence())
2383+
{
2384+
if (!IsSafeToExecuteInShortCircuit(component->getAsTyped()))
2385+
{
2386+
return false;
2387+
}
2388+
}
2389+
return true;
2390+
}
2391+
}
2392+
2393+
// Assume everything else is unsafe. This includes safe but complex expressions that are better
2394+
// off not getting executed anyway.
2395+
return false;
2396+
}
2397+
23442398
bool IsShortCircuitNeeded(TIntermOperator *node)
23452399
{
23462400
TOperator op = node->getOp();
@@ -2353,12 +2407,9 @@ bool IsShortCircuitNeeded(TIntermOperator *node)
23532407

23542408
ASSERT(node->getChildCount() == 2);
23552409

2356-
// If the right hand side does not have side effects, short-circuiting is unnecessary.
2357-
// TODO: experiment with the performance of OpLogicalAnd/Or vs short-circuit based on the
2358-
// complexity of the right hand side expression. We could potentially only allow
2359-
// OpLogicalAnd/Or if the right hand side is a constant or an access chain and have more complex
2360-
// expressions be placed inside an if block. http://anglebug.com/40096715
2361-
return node->getChildNode(1)->getAsTyped()->hasSideEffects();
2410+
// If the right hand side is not safe to execute, short-circuiting is needed
2411+
// For example: is_in_bounds(index) && access(data[index])
2412+
return !IsSafeToExecuteInShortCircuit(node->getChildNode(1)->getAsTyped());
23622413
}
23632414

23642415
using WriteUnaryOp = void (*)(spirv::Blob *blob,
@@ -5300,8 +5351,8 @@ bool OutputSPIRVTraverser::visitTernary(Visit visit, TIntermTernary *node)
53005351
// prior to 1.4 requires the type to be either scalar or vector.
53015352
const TType &type = node->getType();
53025353
bool canUseOpSelect = (type.isScalar() || type.isVector() || mCompileOptions.emitSPIRV14) &&
5303-
!node->getTrueExpression()->hasSideEffects() &&
5304-
!node->getFalseExpression()->hasSideEffects();
5354+
IsSafeToExecuteInShortCircuit(node->getTrueExpression()) &&
5355+
IsSafeToExecuteInShortCircuit(node->getFalseExpression());
53055356

53065357
// Don't use OpSelect on buggy drivers. Technically this is only needed if the two sides don't
53075358
// have matching use of RelaxedPrecision, but not worth being precise about it.

src/tests/angle_end2end_tests_expectations.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,6 +1153,7 @@
11531153
42266091 WIN INTEL : *EmulateCopyTexImage2DFromRenderbuffers* = SKIP
11541154

11551155
42266092 WIN SWIFTSHADER : VulkanPerformanceCounterTest.EndXfbAfterRenderPassClosed/* = SKIP
1156+
466331749 WIN SWIFTSHADER : GLSLTest_ES31.ShortCircuitOutOfBoundsAccess/* = SKIP
11561157

11571158
// Flat shading bug with lighting
11581159
42264726 VULKAN : LightsTest.FlatLitMesh/* = SKIP
@@ -1812,6 +1813,12 @@
18121813
42264369 ANDROID GLES : TextureBufferTestES31.DrawIncompleteNonZeroTexture/* = SKIP
18131814
42264369 ANDROID GLES : TextureBufferTestES31.DrawCompleteZeroTexture/* = SKIP
18141815
42264369 ANDROID GLES : TextureBufferTestES31.DrawCompleteNonZeroTexture/* = SKIP
1816+
42264369 ANDROID GLES : TextureBufferTestES31.TexBufferDrawTwice/* = SKIP
1817+
42264369 ANDROID GLES : TextureBufferTestES31.UseAsUBOThenUpdateThenAsTextureBuffer/* = SKIP
1818+
42264369 ANDROID GLES : TextureBufferTestES31.MapTextureBufferInvalidateThenWrite/* = SKIP
1819+
42264369 ANDROID GLES : ComputeShaderTest.ImageBufferMapWrite/* = SKIP
1820+
42264369 ANDROID GLES : ComputeShaderTest.ImageBufferMapWriteAndBufferSubData/* = SKIP
1821+
42264369 ANDROID GLES : GLSLTest_ES31.ShortCircuitOutOfBoundsAccess/* = SKIP
18151822

18161823
// WebGPU failures
18171824
// All tests are flaky on Linux Intel due to X errors.

src/tests/gl_tests/ComputeShaderTest.cpp

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5096,10 +5096,6 @@ TEST_P(ComputeShaderTest, ImageBufferMapWrite)
50965096
{
50975097
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_buffer"));
50985098

5099-
// Claims to support GL_OES_texture_buffer, but fails compilation of shader because "extension
5100-
// 'GL_OES_texture_buffer' is not supported". http://anglebug.com/42264369
5101-
ANGLE_SKIP_TEST_IF(IsQualcomm() && IsOpenGLES());
5102-
51035099
constexpr char kComputeImageBuffer[] = R"(#version 310 es
51045100
#extension GL_OES_texture_buffer : require
51055101
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
@@ -5167,10 +5163,6 @@ TEST_P(ComputeShaderTest, ImageBufferMapWriteAndBufferSubData)
51675163
{
51685164
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_buffer"));
51695165

5170-
// Claims to support GL_OES_texture_buffer, but fails compilation of shader because "extension
5171-
// 'GL_OES_texture_buffer' is not supported". http://anglebug.com/42264369
5172-
ANGLE_SKIP_TEST_IF(IsQualcomm() && IsOpenGLES());
5173-
51745166
// http://anglebug.com/42265043. Known bug.
51755167
ANGLE_SKIP_TEST_IF(IsVulkan());
51765168

src/tests/gl_tests/GLSLTest.cpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3820,6 +3820,99 @@ void main()
38203820
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
38213821
}
38223822

3823+
// Test that short circuit doesn't evaluate out of bounds expressions.
3824+
TEST_P(GLSLTest_ES31, ShortCircuitOutOfBoundsAccess)
3825+
{
3826+
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_buffer"));
3827+
3828+
// Note that the uniform doesn't need to be set, and will contain the default value of false.
3829+
constexpr char kCS[] = R"(#version 310 es
3830+
#extension GL_OES_texture_buffer : require
3831+
layout(local_size_x = 128, local_size_y = 1, local_size_z = 1) in;
3832+
3833+
uniform bool falseValue;
3834+
uniform uint zero;
3835+
3836+
layout(binding = 0) readonly buffer Input
3837+
{
3838+
uint inData[];
3839+
};
3840+
3841+
layout(binding = 1, std430) writeonly buffer Output
3842+
{
3843+
uint outData[];
3844+
};
3845+
3846+
layout(r32ui, binding = 0) uniform highp uimageBuffer image;
3847+
3848+
void main()
3849+
{
3850+
outData[0] = falseValue ? inData[123456] : 1u;
3851+
outData[1] = falseValue ? inData[~zero] : 2u;
3852+
outData[2] = falseValue ? inData[123456] : 3u;
3853+
outData[3] = !falseValue ? 4u : inData[~zero];
3854+
outData[4] = !falseValue ? 5u : imageLoad(image, 1234567).x;
3855+
outData[5] = falseValue ? imageLoad(image, int(~zero)).x : 6u;
3856+
3857+
bool eval6 = falseValue && bool(inData[123456]);
3858+
bool eval7 = falseValue && bool(inData[~zero]);
3859+
bool eval8 = !falseValue || bool(inData[123456]);
3860+
bool eval9 = !falseValue || bool(inData[~zero]);
3861+
bool eval10 = falseValue && bool(imageLoad(image, 1234567).x);
3862+
bool eval11 = !falseValue || bool(imageLoad(image, int(~zero)).x);
3863+
3864+
outData[6] = eval6 ? 1234u : 7u;
3865+
outData[7] = eval7 ? 2345u : 8u;
3866+
outData[8] = eval8 ? 9u : 3456u;
3867+
outData[9] = eval9 ? 10u : 4567u;
3868+
outData[10] = eval10 ? 5678u : 11u;
3869+
outData[11] = eval11 ? 12u : 6789u;
3870+
})";
3871+
3872+
ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
3873+
3874+
GLBuffer input;
3875+
glBindBuffer(GL_SHADER_STORAGE_BUFFER, input);
3876+
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(uint32_t), nullptr, GL_STATIC_DRAW);
3877+
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, input);
3878+
3879+
constexpr std::array<uint32_t, 12> kInitialData = {};
3880+
3881+
GLBuffer output;
3882+
glBindBuffer(GL_SHADER_STORAGE_BUFFER, output);
3883+
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(kInitialData), kInitialData.data(),
3884+
GL_STATIC_DRAW);
3885+
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, output);
3886+
3887+
GLBuffer imageBufferStorage;
3888+
GLTexture imageBuffer;
3889+
3890+
glBindBuffer(GL_TEXTURE_BUFFER, imageBufferStorage);
3891+
glBufferData(GL_TEXTURE_BUFFER, sizeof(uint32_t), nullptr, GL_STATIC_DRAW);
3892+
3893+
glBindTexture(GL_TEXTURE_BUFFER, imageBuffer);
3894+
glTexBufferEXT(GL_TEXTURE_BUFFER, GL_R32UI, imageBufferStorage);
3895+
glBindImageTexture(0, imageBuffer, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32UI);
3896+
ASSERT_GL_NO_ERROR();
3897+
3898+
glUseProgram(program);
3899+
glDispatchCompute(1, 1, 1);
3900+
glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
3901+
glFinish();
3902+
3903+
std::array<uint32_t, 12> readback = {};
3904+
void *mapped =
3905+
glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeof(kInitialData), GL_MAP_READ_BIT);
3906+
memcpy(readback.data(), mapped, sizeof(kInitialData));
3907+
glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
3908+
ASSERT_GL_NO_ERROR();
3909+
3910+
for (size_t i = 0; i < kInitialData.size(); ++i)
3911+
{
3912+
EXPECT_EQ(readback[i], i + 1) << i;
3913+
}
3914+
}
3915+
38233916
// Test that nesting ternary and short-circuitting operators work.
38243917
TEST_P(GLSLTest, NestedTernaryAndShortCircuit)
38253918
{

src/tests/gl_tests/TextureTest.cpp

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16692,10 +16692,6 @@ TEST_P(TextureBufferTestES31, TexBufferDrawTwice)
1669216692
{
1669316693
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_buffer"));
1669416694

16695-
// TODO(http://anglebug.com/42264369): Claims to support GL_OES_texture_buffer, but fails
16696-
// compilation of shader because "extension 'GL_OES_texture_buffer' is not supported".
16697-
ANGLE_SKIP_TEST_IF(IsQualcomm() && IsOpenGLES());
16698-
1669916695
const std::array<GLColor, 1> kTexData = {GLColor::red};
1670016696

1670116697
GLBuffer buffer;
@@ -16983,10 +16979,6 @@ TEST_P(TextureBufferTestES31, UseAsUBOThenUpdateThenAsTextureBuffer)
1698316979
{
1698416980
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_buffer"));
1698516981

16986-
// Claims to support GL_OES_texture_buffer, but fails compilation of shader because "extension
16987-
// 'GL_OES_texture_buffer' is not supported". http://anglebug.com/42264369
16988-
ANGLE_SKIP_TEST_IF(IsQualcomm() && IsOpenGLES());
16989-
1699016982
const std::array<GLColor, 4> kInitialData = {GLColor::red, GLColor::red, GLColor::red,
1699116983
GLColor::red};
1699216984
const std::array<GLColor, 4> kUpdateData = {GLColor::blue, GLColor::blue, GLColor::blue,
@@ -17053,9 +17045,6 @@ TEST_P(TextureBufferTestES31, MapTextureBufferInvalidateThenWrite)
1705317045
{
1705417046
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_buffer"));
1705517047

17056-
// TODO(http://anglebug.com/42264369): Claims to support GL_OES_texture_buffer, but fails
17057-
// compilation of shader because "extension 'GL_OES_texture_buffer' is not supported".
17058-
ANGLE_SKIP_TEST_IF(IsQualcomm() && IsOpenGLES());
1705917048
// TODO(http://anglebug.com/42264910): The OpenGL backend doesn't correctly handle texture
1706017049
// buffers being invalidated when mapped.
1706117050
ANGLE_SKIP_TEST_IF(IsOpenGL());

0 commit comments

Comments
 (0)