Updated spirv-cross.

This commit is contained in:
Бранимир Караџић
2025-12-13 11:48:32 -08:00
committed by Branimir Karadžić
parent 1a4e866800
commit 3d59e16fd7
8 changed files with 167 additions and 43 deletions

View File

@@ -742,6 +742,14 @@ bool Compiler::is_physical_pointer(const SPIRType &type) const
return type.op == OpTypePointer && type.storage == StorageClassPhysicalStorageBuffer;
}
bool Compiler::is_physical_or_buffer_pointer(const SPIRType &type) const
{
return type.op == OpTypePointer &&
(type.storage == StorageClassPhysicalStorageBuffer || type.storage == StorageClassUniform ||
type.storage == StorageClassStorageBuffer || type.storage == StorageClassWorkgroup ||
type.storage == StorageClassPushConstant);
}
bool Compiler::is_physical_pointer_to_buffer_block(const SPIRType &type) const
{
return is_physical_pointer(type) && get_pointee_type(type).self == type.parent_type &&

View File

@@ -694,6 +694,7 @@ protected:
bool is_array(const SPIRType &type) const;
bool is_pointer(const SPIRType &type) const;
bool is_physical_pointer(const SPIRType &type) const;
bool is_physical_or_buffer_pointer(const SPIRType &type) const;
bool is_physical_pointer_to_buffer_block(const SPIRType &type) const;
static bool is_runtime_size_array(const SPIRType &type);
uint32_t expression_type_id(uint32_t id) const;

View File

@@ -5051,10 +5051,10 @@ void CompilerGLSL::emit_polyfills(uint32_t polyfills, bool relaxed)
// Returns a string representation of the ID, usable as a function arg.
// Default is to simply return the expression representation fo the arg ID.
// Subclasses may override to modify the return value.
string CompilerGLSL::to_func_call_arg(const SPIRFunction::Parameter &, uint32_t id)
string CompilerGLSL::to_func_call_arg(const SPIRFunction::Parameter &arg, uint32_t id)
{
// BDA expects pointers through function interface.
if (is_physical_pointer(expression_type(id)))
if (!arg.alias_global_variable && is_physical_or_buffer_pointer(expression_type(id)))
return to_pointer_expression(id);
// Make sure that we use the name of the original variable, and not the parameter alias.
@@ -6896,6 +6896,16 @@ void CompilerGLSL::emit_uninitialized_temporary(uint32_t result_type, uint32_t r
}
}
bool CompilerGLSL::can_declare_inline_temporary(uint32_t id) const
{
if (!block_temporary_hoisting && current_continue_block && !hoisted_temporaries.count(id))
return false;
if (hoisted_temporaries.count(id))
return false;
return true;
}
string CompilerGLSL::declare_temporary(uint32_t result_type, uint32_t result_id)
{
auto &type = get<SPIRType>(result_type);
@@ -6973,6 +6983,42 @@ SPIRExpression &CompilerGLSL::emit_op(uint32_t result_type, uint32_t result_id,
}
}
void CompilerGLSL::emit_transposed_op(uint32_t result_type, uint32_t result_id, const string &rhs, bool forwarding)
{
if (forwarding && (forced_temporaries.find(result_id) == end(forced_temporaries)))
{
// Just forward it without temporary.
// If the forward is trivial, we do not force flushing to temporary for this expression.
forwarded_temporaries.insert(result_id);
auto &e = set<SPIRExpression>(result_id, rhs, result_type, true);
e.need_transpose = true;
}
else if (can_declare_inline_temporary(result_id))
{
// If expression isn't immutable, bind it to a temporary and make the new temporary immutable (they always are).
// Since the expression is transposed, we have to ensure the temporary is the transposed type.
auto &transposed_type_id = extra_sub_expressions[result_id];
if (!transposed_type_id)
{
auto dummy_type = get<SPIRType>(result_type);
std::swap(dummy_type.columns, dummy_type.vecsize);
transposed_type_id = ir.increase_bound_by(1);
set<SPIRType>(transposed_type_id, dummy_type);
}
statement(declare_temporary(transposed_type_id, result_id), rhs, ";");
auto &e = set<SPIRExpression>(result_id, to_name(result_id), result_type, true);
e.need_transpose = true;
}
else
{
// If we cannot declare the temporary because it's already been hoisted, we don't have the
// chance to override the temporary type ourselves. Just transpose() the expression.
emit_op(result_type, result_id, join("transpose(", rhs, ")"), forwarding);
}
}
void CompilerGLSL::emit_unary_op(uint32_t result_type, uint32_t result_id, uint32_t op0, const char *op)
{
bool forward = should_forward(op0);
@@ -11581,7 +11627,7 @@ bool CompilerGLSL::should_dereference(uint32_t id)
// If id is a variable but not a phi variable, we should not dereference it.
// BDA passed around as parameters are always pointers.
if (auto *var = maybe_get<SPIRVariable>(id))
return (var->parameter && is_physical_pointer(type)) || var->phi_variable;
return (var->parameter && is_physical_or_buffer_pointer(type)) || var->phi_variable;
if (auto *expr = maybe_get<SPIRExpression>(id))
{
@@ -11617,8 +11663,8 @@ bool CompilerGLSL::should_dereference(uint32_t id)
bool CompilerGLSL::should_dereference_caller_param(uint32_t id)
{
const auto &type = expression_type(id);
// BDA is always passed around as pointers.
if (is_physical_pointer(type))
// BDA is always passed around as pointers. Similarly, we need to pass variable buffer pointers as pointers.
if (is_physical_or_buffer_pointer(type))
return false;
return should_dereference(id);
@@ -13507,8 +13553,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction)
auto expr = join(enclose_expression(to_unpacked_row_major_matrix_expression(ops[3])), " * ",
enclose_expression(to_unpacked_row_major_matrix_expression(ops[2])));
bool forward = should_forward(ops[2]) && should_forward(ops[3]);
auto &e = emit_op(ops[0], ops[1], expr, forward);
e.need_transpose = true;
emit_transposed_op(ops[0], ops[1], expr, forward);
a->need_transpose = true;
b->need_transpose = true;
inherit_expression_dependencies(ops[1], ops[2]);
@@ -13531,8 +13576,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction)
auto expr = join(enclose_expression(to_unpacked_row_major_matrix_expression(ops[2])), " * ",
to_enclosed_unpacked_expression(ops[3]));
bool forward = should_forward(ops[2]) && should_forward(ops[3]);
auto &e = emit_op(ops[0], ops[1], expr, forward);
e.need_transpose = true;
emit_transposed_op(ops[0], ops[1], expr, forward);
a->need_transpose = true;
inherit_expression_dependencies(ops[1], ops[2]);
inherit_expression_dependencies(ops[1], ops[3]);

View File

@@ -763,6 +763,7 @@ protected:
bool expression_read_implies_multiple_reads(uint32_t id) const;
SPIRExpression &emit_op(uint32_t result_type, uint32_t result_id, const std::string &rhs, bool forward_rhs,
bool suppress_usage_tracking = false);
void emit_transposed_op(uint32_t result_type, uint32_t result_id, const std::string &rhs, bool forward_rhs);
void access_chain_internal_append_index(std::string &expr, uint32_t base, const SPIRType *type,
AccessChainFlags flags, bool &access_chain_is_arrayed, uint32_t index);
@@ -805,6 +806,7 @@ protected:
const char *index_to_swizzle(uint32_t index);
std::string remap_swizzle(const SPIRType &result_type, uint32_t input_components, const std::string &expr);
std::string declare_temporary(uint32_t type, uint32_t id);
bool can_declare_inline_temporary(uint32_t id) const;
void emit_uninitialized_temporary(uint32_t type, uint32_t id);
SPIRExpression &emit_uninitialized_temporary_expression(uint32_t type, uint32_t id);
virtual void append_global_func_args(const SPIRFunction &func, uint32_t index, SmallVector<std::string> &arglist);

View File

@@ -491,9 +491,9 @@ string CompilerHLSL::type_to_glsl(const SPIRType &type, uint32_t id)
case SPIRType::Double:
return join("double", type.vecsize);
case SPIRType::Int64:
return join("i64vec", type.vecsize);
return join("int64_t", type.vecsize);
case SPIRType::UInt64:
return join("u64vec", type.vecsize);
return join("uint64_t", type.vecsize);
default:
return "???";
}
@@ -1595,6 +1595,7 @@ void CompilerHLSL::replace_illegal_names()
"Texture3D", "TextureCube", "TextureCubeArray", "true", "typedef", "triangle",
"triangleadj", "TriangleStream", "uint", "uniform", "unorm", "unsigned",
"vector", "vertexfragment", "VertexShader", "vertices", "void", "volatile", "while",
"signed",
};
CompilerGLSL::replace_illegal_names(keywords);
@@ -1709,9 +1710,11 @@ void CompilerHLSL::emit_resources()
ir.for_each_typed_id<SPIRVariable>([&](uint32_t, SPIRVariable &var) {
auto &type = this->get<SPIRType>(var.basetype);
bool is_hidden = is_hidden_io_variable(var);
if (var.storage != StorageClassFunction && !var.remapped_variable && type.pointer &&
(var.storage == StorageClassInput || var.storage == StorageClassOutput) && !is_builtin_variable(var) &&
interface_variable_exists_in_entry_point(var.self))
interface_variable_exists_in_entry_point(var.self) && !is_hidden)
{
// Builtin variables are handled separately.
emit_interface_block_globally(var);
@@ -1747,8 +1750,10 @@ void CompilerHLSL::emit_resources()
if (var.storage != StorageClassInput && var.storage != StorageClassOutput)
return;
bool is_hidden = is_hidden_io_variable(var);
if (!var.remapped_variable && type.pointer && !is_builtin_variable(var) &&
interface_variable_exists_in_entry_point(var.self))
interface_variable_exists_in_entry_point(var.self) && !is_hidden)
{
if (block)
{
@@ -3482,10 +3487,12 @@ void CompilerHLSL::emit_hlsl_entry_point()
if (var.storage != StorageClassInput)
return;
bool is_hidden = is_hidden_io_variable(var);
bool need_matrix_unroll = var.storage == StorageClassInput && execution.model == ExecutionModelVertex;
if (!var.remapped_variable && type.pointer && !is_builtin_variable(var) &&
interface_variable_exists_in_entry_point(var.self))
interface_variable_exists_in_entry_point(var.self) && !is_hidden)
{
if (block)
{
@@ -7119,6 +7126,30 @@ bool CompilerHLSL::is_hlsl_force_storage_buffer_as_uav(ID id) const
return (force_uav_buffer_bindings.find({ desc_set, binding }) != force_uav_buffer_bindings.end());
}
bool CompilerHLSL::is_hidden_io_variable(const SPIRVariable &var) const
{
if (!is_hidden_variable(var))
return false;
// It is too risky to remove stage IO variables that are linkable since it affects link compatibility.
// For vertex inputs and fragment outputs, it's less of a concern and we want reflection data
// to match reality.
bool is_external_linkage =
(get_execution_model() == ExecutionModelVertex && var.storage == StorageClassInput) ||
(get_execution_model() == ExecutionModelFragment && var.storage == StorageClassOutput);
if (!is_external_linkage)
return false;
// Unused output I/O variables might still be required to implement framebuffer fetch.
if (var.storage == StorageClassOutput && !is_legacy() &&
location_is_framebuffer_fetch(get_decoration(var.self, DecorationLocation)) != 0)
return false;
return true;
}
void CompilerHLSL::set_hlsl_force_storage_buffer_as_uav(uint32_t desc_set, uint32_t binding)
{
SetBindingPair pair = { desc_set, binding };

View File

@@ -298,6 +298,7 @@ private:
SPIRType::BaseType get_builtin_basetype(BuiltIn builtin, SPIRType::BaseType default_type) override;
bool is_hlsl_force_storage_buffer_as_uav(ID id) const;
bool is_hidden_io_variable(const SPIRVariable &var) const;
Options hlsl_options;

View File

@@ -1514,7 +1514,7 @@ void CompilerMSL::emit_entry_point_declarations()
if (is_array(type))
{
is_using_builtin_array = true;
statement(get_argument_address_space(var), " ", type_to_glsl(type), "* ", to_restrict(var_id, true), name,
statement(get_variable_address_space(var), " ", type_to_glsl(type), "* ", to_restrict(var_id, true), name,
type_to_array_glsl(type, var_id), " =");
uint32_t array_size = get_resource_array_size(type, var_id);
@@ -1525,8 +1525,8 @@ void CompilerMSL::emit_entry_point_declarations()
for (uint32_t i = 0; i < array_size; i++)
{
statement("(", get_argument_address_space(var), " ", type_to_glsl(type), "* ",
to_restrict(var_id, false), ")((", get_argument_address_space(var), " char* ",
statement("(", get_variable_address_space(var), " ", type_to_glsl(type), "* ",
to_restrict(var_id, false), ")((", get_variable_address_space(var), " char* ",
to_restrict(var_id, false), ")", to_name(arg_id), ".", dynamic_buffer.second.mbr_name,
"[", i, "]", " + ", to_name(dynamic_offsets_buffer_id), "[", base_index + i, "]),");
}
@@ -1537,9 +1537,9 @@ void CompilerMSL::emit_entry_point_declarations()
}
else
{
statement(get_argument_address_space(var), " auto& ", to_restrict(var_id, true), name, " = *(",
get_argument_address_space(var), " ", type_to_glsl(type), "* ", to_restrict(var_id, false), ")((",
get_argument_address_space(var), " char* ", to_restrict(var_id, false), ")", to_name(arg_id), ".",
statement(get_variable_address_space(var), " auto& ", to_restrict(var_id, true), name, " = *(",
get_variable_address_space(var), " ", type_to_glsl(type), "* ", to_restrict(var_id, false), ")((",
get_variable_address_space(var), " char* ", to_restrict(var_id, false), ")", to_name(arg_id), ".",
dynamic_buffer.second.mbr_name, " + ", to_name(dynamic_offsets_buffer_id), "[", base_index, "]);");
}
}
@@ -1594,7 +1594,7 @@ void CompilerMSL::emit_entry_point_declarations()
statement("spvDescriptorArray<sampler> ", name, "Smplr {", resource_name, "Smplr};");
break;
case SPIRType::Struct:
statement("spvDescriptorArray<", get_argument_address_space(var), " ", type_to_glsl(buffer_type), "*> ",
statement("spvDescriptorArray<", get_variable_address_space(var), " ", type_to_glsl(buffer_type), "*> ",
name, " {", resource_name, "};");
break;
default:
@@ -1605,7 +1605,7 @@ void CompilerMSL::emit_entry_point_declarations()
else if (!type.array.empty() && type.basetype == SPIRType::Struct)
{
// Emit only buffer arrays here.
statement(get_argument_address_space(var), " ", type_to_glsl(buffer_type), "* ",
statement(get_variable_address_space(var), " ", type_to_glsl(buffer_type), "* ",
to_restrict(var.self, true), name, "[] =");
begin_scope();
uint32_t array_size = get_resource_array_size(type, var.self);
@@ -1629,7 +1629,7 @@ void CompilerMSL::emit_entry_point_declarations()
continue;
const auto &type = get_variable_data_type(var);
auto addr_space = get_argument_address_space(var);
auto addr_space = get_variable_address_space(var);
// This resource name has already been added.
auto name = to_name(var_id);
@@ -10158,8 +10158,7 @@ void CompilerMSL::emit_instruction(const Instruction &instruction)
enclose_expression(to_unpacked_row_major_matrix_expression(ops[2])), ")");
bool forward = should_forward(ops[2]) && should_forward(ops[3]);
auto &e = emit_op(ops[0], ops[1], expr, forward);
e.need_transpose = true;
emit_transposed_op(ops[0], ops[1], expr, forward);
a->need_transpose = true;
b->need_transpose = true;
inherit_expression_dependencies(ops[1], ops[2]);
@@ -11141,7 +11140,7 @@ void CompilerMSL::emit_atomic_func_op(uint32_t result_type, uint32_t result_id,
}
else if (var && ptr_type.storage != StorageClassPhysicalStorageBuffer)
{
exp += get_argument_address_space(*var);
exp += get_variable_address_space(*var);
}
else
{
@@ -14034,11 +14033,17 @@ bool CompilerMSL::uses_explicit_early_fragment_test()
}
// In MSL, address space qualifiers are required for all pointer or reference variables
string CompilerMSL::get_argument_address_space(const SPIRVariable &argument)
string CompilerMSL::get_variable_address_space(const SPIRVariable &argument)
{
const auto &type = get<SPIRType>(argument.basetype);
// BDA is always passed around by value. There is no storage class for the argument itself.
if (is_physical_pointer(type))
return get_type_address_space(type, argument.self, true);
}
string CompilerMSL::get_leaf_argument_address_space(const SPIRVariable &argument)
{
const auto &type = get<SPIRType>(argument.basetype);
// BDA and variable buffer pointer is always passed around by (pointer) value. There is no storage class for the argument itself.
if (is_physical_or_buffer_pointer(type))
return "";
return get_type_address_space(type, argument.self, true);
}
@@ -14115,6 +14120,7 @@ string CompilerMSL::get_type_address_space(const SPIRType &type, uint32_t id, bo
}
else if (!argument)
{
// This is used for helper UBOs we insert ourselves.
addr_space = "constant";
}
else if (type_is_msl_framebuffer_fetch(type))
@@ -14122,6 +14128,7 @@ string CompilerMSL::get_type_address_space(const SPIRType &type, uint32_t id, bo
// Subpass inputs are passed around by value.
addr_space = "";
}
break;
case StorageClassFunction:
@@ -14638,7 +14645,7 @@ string CompilerMSL::entry_point_args_argument_buffer(bool append_comma)
claimed_bindings.set(buffer_binding);
ep_args += get_argument_address_space(var) + " ";
ep_args += get_variable_address_space(var) + " ";
if (recursive_inputs.count(type.self))
ep_args += string("void* ") + to_restrict(id, true) + to_name(id) + "_vp";
@@ -14852,7 +14859,7 @@ void CompilerMSL::entry_point_args_discrete_descriptors(string &ep_args)
// Declare the primary alias as void*
if (!ep_args.empty())
ep_args += ", ";
ep_args += get_argument_address_space(var) + " void* " + primary_name;
ep_args += get_variable_address_space(var) + " void* " + primary_name;
ep_args += " [[buffer(" + convert_to_string(r.index) + ")";
if (interlocked_resources.count(var_id))
ep_args += ", raster_order_group(0)";
@@ -14900,7 +14907,7 @@ void CompilerMSL::entry_point_args_discrete_descriptors(string &ep_args)
{
if (!ep_args.empty())
ep_args += ", ";
ep_args += get_argument_address_space(var) + " " + type_to_glsl(type) + "* " +
ep_args += get_variable_address_space(var) + " " + type_to_glsl(type) + "* " +
to_restrict(var_id, true) + r.name + "_" + convert_to_string(i);
ep_args += " [[buffer(" + convert_to_string(r.index + i) + ")";
if (interlocked_resources.count(var_id))
@@ -14913,7 +14920,7 @@ void CompilerMSL::entry_point_args_discrete_descriptors(string &ep_args)
{
if (!ep_args.empty())
ep_args += ", ";
ep_args += get_argument_address_space(var) + " ";
ep_args += get_variable_address_space(var) + " ";
if (recursive_inputs.count(type.self))
ep_args += string("void* ") + to_restrict(var_id, true) + r.name + "_vp";
@@ -15105,7 +15112,7 @@ void CompilerMSL::fix_up_shader_inputs_outputs()
{
recursive_inputs.insert(type.self);
entry_func.fixup_hooks_in.push_back([this, &type, &var, var_id]() {
auto addr_space = get_argument_address_space(var);
auto addr_space = get_variable_address_space(var);
auto var_name = to_name(var_id);
statement(addr_space, " auto& ", to_restrict(var_id, true), var_name,
" = *(", addr_space, " ", type_to_glsl(type), "*)", var_name, "_vp;");
@@ -15802,7 +15809,7 @@ string CompilerMSL::argument_decl(const SPIRFunction::Parameter &arg)
// Physical pointer types are passed by pointer, not reference.
auto &data_type = get_variable_data_type(var);
bool passed_by_value = is_physical_pointer(var_type);
bool passed_by_value = arg.alias_global_variable ? false : is_physical_or_buffer_pointer(var_type);
auto &type = passed_by_value ? var_type : data_type;
// If we need to modify the name of the variable, make sure we use the original variable.
@@ -15845,7 +15852,7 @@ string CompilerMSL::argument_decl(const SPIRFunction::Parameter &arg)
spv_function_implementations.count(SPVFuncImplDynamicImageSampler);
// Allow Metal to use the array<T> template to make arrays a value type
string address_space = get_argument_address_space(var);
string address_space = arg.alias_global_variable ? get_variable_address_space(var) : get_leaf_argument_address_space(var);
bool builtin = has_decoration(var.self, DecorationBuiltIn);
auto builtin_type = BuiltIn(get_decoration(arg.id, DecorationBuiltIn));
@@ -15942,10 +15949,29 @@ string CompilerMSL::argument_decl(const SPIRFunction::Parameter &arg)
}
else
{
if (!address_space.empty())
decl = join(address_space, " ", decl);
decl += " ";
decl += to_expression(name_id);
// Variable pointer to array is kinda awkward ...
bool pointer_to_logical_buffer_array =
!is_physical_pointer(type) && is_pointer(type) &&
has_decoration(type.parent_type, DecorationArrayStride);
if (pointer_to_logical_buffer_array)
{
decl.pop_back();
decl += " (*";
decl += to_expression(name_id);
decl += ")";
bool old_is_using_builtin_array = is_using_builtin_array;
is_using_builtin_array = true;
decl += type_to_array_glsl(type, name_id);
is_using_builtin_array = old_is_using_builtin_array;
}
else
{
if (!address_space.empty())
decl = join(address_space, " ", decl);
decl += " ";
decl += to_expression(name_id);
}
}
}
else if (is_array(type) && !type_is_image)
@@ -16252,6 +16278,7 @@ const std::unordered_set<std::string> &CompilerMSL::get_reserved_keyword_set()
"quad_broadcast",
"thread",
"threadgroup",
"signed",
};
return keywords;
@@ -16395,6 +16422,7 @@ const std::unordered_set<std::string> &CompilerMSL::get_illegal_func_names()
"uint16",
"float8",
"float16",
"signed",
};
return illegal_func_names;
@@ -16563,8 +16591,11 @@ string CompilerMSL::type_to_glsl(const SPIRType &type, uint32_t id, bool member)
// the C-style nesting works right.
// FIXME: This is somewhat of a hack.
bool old_is_using_builtin_array = is_using_builtin_array;
bool pointer_to_buffer_array = is_pointer(type) && has_decoration(type.parent_type, DecorationArrayStride);
if (is_physical_pointer(type))
is_using_builtin_array = false;
else if (pointer_to_buffer_array)
is_using_builtin_array = true;
type_name = join(type_address_space, " ", type_to_glsl(*p_parent_type, id));
@@ -17930,7 +17961,10 @@ string CompilerMSL::builtin_qualifier(BuiltIn builtin)
{
// thread_execution_width is an alias for threads_per_simdgroup, and it's only available since 1.0,
// but not in fragment.
return "thread_execution_width";
if (msl_options.supports_msl_version(3, 0))
return "threads_per_simdgroup";
else
return "thread_execution_width";
}
case BuiltInNumSubgroups:
@@ -17960,6 +17994,7 @@ string CompilerMSL::builtin_qualifier(BuiltIn builtin)
return "thread_index_in_simdgroup";
}
else if (execution.model == ExecutionModelKernel || execution.model == ExecutionModelGLCompute ||
execution.model == ExecutionModelTaskEXT || execution.model == ExecutionModelMeshEXT ||
execution.model == ExecutionModelTessellationControl ||
(execution.model == ExecutionModelVertex && msl_options.vertex_for_tessellation))
{
@@ -19898,7 +19933,7 @@ void CompilerMSL::analyze_argument_buffers()
{
recursive_inputs.insert(type_id);
auto &entry_func = this->get<SPIRFunction>(ir.default_entry_point);
auto addr_space = get_argument_address_space(buffer_var);
auto addr_space = get_variable_address_space(buffer_var);
entry_func.fixup_hooks_in.push_back([this, addr_space, buffer_name, buffer_type_name]() {
statement(addr_space, " auto& ", buffer_name, " = *(", addr_space, " ", buffer_type_name, "*)", buffer_name, "_vp;");
});

View File

@@ -1126,7 +1126,9 @@ protected:
void mark_struct_members_packed(const SPIRType &type);
void ensure_member_packing_rules_msl(SPIRType &ib_type, uint32_t index);
bool validate_member_packing_rules_msl(const SPIRType &type, uint32_t index) const;
std::string get_argument_address_space(const SPIRVariable &argument);
std::string get_variable_address_space(const SPIRVariable &argument);
// Special case of get_variable_address_space which is only used for leaf functions.
std::string get_leaf_argument_address_space(const SPIRVariable &argument);
std::string get_type_address_space(const SPIRType &type, uint32_t id, bool argument = false);
bool decoration_flags_signal_volatile(const Bitset &flags) const;
bool decoration_flags_signal_coherent(const Bitset &flags) const;