blob: 25b656976a9cfb8ecc01c7748d619e4f2253b2a7 [file]
/* Copyright (c) 2024-2026 LunarG, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <spirv/unified1/spirv.hpp>
#include "gpuav/core/gpuav.h"
#include "gpuav/resources/gpuav_state_trackers.h"
#include "gpuav/shaders/gpuav_error_codes.h"
#include "gpuav/shaders/gpuav_error_header.h"
#include "error_message/spirv_logging.h"
#include "gpuav/shaders/gpuav_shaders_constants.h"
namespace gpuav {
void RegisterSharedMemoryDataRaceValidation(Validator& gpuav, CommandBufferSubState& cb) {
if (!gpuav.gpuav_settings.shader_instrumentation.shared_memory_data_race) {
return;
}
cb.on_instrumentation_error_logger_register_functions.emplace_back(
[](Validator& gpuav, CommandBufferSubState& cb, const LastBound& last_bound) {
CommandBufferSubState::InstrumentationErrorLogger inst_error_logger =
[](Validator& gpuav, const Location& loc, const uint32_t* error_record,
const InstrumentedShader* instrumented_shader, std::string& out_error_msg, std::string& out_vuid_msg) {
using namespace glsl;
bool error_found = false;
if (GetErrorGroup(error_record) != kErrorGroup_SharedMemoryDataRace) {
return error_found;
}
error_found = true;
const uint32_t thread_id = error_record[kInst_LogError_ParameterOffset_0];
// Packed shadow word of the "other" access in the race.
// See shared_memory_data_race.comp for the bit layout.
const uint32_t raced_against = error_record[kInst_LogError_ParameterOffset_1];
// matches constants from shared_memory_data_race.comp
constexpr uint32_t inst_offset_shift = glsl::kSharedMemoryDataRace_InstOffsetShift;
constexpr uint32_t thread_id_bits = glsl::kSharedMemoryDataRace_ThreadIdMask;
constexpr uint32_t inst_offset_bits = glsl::kSharedMemoryDataRace_InstOffsetBits;
const uint32_t collide_id = raced_against & thread_id_bits;
const uint32_t collide_inst_offset = (raced_against >> inst_offset_shift) & inst_offset_bits;
uint32_t variable_id = error_record[kInst_LogError_ParameterOffset_2];
const uint32_t error_sub_code = GetSubError(error_record);
std::ostringstream strm;
strm << "A data race was detected on the shared memory variable \"";
if (instrumented_shader) {
::spirv::FindGlobalName(strm, instrumented_shader->original_spirv, (uint32_t)spv::OpVariable, variable_id);
} else {
strm << "[error, original SPIR-V not found]";
}
strm << "\" in local invocation index " << thread_id << " while performing a ";
switch (error_sub_code) {
case kErrorSubCode_SharedMemoryDataRace_RaceOnStore: {
strm << "store";
out_vuid_msg = "SharedMemoryDataRace-RaceOnStore";
} break;
case kErrorSubCode_SharedMemoryDataRace_RaceOnLoad: {
strm << "load";
out_vuid_msg = "SharedMemoryDataRace-RaceOnLoad";
} break;
case kErrorSubCode_SharedMemoryDataRace_RaceOnLoadStoreVsAtomic: {
strm << "load or store";
out_vuid_msg = "SharedMemoryDataRace-RaceOnLoadStoreVsAtomic";
} break;
case kErrorSubCode_SharedMemoryDataRace_RaceOnAtomic: {
strm << "atomic";
out_vuid_msg = "SharedMemoryDataRace-RaceOnAtomic";
} break;
default:
strm << "UNKNOWN";
error_found = false;
break;
}
strm << " operation. (Likely against ";
// 0xFFF is reserved (the pass caps anything above) and is used as an "unknown thread" marker,
// either written by atomic / coopmat_store,
// or left over from SENTINEL when an atomicOr-path access touched a fresh slot.
if (collide_id == thread_id_bits) {
strm << "unknown invocation";
} else {
strm << "local invocation index " << collide_id;
}
strm << ")";
// Print the source location of the offending access.
if (instrumented_shader) {
const uint32_t self_inst_offset = error_record[kHeader_StageInstructionIdOffset] & kInstructionId_Mask;
// Two cases we don't have the offset
// - zero
// inst_position didn't fit in the 17-bit field, only happens on huge shaders
// - kInstOffsetBits
// slot was still SENTINEL when an atomicOr-path access ran, so its offset wasn't recorded
const bool have_offset = collide_inst_offset != 0 && collide_inst_offset != inst_offset_bits &&
collide_inst_offset < instrumented_shader->original_spirv.size();
if (have_offset && collide_inst_offset == self_inst_offset) {
// If instruction is racing against itself, don't want to print two source lines
strm << "\nThis race is between two invocations executing the same instruction.\n";
} else {
strm << "\nThe other access in this race was at:\n";
if (have_offset) {
::spirv::FindShaderSource(strm, instrumented_shader->original_spirv, collide_inst_offset,
gpuav.gpuav_settings.debug_printf_only);
} else {
strm << "(specific conflicting instruction not recorded)\n";
}
}
}
out_error_msg += strm.str();
return error_found;
};
return inst_error_logger;
});
}
} // namespace gpuav