mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-12-12 11:40:14 +00:00
3ab0d22608
* Support SPV_KHR_untyped_pointers Covers: - assembler - disassembler - validator fix copyright Validate OpTypeUntypedPointerKHR * Disallow an untyped pointer in a typed pointer * Validate capability requirements for untyped pointer * Allow duplicate untyped pointer declarations Add round trip tests Validate OpUntypedVariableKHR Validate untyped access chains * Add a test for opcodes that generate untyped pointers * simplify some checks for operands needing types * validate OpUnypedAccessChainKHR, OpUntypedInBoundsAccessChainKHR, OpUntypedPtrAccessChainKHR, OpUntypedInBoundsPtrAccessChainKHR Unify variable validation Validate OpCopyMemorySized * Fix some opcode tests to accound for untyped pointers * Add validation for OpCopyMemorySized for shaders and untyped pointers * fix up tests Validate pointer comparisons and bitcast * Update more helpers * Fix entry validation to allow OpUntypedVariableKHR * Validate OpPtrEqual, OpPtrNotEqual and OpPtrDiff * Validate OpBitcast Validate atomics and untyped pointers Make interface variable validation aware of untyped pointers * Check OpUntypedVariableKHR in interface validation More untyped pointer validation * Validate interfaces more thoroughly * Validate layouts for untyped pointer uses * Improve capability checks for vulkan with OpTypeUntypedPointerKHR * workgroup member explicit layout validation updates More validation * validate function arguments and parameters * handle untyped pointer and variable in more places Add a friendly assembly name for untyped pointers Update OpCopyMemory validation and tests Fix test for token update Fixes for validation * Allow typed pointers to contain untyped pointers * Fix decoration validation * add untyped pointer as a case for size and alignments Fix interface validation * Grabbed the wrong storage class operand for untyped variables * Add ability to specify assembler options in validation tests Add passthrough validation for OpUntypedArrayLengthKHR More validation of untyped pointers * Validate OpUntypedArrayLengthKHR * Validate layout for OpLoad, OpStore, and OpUntypedArrayLengthKHR Validation support for cooperative matrix and untyped pointers * Allow untyped pointers for cooperative matrix KHR load and store Updates to match spec * Remove extra capability references * Swap untyped variable data type and storage class operands * update validation of variables * update deps --------- Co-authored-by: David Neto <dneto@google.com>
338 lines
11 KiB
C++
338 lines
11 KiB
C++
// Copyright (c) 2016 Google 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 "source/name_mapper.h"
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <iterator>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
|
|
#include "source/binary.h"
|
|
#include "source/latest_version_spirv_header.h"
|
|
#include "source/parsed_operand.h"
|
|
#include "spirv-tools/libspirv.h"
|
|
|
|
namespace spvtools {
|
|
namespace {
|
|
|
|
// Converts a uint32_t to its string decimal representation.
|
|
std::string to_string(uint32_t id) {
|
|
// Use stringstream, since some versions of Android compilers lack
|
|
// std::to_string.
|
|
std::stringstream os;
|
|
os << id;
|
|
return os.str();
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
NameMapper GetTrivialNameMapper() { return to_string; }
|
|
|
|
FriendlyNameMapper::FriendlyNameMapper(const spv_const_context context,
|
|
const uint32_t* code,
|
|
const size_t wordCount)
|
|
: grammar_(AssemblyGrammar(context)) {
|
|
spv_diagnostic diag = nullptr;
|
|
// We don't care if the parse fails.
|
|
spvBinaryParse(context, this, code, wordCount, nullptr,
|
|
ParseInstructionForwarder, &diag);
|
|
spvDiagnosticDestroy(diag);
|
|
}
|
|
|
|
std::string FriendlyNameMapper::NameForId(uint32_t id) {
|
|
auto iter = name_for_id_.find(id);
|
|
if (iter == name_for_id_.end()) {
|
|
// It must have been an invalid module, so just return a trivial mapping.
|
|
// We don't care about uniqueness.
|
|
return to_string(id);
|
|
} else {
|
|
return iter->second;
|
|
}
|
|
}
|
|
|
|
std::string FriendlyNameMapper::Sanitize(const std::string& suggested_name) {
|
|
if (suggested_name.empty()) return "_";
|
|
// Otherwise, replace invalid characters by '_'.
|
|
std::string result;
|
|
std::string valid =
|
|
"abcdefghijklmnopqrstuvwxyz"
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
"_0123456789";
|
|
std::transform(suggested_name.begin(), suggested_name.end(),
|
|
std::back_inserter(result), [&valid](const char c) {
|
|
return (std::string::npos == valid.find(c)) ? '_' : c;
|
|
});
|
|
return result;
|
|
}
|
|
|
|
void FriendlyNameMapper::SaveName(uint32_t id,
|
|
const std::string& suggested_name) {
|
|
if (name_for_id_.find(id) != name_for_id_.end()) return;
|
|
|
|
const std::string sanitized_suggested_name = Sanitize(suggested_name);
|
|
std::string name = sanitized_suggested_name;
|
|
auto inserted = used_names_.insert(name);
|
|
if (!inserted.second) {
|
|
const std::string base_name = sanitized_suggested_name + "_";
|
|
for (uint32_t index = 0; !inserted.second; ++index) {
|
|
name = base_name + to_string(index);
|
|
inserted = used_names_.insert(name);
|
|
}
|
|
}
|
|
name_for_id_[id] = name;
|
|
}
|
|
|
|
void FriendlyNameMapper::SaveBuiltInName(uint32_t target_id,
|
|
uint32_t built_in) {
|
|
#define GLCASE(name) \
|
|
case spv::BuiltIn::name: \
|
|
SaveName(target_id, "gl_" #name); \
|
|
return;
|
|
#define GLCASE2(name, suggested) \
|
|
case spv::BuiltIn::name: \
|
|
SaveName(target_id, "gl_" #suggested); \
|
|
return;
|
|
#define CASE(name) \
|
|
case spv::BuiltIn::name: \
|
|
SaveName(target_id, #name); \
|
|
return;
|
|
switch (spv::BuiltIn(built_in)) {
|
|
GLCASE(Position)
|
|
GLCASE(PointSize)
|
|
GLCASE(ClipDistance)
|
|
GLCASE(CullDistance)
|
|
GLCASE2(VertexId, VertexID)
|
|
GLCASE2(InstanceId, InstanceID)
|
|
GLCASE2(PrimitiveId, PrimitiveID)
|
|
GLCASE2(InvocationId, InvocationID)
|
|
GLCASE(Layer)
|
|
GLCASE(ViewportIndex)
|
|
GLCASE(TessLevelOuter)
|
|
GLCASE(TessLevelInner)
|
|
GLCASE(TessCoord)
|
|
GLCASE(PatchVertices)
|
|
GLCASE(FragCoord)
|
|
GLCASE(PointCoord)
|
|
GLCASE(FrontFacing)
|
|
GLCASE2(SampleId, SampleID)
|
|
GLCASE(SamplePosition)
|
|
GLCASE(SampleMask)
|
|
GLCASE(FragDepth)
|
|
GLCASE(HelperInvocation)
|
|
GLCASE2(NumWorkgroups, NumWorkGroups)
|
|
GLCASE2(WorkgroupSize, WorkGroupSize)
|
|
GLCASE2(WorkgroupId, WorkGroupID)
|
|
GLCASE2(LocalInvocationId, LocalInvocationID)
|
|
GLCASE2(GlobalInvocationId, GlobalInvocationID)
|
|
GLCASE(LocalInvocationIndex)
|
|
CASE(WorkDim)
|
|
CASE(GlobalSize)
|
|
CASE(EnqueuedWorkgroupSize)
|
|
CASE(GlobalOffset)
|
|
CASE(GlobalLinearId)
|
|
CASE(SubgroupSize)
|
|
CASE(SubgroupMaxSize)
|
|
CASE(NumSubgroups)
|
|
CASE(NumEnqueuedSubgroups)
|
|
CASE(SubgroupId)
|
|
CASE(SubgroupLocalInvocationId)
|
|
GLCASE(VertexIndex)
|
|
GLCASE(InstanceIndex)
|
|
GLCASE(BaseInstance)
|
|
CASE(SubgroupEqMaskKHR)
|
|
CASE(SubgroupGeMaskKHR)
|
|
CASE(SubgroupGtMaskKHR)
|
|
CASE(SubgroupLeMaskKHR)
|
|
CASE(SubgroupLtMaskKHR)
|
|
default:
|
|
break;
|
|
}
|
|
#undef GLCASE
|
|
#undef GLCASE2
|
|
#undef CASE
|
|
}
|
|
|
|
spv_result_t FriendlyNameMapper::ParseInstruction(
|
|
const spv_parsed_instruction_t& inst) {
|
|
const auto result_id = inst.result_id;
|
|
switch (spv::Op(inst.opcode)) {
|
|
case spv::Op::OpName:
|
|
SaveName(inst.words[1], spvDecodeLiteralStringOperand(inst, 1));
|
|
break;
|
|
case spv::Op::OpDecorate:
|
|
// Decorations come after OpName. So OpName will take precedence over
|
|
// decorations.
|
|
//
|
|
// In theory, we should also handle OpGroupDecorate. But that's unlikely
|
|
// to occur.
|
|
if (spv::Decoration(inst.words[2]) == spv::Decoration::BuiltIn) {
|
|
assert(inst.num_words > 3);
|
|
SaveBuiltInName(inst.words[1], inst.words[3]);
|
|
}
|
|
break;
|
|
case spv::Op::OpTypeVoid:
|
|
SaveName(result_id, "void");
|
|
break;
|
|
case spv::Op::OpTypeBool:
|
|
SaveName(result_id, "bool");
|
|
break;
|
|
case spv::Op::OpTypeInt: {
|
|
std::string signedness;
|
|
std::string root;
|
|
const auto bit_width = inst.words[2];
|
|
switch (bit_width) {
|
|
case 8:
|
|
root = "char";
|
|
break;
|
|
case 16:
|
|
root = "short";
|
|
break;
|
|
case 32:
|
|
root = "int";
|
|
break;
|
|
case 64:
|
|
root = "long";
|
|
break;
|
|
default:
|
|
root = to_string(bit_width);
|
|
signedness = "i";
|
|
break;
|
|
}
|
|
if (0 == inst.words[3]) signedness = "u";
|
|
SaveName(result_id, signedness + root);
|
|
} break;
|
|
case spv::Op::OpTypeFloat: {
|
|
const auto bit_width = inst.words[2];
|
|
// TODO: Handle optional fpencoding enum once actually used.
|
|
switch (bit_width) {
|
|
case 16:
|
|
SaveName(result_id, "half");
|
|
break;
|
|
case 32:
|
|
SaveName(result_id, "float");
|
|
break;
|
|
case 64:
|
|
SaveName(result_id, "double");
|
|
break;
|
|
default:
|
|
SaveName(result_id, std::string("fp") + to_string(bit_width));
|
|
break;
|
|
}
|
|
} break;
|
|
case spv::Op::OpTypeVector:
|
|
SaveName(result_id, std::string("v") + to_string(inst.words[3]) +
|
|
NameForId(inst.words[2]));
|
|
break;
|
|
case spv::Op::OpTypeMatrix:
|
|
SaveName(result_id, std::string("mat") + to_string(inst.words[3]) +
|
|
NameForId(inst.words[2]));
|
|
break;
|
|
case spv::Op::OpTypeArray:
|
|
SaveName(result_id, std::string("_arr_") + NameForId(inst.words[2]) +
|
|
"_" + NameForId(inst.words[3]));
|
|
break;
|
|
case spv::Op::OpTypeRuntimeArray:
|
|
SaveName(result_id,
|
|
std::string("_runtimearr_") + NameForId(inst.words[2]));
|
|
break;
|
|
case spv::Op::OpTypePointer:
|
|
SaveName(result_id, std::string("_ptr_") +
|
|
NameForEnumOperand(SPV_OPERAND_TYPE_STORAGE_CLASS,
|
|
inst.words[2]) +
|
|
"_" + NameForId(inst.words[3]));
|
|
break;
|
|
case spv::Op::OpTypeUntypedPointerKHR:
|
|
SaveName(result_id, std::string("_ptr_") +
|
|
NameForEnumOperand(SPV_OPERAND_TYPE_STORAGE_CLASS,
|
|
inst.words[2]));
|
|
break;
|
|
case spv::Op::OpTypePipe:
|
|
SaveName(result_id,
|
|
std::string("Pipe") +
|
|
NameForEnumOperand(SPV_OPERAND_TYPE_ACCESS_QUALIFIER,
|
|
inst.words[2]));
|
|
break;
|
|
case spv::Op::OpTypeEvent:
|
|
SaveName(result_id, "Event");
|
|
break;
|
|
case spv::Op::OpTypeDeviceEvent:
|
|
SaveName(result_id, "DeviceEvent");
|
|
break;
|
|
case spv::Op::OpTypeReserveId:
|
|
SaveName(result_id, "ReserveId");
|
|
break;
|
|
case spv::Op::OpTypeQueue:
|
|
SaveName(result_id, "Queue");
|
|
break;
|
|
case spv::Op::OpTypeOpaque:
|
|
SaveName(result_id, std::string("Opaque_") +
|
|
Sanitize(spvDecodeLiteralStringOperand(inst, 1)));
|
|
break;
|
|
case spv::Op::OpTypePipeStorage:
|
|
SaveName(result_id, "PipeStorage");
|
|
break;
|
|
case spv::Op::OpTypeNamedBarrier:
|
|
SaveName(result_id, "NamedBarrier");
|
|
break;
|
|
case spv::Op::OpTypeStruct:
|
|
// Structs are mapped rather simplisitically. Just indicate that they
|
|
// are a struct and then give the raw Id number.
|
|
SaveName(result_id, std::string("_struct_") + to_string(result_id));
|
|
break;
|
|
case spv::Op::OpConstantTrue:
|
|
SaveName(result_id, "true");
|
|
break;
|
|
case spv::Op::OpConstantFalse:
|
|
SaveName(result_id, "false");
|
|
break;
|
|
case spv::Op::OpConstant: {
|
|
std::ostringstream value;
|
|
EmitNumericLiteral(&value, inst, inst.operands[2]);
|
|
auto value_str = value.str();
|
|
// Use 'n' to signify negative. Other invalid characters will be mapped
|
|
// to underscore.
|
|
for (auto& c : value_str)
|
|
if (c == '-') c = 'n';
|
|
SaveName(result_id, NameForId(inst.type_id) + "_" + value_str);
|
|
} break;
|
|
default:
|
|
// If this instruction otherwise defines an Id, then save a mapping for
|
|
// it. This is needed to ensure uniqueness in there is an OpName with
|
|
// string something like "1" that might collide with this result_id.
|
|
// We should only do this if a name hasn't already been registered by some
|
|
// previous forward reference.
|
|
if (result_id && name_for_id_.find(result_id) == name_for_id_.end())
|
|
SaveName(result_id, to_string(result_id));
|
|
break;
|
|
}
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
std::string FriendlyNameMapper::NameForEnumOperand(spv_operand_type_t type,
|
|
uint32_t word) {
|
|
spv_operand_desc desc = nullptr;
|
|
if (SPV_SUCCESS == grammar_.lookupOperand(type, word, &desc)) {
|
|
return desc->name;
|
|
} else {
|
|
// Invalid input. Just give something.
|
|
return std::string("StorageClass") + to_string(word);
|
|
}
|
|
}
|
|
|
|
} // namespace spvtools
|