Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions layers/gpuav/instrumentation/sanitizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,23 @@ void RegisterSanitizer(Validator &gpuav, CommandBufferSubState &cb) {
strm << ", but it must be 0, 1, 2, or 3" << GetSpirvSpecLink(spv::OpImageGather);
out_vuid_msg = "SPIRV-Sanitizer-Image-Gather";
} break;
case kErrorSubCodeSanitizerPow: {
// Pow is only valid with a scalar/vector of 16/32-bit float
const uint32_t vector_size = error_record[kInstLogErrorParameterOffset_0];
// Casting produces artifacts in float value, need to memcpy
float x_value = 0.0f;
float y_value = 0.0f;
memcpy(&x_value, &error_record[kInstLogErrorParameterOffset_1], sizeof(float));
Copy link
Contributor

@arno-lunarg arno-lunarg Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you cast so that it does not work?
x_value = *(float*)(error_record + kInstLogErrorParameterOffset_1); should work
In practice this code seems fine, it gets optimised (https://godbolt.org/z/7rde8v97G) but still

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my 5c, since i worked a lot for memcpy cast, in case that’s the topic of discussion. it’s totally fine with all compilers, works as intrinsics for small types (does not generate code). It’s better than reinterpret cast - the latter violates aliasing rules. But if we start using c++ 20, then std::bit_cast is the way to go.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

basically without bit_cast I dont know if it is possible to write c++ that does not violate strict type aliasing rules when converting int to float.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you cast so that it does not work?

Zero idea, I tried originally, nothing was working

I saw the bit_cast solution online, but we need that jump first

memcpy(&y_value, &error_record[kInstLogErrorParameterOffset_2], sizeof(float));
strm << "Pow (from GLSL.std.450) has an undefined result because operand (x < 0) or (x == 0 && y <= 0)\n ";
if (vector_size > 0) {
// Would need a new way to print more than 2 bytes out to get this to work
strm << "Using a vector of size " << vector_size << " but currently only can print out scalar values";
} else {
strm << "X == " << x_value << ", Y == " << y_value;
}
out_vuid_msg = "SPIRV-Sanitizer-Pow";
} break;
default:
error_found = false;
break;
Expand Down
3 changes: 2 additions & 1 deletion layers/gpuav/shaders/gpuav_error_codes.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ const int kErrorSubCode_IndexedDraw_OOBInstanceIndex = 2;
const int kErrorSubCodeSanitizerEmpty = 0; // reserved to mean no error was set
const int kErrorSubCodeSanitizerDivideZero = 1;
const int kErrorSubCodeSanitizerImageGather = 2;
const int kErrorSubCodeSanitizerCount = 3; // update when adding new item
const int kErrorSubCodeSanitizerPow = 3;
const int kErrorSubCodeSanitizerCount = 4; // update when adding new item

// Pre Draw
//
Expand Down
16 changes: 16 additions & 0 deletions layers/gpuav/shaders/instrumentation/sanitizer.comp
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,19 @@ void inst_sanitizer_image_gather(const uint inst_offset, const uint component)
0,
0);
}

bool inst_sanitizer_pow(const bool is_invalid, const uint inst_offset, const uint vector_size, const uint x, const uint y)
{
// Using a boolean here allows us to just calculate an expression and pass the result here to save us needing to do un-fun control flow.
// In practice, the driver's compiler will detect this easily and fold in the function for us
if (is_invalid) {
error_payload = ErrorPayload(
inst_offset,
SpecConstantLinkShaderId | (kErrorGroupInstSanitizer << kErrorGroupShift) | (kErrorSubCodeSanitizerPow << kErrorSubCodeShift),
vector_size,
x,
y);
return false;
}
return true;
}
104 changes: 99 additions & 5 deletions layers/gpuav/spirv/sanitizer_pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#include "function_basic_block.h"
#include "gpuav/shaders/gpuav_error_codes.h"
#include "module.h"
#include <spirv/unified1/GLSL.std.450.h>
#include <cstdint>
#include <spirv/unified1/spirv.hpp>
#include <iostream>

Expand All @@ -32,7 +34,9 @@ const static OfflineModule kOfflineModule = {instrumentation_sanitizer_comp, ins
const static OfflineFunction kOfflineFunctions[glsl::kErrorSubCodeSanitizerCount] = {
{"empty", 0},
{"inst_sanitizer_divide_by_zero", instrumentation_sanitizer_comp_function_0_offset},
{"inst_sanitizer_image_gather", instrumentation_sanitizer_comp_function_1_offset}};
{"inst_sanitizer_image_gather", instrumentation_sanitizer_comp_function_1_offset},
{"inst_sanitizer_pow", instrumentation_sanitizer_comp_function_2_offset},
};

SanitizerPass::SanitizerPass(Module& module) : Pass(module, kOfflineModule) {
for (uint32_t i = 0; i < glsl::kErrorSubCodeSanitizerCount; i++) {
Expand Down Expand Up @@ -61,18 +65,61 @@ uint32_t SanitizerPass::DivideByZeroCheck(BasicBlock& block, InstructionIt* inst
block.CreateInstruction(compare_op, {bool_type.Id(), compare_id, null_type_id, divisor_id}, inst_it);
return compare_id;
} else {
const Type& bool_vector_type = type_manager_.GetTypeVector(bool_type, vector_size);
const uint32_t bool_vector_type_id = type_manager_.GetTypeVector(bool_type, vector_size).Id();
const uint32_t zero_id = type_manager_.GetConstantZeroVector(*meta.result_type).Id();

const uint32_t compare_id = module_.TakeNextId();
block.CreateInstruction(compare_op, {bool_vector_type.Id(), compare_id, zero_id, divisor_id}, inst_it);
block.CreateInstruction(compare_op, {bool_vector_type_id, compare_id, zero_id, divisor_id}, inst_it);

const uint32_t any_id = module_.TakeNextId();
block.CreateInstruction(spv::OpAny, {bool_type.Id(), any_id, compare_id}, inst_it);
return any_id;
}
}

// Based off https://godbolt.org/z/dbToMGKTd - but found can hand-roll the spirv much better
// bool is_invalid = (x < 0.0 || (x == 0.0 && y <= 0.0));
// Returns an ID of type OpTypeBool
uint32_t SanitizerPass::PowCheck(BasicBlock& block, InstructionIt* inst_it, const InstructionMeta& meta) {
const Type& bool_type = type_manager_.GetTypeBool();
const uint32_t vector_size = meta.result_type->VectorSize();

uint32_t bool_compare_type_id = 0;
uint32_t null_type_id = 0;
if (vector_size == 0) {
bool_compare_type_id = bool_type.Id();
null_type_id = type_manager_.GetConstantNull(*meta.result_type).Id();
} else {
bool_compare_type_id = type_manager_.GetTypeVector(bool_type, vector_size).Id();
null_type_id = type_manager_.GetConstantZeroVector(*meta.result_type).Id();
}

const uint32_t x_value_id = meta.target_instruction->Word(5);
const uint32_t y_value_id = meta.target_instruction->Word(6);

const uint32_t compare_1_id = module_.TakeNextId();
const uint32_t compare_2_id = module_.TakeNextId();
const uint32_t compare_3_id = module_.TakeNextId();
block.CreateInstruction(spv::OpFOrdLessThan, {bool_compare_type_id, compare_1_id, x_value_id, null_type_id}, inst_it);
block.CreateInstruction(spv::OpFOrdEqual, {bool_compare_type_id, compare_2_id, x_value_id, null_type_id}, inst_it);
block.CreateInstruction(spv::OpFOrdLessThanEqual, {bool_compare_type_id, compare_3_id, y_value_id, null_type_id}, inst_it);

const uint32_t compare_and_id = module_.TakeNextId();
const uint32_t compare_or_id = module_.TakeNextId();
block.CreateInstruction(spv::OpLogicalAnd, {bool_compare_type_id, compare_and_id, compare_2_id, compare_3_id}, inst_it);
block.CreateInstruction(spv::OpLogicalOr, {bool_compare_type_id, compare_or_id, compare_1_id, compare_and_id}, inst_it);

uint32_t result_bool_id = 0;
if (vector_size == 0) {
result_bool_id = compare_or_id;
} else {
result_bool_id = module_.TakeNextId();
block.CreateInstruction(spv::OpAny, {bool_type.Id(), result_bool_id, compare_or_id}, inst_it);
}

return result_bool_id;
}

uint32_t SanitizerPass::CreateFunctionCall(BasicBlock& block, InstructionIt* inst_it, const InstructionMeta& meta) {
const uint32_t function_result = module_.TakeNextId();
const uint32_t function_def = GetLinkFunctionId(meta.sub_code);
Expand All @@ -99,6 +146,34 @@ uint32_t SanitizerPass::CreateFunctionCall(BasicBlock& block, InstructionIt* ins
const_cast<Instruction*>(meta.target_instruction)->UpdateWord(5, safe_value_id);
block.CreateInstruction(spv::OpFunctionCall,
{void_type, function_result, function_def, inst_position_id, component_value_id}, inst_it);
} else if (meta.sub_code == glsl::kErrorSubCodeSanitizerPow) {
const uint32_t is_invalid_id = PowCheck(block, inst_it, meta);

const uint32_t bool_type = type_manager_.GetTypeBool().Id();
const uint32_t vector_size = meta.result_type->VectorSize();
const uint32_t vector_size_id = type_manager_.CreateConstantUInt32(vector_size).Id();

uint32_t x_value_id = 0;
uint32_t y_value_id = 0;
if (vector_size == 0) {
// cast as uint as that is how we are encoding the payload currently
const Type& uint32_type = type_manager_.GetTypeInt(32, false);
const uint32_t x_value_float = meta.target_instruction->Word(5);
const uint32_t y_value_float = meta.target_instruction->Word(6);
x_value_id = module_.TakeNextId();
y_value_id = module_.TakeNextId();
block.CreateInstruction(spv::OpBitcast, {uint32_type.Id(), x_value_id, x_value_float}, inst_it);
block.CreateInstruction(spv::OpBitcast, {uint32_type.Id(), y_value_id, y_value_float}, inst_it);
} else {
// Put something valid, these are ignored on when printing error
x_value_id = type_manager_.GetConstantZeroUint32().Id();
y_value_id = type_manager_.GetConstantZeroUint32().Id();
}

block.CreateInstruction(
spv::OpFunctionCall,
{bool_type, function_result, function_def, is_invalid_id, inst_position_id, vector_size_id, x_value_id, y_value_id},
inst_it);
} else {
assert(false);
}
Expand Down Expand Up @@ -136,6 +211,7 @@ bool SanitizerPass::IsConstantZero(const Constant& constant) const {

bool SanitizerPass::RequiresInstrumentation(const Instruction& inst, InstructionMeta& meta) {
const spv::Op opcode = (spv::Op)inst.Opcode();
meta.target_instruction = &inst;

if (IsValueIn(opcode, {spv::OpUDiv, spv::OpSDiv, spv::OpUMod, spv::OpSMod, spv::OpSRem, spv::OpFMod, spv::OpFRem})) {
// Note - It is valid to divide by zero for a float (you get NaN), but invalid for an int.
Expand All @@ -154,7 +230,6 @@ bool SanitizerPass::RequiresInstrumentation(const Instruction& inst, Instruction
}

meta.result_type = type_manager_.FindTypeById(inst.TypeId());
meta.target_instruction = &inst;
meta.sub_code = glsl::kErrorSubCodeSanitizerDivideZero;
return true;
} else if (opcode == spv::OpImageGather) {
Expand All @@ -163,19 +238,38 @@ bool SanitizerPass::RequiresInstrumentation(const Instruction& inst, Instruction
const uint32_t constant_value = constant->GetValueUint32();
// TODO - Support spec constants
if (!constant->is_spec_constant_ && constant_value > 3) {
meta.target_instruction = &inst;
meta.sub_code = glsl::kErrorSubCodeSanitizerImageGather;
meta.skip_safe_mode = true;
meta.constant_value = constant_value;
return true;
}
}
} else if (opcode == spv::OpExtInst && inst.Word(3) == glsl_std450_id_) {
uint32_t glsl_opcode = inst.Word(4);
if (glsl_opcode == GLSLstd450Pow) {
meta.sub_code = glsl::kErrorSubCodeSanitizerPow;
} else {
return false;
}

// all of these only have results that are undefined
meta.skip_safe_mode = true;
meta.result_type = type_manager_.FindTypeById(inst.TypeId());
return true;
}

return false;
}

bool SanitizerPass::Instrument() {
for (const auto& inst : module_.ext_inst_imports_) {
const char* import_string = inst->GetAsString(2);
if (strcmp(import_string, "GLSL.std.450") == 0) {
glsl_std450_id_ = inst->ResultId();
break;
}
}

// Can safely loop function list as there is no injecting of new Functions until linking time
for (const auto& function : module_.functions_) {
if (function->instrumentation_added_) {
Expand Down
3 changes: 3 additions & 0 deletions layers/gpuav/spirv/sanitizer_pass.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class SanitizerPass : public Pass {
bool RequiresInstrumentation(const Instruction& inst, InstructionMeta& meta);

uint32_t DivideByZeroCheck(BasicBlock& block, InstructionIt* inst_it, const InstructionMeta& meta);
uint32_t PowCheck(BasicBlock& block, InstructionIt* inst_it, const InstructionMeta& meta);
uint32_t CreateFunctionCall(BasicBlock& block, InstructionIt* inst_it, const InstructionMeta& meta);

uint32_t GetLinkFunctionId(uint32_t sub_code);
Expand All @@ -62,6 +63,8 @@ class SanitizerPass : public Pass {

// Function IDs to link in
uint32_t link_function_ids_[glsl::kErrorSubCodeSanitizerCount];

uint32_t glsl_std450_id_ = 0; // GLSL.std.450
};

} // namespace spirv
Expand Down
36 changes: 32 additions & 4 deletions layers/gpuav/spirv/type_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -482,12 +482,16 @@ const Constant& TypeManager::AddConstant(std::unique_ptr<Instruction> new_inst,

if (inst->Opcode() == spv::OpConstant) {
if (type.inst_.Opcode() == spv::OpTypeInt && type.inst_.Word(2) == 32) {
int_32bit_constants_.push_back(new_constant);
} else if (type.inst_.Opcode() == spv::OpTypeFloat && type.inst_.Word(2) == 32) {
float_32bit_constants_.push_back(new_constant);
int_32bit_constants_.emplace_back(new_constant);
} else if (type.inst_.Opcode() == spv::OpTypeFloat) {
if (type.inst_.Word(2) == 16) {
float_16bit_constants_.emplace_back(new_constant);
} else if (type.inst_.Word(2) == 32) {
float_32bit_constants_.emplace_back(new_constant);
}
}
} else if (inst->Opcode() == spv::OpConstantNull) {
null_constants_.push_back(new_constant);
null_constants_.emplace_back(new_constant);
}

return *new_constant;
Expand All @@ -502,6 +506,15 @@ const Constant* TypeManager::FindConstantInt32(uint32_t type_id, uint32_t value)
return nullptr;
}

const Constant* TypeManager::FindConstantFloat16(uint32_t type_id, uint32_t value) const {
for (const auto constant : float_16bit_constants_) {
if (constant->type_.Id() == type_id && value == constant->inst_.Word(3)) {
return constant;
}
}
return nullptr;
}

const Constant* TypeManager::FindConstantFloat32(uint32_t type_id, uint32_t value) const {
for (const auto constant : float_32bit_constants_) {
if (constant->type_.Id() == type_id && value == constant->inst_.Word(3)) {
Expand Down Expand Up @@ -561,6 +574,21 @@ const Constant& TypeManager::GetConstantOneUint32() {
return *uint_32bit_one_constants_;
}

// It is common to use float16(0) as a default, so having it cached is helpful
const Constant& TypeManager::GetConstantZeroFloat16() {
if (!float_16bit_zero_constants_) {
const Type& float_16_type = GetTypeFloat(16);
float_16bit_zero_constants_ = FindConstantFloat16(float_16_type.Id(), 0);
if (!float_16bit_zero_constants_) {
const uint32_t constant_id = module_.TakeNextId();
auto new_inst = std::make_unique<Instruction>(4, spv::OpConstant);
new_inst->Fill({float_16_type.Id(), constant_id, 0});
float_16bit_zero_constants_ = &AddConstant(std::move(new_inst), float_16_type);
}
}
return *float_16bit_zero_constants_;
}

// It is common to use float(0) as a default, so having it cached is helpful
const Constant& TypeManager::GetConstantZeroFloat32() {
if (!float_32bit_zero_constants_) {
Expand Down
5 changes: 5 additions & 0 deletions layers/gpuav/spirv/type_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ struct Type {
bool IsArray() const;
bool IsSignedInt() const;
bool IsIVec3(const TypeManager& type_manager) const;
// If returns 0, means it is a scalar
uint32_t VectorSize() const;
// 64-bit floats/int take up 2 dwords
bool Is64Bit() const;
Expand Down Expand Up @@ -145,12 +146,14 @@ class TypeManager {
const Constant& AddConstant(std::unique_ptr<Instruction> new_inst, const Type& type);
const Constant* FindConstantById(uint32_t id) const;
const Constant* FindConstantInt32(uint32_t type_id, uint32_t value) const;
const Constant* FindConstantFloat16(uint32_t type_id, uint32_t value) const;
const Constant* FindConstantFloat32(uint32_t type_id, uint32_t value) const;
// most constants are uint
const Constant& CreateConstantUInt32(uint32_t value);
const Constant& GetConstantUInt32(uint32_t value);
const Constant& GetConstantZeroUint32();
const Constant& GetConstantOneUint32();
const Constant& GetConstantZeroFloat16();
const Constant& GetConstantZeroFloat32();
const Constant& GetConstantZeroVec3();
const Constant& GetConstantZeroUvec4();
Expand Down Expand Up @@ -196,9 +199,11 @@ class TypeManager {
std::vector<const Type*> linking_struct_types_;

std::vector<const Constant*> int_32bit_constants_;
std::vector<const Constant*> float_16bit_constants_;
std::vector<const Constant*> float_32bit_constants_;
const Constant* uint_32bit_zero_constants_ = nullptr;
const Constant* uint_32bit_one_constants_ = nullptr;
const Constant* float_16bit_zero_constants_ = nullptr;
const Constant* float_32bit_zero_constants_ = nullptr;
const Constant* vec3_zero_constants_ = nullptr;
const Constant* uvec4_zero_constants_ = nullptr;
Expand Down
Loading