Updated spirv-cross.

This commit is contained in:
Бранимир Караџић
2021-11-07 18:07:27 -08:00
parent c48d6f41d3
commit edf8e865b2
7 changed files with 352 additions and 46 deletions

View File

@@ -181,6 +181,30 @@ bool Compiler::block_is_pure(const SPIRBlock &block)
// This is a global side effect of the function.
return false;
case OpExtInst:
{
uint32_t extension_set = ops[2];
if (get<SPIRExtension>(extension_set).ext == SPIRExtension::GLSL)
{
auto op_450 = static_cast<GLSLstd450>(ops[3]);
switch (op_450)
{
case GLSLstd450Modf:
case GLSLstd450Frexp:
{
auto &type = expression_type(ops[5]);
if (type.storage != StorageClassFunction)
return false;
break;
}
default:
break;
}
}
break;
}
default:
break;
}
@@ -716,6 +740,15 @@ bool Compiler::InterfaceVariableAccessHandler::handle(Op opcode, const uint32_t
break;
}
case GLSLstd450Modf:
case GLSLstd450Fract:
{
auto *var = compiler.maybe_get<SPIRVariable>(args[5]);
if (var && storage_class_is_interface(var->storage))
variables.insert(args[5]);
break;
}
default:
break;
}
@@ -1729,10 +1762,22 @@ size_t Compiler::get_declared_struct_size(const SPIRType &type) const
if (type.member_types.empty())
SPIRV_CROSS_THROW("Declared struct in block cannot be empty.");
uint32_t last = uint32_t(type.member_types.size() - 1);
size_t offset = type_struct_member_offset(type, last);
size_t size = get_declared_struct_member_size(type, last);
return offset + size;
// Offsets can be declared out of order, so we need to deduce the actual size
// based on last member instead.
uint32_t member_index = 0;
size_t highest_offset = 0;
for (uint32_t i = 0; i < uint32_t(type.member_types.size()); i++)
{
size_t offset = type_struct_member_offset(type, i);
if (offset > highest_offset)
{
highest_offset = offset;
member_index = i;
}
}
size_t size = get_declared_struct_member_size(type, member_index);
return highest_offset + size;
}
size_t Compiler::get_declared_struct_size_runtime_array(const SPIRType &type, size_t array_size) const
@@ -3278,6 +3323,33 @@ bool Compiler::AnalyzeVariableScopeAccessHandler::handle(spv::Op op, const uint3
for (uint32_t i = 4; i < length; i++)
notify_variable_access(args[i], current_block->self);
notify_variable_access(args[1], current_block->self);
uint32_t extension_set = args[2];
if (compiler.get<SPIRExtension>(extension_set).ext == SPIRExtension::GLSL)
{
auto op_450 = static_cast<GLSLstd450>(args[3]);
switch (op_450)
{
case GLSLstd450Modf:
case GLSLstd450Frexp:
{
uint32_t ptr = args[5];
auto *var = compiler.maybe_get_backing_variable(ptr);
if (var)
{
accessed_variables_to_block[var->self].insert(current_block->self);
if (var->self == ptr)
complete_write_variables_to_block[var->self].insert(current_block->self);
else
partial_write_variables_to_block[var->self].insert(current_block->self);
}
break;
}
default:
break;
}
}
break;
}
@@ -4677,31 +4749,181 @@ Compiler::PhysicalStorageBufferPointerHandler::PhysicalStorageBufferPointerHandl
{
}
bool Compiler::PhysicalStorageBufferPointerHandler::handle(Op op, const uint32_t *args, uint32_t)
Compiler::PhysicalBlockMeta *Compiler::PhysicalStorageBufferPointerHandler::find_block_meta(uint32_t id) const
{
if (op == OpConvertUToPtr || op == OpBitcast)
auto chain_itr = access_chain_to_physical_block.find(id);
if (chain_itr != access_chain_to_physical_block.end())
return chain_itr->second;
else
return nullptr;
}
void Compiler::PhysicalStorageBufferPointerHandler::mark_aligned_access(uint32_t id, const uint32_t *args, uint32_t length)
{
uint32_t mask = *args;
args++;
length--;
if (length && (mask & MemoryAccessVolatileMask) != 0)
{
auto &type = compiler.get<SPIRType>(args[0]);
if (type.storage == StorageClassPhysicalStorageBufferEXT && type.pointer && type.pointer_depth == 1)
args++;
length--;
}
if (length && (mask & MemoryAccessAlignedMask) != 0)
{
uint32_t alignment = *args;
auto *meta = find_block_meta(id);
// This makes the assumption that the application does not rely on insane edge cases like:
// Bind buffer with ADDR = 8, use block offset of 8 bytes, load/store with 16 byte alignment.
// If we emit the buffer with alignment = 16 here, the first element at offset = 0 should
// actually have alignment of 8 bytes, but this is too theoretical and awkward to support.
// We could potentially keep track of any offset in the access chain, but it's
// practically impossible for high level compilers to emit code like that,
// so deducing overall alignment requirement based on maximum observed Alignment value is probably fine.
if (meta && alignment > meta->alignment)
meta->alignment = alignment;
}
}
bool Compiler::PhysicalStorageBufferPointerHandler::type_is_bda_block_entry(uint32_t type_id) const
{
auto &type = compiler.get<SPIRType>(type_id);
return type.storage == StorageClassPhysicalStorageBufferEXT && type.pointer &&
type.pointer_depth == 1 && !compiler.type_is_array_of_pointers(type);
}
uint32_t Compiler::PhysicalStorageBufferPointerHandler::get_minimum_scalar_alignment(const SPIRType &type) const
{
if (type.storage == spv::StorageClassPhysicalStorageBufferEXT)
return 8;
else if (type.basetype == SPIRType::Struct)
{
uint32_t alignment = 0;
for (auto &member_type : type.member_types)
{
// If we need to cast to a pointer type which is not a block, we might need to synthesize ourselves
// a block type which wraps this POD type.
if (type.basetype != SPIRType::Struct)
types.insert(args[0]);
uint32_t member_align = get_minimum_scalar_alignment(compiler.get<SPIRType>(member_type));
if (member_align > alignment)
alignment = member_align;
}
return alignment;
}
else
return type.width / 8;
}
void Compiler::PhysicalStorageBufferPointerHandler::setup_meta_chain(uint32_t type_id, uint32_t var_id)
{
if (type_is_bda_block_entry(type_id))
{
auto &meta = physical_block_type_meta[type_id];
access_chain_to_physical_block[var_id] = &meta;
auto &type = compiler.get<SPIRType>(type_id);
if (type.basetype != SPIRType::Struct)
non_block_types.insert(type_id);
if (meta.alignment == 0)
meta.alignment = get_minimum_scalar_alignment(compiler.get_pointee_type(type));
}
}
bool Compiler::PhysicalStorageBufferPointerHandler::handle(Op op, const uint32_t *args, uint32_t length)
{
// When a BDA pointer comes to life, we need to keep a mapping of SSA ID -> type ID for the pointer type.
// For every load and store, we'll need to be able to look up the type ID being accessed and mark any alignment
// requirements.
switch (op)
{
case OpConvertUToPtr:
case OpBitcast:
case OpCompositeExtract:
// Extract can begin a new chain if we had a struct or array of pointers as input.
// We don't begin chains before we have a pure scalar pointer.
setup_meta_chain(args[0], args[1]);
break;
case OpAccessChain:
case OpInBoundsAccessChain:
case OpPtrAccessChain:
case OpCopyObject:
{
auto itr = access_chain_to_physical_block.find(args[2]);
if (itr != access_chain_to_physical_block.end())
access_chain_to_physical_block[args[1]] = itr->second;
break;
}
case OpLoad:
{
setup_meta_chain(args[0], args[1]);
if (length >= 4)
mark_aligned_access(args[2], args + 3, length - 3);
break;
}
case OpStore:
{
if (length >= 3)
mark_aligned_access(args[0], args + 2, length - 2);
break;
}
default:
break;
}
return true;
}
uint32_t Compiler::PhysicalStorageBufferPointerHandler::get_base_non_block_type_id(uint32_t type_id) const
{
auto *type = &compiler.get<SPIRType>(type_id);
while (type->pointer &&
type->storage == StorageClassPhysicalStorageBufferEXT &&
!type_is_bda_block_entry(type_id))
{
type_id = type->parent_type;
type = &compiler.get<SPIRType>(type_id);
}
assert(type_is_bda_block_entry(type_id));
return type_id;
}
void Compiler::PhysicalStorageBufferPointerHandler::analyze_non_block_types_from_block(const SPIRType &type)
{
for (auto &member : type.member_types)
{
auto &subtype = compiler.get<SPIRType>(member);
if (subtype.basetype != SPIRType::Struct && subtype.pointer &&
subtype.storage == spv::StorageClassPhysicalStorageBufferEXT)
{
non_block_types.insert(get_base_non_block_type_id(member));
}
else if (subtype.basetype == SPIRType::Struct && !subtype.pointer)
analyze_non_block_types_from_block(subtype);
}
}
void Compiler::analyze_non_block_pointer_types()
{
PhysicalStorageBufferPointerHandler handler(*this);
traverse_all_reachable_opcodes(get<SPIRFunction>(ir.default_entry_point), handler);
physical_storage_non_block_pointer_types.reserve(handler.types.size());
for (auto type : handler.types)
// Analyze any block declaration we have to make. It might contain
// physical pointers to POD types which we never used, and thus never added to the list.
// We'll need to add those pointer types to the set of types we declare.
ir.for_each_typed_id<SPIRType>([&](uint32_t, SPIRType &type) {
if (has_decoration(type.self, DecorationBlock) || has_decoration(type.self, DecorationBufferBlock))
handler.analyze_non_block_types_from_block(type);
});
physical_storage_non_block_pointer_types.reserve(handler.non_block_types.size());
for (auto type : handler.non_block_types)
physical_storage_non_block_pointer_types.push_back(type);
sort(begin(physical_storage_non_block_pointer_types), end(physical_storage_non_block_pointer_types));
physical_storage_type_to_alignment = move(handler.physical_block_type_meta);
}
bool Compiler::InterlockedResourceAccessPrepassHandler::handle(Op op, const uint32_t *, uint32_t)

View File

@@ -1010,15 +1010,32 @@ protected:
uint32_t write_count = 0;
};
struct PhysicalBlockMeta
{
uint32_t alignment = 0;
};
struct PhysicalStorageBufferPointerHandler : OpcodeHandler
{
explicit PhysicalStorageBufferPointerHandler(Compiler &compiler_);
bool handle(spv::Op op, const uint32_t *args, uint32_t length) override;
Compiler &compiler;
std::unordered_set<uint32_t> types;
std::unordered_set<uint32_t> non_block_types;
std::unordered_map<uint32_t, PhysicalBlockMeta> physical_block_type_meta;
std::unordered_map<uint32_t, PhysicalBlockMeta *> access_chain_to_physical_block;
void mark_aligned_access(uint32_t id, const uint32_t *args, uint32_t length);
PhysicalBlockMeta *find_block_meta(uint32_t id) const;
bool type_is_bda_block_entry(uint32_t type_id) const;
void setup_meta_chain(uint32_t type_id, uint32_t var_id);
uint32_t get_minimum_scalar_alignment(const SPIRType &type) const;
void analyze_non_block_types_from_block(const SPIRType &type);
uint32_t get_base_non_block_type_id(uint32_t type_id) const;
};
void analyze_non_block_pointer_types();
SmallVector<uint32_t> physical_storage_non_block_pointer_types;
std::unordered_map<uint32_t, PhysicalBlockMeta> physical_storage_type_to_alignment;
void analyze_variable_scope(SPIRFunction &function, AnalyzeVariableScopeAccessHandler &handler);
void find_function_local_luts(SPIRFunction &function, const AnalyzeVariableScopeAccessHandler &handler,

View File

@@ -327,8 +327,8 @@ public:
void reserve(size_t count) SPIRV_CROSS_NOEXCEPT
{
if ((count > std::numeric_limits<size_t>::max() / sizeof(T)) ||
(count > std::numeric_limits<size_t>::max() / 2))
if ((count > (std::numeric_limits<size_t>::max)() / sizeof(T)) ||
(count > (std::numeric_limits<size_t>::max)() / 2))
{
// Only way this should ever happen is with garbage input, terminate.
std::terminate();

View File

@@ -2171,8 +2171,9 @@ void CompilerGLSL::emit_buffer_block_legacy(const SPIRVariable &var)
statement("");
}
void CompilerGLSL::emit_buffer_reference_block(SPIRType &type, bool forward_declaration)
void CompilerGLSL::emit_buffer_reference_block(uint32_t type_id, bool forward_declaration)
{
auto &type = get<SPIRType>(type_id);
string buffer_name;
if (forward_declaration)
@@ -2215,8 +2216,19 @@ void CompilerGLSL::emit_buffer_reference_block(SPIRType &type, bool forward_decl
if (!forward_declaration)
{
auto itr = physical_storage_type_to_alignment.find(type_id);
uint32_t alignment = 0;
if (itr != physical_storage_type_to_alignment.end())
alignment = itr->second.alignment;
if (type.basetype == SPIRType::Struct)
{
SmallVector<std::string> attributes;
attributes.push_back("buffer_reference");
if (alignment)
attributes.push_back(join("buffer_reference_align = ", alignment));
attributes.push_back(buffer_to_packing_standard(type, true));
auto flags = ir.get_buffer_block_type_flags(type);
string decorations;
if (flags.get(DecorationRestrict))
@@ -2227,9 +2239,11 @@ void CompilerGLSL::emit_buffer_reference_block(SPIRType &type, bool forward_decl
decorations += " writeonly";
if (flags.get(DecorationNonWritable))
decorations += " readonly";
statement("layout(buffer_reference, ", buffer_to_packing_standard(type, true),
")", decorations, " buffer ", buffer_name);
statement("layout(", merge(attributes), ")", decorations, " buffer ", buffer_name);
}
else if (alignment)
statement("layout(buffer_reference, buffer_reference_align = ", alignment, ") buffer ", buffer_name);
else
statement("layout(buffer_reference) buffer ", buffer_name);
@@ -3447,28 +3461,28 @@ void CompilerGLSL::emit_resources()
{
for (auto type : physical_storage_non_block_pointer_types)
{
emit_buffer_reference_block(get<SPIRType>(type), false);
emit_buffer_reference_block(type, false);
}
// Output buffer reference blocks.
// Do this in two stages, one with forward declaration,
// and one without. Buffer reference blocks can reference themselves
// to support things like linked lists.
ir.for_each_typed_id<SPIRType>([&](uint32_t, SPIRType &type) {
bool has_block_flags = has_decoration(type.self, DecorationBlock);
if (has_block_flags && type.pointer && type.pointer_depth == 1 && !type_is_array_of_pointers(type) &&
ir.for_each_typed_id<SPIRType>([&](uint32_t self, SPIRType &type) {
if (type.basetype == SPIRType::Struct && type.pointer &&
type.pointer_depth == 1 && !type_is_array_of_pointers(type) &&
type.storage == StorageClassPhysicalStorageBufferEXT)
{
emit_buffer_reference_block(type, true);
emit_buffer_reference_block(self, true);
}
});
ir.for_each_typed_id<SPIRType>([&](uint32_t, SPIRType &type) {
bool has_block_flags = has_decoration(type.self, DecorationBlock);
if (has_block_flags && type.pointer && type.pointer_depth == 1 && !type_is_array_of_pointers(type) &&
ir.for_each_typed_id<SPIRType>([&](uint32_t self, SPIRType &type) {
if (type.basetype == SPIRType::Struct &&
type.pointer && type.pointer_depth == 1 && !type_is_array_of_pointers(type) &&
type.storage == StorageClassPhysicalStorageBufferEXT)
{
emit_buffer_reference_block(type, false);
emit_buffer_reference_block(self, false);
}
});
}

View File

@@ -592,7 +592,7 @@ protected:
void emit_resources();
void emit_extension_workarounds(spv::ExecutionModel model);
void emit_buffer_block_native(const SPIRVariable &var);
void emit_buffer_reference_block(SPIRType &type, bool forward_declaration);
void emit_buffer_reference_block(uint32_t type_id, bool forward_declaration);
void emit_buffer_block_legacy(const SPIRVariable &var);
void emit_buffer_block_flattened(const SPIRVariable &type);
void fixup_implicit_builtin_block_names();

View File

@@ -1710,6 +1710,16 @@ void CompilerMSL::extract_global_variables_from_function(uint32_t func_id, std::
added_arg_ids.insert(stage_in_var_id);
break;
}
case GLSLstd450Modf:
case GLSLstd450Frexp:
{
uint32_t base_id = ops[5];
if (global_var_ids.find(base_id) != global_var_ids.end())
added_arg_ids.insert(base_id);
break;
}
default:
break;
}
@@ -2251,7 +2261,7 @@ void CompilerMSL::add_plain_variable_to_interface_block(StorageClass storage, co
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationComponent, comp);
mark_location_as_used_by_shader(locn, get<SPIRType>(type_id), storage);
}
else if (is_builtin && is_tessellation_shader() && inputs_by_builtin.count(builtin))
else if (is_builtin && is_tessellation_shader() && storage == StorageClassInput && inputs_by_builtin.count(builtin))
{
uint32_t locn = inputs_by_builtin[builtin].location;
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
@@ -2416,7 +2426,7 @@ void CompilerMSL::add_composite_variable_to_interface_block(StorageClass storage
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationComponent, comp);
mark_location_as_used_by_shader(locn, *usable_type, storage);
}
else if (is_builtin && is_tessellation_shader() && inputs_by_builtin.count(builtin))
else if (is_builtin && is_tessellation_shader() && storage == StorageClassInput && inputs_by_builtin.count(builtin))
{
uint32_t locn = inputs_by_builtin[builtin].location + i;
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
@@ -2589,7 +2599,7 @@ void CompilerMSL::add_composite_member_variable_to_interface_block(StorageClass
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
mark_location_as_used_by_shader(locn, *usable_type, storage);
}
else if (is_builtin && is_tessellation_shader() && inputs_by_builtin.count(builtin))
else if (is_builtin && is_tessellation_shader() && storage == StorageClassInput && inputs_by_builtin.count(builtin))
{
uint32_t locn = inputs_by_builtin[builtin].location + i;
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
@@ -2780,7 +2790,7 @@ void CompilerMSL::add_plain_member_variable_to_interface_block(StorageClass stor
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
mark_location_as_used_by_shader(locn, get<SPIRType>(mbr_type_id), storage);
}
else if (is_builtin && is_tessellation_shader() && inputs_by_builtin.count(builtin))
else if (is_builtin && is_tessellation_shader() && storage == StorageClassInput && inputs_by_builtin.count(builtin))
{
uint32_t locn = 0;
auto builtin_itr = inputs_by_builtin.find(builtin);
@@ -4171,11 +4181,7 @@ void CompilerMSL::ensure_member_packing_rules_msl(SPIRType &ib_type, uint32_t in
set_decoration(type_id, DecorationArrayStride, array_stride);
// Remove packed_ for vectors of size 1, 2 and 4.
if (has_extended_decoration(ib_type.self, SPIRVCrossDecorationPhysicalTypePacked))
SPIRV_CROSS_THROW("Unable to remove packed decoration as entire struct must be fully packed. Do not mix "
"scalar and std140 layout rules.");
else
unset_extended_member_decoration(ib_type.self, index, SPIRVCrossDecorationPhysicalTypePacked);
unset_extended_member_decoration(ib_type.self, index, SPIRVCrossDecorationPhysicalTypePacked);
}
else if (is_matrix(mbr_type))
{
@@ -4202,11 +4208,7 @@ void CompilerMSL::ensure_member_packing_rules_msl(SPIRType &ib_type, uint32_t in
set_extended_member_decoration(ib_type.self, index, SPIRVCrossDecorationPhysicalTypeID, type_id);
// Remove packed_ for vectors of size 1, 2 and 4.
if (has_extended_decoration(ib_type.self, SPIRVCrossDecorationPhysicalTypePacked))
SPIRV_CROSS_THROW("Unable to remove packed decoration as entire struct must be fully packed. Do not mix "
"scalar and std140 layout rules.");
else
unset_extended_member_decoration(ib_type.self, index, SPIRVCrossDecorationPhysicalTypePacked);
unset_extended_member_decoration(ib_type.self, index, SPIRVCrossDecorationPhysicalTypePacked);
}
else
SPIRV_CROSS_THROW("Found a buffer packing case which we cannot represent in MSL.");
@@ -9171,8 +9173,16 @@ void CompilerMSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop,
case GLSLstd450Frexp:
{
// Special case. If the variable is a scalar access chain, we cannot use it directly. We have to emit a temporary.
// Another special case is if the variable is in a storage class which is not thread.
auto *ptr = maybe_get<SPIRExpression>(args[1]);
if (ptr && ptr->access_chain && is_scalar(expression_type(args[1])))
auto &type = expression_type(args[1]);
bool is_thread_storage = storage_class_array_is_thread(type.storage);
if (type.storage == StorageClassOutput && capture_output_to_buffer)
is_thread_storage = false;
if (!is_thread_storage ||
(ptr && ptr->access_chain && is_scalar(expression_type(args[1]))))
{
register_call_out_argument(args[1]);
forced_temporaries.insert(id);
@@ -9183,7 +9193,7 @@ void CompilerMSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop,
if (!tmp_id)
tmp_id = ir.increase_bound_by(1);
uint32_t tmp_type_id = get_pointee_type_id(ptr->expression_type);
uint32_t tmp_type_id = get_pointee_type_id(expression_type_id(args[1]));
emit_uninitialized_temporary_expression(tmp_type_id, tmp_id);
emit_binary_func_op(result_type, id, args[0], tmp_id, eop == GLSLstd450Modf ? "modf" : "frexp");
statement(to_expression(args[1]), " = ", to_expression(tmp_id), ";");

View File

@@ -961,6 +961,49 @@ void Parser::parse(const Instruction &instruction)
current_block->false_block = ops[2];
current_block->terminator = SPIRBlock::Select;
if (current_block->true_block == current_block->false_block)
{
// Bogus conditional, translate to a direct branch.
// Avoids some ugly edge cases later when analyzing CFGs.
// There are some super jank cases where the merge block is different from the true/false,
// and later branches can "break" out of the selection construct this way.
// This is complete nonsense, but CTS hits this case.
// In this scenario, we should see the selection construct as more of a Switch with one default case.
// The problem here is that this breaks any attempt to break out of outer switch statements,
// but it's theoretically solvable if this ever comes up using the ladder breaking system ...
if (current_block->true_block != current_block->next_block &&
current_block->merge == SPIRBlock::MergeSelection)
{
uint32_t ids = ir.increase_bound_by(2);
SPIRType type;
type.basetype = SPIRType::Int;
type.width = 32;
set<SPIRType>(ids, type);
auto &c = set<SPIRConstant>(ids + 1, ids);
current_block->condition = c.self;
current_block->default_block = current_block->true_block;
current_block->terminator = SPIRBlock::MultiSelect;
ir.block_meta[current_block->next_block] &= ~ParsedIR::BLOCK_META_SELECTION_MERGE_BIT;
ir.block_meta[current_block->next_block] |= ParsedIR::BLOCK_META_MULTISELECT_MERGE_BIT;
}
else
{
ir.block_meta[current_block->next_block] &= ~ParsedIR::BLOCK_META_SELECTION_MERGE_BIT;
current_block->next_block = current_block->true_block;
current_block->condition = 0;
current_block->true_block = 0;
current_block->false_block = 0;
current_block->merge_block = 0;
current_block->merge = SPIRBlock::MergeNone;
current_block->terminator = SPIRBlock::Direct;
}
}
current_block = nullptr;
break;
}