ES31: Add atomic memory functions

BUG=angleproject:1442
TEST=angle_unittests, angle_end2end_tests
     dEQP-GLES31.functional.compute.shared_var.atomic*
     dEQP-GLES31.functional.compute.basic.shared_atomic_op*
     dEQP-GLES31.functional.ssbo.atomic.*

Change-Id: I82b54fde3a852d3bd917b1e19680baa1c28fce4d
Reviewed-on: https://chromium-review.googlesource.com/765061
Commit-Queue: Olli Etuaho <oetuaho@nvidia.com>
Reviewed-by: Olli Etuaho <oetuaho@nvidia.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/compiler/translator/Initialize.cpp b/src/compiler/translator/Initialize.cpp
index aeb8d58..7283bdb 100644
--- a/src/compiler/translator/Initialize.cpp
+++ b/src/compiler/translator/Initialize.cpp
@@ -635,6 +635,26 @@
     symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, uint1, "atomicCounterIncrement", atomicCounter);
     symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, uint1, "atomicCounterDecrement", atomicCounter);
 
+    // Insert all atomic memory functions
+    const TType *int1InOut  = TCache::getType(EbtInt, EvqInOut);
+    const TType *uint1InOut = TCache::getType(EbtUInt, EvqInOut);
+    symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, uint1, "atomicAdd", uint1InOut, uint1);
+    symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, int1, "atomicAdd", int1InOut, int1);
+    symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, uint1, "atomicMin", uint1InOut, uint1);
+    symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, int1, "atomicMin", int1InOut, int1);
+    symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, uint1, "atomicMax", uint1InOut, uint1);
+    symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, int1, "atomicMax", int1InOut, int1);
+    symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, uint1, "atomicAnd", uint1InOut, uint1);
+    symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, int1, "atomicAnd", int1InOut, int1);
+    symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, uint1, "atomicOr", uint1InOut, uint1);
+    symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, int1, "atomicOr", int1InOut, int1);
+    symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, uint1, "atomicXor", uint1InOut, uint1);
+    symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, int1, "atomicXor", int1InOut, int1);
+    symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, uint1, "atomicExchange", uint1InOut, uint1);
+    symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, int1, "atomicExchange", int1InOut, int1);
+    symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, uint1, "atomicCompSwap", uint1InOut, uint1, uint1);
+    symbolTable.insertBuiltIn(ESSL3_1_BUILTINS, int1, "atomicCompSwap", int1InOut, int1, int1);
+
     const TType *gimage2D      = TCache::getType(EbtGImage2D);
     const TType *gimage3D      = TCache::getType(EbtGImage3D);
     const TType *gimage2DArray = TCache::getType(EbtGImage2DArray);
diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp
index ab0ece3..523228a 100644
--- a/src/compiler/translator/ParseContext.cpp
+++ b/src/compiler/translator/ParseContext.cpp
@@ -32,6 +32,22 @@
 
 const int kWebGLMaxStructNesting = 4;
 
+const std::array<const char *, 8> kAtomicBuiltin = {{"atomicAdd", "atomicMin", "atomicMax",
+                                                     "atomicAnd", "atomicOr", "atomicXor",
+                                                     "atomicExchange", "atomicCompSwap"}};
+
+bool IsAtomicBuiltin(const TString &name)
+{
+    for (size_t i = 0; i < kAtomicBuiltin.size(); ++i)
+    {
+        if (name.compare(kAtomicBuiltin[i]) == 0)
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
 bool ContainsSampler(const TStructure *structType);
 
 bool ContainsSampler(const TType &type)
@@ -116,6 +132,16 @@
     }
 }
 
+bool IsBufferOrSharedVariable(TIntermTyped *var)
+{
+    if (var->isInterfaceBlock() || var->getQualifier() == EvqBuffer ||
+        var->getQualifier() == EvqShared)
+    {
+        return true;
+    }
+    return false;
+}
+
 }  // namespace
 
 // This tracks each binding point's current default offset for inheritance of subsequent
@@ -5592,6 +5618,35 @@
     }
 }
 
+void TParseContext::checkAtomicMemoryBuiltinFunctions(TIntermAggregate *functionCall)
+{
+    const TString &name = functionCall->getFunctionSymbolInfo()->getName();
+    if (IsAtomicBuiltin(name))
+    {
+        TIntermSequence *arguments = functionCall->getSequence();
+        TIntermTyped *memNode      = (*arguments)[0]->getAsTyped();
+
+        if (IsBufferOrSharedVariable(memNode))
+        {
+            return;
+        }
+
+        while (memNode->getAsBinaryNode())
+        {
+            memNode = memNode->getAsBinaryNode()->getLeft();
+            if (IsBufferOrSharedVariable(memNode))
+            {
+                return;
+            }
+        }
+
+        error(memNode->getLine(),
+              "The value passed to the mem argument of an atomic memory function does not "
+              "correspond to a buffer or shared variable.",
+              functionCall->getFunctionSymbolInfo()->getName().c_str());
+    }
+}
+
 // GLSL ES 3.10 Revision 4, 4.9 Memory Access Qualifiers
 void TParseContext::checkImageMemoryAccessForBuiltinFunctions(TIntermAggregate *functionCall)
 {
@@ -5828,6 +5883,7 @@
                     checkTextureOffsetConst(callNode);
                     checkTextureGather(callNode);
                     checkImageMemoryAccessForBuiltinFunctions(callNode);
+                    checkAtomicMemoryBuiltinFunctions(callNode);
                 }
                 else
                 {
diff --git a/src/compiler/translator/ParseContext.h b/src/compiler/translator/ParseContext.h
index c611b31..e917db7 100644
--- a/src/compiler/translator/ParseContext.h
+++ b/src/compiler/translator/ParseContext.h
@@ -417,6 +417,7 @@
     void checkImageMemoryAccessForBuiltinFunctions(TIntermAggregate *functionCall);
     void checkImageMemoryAccessForUserDefinedFunctions(const TFunction *functionDefinition,
                                                        const TIntermAggregate *functionCall);
+    void checkAtomicMemoryBuiltinFunctions(TIntermAggregate *functionCall);
     TIntermSequence *createEmptyArgumentsList();
 
     // fnCall is only storing the built-in op, and function name or constructor type. arguments
diff --git a/src/tests/compiler_tests/ShaderValidation_test.cpp b/src/tests/compiler_tests/ShaderValidation_test.cpp
index b581a40..e3f587e 100644
--- a/src/tests/compiler_tests/ShaderValidation_test.cpp
+++ b/src/tests/compiler_tests/ShaderValidation_test.cpp
@@ -5325,3 +5325,232 @@
         FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
     }
 }
+
+// Test that the value passed to the mem argument of an atomic memory function can be a shared
+// variable.
+TEST_F(ComputeShaderValidationTest, AtomicAddWithSharedVariable)
+{
+    const std::string &shaderString =
+        R"(#version 310 es
+
+        layout(local_size_x = 5) in;
+        shared uint myShared;
+
+        void main() {
+            atomicAdd(myShared, 2u);
+        })";
+
+    if (!compile(shaderString))
+    {
+        FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
+    }
+}
+
+// Test that it is acceptable to pass an element of an array to the mem argument of an atomic memory
+// function, as long as the underlying array is a buffer or shared variable.
+TEST_F(ComputeShaderValidationTest, AtomicAddWithSharedVariableArray)
+{
+    const std::string &shaderString =
+        R"(#version 310 es
+
+        layout(local_size_x = 5) in;
+        shared uint myShared[2];
+
+        void main() {
+            atomicAdd(myShared[0], 2u);
+        })";
+
+    if (!compile(shaderString))
+    {
+        FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
+    }
+}
+
+// Test that it is acceptable to pass a single component of a vector to the mem argument of an
+// atomic memory function, as long as the underlying vector is a buffer or shared variable.
+TEST_F(ComputeShaderValidationTest, AtomicAddWithSharedVariableVector)
+{
+    const std::string &shaderString =
+        R"(#version 310 es
+
+        layout(local_size_x = 5) in;
+        shared uvec4 myShared;
+
+        void main() {
+            atomicAdd(myShared[0], 2u);
+        })";
+
+    if (!compile(shaderString))
+    {
+        FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
+    }
+}
+
+// Test that the value passed to the mem argument of an atomic memory function can be a buffer
+// variable.
+TEST_F(FragmentShaderValidationTest, AtomicAddWithBufferVariable)
+{
+    const std::string &shaderString =
+        R"(#version 310 es
+
+        layout(std140) buffer bufferName1{
+            uint u1;
+        };
+
+        void main()
+        {
+            atomicAdd(u1, 2u);
+        })";
+
+    if (!compile(shaderString))
+    {
+        FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
+    }
+}
+
+// Test that it is acceptable to pass an element of an array to the mem argument of an atomic memory
+// function, as long as the underlying array is a buffer or shared variable.
+TEST_F(FragmentShaderValidationTest, AtomicAddWithBufferVariableArrayElement)
+{
+    const std::string &shaderString =
+        R"(#version 310 es
+
+        layout(std140) buffer bufferName1{
+            uint u1[2];
+        };
+
+        void main()
+        {
+            atomicAdd(u1[0], 2u);
+        })";
+
+    if (!compile(shaderString))
+    {
+        FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
+    }
+}
+
+// Test that it is acceptable to pass a member of a shader storage block instance to the mem
+// argument of an atomic memory function.
+TEST_F(FragmentShaderValidationTest, AtomicAddWithBufferVariableInBlockInstance)
+{
+    const std::string &shaderString =
+        R"(#version 310 es
+
+        layout(std140) buffer bufferName{
+            uint u1;
+        } instanceName;
+
+        void main()
+        {
+            atomicAdd(instanceName.u1, 2u);
+        })";
+
+    if (!compile(shaderString))
+    {
+        FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
+    }
+}
+
+// Test that it is acceptable to pass a member of a shader storage block instance array to the mem
+// argument of an atomic memory function.
+TEST_F(FragmentShaderValidationTest, AtomicAddWithBufferVariableInBlockInstanceArray)
+{
+    const std::string &shaderString =
+        R"(#version 310 es
+
+        layout(std140) buffer bufferName{
+            uint u1;
+        } instanceName[1];
+
+        void main()
+        {
+            atomicAdd(instanceName[0].u1, 2u);
+        })";
+
+    if (!compile(shaderString))
+    {
+        FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
+    }
+}
+
+// Test that it is acceptable to pass an element of an array  of a shader storage block instance to
+// the mem argument of an atomic memory function.
+TEST_F(FragmentShaderValidationTest, AtomicAddWithElementOfArrayInBlockInstance)
+{
+    const std::string &shaderString =
+        R"(#version 310 es
+
+        layout(std140) buffer blockName {
+            uint data[2];
+        } instanceName;
+
+        void main()
+        {
+            atomicAdd(instanceName.data[0], 2u);
+        })";
+
+    if (!compile(shaderString))
+    {
+        FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
+    }
+}
+
+// Test that it is not allowed to pass an atomic counter variable to the mem argument of an atomic
+// memory function.
+TEST_F(FragmentShaderValidationTest, AtomicAddWithAtomicCounter)
+{
+    const std::string &shaderString =
+        R"(#version 310 es
+
+        layout(binding = 0, offset = 4) uniform atomic_uint ac;
+
+        void main()
+        {
+            atomicAdd(ac, 2u);
+        })";
+
+    if (compile(shaderString))
+    {
+        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
+    }
+}
+
+// Test that it is not allowed to pass an element of an atomic counter array to the mem argument of
+// an atomic memory function.
+TEST_F(FragmentShaderValidationTest, AtomicAddWithAtomicCounterArray)
+{
+    const std::string &shaderString =
+        R"(#version 310 es
+
+        layout(binding = 0, offset = 4) uniform atomic_uint ac[2];
+
+        void main()
+        {
+            atomicAdd(ac[0], 2u);
+        })";
+
+    if (compile(shaderString))
+    {
+        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
+    }
+}
+
+// Test that it is not allowed to pass a local uint value to the mem argument of an atomic memory
+// function.
+TEST_F(FragmentShaderValidationTest, AtomicAddWithNonStorageVariable)
+{
+    const std::string &shaderString =
+        R"(#version 310 es
+
+        void main()
+        {
+            uint test = 1u;
+            atomicAdd(test, 2u);
+        })";
+
+    if (compile(shaderString))
+    {
+        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
+    }
+}
diff --git a/src/tests/deqp_support/deqp_gles31_test_expectations.txt b/src/tests/deqp_support/deqp_gles31_test_expectations.txt
index e13e35c..1c50f3b 100644
--- a/src/tests/deqp_support/deqp_gles31_test_expectations.txt
+++ b/src/tests/deqp_support/deqp_gles31_test_expectations.txt
@@ -80,6 +80,11 @@
 1442 D3D11 : dEQP-GLES31.functional.compute.basic.ubo_to_ssbo_single_group = FAIL
 1442 D3D11 : dEQP-GLES31.functional.compute.basic.ubo_to_ssbo_multiple_invocations = FAIL
 1442 D3D11 : dEQP-GLES31.functional.compute.basic.ubo_to_ssbo_multiple_groups = FAIL
+1442 D3D11 : dEQP-GLES31.functional.compute.basic.shared_atomic_op_single_invocation = FAIL
+1442 D3D11 : dEQP-GLES31.functional.compute.basic.shared_atomic_op_single_group = FAIL
+1442 D3D11 : dEQP-GLES31.functional.compute.basic.shared_atomic_op_multiple_invocations = FAIL
+1442 D3D11 : dEQP-GLES31.functional.compute.basic.shared_atomic_op_multiple_groups = FAIL
+1442 D3D11 : dEQP-GLES31.functional.compute.shared_var.atomic.* = FAIL
 1442 D3D11 : dEQP-GLES31.functional.compute.shared_var.basic_type.* = FAIL
 1442 D3D11 : dEQP-GLES31.functional.compute.shared_var.work_group_size.* = FAIL
 1442 D3D11 : dEQP-GLES31.functional.atomic_counter.* = FAIL
@@ -1176,10 +1181,6 @@
 1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.write_multiple_unsized_arr_multiple_groups = FAIL
 1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.ssbo_cmd_barrier_single = FAIL
 1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.ssbo_cmd_barrier_multiple = FAIL
-1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.shared_atomic_op_single_invocation = FAIL
-1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.shared_atomic_op_single_group = FAIL
-1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.shared_atomic_op_multiple_invocations = FAIL
-1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.shared_atomic_op_multiple_groups = FAIL
 1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.copy_image_to_ssbo_large = FAIL
 1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.copy_ssbo_to_image_large = FAIL
 1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.image_barrier_single = FAIL
@@ -1188,7 +1189,6 @@
 1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.atomic_counter_single_group = FAIL
 1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.atomic_counter_multiple_invocations = FAIL
 1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.atomic_counter_multiple_groups = FAIL
-1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.shared_var.atomic.* = FAIL
 1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.indirect_dispatch.* = FAIL
 1442 OPENGL D3D11 : dEQP-GLES31.functional.ssbo.* = FAIL
 1442 OPENGL D3D11 : dEQP-GLES31.functional.ubo.2_level_array.* = FAIL
diff --git a/src/tests/gl_tests/ShaderStorageBufferTest.cpp b/src/tests/gl_tests/ShaderStorageBufferTest.cpp
index 01317bb..1d0babd 100644
--- a/src/tests/gl_tests/ShaderStorageBufferTest.cpp
+++ b/src/tests/gl_tests/ShaderStorageBufferTest.cpp
@@ -134,6 +134,59 @@
     EXPECT_GL_NO_ERROR();
 }
 
+// Test atomic memory functions.
+TEST_P(ShaderStorageBufferTest31, AtomicMemoryFunctions)
+{
+    ANGLE_SKIP_TEST_IF(IsAMD() && IsDesktopOpenGL() && IsWindows());
+    ANGLE_SKIP_TEST_IF(IsLinux() && IsIntel() && IsOpenGL());
+
+    const std::string &csSource =
+        R"(#version 310 es
+
+        layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
+        layout(binding = 1) buffer blockName {
+            uint data[2];
+        } instanceName;
+
+        void main()
+        {
+            instanceName.data[0] = 0u;
+            instanceName.data[1] = 0u;
+            atomicAdd(instanceName.data[0], 5u);
+            atomicMax(instanceName.data[1], 7u);
+
+        })";
+
+    ANGLE_GL_COMPUTE_PROGRAM(program, csSource);
+
+    glUseProgram(program.get());
+
+    unsigned int bufferData[2] = {0u};
+    // Create shader storage buffer
+    GLBuffer shaderStorageBuffer;
+    glBindBuffer(GL_SHADER_STORAGE_BUFFER, shaderStorageBuffer);
+    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(bufferData), nullptr, GL_STATIC_DRAW);
+
+    // Bind shader storage buffer
+    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, shaderStorageBuffer);
+
+    // Dispath compute
+    glDispatchCompute(1, 1, 1);
+
+    glFinish();
+
+    // Read back shader storage buffer
+    glBindBuffer(GL_SHADER_STORAGE_BUFFER, shaderStorageBuffer);
+    void *ptr = glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeof(bufferData), GL_MAP_READ_BIT);
+    memcpy(bufferData, ptr, sizeof(bufferData));
+    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
+    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
+    EXPECT_EQ(5u, bufferData[0]);
+    EXPECT_EQ(7u, bufferData[1]);
+
+    EXPECT_GL_NO_ERROR();
+}
+
 ANGLE_INSTANTIATE_TEST(ShaderStorageBufferTest31, ES31_OPENGL(), ES31_OPENGLES());
 
 }  // namespace