diff --git a/src/compiler/translator/HashNames.cpp b/src/compiler/translator/HashNames.cpp index 62b4f6d063..88e1924c5d 100644 --- a/src/compiler/translator/HashNames.cpp +++ b/src/compiler/translator/HashNames.cpp @@ -52,6 +52,18 @@ void AddToNameMapIfNotMapped(const ImmutableString &name, } } +bool NameMapContainsHashedName(const NameMap &nameMap, const ImmutableString &hashedName) +{ + for (const auto &entry : nameMap) + { + if (hashedName == entry.second) + { + return true; + } + } + return false; +} + } // anonymous namespace ImmutableString HashName(const ImmutableString &name, @@ -74,9 +86,35 @@ ImmutableString HashName(const ImmutableString &name, return res; } - // Has a hash function - ImmutableString hashedName = HashName(name, hashFunction); - AddToNameMapIfNotMapped(name, hashedName, nameMap); + // Has a hash function. If this name has already been mapped, reuse the + // existing entry so the same source identifier always maps to the same + // output identifier. + if (nameMap) + { + NameMap::const_iterator it = nameMap->find(name.data()); + if (it != nameMap->end()) + { + return ImmutableString(it->second); + } + } + + const ImmutableString baseHashedName = HashName(name, hashFunction); + if (!nameMap) + { + return baseHashedName; + } + + // Ensure the chosen output identifier is not already assigned to a + // different source identifier. If it is, append a numeric suffix until a + // unique name is found. + ImmutableString hashedName = baseHashedName; + unsigned int suffix = 0u; + while (NameMapContainsHashedName(*nameMap, hashedName)) + { + ++suffix; + hashedName = BuildConcatenatedImmutableString(baseHashedName, '_', suffix); + } + (*nameMap)[name.data()] = hashedName.data(); return hashedName; } diff --git a/src/tests/angle_unittests.gni b/src/tests/angle_unittests.gni index f88a02958c..5e8e9ac1a7 100644 --- a/src/tests/angle_unittests.gni +++ b/src/tests/angle_unittests.gni @@ -95,6 +95,7 @@ angle_unittests_compiler_tests_sources = [ "compiler_tests/FloatLex_test.cpp", "compiler_tests/GeometryShader_test.cpp", "compiler_tests/GlFragDataNotModified_test.cpp", + "compiler_tests/HashNames_test.cpp", "compiler_tests/ImmutableString_test.cpp", "compiler_tests/IntermNode_test.cpp", "compiler_tests/NV_draw_buffers_test.cpp", diff --git a/src/tests/compiler_tests/HashNames_test.cpp b/src/tests/compiler_tests/HashNames_test.cpp new file mode 100644 index 0000000000..505896bff1 --- /dev/null +++ b/src/tests/compiler_tests/HashNames_test.cpp @@ -0,0 +1,84 @@ +// +// Copyright 2026 The ANGLE Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// HashNames_test.cpp: +// Tests for hashed identifier names in the translated GLSL output. +// + +#include "GLSLANG/ShaderLang.h" +#include "angle_gl.h" +#include "gtest/gtest.h" +#include "tests/test_utils/compiler_test.h" + +using namespace sh; + +namespace +{ + +// A hash that depends only on the first character so that distinct identifiers +// beginning with the same letter map to the same hashed name. +khronos_uint64_t FirstCharHash(const char *str, size_t len) +{ + return len > 0 ? static_cast(str[0]) : 0u; +} + +class HashNamesTest : public MatchOutputCodeTest +{ + public: + HashNamesTest() : MatchOutputCodeTest(GL_FRAGMENT_SHADER, SH_ESSL_OUTPUT) + { + getResources()->HashFunction = FirstCharHash; + } +}; + +// When two distinct source identifiers map to the same hash value the +// translator must still emit distinct names so the inner declaration does not +// shadow the outer one in the translated output. +TEST_F(HashNamesTest, CollidingNamesEmitDistinctIdentifiers) +{ + const std::string &shaderString = + R"(precision mediump float; + void main() + { + float aOuter[8]; + aOuter[0] = 1.0; + { + float aInner[2]; + aInner[0] = 2.0; + gl_FragColor = vec4(aOuter[0] + aInner[0]); + } + })"; + compile(shaderString); + + // The first identifier encountered keeps the bare hashed name. + EXPECT_TRUE(foundInCode("webgl_61[8]")); + // The second identifier must be emitted with a different name. + EXPECT_FALSE(foundInCode("webgl_61[2]")); +} + +// When more than two source identifiers map to the same hash value each must be +// emitted with its own distinct name. +TEST_F(HashNamesTest, MultipleCollidingNamesEmitDistinctIdentifiers) +{ + const std::string &shaderString = + R"(precision mediump float; + void main() + { + float aFirst[5]; + aFirst[0] = 1.0; + float aSecond[3]; + aSecond[0] = 2.0; + float aThird[7]; + aThird[0] = 3.0; + gl_FragColor = vec4(aFirst[0] + aSecond[0] + aThird[0]); + })"; + compile(shaderString); + + EXPECT_TRUE(foundInCode("webgl_61[5]")); + EXPECT_FALSE(foundInCode("webgl_61[3]")); + EXPECT_FALSE(foundInCode("webgl_61[7]")); +} + +} // anonymous namespace