mirror of
https://github.com/AssetRipper/dxil-spirv.git
synced 2026-11-13 18:07:11 -10:00
3954 lines
150 KiB
C++
3954 lines
150 KiB
C++
/* Copyright (c) 2019-2022 Hans-Kristian Arntzen for Valve Corporation
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
* the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "spirv_module.hpp"
|
|
#include "descriptor_qa.hpp"
|
|
#include "spirv_module_instrumentation.hpp"
|
|
#include "SpvBuilder.h"
|
|
#include "node.hpp"
|
|
#include "scratch_pool.hpp"
|
|
#include "logging.hpp"
|
|
#include "GLSL.std.450.h"
|
|
#include <limits>
|
|
|
|
namespace dxil_spv
|
|
{
|
|
constexpr uint32_t GENERATOR = 1967215134;
|
|
struct SPIRVModule::Impl : BlockEmissionInterface
|
|
{
|
|
explicit Impl(SPIRVModule &module)
|
|
: self(module), builder(GENERATOR, &build_logger)
|
|
{
|
|
}
|
|
|
|
SPIRVModule &self;
|
|
|
|
spv::SpvBuildLogger build_logger;
|
|
spv::Builder builder;
|
|
spv::Function *entry_function = nullptr;
|
|
spv::Function *active_function = nullptr;
|
|
spv::Instruction *entry_point = nullptr;
|
|
|
|
void emit_entry_point(spv::ExecutionModel model, const char *name, bool physical_storage,
|
|
spv::MemoryModel memory_model);
|
|
bool finalize_spirv(Vector<uint32_t> &spirv);
|
|
|
|
void register_block(CFGNode *node) override;
|
|
void emit_basic_block(CFGNode *node) override;
|
|
void emit_entry_point_function_body(CFGStructurizer &structurizer);
|
|
void emit_leaf_function_body(spv::Function *func, CFGStructurizer &structurizer);
|
|
static spv::Block *get_spv_block(CFGNode *node);
|
|
|
|
void enable_shader_discard(bool supports_demote);
|
|
void build_discard_call_early();
|
|
void build_discard_call_early_cond(spv::Id cond);
|
|
void build_demote_call_cond(spv::Id cond);
|
|
void build_discard_call_exit();
|
|
spv::Id build_descriptor_qa_check(SPIRVModule &module);
|
|
spv::Id build_wave_match(SPIRVModule &module, spv::Id type_id);
|
|
spv::Id build_wave_is_first_lane_masked(SPIRVModule &module);
|
|
spv::Id build_wave_active_all_equal_masked(SPIRVModule &module, spv::Id type_id);
|
|
spv::Id build_wave_read_first_lane_masked(SPIRVModule &module, spv::Id type_id);
|
|
spv::Id build_wave_multi_prefix_count_bits(SPIRVModule &module);
|
|
spv::Id build_wave_multi_prefix_op(SPIRVModule &module, spv::Op opcode, spv::Id type_id);
|
|
spv::Id build_robust_physical_cbv_load(SPIRVModule &module, spv::Id type_id, spv::Id ptr_type_id, unsigned alignment);
|
|
spv::Id build_robust_atomic_counter_op(SPIRVModule &module);
|
|
spv::Id build_quad_all(SPIRVModule &module);
|
|
spv::Id build_quad_any(SPIRVModule &module);
|
|
spv::Id build_quad_vote(SPIRVModule &module, HelperCall call);
|
|
spv::Id build_image_atomic_r64_compact(SPIRVModule &module, bool array, bool non_uniform);
|
|
spv::Id build_finish_cross_group_sharing(SPIRVModule &module);
|
|
spv::Id build_allocate_node_records(SPIRVModule &module, bool per_thread);
|
|
spv::Id build_increment_node_count(SPIRVModule &module, bool per_thread);
|
|
spv::Id build_allocate_node_records_waterfall(SPIRVModule &module);
|
|
spv::Id build_node_coalesce_payload_offset(SPIRVModule &module, const spv::Id *ids, uint32_t id_count);
|
|
spv::Id build_coop_mat_fp8_to_fp16(SPIRVModule &module, const spv::Id *ids, uint32_t id_count);
|
|
spv::Id build_coop_mat_fp16_to_fp8(SPIRVModule &module, const spv::Id *ids, uint32_t id_count);
|
|
spv::Id build_coop_mat_saturate_fp8(SPIRVModule &module, spv::Id type_id);
|
|
spv::Id build_coop_mat_transfer(SPIRVModule &module, const spv::Id *ids, uint32_t id_count);
|
|
spv::Id build_coop_mat_saturation_fixup(SPIRVModule &module, spv::Id type_id);
|
|
spv::Id build_is_quad_uniform_control_flow(SPIRVModule &module);
|
|
spv::Id build_validate_bda_load_store(SPIRVModule &module);
|
|
spv::Id build_allocate_invocation_id(SPIRVModule &module);
|
|
spv::Function *discard_function = nullptr;
|
|
spv::Function *discard_function_cond = nullptr;
|
|
spv::Function *demote_function_cond = nullptr;
|
|
spv::Id discard_state_var_id = 0;
|
|
spv::ExecutionModel execution_model = spv::ExecutionModelMax;
|
|
|
|
spv::Id create_variable(spv::StorageClass storage, spv::Id type, const char *name);
|
|
spv::Id create_variable_with_initializer(spv::StorageClass storage, spv::Id type,
|
|
spv::Id initializer, const char *name);
|
|
void register_active_variable(spv::StorageClass storage, spv::Id id);
|
|
|
|
struct
|
|
{
|
|
bool supports_demote = false;
|
|
} caps;
|
|
|
|
spv::Id get_builtin_shader_input(spv::BuiltIn builtin);
|
|
spv::Id get_builtin_shader_output(spv::BuiltIn builtin);
|
|
bool has_builtin_shader_input(spv::BuiltIn builtin) const;
|
|
bool has_builtin_shader_output(spv::BuiltIn builtin) const;
|
|
void register_builtin_shader_input(spv::Id id, spv::BuiltIn builtin);
|
|
bool query_builtin_shader_input(spv::Id id, spv::BuiltIn *builtin) const;
|
|
void register_builtin_shader_output(spv::Id id, spv::BuiltIn builtin);
|
|
bool query_builtin_shader_output(spv::Id id, spv::BuiltIn *builtin) const;
|
|
UnorderedMap<spv::BuiltIn, spv::Id> builtins_input;
|
|
UnorderedMap<spv::Id, spv::BuiltIn> id_to_builtin_input;
|
|
UnorderedMap<spv::BuiltIn, spv::Id> builtins_output;
|
|
UnorderedMap<spv::Id, spv::BuiltIn> id_to_builtin_output;
|
|
|
|
spv::Id get_type_for_builtin(spv::BuiltIn builtin, bool &requires_flat_decoration);
|
|
ScratchPool<Operation> operation_pool;
|
|
|
|
bool spirv_requires_14() const;
|
|
bool builtin_requires_volatile(spv::BuiltIn builtin) const;
|
|
bool execution_model_is_ray_tracing() const;
|
|
bool mark_error = false;
|
|
|
|
spv::Id get_helper_call_id(SPIRVModule &module, HelperCall call, spv::Id type_id);
|
|
spv::Id get_helper_call_id(SPIRVModule &module, HelperCall call, const spv::Id *aux_ids, uint32_t aux_id_count);
|
|
spv::Id descriptor_qa_helper_call_id = 0;
|
|
spv::Id wave_multi_prefix_count_bits_id = 0;
|
|
spv::Id robust_atomic_counter_call_id = 0;
|
|
spv::Id quad_all_call_id = 0;
|
|
spv::Id quad_any_call_id = 0;
|
|
spv::Id wave_is_first_lane_masked_id = 0;
|
|
Vector<std::pair<spv::Id, spv::Id>> wave_match_call_ids;
|
|
Vector<std::pair<spv::Id, spv::Id>> wave_active_all_equal_masked_ids;
|
|
Vector<std::pair<spv::Id, spv::Id>> wave_read_first_lane_masked_ids;
|
|
spv::Id image_r64_atomic_call_id = 0;
|
|
spv::Id image_r64_array_atomic_call_id = 0;
|
|
spv::Id image_r64_atomic_non_uniform_call_id = 0;
|
|
spv::Id image_r64_array_atomic_non_uniform_call_id = 0;
|
|
spv::Id finish_cross_group_sharing_call_id = 0;
|
|
spv::Id allocate_thread_node_records_call_id = 0;
|
|
spv::Id allocate_group_node_records_call_id = 0;
|
|
spv::Id increment_thread_node_count_call_id = 0;
|
|
spv::Id increment_group_node_count_call_id = 0;
|
|
spv::Id allocate_thread_node_records_waterfall_call_id = 0;
|
|
spv::Id node_coalesce_payload_offset_call_id = 0;
|
|
spv::Id is_quad_uniform_call_id = 0;
|
|
spv::Id validate_bda_load_store_call_id = 0;
|
|
spv::Id allocate_invocation_id_call_id = 0;
|
|
|
|
struct MultiPrefixOp
|
|
{
|
|
spv::Op opcode;
|
|
spv::Id type_id;
|
|
spv::Id func_id;
|
|
};
|
|
Vector<MultiPrefixOp> wave_multi_prefix_call_ids;
|
|
|
|
struct CBVOp
|
|
{
|
|
spv::Id type_id;
|
|
spv::Id ptr_type_id;
|
|
unsigned alignment;
|
|
spv::Id func_id;
|
|
};
|
|
Vector<CBVOp> physical_cbv_call_ids;
|
|
|
|
struct CoopMatConvOp
|
|
{
|
|
spv::Id input_type;
|
|
spv::Id output_type;
|
|
spv::Id func_id;
|
|
HelperCall call;
|
|
};
|
|
Vector<CoopMatConvOp> coop_mat_conv_ids;
|
|
|
|
DescriptorQAInfo descriptor_qa_info;
|
|
|
|
uint32_t override_spirv_version = 0;
|
|
bool helper_lanes_participate_in_wave_ops = true;
|
|
|
|
void add_instruction(spv::Block *bb, std::unique_ptr<spv::Instruction> inst);
|
|
void add_instrumented_instruction(spv::Op op, spv::Block *bb, spv::Id id);
|
|
InstructionInstrumentationState instruction_instrumentation;
|
|
};
|
|
|
|
spv::Id SPIRVModule::Impl::get_type_for_builtin(spv::BuiltIn builtin, bool &requires_flat)
|
|
{
|
|
requires_flat = false;
|
|
switch (builtin)
|
|
{
|
|
case spv::BuiltInSampleMask:
|
|
requires_flat = true;
|
|
return builder.makeArrayType(builder.makeUintType(32), builder.makeUintConstant(1), 0);
|
|
|
|
case spv::BuiltInTessCoord:
|
|
return builder.makeVectorType(builder.makeFloatType(32), 3);
|
|
|
|
case spv::BuiltInLocalInvocationIndex:
|
|
case spv::BuiltInSampleId:
|
|
case spv::BuiltInVertexIndex:
|
|
case spv::BuiltInInstanceIndex:
|
|
case spv::BuiltInBaseVertex:
|
|
case spv::BuiltInBaseInstance:
|
|
case spv::BuiltInInvocationId:
|
|
case spv::BuiltInPrimitiveId:
|
|
case spv::BuiltInShadingRateKHR:
|
|
case spv::BuiltInPrimitiveShadingRateKHR:
|
|
case spv::BuiltInViewIndex:
|
|
case spv::BuiltInPatchVertices:
|
|
requires_flat = true;
|
|
return builder.makeUintType(32);
|
|
|
|
case spv::BuiltInSubgroupSize:
|
|
case spv::BuiltInNumSubgroups:
|
|
case spv::BuiltInSubgroupLocalInvocationId:
|
|
case spv::BuiltInSubgroupId:
|
|
builder.addCapability(spv::CapabilityGroupNonUniform);
|
|
requires_flat = true;
|
|
return builder.makeUintType(32);
|
|
|
|
case spv::BuiltInGlobalInvocationId:
|
|
case spv::BuiltInLocalInvocationId:
|
|
case spv::BuiltInWorkgroupId:
|
|
case spv::BuiltInLaunchIdKHR:
|
|
case spv::BuiltInLaunchSizeKHR:
|
|
case spv::BuiltInNumWorkgroups:
|
|
return builder.makeVectorType(builder.makeUintType(32), 3);
|
|
|
|
case spv::BuiltInObjectRayOriginKHR:
|
|
case spv::BuiltInWorldRayOriginKHR:
|
|
case spv::BuiltInObjectRayDirectionKHR:
|
|
case spv::BuiltInWorldRayDirectionKHR:
|
|
return builder.makeVectorType(builder.makeFloatType(32), 3);
|
|
|
|
case spv::BuiltInRayTminKHR:
|
|
case spv::BuiltInRayTmaxKHR:
|
|
return builder.makeFloatType(32);
|
|
|
|
case spv::BuiltInWorldToObjectKHR:
|
|
case spv::BuiltInObjectToWorldKHR:
|
|
return builder.makeMatrixType(builder.makeFloatType(32), 4, 3);
|
|
|
|
case spv::BuiltInInstanceCustomIndexKHR:
|
|
case spv::BuiltInInstanceId:
|
|
case spv::BuiltInRayGeometryIndexKHR:
|
|
case spv::BuiltInIncomingRayFlagsKHR:
|
|
case spv::BuiltInHitKindKHR:
|
|
requires_flat = true;
|
|
return builder.makeUintType(32);
|
|
|
|
case spv::BuiltInHelperInvocation:
|
|
case spv::BuiltInFullyCoveredEXT:
|
|
return builder.makeBoolType();
|
|
|
|
case spv::BuiltInClusterIDNV:
|
|
return builder.makeUintType(32);
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void SPIRVModule::Impl::register_builtin_shader_input(spv::Id id, spv::BuiltIn builtin)
|
|
{
|
|
builtins_input[builtin] = id;
|
|
id_to_builtin_input[id] = builtin;
|
|
}
|
|
|
|
void SPIRVModule::Impl::register_builtin_shader_output(spv::Id id, spv::BuiltIn builtin)
|
|
{
|
|
builtins_output[builtin] = id;
|
|
id_to_builtin_output[id] = builtin;
|
|
}
|
|
|
|
bool SPIRVModule::Impl::query_builtin_shader_input(spv::Id id, spv::BuiltIn *builtin) const
|
|
{
|
|
auto itr = id_to_builtin_input.find(id);
|
|
if (itr != id_to_builtin_input.end())
|
|
{
|
|
*builtin = itr->second;
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool SPIRVModule::Impl::query_builtin_shader_output(spv::Id id, spv::BuiltIn *builtin) const
|
|
{
|
|
auto itr = id_to_builtin_output.find(id);
|
|
if (itr != id_to_builtin_output.end())
|
|
{
|
|
*builtin = itr->second;
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool SPIRVModule::Impl::builtin_requires_volatile(spv::BuiltIn builtin) const
|
|
{
|
|
if (!execution_model_is_ray_tracing())
|
|
return false;
|
|
|
|
switch (builtin)
|
|
{
|
|
case spv::BuiltInSubgroupId:
|
|
case spv::BuiltInSubgroupLocalInvocationId:
|
|
case spv::BuiltInSubgroupEqMask:
|
|
case spv::BuiltInSubgroupLtMask:
|
|
case spv::BuiltInSubgroupLeMask:
|
|
case spv::BuiltInSubgroupGtMask:
|
|
case spv::BuiltInSubgroupGeMask:
|
|
return true;
|
|
|
|
case spv::BuiltInRayTmaxKHR:
|
|
return execution_model == spv::ExecutionModelIntersectionKHR;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool SPIRVModule::Impl::has_builtin_shader_input(spv::BuiltIn builtin) const
|
|
{
|
|
return builtins_input.count(builtin) != 0;
|
|
}
|
|
|
|
bool SPIRVModule::Impl::has_builtin_shader_output(spv::BuiltIn builtin) const
|
|
{
|
|
return builtins_output.count(builtin) != 0;
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::get_builtin_shader_input(spv::BuiltIn builtin)
|
|
{
|
|
auto itr = builtins_input.find(builtin);
|
|
if (itr != builtins_input.end())
|
|
return itr->second;
|
|
|
|
bool requires_flat = false;
|
|
spv::Id var_id = create_variable(spv::StorageClassInput, get_type_for_builtin(builtin, requires_flat), nullptr);
|
|
builder.addDecoration(var_id, spv::DecorationBuiltIn, builtin);
|
|
if (builtin_requires_volatile(builtin) && !builder.hasCapability(spv::CapabilityVulkanMemoryModel))
|
|
builder.addDecoration(var_id, spv::DecorationVolatile);
|
|
|
|
// VUID-StandaloneSpirv-Flat-04744
|
|
if (requires_flat && execution_model == spv::ExecutionModelFragment)
|
|
builder.addDecoration(var_id, spv::DecorationFlat);
|
|
register_builtin_shader_input(var_id, builtin);
|
|
return var_id;
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::get_builtin_shader_output(spv::BuiltIn builtin)
|
|
{
|
|
auto itr = builtins_output.find(builtin);
|
|
if (itr != builtins_output.end())
|
|
return itr->second;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
spv::Block *SPIRVModule::Impl::get_spv_block(CFGNode *node)
|
|
{
|
|
assert(node->userdata);
|
|
return static_cast<spv::Block *>(node->userdata);
|
|
}
|
|
|
|
void SPIRVModule::Impl::emit_entry_point(spv::ExecutionModel model,
|
|
const char *name, bool physical_storage,
|
|
spv::MemoryModel memory_model)
|
|
{
|
|
execution_model = model;
|
|
builder.addCapability(spv::Capability::CapabilityShader);
|
|
|
|
if (memory_model == spv::MemoryModelVulkan)
|
|
builder.addCapability(spv::CapabilityVulkanMemoryModel);
|
|
|
|
if (physical_storage)
|
|
{
|
|
builder.setMemoryModel(spv::AddressingModel::AddressingModelPhysicalStorageBuffer64, memory_model);
|
|
builder.addCapability(spv::CapabilityPhysicalStorageBufferAddresses);
|
|
builder.addExtension("SPV_KHR_physical_storage_buffer");
|
|
}
|
|
else
|
|
builder.setMemoryModel(spv::AddressingModel::AddressingModelLogical, memory_model);
|
|
|
|
entry_function = builder.makeEntryPoint("main");
|
|
entry_point = builder.addEntryPoint(model, entry_function, name);
|
|
if (model == spv::ExecutionModel::ExecutionModelFragment)
|
|
builder.addExecutionMode(entry_function, spv::ExecutionModeOriginUpperLeft);
|
|
|
|
if (instruction_instrumentation.info.enabled)
|
|
{
|
|
builder.addExtension("SPV_KHR_float_controls");
|
|
builder.addCapability(spv::CapabilitySignedZeroInfNanPreserve);
|
|
|
|
if (instruction_instrumentation.info.fp16)
|
|
{
|
|
builder.addCapability(spv::CapabilityFloat16);
|
|
builder.addExecutionMode(entry_function, spv::ExecutionModeSignedZeroInfNanPreserve, 16);
|
|
}
|
|
|
|
if (instruction_instrumentation.info.fp32)
|
|
builder.addExecutionMode(entry_function, spv::ExecutionModeSignedZeroInfNanPreserve, 32);
|
|
|
|
if (instruction_instrumentation.info.fp64)
|
|
builder.addExecutionMode(entry_function, spv::ExecutionModeSignedZeroInfNanPreserve, 64);
|
|
}
|
|
}
|
|
|
|
void SPIRVModule::Impl::enable_shader_discard(bool supports_demote)
|
|
{
|
|
caps.supports_demote = supports_demote;
|
|
if (!discard_state_var_id && !caps.supports_demote)
|
|
{
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
discard_state_var_id =
|
|
create_variable(spv::StorageClassPrivate, builder.makeBoolType(), "discard_state");
|
|
builder.setBuildPoint(entry_function->getEntryBlock());
|
|
builder.createStore(builder.makeBoolConstant(false), discard_state_var_id);
|
|
builder.setBuildPoint(current_build_point);
|
|
}
|
|
}
|
|
|
|
void SPIRVModule::Impl::build_discard_call_early()
|
|
{
|
|
builder.createStore(builder.makeBoolConstant(true), discard_state_var_id);
|
|
}
|
|
|
|
void SPIRVModule::Impl::build_demote_call_cond(spv::Id cond)
|
|
{
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
|
|
if (!demote_function_cond)
|
|
{
|
|
spv::Block *entry = nullptr;
|
|
demote_function_cond =
|
|
builder.makeFunctionEntry(spv::NoPrecision, builder.makeVoidType(), "demote_cond",
|
|
{ builder.makeBoolType() }, {}, &entry);
|
|
|
|
auto *true_block = new spv::Block(builder.getUniqueId(), *demote_function_cond);
|
|
auto *false_block = new spv::Block(builder.getUniqueId(), *demote_function_cond);
|
|
builder.setBuildPoint(entry);
|
|
builder.createSelectionMerge(false_block, 0);
|
|
builder.createConditionalBranch(demote_function_cond->getParamId(0), true_block, false_block);
|
|
add_instruction(true_block, std::make_unique<spv::Instruction>(spv::OpDemoteToHelperInvocationEXT));
|
|
builder.setBuildPoint(true_block);
|
|
builder.createBranch(false_block);
|
|
builder.setBuildPoint(false_block);
|
|
builder.makeReturn(false);
|
|
}
|
|
|
|
builder.setBuildPoint(current_build_point);
|
|
builder.createFunctionCall(demote_function_cond, { cond });
|
|
}
|
|
|
|
void SPIRVModule::Impl::build_discard_call_early_cond(spv::Id cond)
|
|
{
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
|
|
if (!discard_function_cond)
|
|
{
|
|
spv::Block *entry = nullptr;
|
|
discard_function_cond =
|
|
builder.makeFunctionEntry(spv::NoPrecision, builder.makeVoidType(), "discard_cond",
|
|
{ builder.makeBoolType() }, {}, &entry);
|
|
|
|
auto *true_block = new spv::Block(builder.getUniqueId(), *discard_function_cond);
|
|
auto *false_block = new spv::Block(builder.getUniqueId(), *discard_function_cond);
|
|
builder.setBuildPoint(entry);
|
|
builder.createSelectionMerge(false_block, 0);
|
|
builder.createConditionalBranch(discard_function_cond->getParamId(0), true_block, false_block);
|
|
builder.setBuildPoint(true_block);
|
|
builder.createStore(builder.makeBoolConstant(true), discard_state_var_id);
|
|
builder.createBranch(false_block);
|
|
builder.setBuildPoint(false_block);
|
|
builder.makeReturn(false);
|
|
}
|
|
|
|
builder.setBuildPoint(current_build_point);
|
|
builder.createFunctionCall(discard_function_cond, { cond });
|
|
}
|
|
|
|
void SPIRVModule::Impl::build_discard_call_exit()
|
|
{
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
|
|
if (!discard_function)
|
|
{
|
|
spv::Block *entry = nullptr;
|
|
discard_function =
|
|
builder.makeFunctionEntry(spv::NoPrecision, builder.makeVoidType(), "discard_exit", {}, {}, &entry);
|
|
|
|
auto *true_block = new spv::Block(builder.getUniqueId(), *discard_function);
|
|
auto *false_block = new spv::Block(builder.getUniqueId(), *discard_function);
|
|
|
|
builder.setBuildPoint(entry);
|
|
spv::Id loaded_state = builder.createLoad(discard_state_var_id);
|
|
builder.createSelectionMerge(false_block, 0);
|
|
builder.createConditionalBranch(loaded_state, true_block, false_block);
|
|
add_instruction(true_block, std::make_unique<spv::Instruction>(spv::OpKill));
|
|
builder.setBuildPoint(false_block);
|
|
builder.makeReturn(false);
|
|
}
|
|
|
|
builder.setBuildPoint(current_build_point);
|
|
builder.createFunctionCall(discard_function, {});
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_descriptor_qa_check(SPIRVModule &module)
|
|
{
|
|
if (!descriptor_qa_helper_call_id)
|
|
descriptor_qa_helper_call_id = build_descriptor_qa_check_function(module);
|
|
return descriptor_qa_helper_call_id;
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_validate_bda_load_store(SPIRVModule &module)
|
|
{
|
|
if (!validate_bda_load_store_call_id)
|
|
{
|
|
validate_bda_load_store_call_id = build_validate_bda_load_store_function(
|
|
module,
|
|
instruction_instrumentation.info.control_desc_set,
|
|
instruction_instrumentation.info.control_binding);
|
|
}
|
|
|
|
return validate_bda_load_store_call_id;
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_allocate_invocation_id(SPIRVModule &module)
|
|
{
|
|
if (!allocate_invocation_id_call_id)
|
|
{
|
|
allocate_invocation_id_call_id = build_allocate_invocation_id_function(
|
|
module,
|
|
instruction_instrumentation.info.control_desc_set,
|
|
instruction_instrumentation.info.control_binding);
|
|
}
|
|
|
|
return allocate_invocation_id_call_id;
|
|
}
|
|
|
|
static const char *opcode_to_multi_prefix_name(spv::Op opcode)
|
|
{
|
|
switch (opcode)
|
|
{
|
|
case spv::OpGroupNonUniformFAdd:
|
|
case spv::OpGroupNonUniformIAdd:
|
|
return "WaveMultiPrefixSum";
|
|
|
|
case spv::OpGroupNonUniformFMul:
|
|
case spv::OpGroupNonUniformIMul:
|
|
return "WaveMultiPrefixProduct";
|
|
|
|
case spv::OpGroupNonUniformBitwiseAnd:
|
|
return "WaveMultiPrefixBitAnd";
|
|
case spv::OpGroupNonUniformBitwiseOr:
|
|
return "WaveMultiPrefixBitOr";
|
|
case spv::OpGroupNonUniformBitwiseXor:
|
|
return "WaveMultiPrefixBitXor";
|
|
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_wave_multi_prefix_op(SPIRVModule &module, spv::Op opcode, spv::Id type_id)
|
|
{
|
|
for (auto &call : wave_multi_prefix_call_ids)
|
|
if (call.opcode == opcode && call.type_id == type_id)
|
|
return call.func_id;
|
|
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
spv::Block *entry = nullptr;
|
|
spv::Id uint_type = builder.makeUintType(32);
|
|
spv::Id uvec4_type = builder.makeVectorType(uint_type, 4);
|
|
spv::Id bool_type = builder.makeBoolType();
|
|
spv::Id bvec4_type = builder.makeVectorType(bool_type, 4);
|
|
|
|
Vector<spv::Id> types = { type_id, uvec4_type };
|
|
if (!helper_lanes_participate_in_wave_ops)
|
|
types.push_back(bool_type);
|
|
auto *func = builder.makeFunctionEntry(spv::NoPrecision, type_id,
|
|
opcode_to_multi_prefix_name(opcode),
|
|
types, {}, &entry);
|
|
spv::Id value_id = func->getParamId(0);
|
|
spv::Id mask_id = func->getParamId(1);
|
|
spv::Id undef_value = builder.createUndefined(type_id);
|
|
|
|
spv::Block *outer_entry = nullptr;
|
|
spv::Block *return_block = nullptr;
|
|
auto *header_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *body_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *merge_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *prefix_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *continue_block = new spv::Block(builder.getUniqueId(), *func);
|
|
|
|
builder.setBuildPoint(entry);
|
|
|
|
if (!helper_lanes_participate_in_wave_ops)
|
|
{
|
|
return_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *inner_entry_block = new spv::Block(builder.getUniqueId(), *func);
|
|
|
|
builder.createSelectionMerge(return_block, 0);
|
|
builder.createConditionalBranch(func->getParamId(2), return_block, inner_entry_block);
|
|
outer_entry = entry;
|
|
entry = inner_entry_block;
|
|
builder.setBuildPoint(entry);
|
|
}
|
|
|
|
{
|
|
auto ballot_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uvec4_type, spv::OpGroupNonUniformBallot);
|
|
ballot_op->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
ballot_op->addIdOperand(builder.makeBoolConstant(true));
|
|
auto mask_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uvec4_type, spv::OpBitwiseAnd);
|
|
mask_op->addIdOperand(ballot_op->getResultId());
|
|
mask_op->addIdOperand(mask_id);
|
|
mask_id = mask_op->getResultId();
|
|
add_instruction(entry, std::move(ballot_op));
|
|
add_instruction(entry, std::move(mask_op));
|
|
builder.createBranch(header_block);
|
|
}
|
|
|
|
builder.setBuildPoint(header_block);
|
|
{
|
|
builder.createLoopMerge(merge_block, body_block, 0);
|
|
builder.createBranch(body_block);
|
|
}
|
|
|
|
builder.setBuildPoint(body_block);
|
|
spv::Id compare_reduce_id;
|
|
{
|
|
auto broadcast_first = std::make_unique<spv::Instruction>(builder.getUniqueId(), uvec4_type, spv::OpGroupNonUniformBroadcastFirst);
|
|
broadcast_first->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
broadcast_first->addIdOperand(mask_id);
|
|
|
|
auto compare = std::make_unique<spv::Instruction>(builder.getUniqueId(), bvec4_type, spv::OpIEqual);
|
|
compare->addIdOperand(mask_id);
|
|
compare->addIdOperand(broadcast_first->getResultId());
|
|
|
|
auto compare_reduce = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, spv::OpAll);
|
|
compare_reduce->addIdOperand(compare->getResultId());
|
|
compare_reduce_id = compare_reduce->getResultId();
|
|
|
|
add_instruction(body_block, std::move(broadcast_first));
|
|
add_instruction(body_block, std::move(compare));
|
|
add_instruction(body_block, std::move(compare_reduce));
|
|
builder.createSelectionMerge(continue_block, 0);
|
|
builder.createConditionalBranch(compare_reduce_id, prefix_block, continue_block);
|
|
}
|
|
|
|
spv::Id result_id;
|
|
builder.setBuildPoint(prefix_block);
|
|
{
|
|
auto prefix_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), type_id, opcode);
|
|
prefix_op->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
prefix_op->addImmediateOperand(spv::GroupOperationExclusiveScan);
|
|
prefix_op->addIdOperand(value_id);
|
|
result_id = prefix_op->getResultId();
|
|
add_instruction(prefix_block, std::move(prefix_op));
|
|
builder.createBranch(continue_block);
|
|
}
|
|
|
|
builder.setBuildPoint(continue_block);
|
|
{
|
|
auto phi = std::make_unique<spv::Instruction>(builder.getUniqueId(), type_id, spv::OpPhi);
|
|
phi->addIdOperand(result_id);
|
|
phi->addIdOperand(prefix_block->getId());
|
|
phi->addIdOperand(undef_value);
|
|
phi->addIdOperand(body_block->getId());
|
|
result_id = phi->getResultId();
|
|
add_instruction(continue_block, std::move(phi));
|
|
builder.createConditionalBranch(compare_reduce_id, merge_block, header_block);
|
|
}
|
|
|
|
builder.setBuildPoint(merge_block);
|
|
|
|
if (return_block)
|
|
{
|
|
builder.createBranch(return_block);
|
|
builder.setBuildPoint(return_block);
|
|
|
|
auto phi = std::make_unique<spv::Instruction>(builder.getUniqueId(), type_id, spv::OpPhi);
|
|
phi->addIdOperand(result_id);
|
|
phi->addIdOperand(merge_block->getId());
|
|
phi->addIdOperand(undef_value);
|
|
phi->addIdOperand(outer_entry->getId());
|
|
result_id = phi->getResultId();
|
|
add_instruction(return_block, std::move(phi));
|
|
}
|
|
|
|
builder.makeReturn(false, result_id);
|
|
|
|
builder.setBuildPoint(current_build_point);
|
|
builder.addCapability(spv::CapabilityGroupNonUniformBallot);
|
|
builder.addCapability(spv::CapabilityGroupNonUniformArithmetic);
|
|
wave_multi_prefix_call_ids.push_back({ opcode, type_id, func->getId() });
|
|
return func->getId();
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_wave_multi_prefix_count_bits(SPIRVModule &module)
|
|
{
|
|
if (wave_multi_prefix_count_bits_id)
|
|
return wave_multi_prefix_count_bits_id;
|
|
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
spv::Id uint_type = builder.makeUintType(32);
|
|
spv::Id uvec4_type = builder.makeVectorType(uint_type, 4);
|
|
spv::Id bool_type = builder.makeBoolType();
|
|
spv::Id bvec4_type = builder.makeVectorType(bool_type, 4);
|
|
|
|
spv::Block *entry = nullptr;
|
|
Vector<spv::Id> types = { bool_type, uvec4_type };
|
|
if (!helper_lanes_participate_in_wave_ops)
|
|
types.push_back(bool_type);
|
|
auto *func = builder.makeFunctionEntry(spv::NoPrecision, uint_type,
|
|
"WaveMultiPrefixCountBits",
|
|
types, {}, &entry);
|
|
spv::Id value_id = func->getParamId(0);
|
|
spv::Id mask_id = func->getParamId(1);
|
|
auto *header_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *body_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *merge_block = new spv::Block(builder.getUniqueId(), *func);
|
|
spv::Block *outer_entry = nullptr;
|
|
spv::Block *return_block = nullptr;
|
|
spv::Id undef_id = 0;
|
|
|
|
builder.setBuildPoint(entry);
|
|
|
|
if (!helper_lanes_participate_in_wave_ops)
|
|
{
|
|
return_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *inner_entry_block = new spv::Block(builder.getUniqueId(), *func);
|
|
undef_id = builder.createUndefined(uint_type);
|
|
builder.createSelectionMerge(return_block, 0);
|
|
builder.createConditionalBranch(func->getParamId(2), return_block, inner_entry_block);
|
|
outer_entry = entry;
|
|
entry = inner_entry_block;
|
|
builder.setBuildPoint(entry);
|
|
}
|
|
|
|
{
|
|
auto ballot_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uvec4_type, spv::OpGroupNonUniformBallot);
|
|
ballot_op->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
ballot_op->addIdOperand(builder.makeBoolConstant(true));
|
|
auto mask_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uvec4_type, spv::OpBitwiseAnd);
|
|
mask_op->addIdOperand(ballot_op->getResultId());
|
|
mask_op->addIdOperand(mask_id);
|
|
mask_id = mask_op->getResultId();
|
|
add_instruction(entry, std::move(ballot_op));
|
|
add_instruction(entry, std::move(mask_op));
|
|
builder.createBranch(header_block);
|
|
}
|
|
|
|
builder.setBuildPoint(header_block);
|
|
{
|
|
builder.createLoopMerge(merge_block, body_block, 0);
|
|
builder.createBranch(body_block);
|
|
}
|
|
|
|
spv::Id result_id;
|
|
builder.setBuildPoint(body_block);
|
|
{
|
|
auto broadcast_first =
|
|
std::make_unique<spv::Instruction>(builder.getUniqueId(), uvec4_type, spv::OpGroupNonUniformBroadcastFirst);
|
|
broadcast_first->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
broadcast_first->addIdOperand(mask_id);
|
|
|
|
auto compare = std::make_unique<spv::Instruction>(builder.getUniqueId(), bvec4_type, spv::OpIEqual);
|
|
compare->addIdOperand(mask_id);
|
|
compare->addIdOperand(broadcast_first->getResultId());
|
|
|
|
auto compare_reduce = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, spv::OpAll);
|
|
compare_reduce->addIdOperand(compare->getResultId());
|
|
spv::Id compare_reduce_id = compare_reduce->getResultId();
|
|
|
|
auto prefix_input = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, spv::OpLogicalAnd);
|
|
prefix_input->addIdOperand(compare_reduce_id);
|
|
prefix_input->addIdOperand(value_id);
|
|
|
|
auto modified_ballot =
|
|
std::make_unique<spv::Instruction>(builder.getUniqueId(), uvec4_type, spv::OpGroupNonUniformBallot);
|
|
modified_ballot->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
modified_ballot->addIdOperand(prefix_input->getResultId());
|
|
auto count =
|
|
std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpGroupNonUniformBallotBitCount);
|
|
count->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
count->addImmediateOperand(spv::GroupOperationExclusiveScan);
|
|
count->addIdOperand(modified_ballot->getResultId());
|
|
result_id = count->getResultId();
|
|
|
|
add_instruction(body_block, std::move(broadcast_first));
|
|
add_instruction(body_block, std::move(compare));
|
|
add_instruction(body_block, std::move(compare_reduce));
|
|
add_instruction(body_block, std::move(prefix_input));
|
|
add_instruction(body_block, std::move(modified_ballot));
|
|
add_instruction(body_block, std::move(count));
|
|
builder.createConditionalBranch(compare_reduce_id, merge_block, header_block);
|
|
}
|
|
|
|
builder.setBuildPoint(merge_block);
|
|
|
|
if (return_block)
|
|
{
|
|
builder.createBranch(return_block);
|
|
builder.setBuildPoint(return_block);
|
|
|
|
auto phi = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpPhi);
|
|
phi->addIdOperand(result_id);
|
|
phi->addIdOperand(merge_block->getId());
|
|
phi->addIdOperand(undef_id);
|
|
phi->addIdOperand(outer_entry->getId());
|
|
result_id = phi->getResultId();
|
|
add_instruction(return_block, std::move(phi));
|
|
}
|
|
|
|
builder.makeReturn(false, result_id);
|
|
builder.setBuildPoint(current_build_point);
|
|
|
|
builder.addCapability(spv::CapabilityGroupNonUniformBallot);
|
|
builder.addCapability(spv::CapabilityGroupNonUniformArithmetic);
|
|
wave_multi_prefix_count_bits_id = func->getId();
|
|
return func->getId();
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_wave_active_all_equal_masked(SPIRVModule &module, spv::Id type_id)
|
|
{
|
|
for (auto &calls : wave_active_all_equal_masked_ids)
|
|
if (calls.first == type_id)
|
|
return calls.second;
|
|
|
|
builder.addCapability(spv::CapabilityGroupNonUniformVote);
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
spv::Block *entry = nullptr;
|
|
spv::Id bool_type = builder.makeBoolType();
|
|
auto *func = builder.makeFunctionEntry(spv::NoPrecision, bool_type,
|
|
"WaveActiveAllEqual",
|
|
{ type_id, bool_type }, {}, &entry);
|
|
|
|
auto *is_helper_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *is_active_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *merge_block = new spv::Block(builder.getUniqueId(), *func);
|
|
spv::Id equal_id, undef_id;
|
|
|
|
builder.setBuildPoint(entry);
|
|
|
|
builder.createSelectionMerge(merge_block, 0);
|
|
builder.createConditionalBranch(func->getParamId(1), is_helper_block, is_active_block);
|
|
|
|
{
|
|
builder.setBuildPoint(is_helper_block);
|
|
// Assist in scalar promotion, if we set something concrete, we will force VGPR.
|
|
undef_id = builder.createUndefined(bool_type);
|
|
builder.createBranch(merge_block);
|
|
}
|
|
|
|
{
|
|
builder.setBuildPoint(is_active_block);
|
|
auto all_equal = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, spv::OpGroupNonUniformAllEqual);
|
|
all_equal->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
all_equal->addIdOperand(func->getParamId(0));
|
|
equal_id = all_equal->getResultId();
|
|
add_instruction(is_active_block, std::move(all_equal));
|
|
builder.createBranch(merge_block);
|
|
}
|
|
|
|
builder.setBuildPoint(merge_block);
|
|
auto phi = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, spv::OpPhi);
|
|
phi->addIdOperand(equal_id);
|
|
phi->addIdOperand(is_active_block->getId());
|
|
phi->addIdOperand(undef_id);
|
|
phi->addIdOperand(is_helper_block->getId());
|
|
equal_id = phi->getResultId();
|
|
add_instruction(merge_block, std::move(phi));
|
|
|
|
builder.makeReturn(false, equal_id);
|
|
builder.setBuildPoint(current_build_point);
|
|
wave_active_all_equal_masked_ids.emplace_back(type_id, func->getId());
|
|
return func->getId();
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_wave_read_first_lane_masked(SPIRVModule &module, spv::Id type_id)
|
|
{
|
|
for (auto &calls : wave_read_first_lane_masked_ids)
|
|
if (calls.first == type_id)
|
|
return calls.second;
|
|
|
|
builder.addCapability(spv::CapabilityGroupNonUniformBallot);
|
|
builder.addCapability(spv::CapabilityGroupNonUniformShuffle);
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
spv::Block *entry = nullptr;
|
|
spv::Id bool_type = builder.makeBoolType();
|
|
auto *func = builder.makeFunctionEntry(spv::NoPrecision, type_id,
|
|
"WaveReadFirstLane",
|
|
{ type_id, bool_type }, {}, &entry);
|
|
|
|
builder.setBuildPoint(entry);
|
|
|
|
spv::Id uint_type = builder.makeUintType(32);
|
|
spv::Id uvec4_type = builder.makeVectorType(uint_type, 4);
|
|
|
|
// Shuffle path is more robust since it will avoid undefs.
|
|
// Also matches codegen on AMD.
|
|
// The branchy style where helpers receive undefs confuses NV it seems ...
|
|
|
|
auto not_inst = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(), bool_type, spv::OpLogicalNot);
|
|
not_inst->addIdOperand(func->getParamId(1));
|
|
|
|
auto ballot = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(), uvec4_type, spv::OpGroupNonUniformBallot);
|
|
ballot->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
ballot->addIdOperand(not_inst->getResultId());
|
|
|
|
auto lsb = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(), builder.makeUintType(32), spv::OpGroupNonUniformBallotFindLSB);
|
|
lsb->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
lsb->addIdOperand(ballot->getResultId());
|
|
|
|
auto shuffle = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(), type_id, spv::OpGroupNonUniformShuffle);
|
|
shuffle->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
shuffle->addIdOperand(func->getParamId(0));
|
|
shuffle->addIdOperand(lsb->getResultId());
|
|
|
|
// Undocumented requirement, if all lanes are helpers, 0 is returned.
|
|
auto ballot_real_lane_count = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(), uint_type, spv::OpGroupNonUniformBallotBitCount);
|
|
ballot_real_lane_count->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
ballot_real_lane_count->addImmediateOperand(spv::GroupOperationReduce);
|
|
ballot_real_lane_count->addIdOperand(ballot->getResultId());
|
|
auto ballot_has_real_lane = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(), bool_type, spv::OpINotEqual);
|
|
ballot_has_real_lane->addIdOperand(ballot_real_lane_count->getResultId());
|
|
ballot_has_real_lane->addIdOperand(builder.makeUintConstant(0));
|
|
|
|
auto select_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), type_id, spv::OpSelect);
|
|
select_op->addIdOperand(ballot_has_real_lane->getResultId());
|
|
select_op->addIdOperand(shuffle->getResultId());
|
|
select_op->addIdOperand(builder.makeNullConstant(type_id));
|
|
spv::Id ret_id = select_op->getResultId();
|
|
|
|
add_instruction(entry, std::move(not_inst));
|
|
add_instruction(entry, std::move(ballot));
|
|
add_instruction(entry, std::move(lsb));
|
|
add_instruction(entry, std::move(shuffle));
|
|
add_instruction(entry, std::move(ballot_real_lane_count));
|
|
add_instruction(entry, std::move(ballot_has_real_lane));
|
|
add_instruction(entry, std::move(select_op));
|
|
builder.makeReturn(false, ret_id);
|
|
|
|
builder.setBuildPoint(current_build_point);
|
|
wave_read_first_lane_masked_ids.emplace_back(type_id, func->getId());
|
|
return func->getId();
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_wave_is_first_lane_masked(SPIRVModule &module)
|
|
{
|
|
if (wave_is_first_lane_masked_id)
|
|
return wave_is_first_lane_masked_id;
|
|
|
|
spv::Block *entry = nullptr;
|
|
spv::Id bool_type = builder.makeBoolType();
|
|
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
builder.addCapability(spv::CapabilityGroupNonUniform);
|
|
|
|
auto *func = builder.makeFunctionEntry(spv::NoPrecision, bool_type,
|
|
"WaveIsFirstLane",
|
|
{ bool_type }, {}, &entry);
|
|
|
|
auto *is_helper_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *is_active_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *merge_block = new spv::Block(builder.getUniqueId(), *func);
|
|
spv::Id elect_id;
|
|
|
|
builder.setBuildPoint(entry);
|
|
|
|
builder.createSelectionMerge(merge_block, 0);
|
|
builder.createConditionalBranch(func->getParamId(0), is_helper_block, is_active_block);
|
|
|
|
{
|
|
builder.setBuildPoint(is_helper_block);
|
|
builder.createBranch(merge_block);
|
|
}
|
|
|
|
{
|
|
builder.setBuildPoint(is_active_block);
|
|
auto elect = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, spv::OpGroupNonUniformElect);
|
|
elect->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
elect_id = elect->getResultId();
|
|
add_instruction(is_active_block, std::move(elect));
|
|
builder.createBranch(merge_block);
|
|
}
|
|
|
|
builder.setBuildPoint(merge_block);
|
|
auto phi = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, spv::OpPhi);
|
|
phi->addIdOperand(elect_id);
|
|
phi->addIdOperand(is_active_block->getId());
|
|
phi->addIdOperand(builder.makeBoolConstant(false));
|
|
phi->addIdOperand(is_helper_block->getId());
|
|
elect_id = phi->getResultId();
|
|
add_instruction(merge_block, std::move(phi));
|
|
|
|
builder.makeReturn(false, elect_id);
|
|
builder.setBuildPoint(current_build_point);
|
|
wave_is_first_lane_masked_id = func->getId();
|
|
return wave_is_first_lane_masked_id;
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_wave_match(SPIRVModule &module, spv::Id type_id)
|
|
{
|
|
for (auto &type : wave_match_call_ids)
|
|
if (type.first == type_id)
|
|
return type.second;
|
|
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
builder.addCapability(spv::CapabilityGroupNonUniform);
|
|
builder.addCapability(spv::CapabilityGroupNonUniformBallot);
|
|
|
|
spv::Block *entry = nullptr;
|
|
spv::Id uint_type = builder.makeUintType(32);
|
|
spv::Id uvec4_type = builder.makeVectorType(uint_type, 4);
|
|
spv::Id bool_type = builder.makeBoolType();
|
|
Vector<spv::Id> types = { type_id };
|
|
if (!helper_lanes_participate_in_wave_ops)
|
|
types.push_back(bool_type);
|
|
auto *func = builder.makeFunctionEntry(spv::NoPrecision, uvec4_type,
|
|
"WaveMatch",
|
|
types, {}, &entry);
|
|
spv::Id value_id = func->getParamId(0);
|
|
|
|
auto *header_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *body_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *merge_block = new spv::Block(builder.getUniqueId(), *func);
|
|
spv::Block *outer_header = nullptr;
|
|
spv::Block *return_block = nullptr;
|
|
spv::Id undef_id = 0;
|
|
|
|
builder.setBuildPoint(entry);
|
|
builder.createBranch(header_block);
|
|
builder.setBuildPoint(header_block);
|
|
|
|
if (!helper_lanes_participate_in_wave_ops)
|
|
{
|
|
return_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *inner_header_block = new spv::Block(builder.getUniqueId(), *func);
|
|
undef_id = builder.createUndefined(uvec4_type);
|
|
builder.createSelectionMerge(return_block, 0);
|
|
builder.createConditionalBranch(func->getParamId(1), return_block, inner_header_block);
|
|
outer_header = header_block;
|
|
header_block = inner_header_block;
|
|
builder.setBuildPoint(header_block);
|
|
}
|
|
|
|
builder.createLoopMerge(merge_block, body_block, 0);
|
|
builder.createBranch(body_block);
|
|
|
|
auto broadcast_first = std::make_unique<spv::Instruction>(builder.getUniqueId(), type_id, spv::OpGroupNonUniformBroadcastFirst);
|
|
broadcast_first->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
broadcast_first->addIdOperand(value_id);
|
|
|
|
// We cannot scalarize floats safely due to NaNs. Caller will bitcast to uint first.
|
|
assert(builder.getTypeClass(type_id) != spv::OpTypeFloat);
|
|
spv::Op equal_op;
|
|
if (builder.getTypeClass(type_id) == spv::OpTypeBool)
|
|
equal_op = spv::OpLogicalEqual;
|
|
else
|
|
equal_op = spv::OpIEqual;
|
|
|
|
auto compare = std::make_unique<spv::Instruction>(builder.getUniqueId(), builder.makeBoolType(), equal_op);
|
|
compare->addIdOperand(value_id);
|
|
compare->addIdOperand(broadcast_first->getResultId());
|
|
spv::Id compare_id = compare->getResultId();
|
|
|
|
auto ballot = std::make_unique<spv::Instruction>(builder.getUniqueId(), uvec4_type, spv::OpGroupNonUniformBallot);
|
|
ballot->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
ballot->addIdOperand(compare->getResultId());
|
|
spv::Id ballot_id = ballot->getResultId();
|
|
|
|
builder.setBuildPoint(body_block);
|
|
add_instruction(body_block, std::move(broadcast_first));
|
|
add_instruction(body_block, std::move(compare));
|
|
add_instruction(body_block, std::move(ballot));
|
|
builder.createConditionalBranch(compare_id, merge_block, header_block);
|
|
|
|
builder.setBuildPoint(merge_block);
|
|
|
|
if (return_block)
|
|
{
|
|
builder.createBranch(return_block);
|
|
builder.setBuildPoint(return_block);
|
|
|
|
auto phi = std::make_unique<spv::Instruction>(builder.getUniqueId(), uvec4_type, spv::OpPhi);
|
|
phi->addIdOperand(ballot_id);
|
|
phi->addIdOperand(merge_block->getId());
|
|
phi->addIdOperand(undef_id);
|
|
phi->addIdOperand(outer_header->getId());
|
|
ballot_id = phi->getResultId();
|
|
add_instruction(return_block, std::move(phi));
|
|
}
|
|
|
|
builder.makeReturn(false, ballot_id);
|
|
builder.setBuildPoint(current_build_point);
|
|
|
|
wave_match_call_ids.emplace_back(type_id, func->getId());
|
|
return func->getId();
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_quad_vote(SPIRVModule &module, HelperCall call)
|
|
{
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
spv::Block *entry = nullptr;
|
|
spv::Id bool_type = builder.makeBoolType();
|
|
auto *func = builder.makeFunctionEntry(spv::NoPrecision, bool_type,
|
|
call == HelperCall::QuadAll ? "QuadAll" : "QuadAny",
|
|
{ bool_type }, {}, &entry);
|
|
|
|
builder.setBuildPoint(entry);
|
|
spv::Id ids[4];
|
|
for (unsigned i = 0; i < 4; i++)
|
|
{
|
|
auto broadcast = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(), bool_type, spv::OpGroupNonUniformQuadBroadcast);
|
|
broadcast->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
broadcast->addIdOperand(func->getParamId(0));
|
|
broadcast->addIdOperand(builder.makeUintConstant(i));
|
|
ids[i] = broadcast->getResultId();
|
|
add_instruction(entry, std::move(broadcast));
|
|
}
|
|
|
|
spv::Op op = call == HelperCall::QuadAll ? spv::OpLogicalAnd : spv::OpLogicalOr;
|
|
|
|
spv::Id ret_id = ids[0];
|
|
for (unsigned i = 1; i < 4; i++)
|
|
{
|
|
auto logic_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, op);
|
|
logic_op->addIdOperand(ret_id);
|
|
logic_op->addIdOperand(ids[i]);
|
|
ret_id = logic_op->getResultId();
|
|
add_instruction(entry, std::move(logic_op));
|
|
}
|
|
|
|
builder.addCapability(spv::CapabilityGroupNonUniformQuad);
|
|
builder.makeReturn(false, ret_id);
|
|
builder.setBuildPoint(current_build_point);
|
|
return func->getId();
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_quad_all(SPIRVModule &module)
|
|
{
|
|
if (quad_all_call_id)
|
|
return quad_all_call_id;
|
|
quad_all_call_id = build_quad_vote(module, HelperCall::QuadAll);
|
|
return quad_all_call_id;
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_quad_any(SPIRVModule &module)
|
|
{
|
|
if (quad_any_call_id)
|
|
return quad_any_call_id;
|
|
quad_any_call_id = build_quad_vote(module, HelperCall::QuadAny);
|
|
return quad_any_call_id;
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_image_atomic_r64_compact(
|
|
SPIRVModule &module, bool array, bool non_uniform)
|
|
{
|
|
spv::Id *call_id;
|
|
const char *name;
|
|
if (array)
|
|
{
|
|
if (non_uniform)
|
|
{
|
|
call_id = &image_r64_array_atomic_non_uniform_call_id;
|
|
name = "WriteFeedbackArrayNonUniform";
|
|
}
|
|
else
|
|
{
|
|
call_id = &image_r64_array_atomic_call_id;
|
|
name = "WriteFeedbackArray";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (non_uniform)
|
|
{
|
|
call_id = &image_r64_atomic_non_uniform_call_id;
|
|
name = "WriteFeedbackNonUniform";
|
|
}
|
|
else
|
|
{
|
|
call_id = &image_r64_atomic_call_id;
|
|
name = "WriteFeedback";
|
|
}
|
|
}
|
|
|
|
if (*call_id)
|
|
return *call_id;
|
|
|
|
builder.addCapability(spv::CapabilityGroupNonUniform);
|
|
builder.addCapability(spv::CapabilityGroupNonUniformBallot);
|
|
builder.addCapability(spv::CapabilityGroupNonUniformArithmetic);
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
|
|
spv::Id image_type = builder.makeImageType(builder.makeUintType(64),
|
|
spv::Dim2D, false, array, false, 2, spv::ImageFormatR64ui);
|
|
spv::Id ptr_image_type = builder.makePointer(spv::StorageClassUniformConstant, image_type);
|
|
|
|
spv::Block *entry = nullptr;
|
|
spv::Id bool_type = builder.makeBoolType();
|
|
spv::Id int_type = builder.makeIntType(32);
|
|
spv::Id coord_type = builder.makeVectorType(int_type, array ? 3 : 2);
|
|
spv::Id bvec_type = builder.makeVectorType(bool_type, array ? 3 : 2);
|
|
spv::Id u64_type = builder.makeUintType(64);
|
|
spv::Id ptr_atomic_type = builder.makePointer(spv::StorageClassImage, u64_type);
|
|
auto *func = builder.makeFunctionEntry(spv::NoPrecision, builder.makeVoidType(), name,
|
|
{ ptr_image_type, coord_type, u64_type, bool_type }, {}, &entry);
|
|
|
|
spv::Id image_ptr = func->getParamId(0);
|
|
spv::Id coord = func->getParamId(1);
|
|
spv::Id value = func->getParamId(2);
|
|
spv::Id active = func->getParamId(3);
|
|
|
|
builder.addName(image_ptr, "img");
|
|
builder.addName(coord, "coord");
|
|
builder.addName(value, "value");
|
|
builder.addName(active, "active_lane");
|
|
|
|
auto *body_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *merge_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *loop_merge_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *loop_body_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *loop_continue_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *write_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *write_merge_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *elect_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *elect_merge_block = new spv::Block(builder.getUniqueId(), *func);
|
|
|
|
builder.setBuildPoint(entry);
|
|
builder.createSelectionMerge(merge_block, 0);
|
|
builder.createConditionalBranch(active, body_block, merge_block);
|
|
|
|
spv::Id var_is_done = builder.createVariableWithInitializer(spv::StorageClassFunction, bool_type,
|
|
builder.makeBoolConstant(false), "is_done");
|
|
|
|
builder.setBuildPoint(body_block);
|
|
{
|
|
auto load = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, spv::OpLoad);
|
|
auto inot = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, spv::OpLogicalNot);
|
|
spv::Id inot_id = inot->getResultId();
|
|
load->addIdOperand(var_is_done);
|
|
inot->addIdOperand(load->getResultId());
|
|
add_instruction(body_block, std::move(load));
|
|
add_instruction(body_block, std::move(inot));
|
|
builder.createLoopMerge(loop_merge_block, loop_continue_block, 0);
|
|
builder.createConditionalBranch(inot_id, loop_body_block, loop_merge_block);
|
|
|
|
builder.setBuildPoint(loop_body_block);
|
|
{
|
|
auto first = std::make_unique<spv::Instruction>(builder.getUniqueId(), coord_type, spv::OpGroupNonUniformBroadcastFirst);
|
|
auto compare_coord = std::make_unique<spv::Instruction>(builder.getUniqueId(), bvec_type, spv::OpIEqual);
|
|
auto all_equal = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, spv::OpAll);
|
|
auto store = std::make_unique<spv::Instruction>(spv::OpStore);
|
|
|
|
first->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
first->addIdOperand(coord);
|
|
compare_coord->addIdOperand(coord);
|
|
compare_coord->addIdOperand(first->getResultId());
|
|
all_equal->addIdOperand(compare_coord->getResultId());
|
|
store->addIdOperand(var_is_done);
|
|
store->addIdOperand(all_equal->getResultId());
|
|
|
|
spv::Id cond_id = all_equal->getResultId();
|
|
add_instruction(loop_body_block, std::move(first));
|
|
add_instruction(loop_body_block, std::move(compare_coord));
|
|
add_instruction(loop_body_block, std::move(all_equal));
|
|
add_instruction(loop_body_block, std::move(store));
|
|
|
|
builder.createSelectionMerge(write_merge_block, 0);
|
|
builder.createConditionalBranch(cond_id, write_block, write_merge_block);
|
|
builder.setBuildPoint(write_block);
|
|
{
|
|
auto or_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), u64_type, spv::OpGroupNonUniformBitwiseOr);
|
|
auto elect = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, spv::OpGroupNonUniformElect);
|
|
or_op->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
or_op->addImmediateOperand(spv::GroupOperationReduce);
|
|
or_op->addIdOperand(value);
|
|
elect->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
spv::Id elect_id = elect->getResultId();
|
|
spv::Id or_op_id = or_op->getResultId();
|
|
add_instruction(write_block, std::move(or_op));
|
|
add_instruction(write_block, std::move(elect));
|
|
|
|
builder.createSelectionMerge(elect_merge_block, 0);
|
|
builder.createConditionalBranch(elect_id, elect_block, elect_merge_block);
|
|
builder.setBuildPoint(elect_block);
|
|
{
|
|
auto texel = std::make_unique<spv::Instruction>(builder.getUniqueId(), ptr_atomic_type, spv::OpImageTexelPointer);
|
|
if (non_uniform)
|
|
builder.addDecoration(texel->getResultId(), spv::DecorationNonUniform);
|
|
auto atomic_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), u64_type, spv::OpAtomicOr);
|
|
texel->addIdOperand(image_ptr);
|
|
texel->addIdOperand(coord);
|
|
texel->addIdOperand(builder.makeIntConstant(0));
|
|
atomic_op->addIdOperand(texel->getResultId());
|
|
atomic_op->addIdOperand(builder.getAtomicDeviceScopeId());
|
|
atomic_op->addIdOperand(builder.makeUintConstant(0));
|
|
atomic_op->addIdOperand(or_op_id);
|
|
add_instruction(elect_block, std::move(texel));
|
|
add_instruction(elect_block, std::move(atomic_op));
|
|
builder.createBranch(elect_merge_block);
|
|
}
|
|
|
|
builder.setBuildPoint(elect_merge_block);
|
|
builder.createBranch(write_merge_block);
|
|
}
|
|
|
|
builder.setBuildPoint(write_merge_block);
|
|
builder.createBranch(loop_continue_block);
|
|
|
|
builder.setBuildPoint(loop_continue_block);
|
|
builder.createBranch(body_block);
|
|
}
|
|
|
|
builder.setBuildPoint(loop_merge_block);
|
|
builder.createBranch(merge_block);
|
|
}
|
|
|
|
builder.setBuildPoint(merge_block);
|
|
builder.makeReturn(false);
|
|
|
|
builder.addCapability(spv::CapabilityInt64Atomics);
|
|
|
|
builder.setBuildPoint(current_build_point);
|
|
*call_id = func->getId();
|
|
return func->getId();
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_node_coalesce_payload_offset(
|
|
SPIRVModule &module, const spv::Id *ids, uint32_t id_count)
|
|
{
|
|
if (id_count != 3)
|
|
return 0;
|
|
|
|
if (node_coalesce_payload_offset_call_id)
|
|
return node_coalesce_payload_offset_call_id;
|
|
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
spv::Block *entry = nullptr;
|
|
spv::Id uint_type = builder.makeUintType(32);
|
|
spv::Id uvec2_type = builder.makeVectorType(uint_type, 2);
|
|
auto *func = builder.makeFunctionEntry(spv::NoPrecision, uint_type,
|
|
"NodeCoalescePayloadOffset",
|
|
{ uint_type, uvec2_type },
|
|
{}, &entry);
|
|
|
|
builder.addName(func->getParamId(0), "offset");
|
|
builder.addName(func->getParamId(1), "offsets_bda");
|
|
|
|
auto *is_entry_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *is_node_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *merge = new spv::Block(builder.getUniqueId(), *func);
|
|
|
|
spv::Id is_entry_id = ids[0];
|
|
spv::Id private_stride_var_id = ids[1];
|
|
spv::Id u32_array_ptr_type_id = ids[2];
|
|
spv::Id entry_result_id = 0;
|
|
spv::Id node_result_id = 0;
|
|
|
|
builder.setBuildPoint(entry);
|
|
builder.createSelectionMerge(merge, 0);
|
|
builder.createConditionalBranch(is_entry_id, is_entry_block, is_node_block);
|
|
|
|
builder.setBuildPoint(is_entry_block);
|
|
{
|
|
auto load = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(), uint_type, spv::OpLoad);
|
|
auto mul = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(), uint_type, spv::OpIMul);
|
|
|
|
load->addIdOperand(private_stride_var_id);
|
|
mul->addIdOperand(load->getResultId());
|
|
mul->addIdOperand(func->getParamId(0));
|
|
|
|
entry_result_id = mul->getResultId();
|
|
add_instruction(is_entry_block, std::move(load));
|
|
add_instruction(is_entry_block, std::move(mul));
|
|
builder.createBranch(merge);
|
|
}
|
|
|
|
builder.setBuildPoint(is_node_block);
|
|
{
|
|
auto bitcast = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(), u32_array_ptr_type_id, spv::OpBitcast);
|
|
auto chain = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(), builder.makePointer(spv::StorageClassPhysicalStorageBuffer, uint_type),
|
|
spv::OpInBoundsAccessChain);
|
|
auto load = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(), uint_type, spv::OpLoad);
|
|
|
|
bitcast->addIdOperand(func->getParamId(1));
|
|
chain->addIdOperand(bitcast->getResultId());
|
|
chain->addIdOperand(builder.makeUintConstant(0));
|
|
chain->addIdOperand(func->getParamId(0));
|
|
load->addIdOperand(chain->getResultId());
|
|
load->addImmediateOperand(spv::MemoryAccessAlignedMask);
|
|
load->addImmediateOperand(sizeof(uint32_t));
|
|
node_result_id = load->getResultId();
|
|
add_instruction(is_node_block, std::move(bitcast));
|
|
add_instruction(is_node_block, std::move(chain));
|
|
add_instruction(is_node_block, std::move(load));
|
|
builder.createBranch(merge);
|
|
}
|
|
|
|
builder.setBuildPoint(merge);
|
|
|
|
auto phi = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpPhi);
|
|
phi->addIdOperand(entry_result_id);
|
|
phi->addIdOperand(is_entry_block->getId());
|
|
phi->addIdOperand(node_result_id);
|
|
phi->addIdOperand(is_node_block->getId());
|
|
spv::Id return_value = phi->getResultId();
|
|
add_instruction(merge, std::move(phi));
|
|
builder.makeReturn(false, return_value);
|
|
node_coalesce_payload_offset_call_id = func->getId();
|
|
builder.setBuildPoint(current_build_point);
|
|
return func->getId();
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_allocate_node_records_waterfall(SPIRVModule &module)
|
|
{
|
|
if (allocate_thread_node_records_waterfall_call_id)
|
|
return allocate_thread_node_records_waterfall_call_id;
|
|
|
|
spv::Id inner_call_id = module.get_helper_call_id(HelperCall::AllocateThreadNodeRecords);
|
|
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
spv::Block *entry = nullptr;
|
|
spv::Id bool_type = builder.makeBoolType();
|
|
spv::Id uint_type = builder.makeUintType(32);
|
|
spv::Id uint64_type = builder.makeUintType(64);
|
|
auto *func = builder.makeFunctionEntry(spv::NoPrecision, uint_type,
|
|
"AllocateThreadNodeRecordsWaterfall",
|
|
{ uint64_type, uint_type, uint_type, uint_type, uint_type },
|
|
{}, &entry);
|
|
|
|
builder.addName(func->getParamId(0), "AtomicCountersBDA");
|
|
builder.addName(func->getParamId(1), "NodeMetadataIndex");
|
|
builder.addName(func->getParamId(2), "Count");
|
|
builder.addName(func->getParamId(3), "Stride");
|
|
builder.addName(func->getParamId(4), "AllocationOffset");
|
|
|
|
auto *loop_header = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *merge_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *body_header = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *body_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *continue_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *body_merge = new spv::Block(builder.getUniqueId(), *func);
|
|
|
|
builder.setBuildPoint(entry);
|
|
builder.createBranch(loop_header);
|
|
|
|
builder.setBuildPoint(loop_header);
|
|
builder.createLoopMerge(merge_block, continue_block, 0);
|
|
builder.createBranch(body_header);
|
|
|
|
builder.setBuildPoint(body_header);
|
|
auto broadcast = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpGroupNonUniformBroadcastFirst);
|
|
broadcast->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
broadcast->addIdOperand(func->getParamId(1));
|
|
spv::Id broadcast_id = broadcast->getResultId();
|
|
|
|
auto do_work = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, spv::OpIEqual);
|
|
do_work->addIdOperand(broadcast->getResultId());
|
|
do_work->addIdOperand(func->getParamId(1));
|
|
spv::Id do_work_cond_id = do_work->getResultId();
|
|
|
|
add_instruction(body_header, std::move(broadcast));
|
|
add_instruction(body_header, std::move(do_work));
|
|
builder.createSelectionMerge(body_merge, 0);
|
|
builder.createConditionalBranch(do_work_cond_id, body_block, body_merge);
|
|
|
|
builder.setBuildPoint(body_block);
|
|
spv::Id return_value;
|
|
{
|
|
auto call_inner = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpFunctionCall);
|
|
call_inner->addIdOperand(inner_call_id);
|
|
call_inner->addIdOperand(func->getParamId(0));
|
|
call_inner->addIdOperand(broadcast_id);
|
|
call_inner->addIdOperand(func->getParamId(2));
|
|
call_inner->addIdOperand(func->getParamId(3));
|
|
call_inner->addIdOperand(func->getParamId(4));
|
|
|
|
return_value = call_inner->getResultId();
|
|
add_instruction(body_block, std::move(call_inner));
|
|
builder.createBranch(merge_block);
|
|
}
|
|
|
|
builder.setBuildPoint(body_merge);
|
|
builder.createBranch(continue_block);
|
|
builder.setBuildPoint(continue_block);
|
|
builder.createBranch(loop_header);
|
|
|
|
builder.setBuildPoint(merge_block);
|
|
builder.makeReturn(false, return_value);
|
|
builder.setBuildPoint(current_build_point);
|
|
allocate_thread_node_records_waterfall_call_id = func->getId();
|
|
return func->getId();
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_increment_node_count(SPIRVModule &, bool per_thread)
|
|
{
|
|
auto &call_id = per_thread ? increment_thread_node_count_call_id : increment_group_node_count_call_id;
|
|
if (call_id)
|
|
return call_id;
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
spv::Block *entry = nullptr;
|
|
spv::Id bool_type = builder.makeBoolType();
|
|
spv::Id uint_type = builder.makeUintType(32);
|
|
spv::Id uint64_type = builder.makeUintType(64);
|
|
auto *func = builder.makeFunctionEntry(spv::NoPrecision, builder.makeVoidType(),
|
|
per_thread ? "IncrementThreadNodeCount" : "IncrementGroupNodeCount",
|
|
{ uint64_type, uint_type, uint_type },
|
|
{}, &entry);
|
|
|
|
builder.addName(func->getParamId(0), "AtomicCountersBDA");
|
|
builder.addName(func->getParamId(1), "NodeMetadataIndex");
|
|
builder.addName(func->getParamId(2), "Count");
|
|
|
|
spv::Id total_count_id = func->getParamId(2);
|
|
|
|
spv::Block *body_block = nullptr;
|
|
spv::Block *merge_block = nullptr;
|
|
|
|
if (!per_thread)
|
|
{
|
|
body_block = new spv::Block(builder.getUniqueId(), *func);
|
|
merge_block = new spv::Block(builder.getUniqueId(), *func);
|
|
}
|
|
|
|
spv::Id uint_array_type = builder.makeRuntimeArray(uint_type);
|
|
builder.addDecoration(uint_array_type, spv::DecorationArrayStride, 4);
|
|
spv::Id struct_type_id = builder.makeStructType({ uint_type, uint_type, uint_array_type }, "NodeAtomicsEmpty");
|
|
builder.addDecoration(struct_type_id, spv::DecorationBlock);
|
|
builder.addMemberName(struct_type_id, 0, "payloadCount");
|
|
builder.addMemberName(struct_type_id, 1, "fusedCount");
|
|
builder.addMemberName(struct_type_id, 2, "perNodeTotal");
|
|
builder.addMemberDecoration(struct_type_id, 0, spv::DecorationOffset, 0);
|
|
builder.addMemberDecoration(struct_type_id, 1, spv::DecorationOffset, 4);
|
|
builder.addMemberDecoration(struct_type_id, 2, spv::DecorationOffset, 8);
|
|
|
|
auto cast_op = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(),
|
|
builder.makePointer(spv::StorageClassPhysicalStorageBuffer, struct_type_id),
|
|
spv::OpBitcast);
|
|
cast_op->addIdOperand(func->getParamId(0));
|
|
|
|
auto chain_index_op = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(),
|
|
builder.makePointer(spv::StorageClassPhysicalStorageBuffer, uint_type),
|
|
spv::OpInBoundsAccessChain);
|
|
chain_index_op->addIdOperand(cast_op->getResultId());
|
|
chain_index_op->addIdOperand(builder.makeUintConstant(2));
|
|
chain_index_op->addIdOperand(func->getParamId(1));
|
|
|
|
auto atomic_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpAtomicIAdd);
|
|
atomic_op->addIdOperand(chain_index_op->getResultId());
|
|
atomic_op->addIdOperand(builder.getAtomicDeviceScopeId());
|
|
atomic_op->addIdOperand(builder.makeUintConstant(0)); // There is no implied sync.
|
|
atomic_op->addIdOperand(total_count_id);
|
|
|
|
auto *body = per_thread ? entry : body_block;
|
|
add_instruction(body, std::move(cast_op));
|
|
add_instruction(body, std::move(chain_index_op));
|
|
add_instruction(body, std::move(atomic_op));
|
|
|
|
if (!per_thread)
|
|
{
|
|
spv::Id local_invocation_index = get_builtin_shader_input(spv::BuiltInLocalInvocationIndex);
|
|
auto load_local_index = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpLoad);
|
|
load_local_index->addIdOperand(local_invocation_index);
|
|
|
|
auto is_first_lane = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, spv::OpIEqual);
|
|
is_first_lane->addIdOperand(load_local_index->getResultId());
|
|
is_first_lane->addIdOperand(builder.makeUintConstant(0));
|
|
spv::Id is_first_lane_id = is_first_lane->getResultId();
|
|
add_instruction(entry, std::move(load_local_index));
|
|
add_instruction(entry, std::move(is_first_lane));
|
|
|
|
builder.setBuildPoint(entry);
|
|
builder.createSelectionMerge(merge_block, 0);
|
|
builder.createConditionalBranch(is_first_lane_id, body_block, merge_block);
|
|
|
|
builder.setBuildPoint(body_block);
|
|
builder.createBranch(merge_block);
|
|
builder.setBuildPoint(merge_block);
|
|
builder.makeReturn(false);
|
|
}
|
|
else
|
|
{
|
|
builder.setBuildPoint(entry);
|
|
builder.makeReturn(false);
|
|
}
|
|
|
|
builder.setBuildPoint(current_build_point);
|
|
call_id = func->getId();
|
|
return func->getId();
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_allocate_node_records(SPIRVModule &, bool per_thread)
|
|
{
|
|
auto &call_id = per_thread ? allocate_thread_node_records_call_id : allocate_group_node_records_call_id;
|
|
if (call_id)
|
|
return call_id;
|
|
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
spv::Block *entry = nullptr;
|
|
spv::Id bool_type = builder.makeBoolType();
|
|
spv::Id uint_type = builder.makeUintType(32);
|
|
spv::Id uint64_type = builder.makeUintType(64);
|
|
auto *func = builder.makeFunctionEntry(spv::NoPrecision, uint_type,
|
|
per_thread ? "AllocateThreadNodeRecords" : "AllocateGroupNodeRecords",
|
|
{ uint64_type, uint_type, uint_type, uint_type, uint_type },
|
|
{}, &entry);
|
|
|
|
builder.addName(func->getParamId(0), "AtomicCountersBDA");
|
|
builder.addName(func->getParamId(1), "NodeMetadataIndex");
|
|
builder.addName(func->getParamId(2), "Count");
|
|
builder.addName(func->getParamId(3), "Stride");
|
|
builder.addName(func->getParamId(4), "AllocationOffset");
|
|
|
|
spv::Id total_count_id;
|
|
spv::Id phi_undefined_id = 0;
|
|
|
|
if (per_thread)
|
|
{
|
|
// Do this on a per-wave basis. The node metadata index can be divergent, and we need to consider
|
|
// that the outer function is a waterfall loop.
|
|
builder.addCapability(spv::CapabilityGroupNonUniformBallot);
|
|
builder.addCapability(spv::CapabilityGroupNonUniformArithmetic);
|
|
|
|
auto counts_total =
|
|
std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpGroupNonUniformIAdd);
|
|
counts_total->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
counts_total->addImmediateOperand(spv::GroupOperationReduce);
|
|
counts_total->addIdOperand(func->getParamId(2));
|
|
|
|
total_count_id = counts_total->getResultId();
|
|
add_instruction(entry, std::move(counts_total));
|
|
|
|
phi_undefined_id = builder.createUndefined(uint_type);
|
|
}
|
|
else
|
|
{
|
|
total_count_id = func->getParamId(2);
|
|
}
|
|
|
|
auto *return_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto check_empty = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, spv::OpIEqual);
|
|
check_empty->addIdOperand(total_count_id);
|
|
check_empty->addIdOperand(builder.makeUintConstant(0));
|
|
spv::Id return_early_cond_id = check_empty->getResultId();
|
|
add_instruction(entry, std::move(check_empty));
|
|
|
|
auto *reconverge_block = new spv::Block(builder.getUniqueId(), *func);
|
|
builder.setBuildPoint(entry);
|
|
builder.createSelectionMerge(reconverge_block, 0);
|
|
builder.createConditionalBranch(return_early_cond_id, return_block, reconverge_block);
|
|
builder.setBuildPoint(return_block);
|
|
builder.makeReturn(false, builder.makeUintConstant(0));
|
|
|
|
auto *body_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *merge_block = new spv::Block(builder.getUniqueId(), *func);
|
|
|
|
spv::Id shared_id = 0;
|
|
if (!per_thread)
|
|
shared_id = create_variable(spv::StorageClassWorkgroup, uint_type, "AllocateGroupNodeRecordsShared");
|
|
|
|
spv::Id is_first_lane_id;
|
|
|
|
if (per_thread)
|
|
{
|
|
auto is_first_lane = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, spv::OpGroupNonUniformElect);
|
|
is_first_lane->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
is_first_lane_id = is_first_lane->getResultId();
|
|
add_instruction(reconverge_block, std::move(is_first_lane));
|
|
}
|
|
else
|
|
{
|
|
spv::Id local_invocation_index = get_builtin_shader_input(spv::BuiltInLocalInvocationIndex);
|
|
auto load_local_index = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpLoad);
|
|
load_local_index->addIdOperand(local_invocation_index);
|
|
|
|
auto is_first_lane = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, spv::OpIEqual);
|
|
is_first_lane->addIdOperand(load_local_index->getResultId());
|
|
is_first_lane->addIdOperand(builder.makeUintConstant(0));
|
|
is_first_lane_id = is_first_lane->getResultId();
|
|
add_instruction(reconverge_block, std::move(load_local_index));
|
|
add_instruction(reconverge_block, std::move(is_first_lane));
|
|
}
|
|
|
|
builder.setBuildPoint(reconverge_block);
|
|
builder.createSelectionMerge(merge_block, 0);
|
|
builder.createConditionalBranch(is_first_lane_id, body_block, merge_block);
|
|
|
|
spv::Id payload_offset_id;
|
|
builder.setBuildPoint(body_block);
|
|
{
|
|
// Compute required payload bytes to allocate, and align.
|
|
auto mul_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpIMul);
|
|
mul_op->addIdOperand(total_count_id);
|
|
mul_op->addIdOperand(func->getParamId(3));
|
|
|
|
auto add_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpIAdd);
|
|
add_op->addIdOperand(mul_op->getResultId());
|
|
add_op->addIdOperand(builder.makeUintConstant(15));
|
|
|
|
auto and_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpBitwiseAnd);
|
|
and_op->addIdOperand(add_op->getResultId());
|
|
and_op->addIdOperand(builder.makeUintConstant(~15u));
|
|
|
|
spv::Id uint_array_type = builder.makeRuntimeArray(uint_type);
|
|
builder.addDecoration(uint_array_type, spv::DecorationArrayStride, 4);
|
|
spv::Id struct_type_id = builder.makeStructType({ uint_type, uint_type, uint_array_type }, "NodeAtomics");
|
|
builder.addDecoration(struct_type_id, spv::DecorationBlock);
|
|
builder.addMemberName(struct_type_id, 0, "payloadCount");
|
|
builder.addMemberName(struct_type_id, 1, "fusedCount");
|
|
builder.addMemberName(struct_type_id, 2, "perNodeTotal");
|
|
builder.addMemberDecoration(struct_type_id, 0, spv::DecorationOffset, 0);
|
|
builder.addMemberDecoration(struct_type_id, 1, spv::DecorationOffset, 4);
|
|
builder.addMemberDecoration(struct_type_id, 2, spv::DecorationOffset, 8);
|
|
|
|
auto cast_op = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(),
|
|
builder.makePointer(spv::StorageClassPhysicalStorageBuffer, struct_type_id),
|
|
spv::OpBitcast);
|
|
cast_op->addIdOperand(func->getParamId(0));
|
|
|
|
auto chain_payload_total_op = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(),
|
|
builder.makePointer(spv::StorageClassPhysicalStorageBuffer, uint_type),
|
|
spv::OpInBoundsAccessChain);
|
|
chain_payload_total_op->addIdOperand(cast_op->getResultId());
|
|
chain_payload_total_op->addIdOperand(builder.makeUintConstant(0));
|
|
|
|
auto chain_fused_total_op = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(),
|
|
builder.makePointer(spv::StorageClassPhysicalStorageBuffer, uint_type),
|
|
spv::OpInBoundsAccessChain);
|
|
chain_fused_total_op->addIdOperand(cast_op->getResultId());
|
|
chain_fused_total_op->addIdOperand(builder.makeUintConstant(1));
|
|
|
|
auto payload_total_atomic_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpAtomicIAdd);
|
|
payload_total_atomic_op->addIdOperand(chain_payload_total_op->getResultId());
|
|
payload_total_atomic_op->addIdOperand(builder.getAtomicDeviceScopeId());
|
|
payload_total_atomic_op->addIdOperand(builder.makeUintConstant(0)); // There is no implied sync.
|
|
payload_total_atomic_op->addIdOperand(and_op->getResultId());
|
|
|
|
spv::Id node_index = func->getParamId(1);
|
|
|
|
auto chain_node_total_op = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(),
|
|
builder.makePointer(spv::StorageClassPhysicalStorageBuffer, uint_type),
|
|
spv::OpInBoundsAccessChain);
|
|
chain_node_total_op->addIdOperand(cast_op->getResultId());
|
|
chain_node_total_op->addIdOperand(builder.makeUintConstant(2));
|
|
chain_node_total_op->addIdOperand(node_index);
|
|
|
|
auto atomic_total_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpAtomicIAdd);
|
|
atomic_total_op->addIdOperand(chain_node_total_op->getResultId());
|
|
atomic_total_op->addIdOperand(builder.getAtomicDeviceScopeId());
|
|
atomic_total_op->addIdOperand(builder.makeUintConstant(0)); // There is no implied sync.
|
|
atomic_total_op->addIdOperand(total_count_id);
|
|
|
|
auto shift_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpShiftLeftLogical);
|
|
shift_op->addIdOperand(node_index);
|
|
shift_op->addIdOperand(builder.makeUintConstant(8));
|
|
|
|
auto count_minus_1 = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpISub);
|
|
count_minus_1->addIdOperand(total_count_id);
|
|
count_minus_1->addIdOperand(builder.makeUintConstant(1));
|
|
|
|
auto or_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpBitwiseOr);
|
|
or_op->addIdOperand(shift_op->getResultId());
|
|
or_op->addIdOperand(count_minus_1->getResultId());
|
|
|
|
auto fused_atomic_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpAtomicIIncrement);
|
|
fused_atomic_op->addIdOperand(chain_fused_total_op->getResultId());
|
|
fused_atomic_op->addIdOperand(builder.getAtomicDeviceScopeId());
|
|
fused_atomic_op->addIdOperand(builder.makeUintConstant(0)); // There is no implied sync.
|
|
|
|
auto fused_mul2 = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpIMul);
|
|
fused_mul2->addIdOperand(builder.makeUintConstant(2));
|
|
fused_mul2->addIdOperand(fused_atomic_op->getResultId());
|
|
|
|
auto payload_add_atomic_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpIAdd);
|
|
payload_add_atomic_op->addIdOperand(func->getParamId(4));
|
|
payload_add_atomic_op->addIdOperand(fused_mul2->getResultId());
|
|
|
|
auto payload_add_atomic_op_plus_1 = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpIAdd);
|
|
payload_add_atomic_op_plus_1->addIdOperand(payload_add_atomic_op->getResultId());
|
|
payload_add_atomic_op_plus_1->addIdOperand(builder.makeUintConstant(1));
|
|
|
|
auto chain_payload_op = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(),
|
|
builder.makePointer(spv::StorageClassPhysicalStorageBuffer, uint_type),
|
|
spv::OpInBoundsAccessChain);
|
|
chain_payload_op->addIdOperand(cast_op->getResultId());
|
|
chain_payload_op->addIdOperand(builder.makeUintConstant(2));
|
|
chain_payload_op->addIdOperand(payload_add_atomic_op->getResultId());
|
|
|
|
auto store_payload_inst = std::make_unique<spv::Instruction>(spv::OpStore);
|
|
store_payload_inst->addIdOperand(chain_payload_op->getResultId());
|
|
store_payload_inst->addIdOperand(or_op->getResultId());
|
|
store_payload_inst->addImmediateOperand(spv::MemoryAccessAlignedMask);
|
|
store_payload_inst->addImmediateOperand(8);
|
|
|
|
auto chain_payload_op_plus_1 = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(),
|
|
builder.makePointer(spv::StorageClassPhysicalStorageBuffer, uint_type),
|
|
spv::OpInBoundsAccessChain);
|
|
chain_payload_op_plus_1->addIdOperand(cast_op->getResultId());
|
|
chain_payload_op_plus_1->addIdOperand(builder.makeUintConstant(2));
|
|
chain_payload_op_plus_1->addIdOperand(payload_add_atomic_op_plus_1->getResultId());
|
|
|
|
auto store_payload_inst_plus_1 = std::make_unique<spv::Instruction>(spv::OpStore);
|
|
store_payload_inst_plus_1->addIdOperand(chain_payload_op_plus_1->getResultId());
|
|
store_payload_inst_plus_1->addIdOperand(payload_total_atomic_op->getResultId());
|
|
store_payload_inst_plus_1->addImmediateOperand(spv::MemoryAccessAlignedMask);
|
|
store_payload_inst_plus_1->addImmediateOperand(4);
|
|
|
|
payload_offset_id = payload_total_atomic_op->getResultId();
|
|
|
|
add_instruction(body_block, std::move(mul_op));
|
|
add_instruction(body_block, std::move(add_op));
|
|
add_instruction(body_block, std::move(and_op));
|
|
add_instruction(body_block, std::move(cast_op));
|
|
add_instruction(body_block, std::move(chain_payload_total_op));
|
|
add_instruction(body_block, std::move(chain_fused_total_op));
|
|
add_instruction(body_block, std::move(payload_total_atomic_op));
|
|
add_instruction(body_block, std::move(chain_node_total_op));
|
|
add_instruction(body_block, std::move(shift_op));
|
|
add_instruction(body_block, std::move(count_minus_1));
|
|
add_instruction(body_block, std::move(or_op));
|
|
add_instruction(body_block, std::move(fused_atomic_op));
|
|
add_instruction(body_block, std::move(fused_mul2));
|
|
add_instruction(body_block, std::move(atomic_total_op));
|
|
add_instruction(body_block, std::move(payload_add_atomic_op));
|
|
add_instruction(body_block, std::move(payload_add_atomic_op_plus_1));
|
|
add_instruction(body_block, std::move(chain_payload_op));
|
|
add_instruction(body_block, std::move(store_payload_inst));
|
|
add_instruction(body_block, std::move(chain_payload_op_plus_1));
|
|
add_instruction(body_block, std::move(store_payload_inst_plus_1));
|
|
|
|
if (!per_thread)
|
|
{
|
|
auto store_inst = std::make_unique<spv::Instruction>(spv::OpStore);
|
|
store_inst->addIdOperand(shared_id);
|
|
store_inst->addIdOperand(payload_offset_id);
|
|
if (builder.hasCapability(spv::CapabilityVulkanMemoryModel))
|
|
store_inst->addImmediateOperand(spv::MemoryAccessNonPrivatePointerMask);
|
|
add_instruction(body_block, std::move(store_inst));
|
|
}
|
|
|
|
builder.createBranch(merge_block);
|
|
}
|
|
builder.setBuildPoint(merge_block);
|
|
|
|
if (!per_thread)
|
|
{
|
|
auto barrier_op = std::make_unique<spv::Instruction>(spv::OpControlBarrier);
|
|
barrier_op->addIdOperand(builder.makeUintConstant(spv::ScopeWorkgroup));
|
|
barrier_op->addIdOperand(builder.makeUintConstant(spv::ScopeWorkgroup));
|
|
barrier_op->addIdOperand(builder.getWorkgroupBarrierSemanticsId());
|
|
add_instruction(merge_block, std::move(barrier_op));
|
|
}
|
|
|
|
spv::Id return_value;
|
|
|
|
if (per_thread)
|
|
{
|
|
auto counts_offsets =
|
|
std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpGroupNonUniformIAdd);
|
|
counts_offsets->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
counts_offsets->addImmediateOperand(spv::GroupOperationExclusiveScan);
|
|
counts_offsets->addIdOperand(func->getParamId(2));
|
|
|
|
auto phi_node = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpPhi);
|
|
phi_node->addIdOperand(payload_offset_id);
|
|
phi_node->addIdOperand(body_block->getId());
|
|
phi_node->addIdOperand(phi_undefined_id);
|
|
phi_node->addIdOperand(reconverge_block->getId());
|
|
|
|
auto broadcast_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpGroupNonUniformBroadcastFirst);
|
|
broadcast_op->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
broadcast_op->addIdOperand(phi_node->getResultId());
|
|
|
|
auto mul_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpIMul);
|
|
mul_op->addIdOperand(counts_offsets->getResultId());
|
|
mul_op->addIdOperand(func->getParamId(3));
|
|
|
|
auto add_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpIAdd);
|
|
add_op->addIdOperand(broadcast_op->getResultId());
|
|
add_op->addIdOperand(mul_op->getResultId());
|
|
|
|
return_value = add_op->getResultId();
|
|
|
|
add_instruction(merge_block, std::move(phi_node));
|
|
add_instruction(merge_block, std::move(broadcast_op));
|
|
add_instruction(merge_block, std::move(counts_offsets));
|
|
add_instruction(merge_block, std::move(mul_op));
|
|
add_instruction(merge_block, std::move(add_op));
|
|
}
|
|
else
|
|
{
|
|
auto load_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpLoad);
|
|
load_op->addIdOperand(shared_id);
|
|
if (builder.hasCapability(spv::CapabilityVulkanMemoryModel))
|
|
load_op->addImmediateOperand(spv::MemoryAccessNonPrivatePointerMask);
|
|
return_value = load_op->getResultId();
|
|
add_instruction(merge_block, std::move(load_op));
|
|
}
|
|
|
|
if (!per_thread)
|
|
{
|
|
// Avoid WAR hazard for back-to-back allocations.
|
|
auto barrier_op = std::make_unique<spv::Instruction>(spv::OpControlBarrier);
|
|
barrier_op->addIdOperand(builder.makeUintConstant(spv::ScopeWorkgroup));
|
|
barrier_op->addIdOperand(builder.makeUintConstant(spv::ScopeWorkgroup));
|
|
barrier_op->addIdOperand(builder.makeUintConstant(
|
|
spv::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsWorkgroupMemoryMask));
|
|
add_instruction(merge_block, std::move(barrier_op));
|
|
}
|
|
|
|
builder.makeReturn(false, return_value);
|
|
builder.setBuildPoint(current_build_point);
|
|
call_id = func->getId();
|
|
return func->getId();
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_finish_cross_group_sharing(SPIRVModule &module)
|
|
{
|
|
if (finish_cross_group_sharing_call_id)
|
|
return finish_cross_group_sharing_call_id;
|
|
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
spv::Block *entry = nullptr;
|
|
spv::Id bool_type = builder.makeBoolType();
|
|
spv::Id uint_type = builder.makeUintType(32);
|
|
spv::Id uint64_type = builder.makeUintType(64);
|
|
auto *func = builder.makeFunctionEntry(spv::NoPrecision, bool_type,
|
|
"FinishCrossGroupSharing",
|
|
{ uint64_type }, {}, &entry);
|
|
|
|
builder.addName(func->getParamId(0), "CounterBDA");
|
|
|
|
auto *body_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *merge_block = new spv::Block(builder.getUniqueId(), *func);
|
|
|
|
spv::Id local_invocation_index = get_builtin_shader_input(spv::BuiltInLocalInvocationIndex);
|
|
spv::Id shared_id = create_variable(spv::StorageClassWorkgroup, bool_type, "FinishCrossGroupSharingBroadcast");
|
|
|
|
auto load_local_index = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpLoad);
|
|
load_local_index->addIdOperand(local_invocation_index);
|
|
|
|
auto is_first_lane = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, spv::OpIEqual);
|
|
is_first_lane->addIdOperand(load_local_index->getResultId());
|
|
is_first_lane->addIdOperand(builder.makeUintConstant(0));
|
|
spv::Id cond_id = is_first_lane->getResultId();
|
|
|
|
add_instruction(entry, std::move(load_local_index));
|
|
add_instruction(entry, std::move(is_first_lane));
|
|
builder.setBuildPoint(entry);
|
|
builder.createSelectionMerge(merge_block, 0);
|
|
builder.createConditionalBranch(cond_id, body_block, merge_block);
|
|
|
|
builder.setBuildPoint(body_block);
|
|
{
|
|
auto cast_op = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(),
|
|
builder.makePointer(spv::StorageClassPhysicalStorageBuffer, uint_type),
|
|
spv::OpBitcast);
|
|
cast_op->addIdOperand(func->getParamId(0));
|
|
|
|
auto atomic_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpAtomicIDecrement);
|
|
atomic_op->addIdOperand(cast_op->getResultId());
|
|
atomic_op->addIdOperand(builder.getAtomicDeviceScopeId());
|
|
atomic_op->addIdOperand(builder.makeUintConstant(0)); // There is no implied sync.
|
|
|
|
auto is_last_wg = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, spv::OpIEqual);
|
|
is_last_wg->addIdOperand(atomic_op->getResultId());
|
|
is_last_wg->addIdOperand(builder.makeUintConstant(1));
|
|
|
|
auto store_inst = std::make_unique<spv::Instruction>(spv::OpStore);
|
|
store_inst->addIdOperand(shared_id);
|
|
store_inst->addIdOperand(is_last_wg->getResultId());
|
|
if (builder.hasCapability(spv::CapabilityVulkanMemoryModel))
|
|
store_inst->addImmediateOperand(spv::MemoryAccessNonPrivatePointerMask);
|
|
|
|
add_instruction(body_block, std::move(cast_op));
|
|
add_instruction(body_block, std::move(atomic_op));
|
|
add_instruction(body_block, std::move(is_last_wg));
|
|
add_instruction(body_block, std::move(store_inst));
|
|
builder.createBranch(merge_block);
|
|
}
|
|
builder.setBuildPoint(merge_block);
|
|
|
|
// We don't need double barrier since FinishGroupSharing can only be called once.
|
|
// There is no risk of WAR hazard on the same shared memory.
|
|
auto barrier_op = std::make_unique<spv::Instruction>(spv::OpControlBarrier);
|
|
barrier_op->addIdOperand(builder.makeUintConstant(spv::ScopeWorkgroup));
|
|
barrier_op->addIdOperand(builder.makeUintConstant(spv::ScopeWorkgroup));
|
|
barrier_op->addIdOperand(builder.getWorkgroupBarrierSemanticsId());
|
|
|
|
auto load_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, spv::OpLoad);
|
|
load_op->addIdOperand(shared_id);
|
|
if (builder.hasCapability(spv::CapabilityVulkanMemoryModel))
|
|
load_op->addImmediateOperand(spv::MemoryAccessNonPrivatePointerMask);
|
|
|
|
spv::Id return_value = load_op->getResultId();
|
|
add_instruction(merge_block, std::move(barrier_op));
|
|
add_instruction(merge_block, std::move(load_op));
|
|
|
|
builder.makeReturn(false, return_value);
|
|
builder.setBuildPoint(current_build_point);
|
|
finish_cross_group_sharing_call_id = func->getId();
|
|
return func->getId();
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_robust_atomic_counter_op(SPIRVModule &module)
|
|
{
|
|
if (robust_atomic_counter_call_id)
|
|
return robust_atomic_counter_call_id;
|
|
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
|
|
spv::Block *entry = nullptr;
|
|
spv::Id uint_type = builder.makeUintType(32);
|
|
spv::Id bda_type = builder.makeVectorType(uint_type, 2);
|
|
auto *func = builder.makeFunctionEntry(spv::NoPrecision, uint_type,
|
|
"RobustPhysicalAtomicCounter",
|
|
{ bda_type, uint_type, uint_type }, {}, &entry);
|
|
|
|
auto *body_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *merge_block = new spv::Block(builder.getUniqueId(), *func);
|
|
|
|
spv::Id bool_type = builder.makeBoolType();
|
|
spv::Id bvec2_type = builder.makeVectorType(bool_type, 2);
|
|
|
|
spv::Id null_bda = builder.makeNullConstant(bda_type);
|
|
auto compare = std::make_unique<spv::Instruction>(builder.getUniqueId(), bvec2_type, spv::OpINotEqual);
|
|
compare->addIdOperand(func->getParamId(0));
|
|
compare->addIdOperand(null_bda);
|
|
auto not_zero = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, spv::OpAny);
|
|
not_zero->addIdOperand(compare->getResultId());
|
|
spv::Id cond_id = not_zero->getResultId();
|
|
add_instruction(entry, std::move(compare));
|
|
add_instruction(entry, std::move(not_zero));
|
|
builder.setBuildPoint(entry);
|
|
builder.createSelectionMerge(merge_block, 0);
|
|
builder.createConditionalBranch(cond_id, body_block, merge_block);
|
|
|
|
spv::Id loaded_id;
|
|
{
|
|
builder.setBuildPoint(body_block);
|
|
spv::Id uint_ptr_type = builder.makePointer(spv::StorageClassPhysicalStorageBuffer, uint_type);
|
|
auto bitcast_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_ptr_type, spv::OpBitcast);
|
|
bitcast_op->addIdOperand(func->getParamId(0));
|
|
auto atomic_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpAtomicIAdd);
|
|
atomic_op->addIdOperand(bitcast_op->getResultId());
|
|
atomic_op->addIdOperand(builder.getAtomicDeviceScopeId());
|
|
atomic_op->addIdOperand(builder.makeUintConstant(0));
|
|
atomic_op->addIdOperand(func->getParamId(1));
|
|
auto add_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpIAdd);
|
|
add_op->addIdOperand(atomic_op->getResultId());
|
|
add_op->addIdOperand(func->getParamId(2));
|
|
loaded_id = add_op->getResultId();
|
|
add_instruction(body_block, std::move(bitcast_op));
|
|
add_instruction(body_block, std::move(atomic_op));
|
|
add_instruction(body_block, std::move(add_op));
|
|
builder.createBranch(merge_block);
|
|
}
|
|
|
|
builder.setBuildPoint(merge_block);
|
|
auto phi_op = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(), uint_type, spv::OpPhi);
|
|
phi_op->addIdOperand(builder.makeUintConstant(0));
|
|
phi_op->addIdOperand(entry->getId());
|
|
phi_op->addIdOperand(loaded_id);
|
|
phi_op->addIdOperand(body_block->getId());
|
|
spv::Id return_value = phi_op->getResultId();
|
|
add_instruction(merge_block, std::move(phi_op));
|
|
builder.makeReturn(false, return_value);
|
|
|
|
builder.setBuildPoint(current_build_point);
|
|
robust_atomic_counter_call_id = func->getId();
|
|
return func->getId();
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_robust_physical_cbv_load(SPIRVModule &module, spv::Id type_id, spv::Id ptr_type_id,
|
|
unsigned alignment)
|
|
{
|
|
for (auto &func : physical_cbv_call_ids)
|
|
if (func.ptr_type_id == ptr_type_id && func.type_id == type_id && func.alignment == alignment)
|
|
return func.func_id;
|
|
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
|
|
spv::Block *entry = nullptr;
|
|
spv::Id uint_type = builder.makeUintType(32);
|
|
spv::Id bda_type = builder.makeVectorType(uint_type, 2);
|
|
auto *func = builder.makeFunctionEntry(spv::NoPrecision, type_id,
|
|
"RobustPhysicalCBVLoad",
|
|
{ bda_type, uint_type }, {}, &entry);
|
|
|
|
spv::Id bda_value_id = func->getParamId(0);
|
|
spv::Id index_id = func->getParamId(1);
|
|
|
|
auto *body_block = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *merge_block = new spv::Block(builder.getUniqueId(), *func);
|
|
|
|
builder.setBuildPoint(entry);
|
|
auto compare = std::make_unique<spv::Instruction>(builder.getUniqueId(), builder.makeBoolType(), spv::OpULessThan);
|
|
compare->addIdOperand(index_id);
|
|
compare->addIdOperand(builder.makeUintConstant(64 * 1024 / alignment));
|
|
spv::Id compare_id = compare->getResultId();
|
|
add_instruction(entry, std::move(compare));
|
|
builder.createSelectionMerge(merge_block, 0);
|
|
builder.createConditionalBranch(compare_id, body_block, merge_block);
|
|
|
|
spv::Id loaded_id;
|
|
{
|
|
builder.setBuildPoint(body_block);
|
|
auto bitcast_op = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(), ptr_type_id, spv::OpBitcast);
|
|
auto chain_op = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(),
|
|
builder.makePointer(spv::StorageClassPhysicalStorageBuffer, type_id),
|
|
spv::OpInBoundsAccessChain);
|
|
auto load_op = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(), type_id, spv::OpLoad);
|
|
bitcast_op->addIdOperand(bda_value_id);
|
|
chain_op->addIdOperand(bitcast_op->getResultId());
|
|
chain_op->addIdOperand(builder.makeUintConstant(0));
|
|
chain_op->addIdOperand(index_id);
|
|
load_op->addIdOperand(chain_op->getResultId());
|
|
load_op->addImmediateOperand(spv::MemoryAccessAlignedMask);
|
|
load_op->addImmediateOperand(alignment);
|
|
loaded_id = load_op->getResultId();
|
|
add_instruction(body_block, std::move(bitcast_op));
|
|
add_instruction(body_block, std::move(chain_op));
|
|
add_instruction(body_block, std::move(load_op));
|
|
builder.createBranch(merge_block);
|
|
}
|
|
|
|
builder.setBuildPoint(merge_block);
|
|
auto phi_op = std::make_unique<spv::Instruction>(
|
|
builder.getUniqueId(), type_id, spv::OpPhi);
|
|
phi_op->addIdOperand(builder.makeNullConstant(type_id));
|
|
phi_op->addIdOperand(entry->getId());
|
|
phi_op->addIdOperand(loaded_id);
|
|
phi_op->addIdOperand(body_block->getId());
|
|
spv::Id return_value = phi_op->getResultId();
|
|
add_instruction(merge_block, std::move(phi_op));
|
|
builder.makeReturn(false, return_value);
|
|
|
|
builder.setBuildPoint(current_build_point);
|
|
physical_cbv_call_ids.push_back({ type_id, ptr_type_id, alignment, func->getId() });
|
|
return func->getId();
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_is_quad_uniform_control_flow(SPIRVModule &module)
|
|
{
|
|
if (is_quad_uniform_call_id)
|
|
return is_quad_uniform_call_id;
|
|
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
spv::Block *entry = nullptr;
|
|
spv::Id uint_type = builder.makeUintType(32);
|
|
spv::Id uvec4_type = builder.makeVectorType(uint_type, 4);
|
|
spv::Id bool_type = builder.makeBoolType();
|
|
|
|
auto *func = builder.makeFunctionEntry(spv::NoPrecision, bool_type,
|
|
"IsQuadUniformControlFlow",
|
|
{}, {}, &entry);
|
|
|
|
auto ballot_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), uvec4_type, spv::OpGroupNonUniformBallot);
|
|
ballot_op->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
ballot_op->addIdOperand(builder.makeBoolConstant(true));
|
|
|
|
spv::Id uint_2 = builder.makeUintConstant(2);
|
|
spv::Id uint_1 = builder.makeUintConstant(1);
|
|
uint_2 = builder.makeCompositeConstant(uvec4_type, { uint_2, uint_2, uint_2, uint_2 });
|
|
uint_1 = builder.makeCompositeConstant(uvec4_type, { uint_1, uint_1, uint_1, uint_1 });
|
|
|
|
auto shift_2 = std::make_unique<spv::Instruction>(builder.getUniqueId(), uvec4_type, spv::OpShiftRightLogical);
|
|
shift_2->addIdOperand(ballot_op->getResultId());
|
|
shift_2->addIdOperand(uint_2);
|
|
auto or_2 = std::make_unique<spv::Instruction>(builder.getUniqueId(), uvec4_type, spv::OpBitwiseOr);
|
|
or_2->addIdOperand(ballot_op->getResultId());
|
|
or_2->addIdOperand(shift_2->getResultId());
|
|
auto shift_1 = std::make_unique<spv::Instruction>(builder.getUniqueId(), uvec4_type, spv::OpShiftRightLogical);
|
|
shift_1->addIdOperand(or_2->getResultId());
|
|
shift_1->addIdOperand(uint_1);
|
|
auto or_1 = std::make_unique<spv::Instruction>(builder.getUniqueId(), uvec4_type, spv::OpBitwiseOr);
|
|
or_1->addIdOperand(or_2->getResultId());
|
|
or_1->addIdOperand(shift_1->getResultId());
|
|
|
|
auto load_invocation_id = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpLoad);
|
|
load_invocation_id->addIdOperand(get_builtin_shader_input(spv::BuiltInSubgroupLocalInvocationId));
|
|
|
|
auto and_invocation_id = std::make_unique<spv::Instruction>(builder.getUniqueId(), uint_type, spv::OpBitwiseAnd);
|
|
and_invocation_id->addIdOperand(load_invocation_id->getResultId());
|
|
and_invocation_id->addIdOperand(builder.makeUintConstant(~3u));
|
|
|
|
auto extract_op = std::make_unique<spv::Instruction>(builder.getUniqueId(), bool_type, spv::OpGroupNonUniformBallotBitExtract);
|
|
extract_op->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
extract_op->addIdOperand(or_1->getResultId());
|
|
extract_op->addIdOperand(and_invocation_id->getResultId());
|
|
spv::Id result_id = extract_op->getResultId();
|
|
|
|
entry->addInstruction(std::move(ballot_op));
|
|
entry->addInstruction(std::move(shift_2));
|
|
entry->addInstruction(std::move(or_2));
|
|
entry->addInstruction(std::move(shift_1));
|
|
entry->addInstruction(std::move(or_1));
|
|
entry->addInstruction(std::move(load_invocation_id));
|
|
entry->addInstruction(std::move(and_invocation_id));
|
|
entry->addInstruction(std::move(extract_op));
|
|
|
|
builder.makeReturn(false, result_id);
|
|
builder.setBuildPoint(current_build_point);
|
|
builder.addCapability(spv::CapabilityGroupNonUniformBallot);
|
|
builder.addCapability(spv::CapabilityGroupNonUniform);
|
|
|
|
is_quad_uniform_call_id = func->getId();
|
|
return is_quad_uniform_call_id;
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_coop_mat_saturation_fixup(SPIRVModule &module, spv::Id type_id)
|
|
{
|
|
for (auto &ops : coop_mat_conv_ids)
|
|
if (ops.output_type == type_id && ops.input_type == type_id && ops.call == HelperCall::CoopMatSaturationFixup)
|
|
return ops.func_id;
|
|
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
|
|
spv::Block *entry = nullptr;
|
|
spv::Id uint_type = builder.makeUintType(32);
|
|
spv::Id bool_type = builder.makeBoolType();
|
|
|
|
// RADV workaround. It fails to understand coopmat passed as value.
|
|
spv::Id input_ptr_type = builder.makePointer(spv::StorageClassFunction, type_id);
|
|
|
|
auto *func = builder.makeFunctionEntry(spv::NoPrecision, type_id,
|
|
"CoopMatSaturationFixup",
|
|
{ input_ptr_type }, {}, &entry);
|
|
|
|
spv::Id output_id = create_variable(spv::StorageClassFunction, type_id, "coop_output");
|
|
auto *len = builder.addInstruction(uint_type, spv::OpCooperativeMatrixLengthKHR);
|
|
len->addIdOperand(type_id);
|
|
|
|
auto *header = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *merge = new spv::Block(builder.getUniqueId(), *func);
|
|
builder.createBranch(header);
|
|
builder.setBuildPoint(header);
|
|
|
|
unsigned bits = builder.getScalarTypeWidth(builder.getContainedTypeId(type_id));
|
|
|
|
{
|
|
auto *phi = builder.addInstruction(uint_type, spv::OpPhi);
|
|
auto *iter = builder.addInstruction(uint_type, spv::OpIAdd);
|
|
iter->addIdOperand(phi->getResultId());
|
|
iter->addIdOperand(builder.makeUintConstant(1));
|
|
|
|
phi->addIdOperand(builder.makeUintConstant(0));
|
|
phi->addIdOperand(entry->getId());
|
|
phi->addIdOperand(iter->getResultId());
|
|
phi->addIdOperand(header->getId());
|
|
|
|
auto *input_chain = builder.addInstruction(
|
|
builder.makePointer(spv::StorageClassFunction, builder.getContainedTypeId(type_id)), spv::OpInBoundsAccessChain);
|
|
input_chain->addIdOperand(func->getParamId(0));
|
|
input_chain->addIdOperand(phi->getResultId());
|
|
|
|
auto *load = builder.addInstruction(builder.getContainedTypeId(type_id), spv::OpLoad);
|
|
load->addIdOperand(input_chain->getResultId());
|
|
|
|
auto *is_inf = builder.addInstruction(builder.makeBoolType(), spv::OpIsInf);
|
|
is_inf->addIdOperand(load->getResultId());
|
|
|
|
auto *select = builder.addInstruction(builder.getContainedTypeId(type_id), spv::OpSelect);
|
|
select->addIdOperand(is_inf->getResultId());
|
|
if (bits == 32)
|
|
select->addIdOperand(builder.makeFloatConstant(std::numeric_limits<float>::quiet_NaN()));
|
|
else
|
|
select->addIdOperand(builder.makeFloat16Constant(0xffff));
|
|
select->addIdOperand(load->getResultId());
|
|
|
|
auto *output_chain = builder.addInstruction(
|
|
builder.makePointer(spv::StorageClassFunction, builder.getContainedTypeId(type_id)), spv::OpInBoundsAccessChain);
|
|
output_chain->addIdOperand(output_id);
|
|
output_chain->addIdOperand(phi->getResultId());
|
|
auto *store = builder.addInstruction(spv::OpStore);
|
|
store->addIdOperand(output_chain->getResultId());
|
|
store->addIdOperand(select->getResultId());
|
|
|
|
auto *cmp = builder.addInstruction(bool_type, spv::OpULessThan);
|
|
cmp->addIdOperand(iter->getResultId());
|
|
cmp->addIdOperand(len->getResultId());
|
|
builder.createLoopMerge(merge, header, 0);
|
|
builder.createConditionalBranch(cmp->getResultId(), header, merge);
|
|
}
|
|
|
|
builder.setBuildPoint(merge);
|
|
auto *loaded_result = builder.addInstruction(type_id, spv::OpLoad);
|
|
loaded_result->addIdOperand(output_id);
|
|
builder.makeReturn(false, loaded_result->getResultId());
|
|
builder.setBuildPoint(current_build_point);
|
|
coop_mat_conv_ids.push_back({ type_id, type_id, func->getId(), HelperCall::CoopMatSaturationFixup });
|
|
return func->getId();
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_coop_mat_transfer(SPIRVModule &module, const spv::Id *ids, uint32_t id_count)
|
|
{
|
|
if (id_count != 2)
|
|
return 0;
|
|
|
|
spv::Id input_type = ids[0];
|
|
spv::Id output_type = ids[1];
|
|
for (auto &ops : coop_mat_conv_ids)
|
|
if (ops.output_type == output_type && ops.input_type == input_type && ops.call == HelperCall::CoopMatTransfer)
|
|
return ops.func_id;
|
|
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
|
|
spv::Block *entry = nullptr;
|
|
spv::Id uint_type = builder.makeUintType(32);
|
|
spv::Id bool_type = builder.makeBoolType();
|
|
|
|
// RADV workaround. It fails to understand coopmat passed as value.
|
|
spv::Id input_ptr_type = builder.makePointer(spv::StorageClassFunction, input_type);
|
|
|
|
auto *func = builder.makeFunctionEntry(spv::NoPrecision, output_type,
|
|
"CoopMatTransfer",
|
|
{ input_ptr_type }, {}, &entry);
|
|
|
|
spv::Id output_id = create_variable(spv::StorageClassFunction, output_type, "coop_output");
|
|
auto *len = builder.addInstruction(uint_type, spv::OpCooperativeMatrixLengthKHR);
|
|
len->addIdOperand(output_type);
|
|
|
|
auto *header = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *merge = new spv::Block(builder.getUniqueId(), *func);
|
|
builder.createBranch(header);
|
|
builder.setBuildPoint(header);
|
|
{
|
|
auto *phi = builder.addInstruction(uint_type, spv::OpPhi);
|
|
auto *iter = builder.addInstruction(uint_type, spv::OpIAdd);
|
|
iter->addIdOperand(phi->getResultId());
|
|
iter->addIdOperand(builder.makeUintConstant(1));
|
|
|
|
phi->addIdOperand(builder.makeUintConstant(0));
|
|
phi->addIdOperand(entry->getId());
|
|
phi->addIdOperand(iter->getResultId());
|
|
phi->addIdOperand(header->getId());
|
|
|
|
auto *input_chain = builder.addInstruction(
|
|
builder.makePointer(spv::StorageClassFunction, builder.getContainedTypeId(input_type)), spv::OpInBoundsAccessChain);
|
|
input_chain->addIdOperand(func->getParamId(0));
|
|
input_chain->addIdOperand(phi->getResultId());
|
|
|
|
auto *load = builder.addInstruction(builder.getContainedTypeId(input_type), spv::OpLoad);
|
|
load->addIdOperand(input_chain->getResultId());
|
|
|
|
auto *output_chain = builder.addInstruction(
|
|
builder.makePointer(spv::StorageClassFunction, builder.getContainedTypeId(output_type)), spv::OpInBoundsAccessChain);
|
|
output_chain->addIdOperand(output_id);
|
|
output_chain->addIdOperand(phi->getResultId());
|
|
auto *store = builder.addInstruction(spv::OpStore);
|
|
store->addIdOperand(output_chain->getResultId());
|
|
store->addIdOperand(load->getResultId());
|
|
|
|
auto *cmp = builder.addInstruction(bool_type, spv::OpULessThan);
|
|
cmp->addIdOperand(iter->getResultId());
|
|
cmp->addIdOperand(len->getResultId());
|
|
builder.createLoopMerge(merge, header, 0);
|
|
builder.createConditionalBranch(cmp->getResultId(), header, merge);
|
|
}
|
|
|
|
builder.setBuildPoint(merge);
|
|
auto *loaded_result = builder.addInstruction(output_type, spv::OpLoad);
|
|
loaded_result->addIdOperand(output_id);
|
|
builder.makeReturn(false, loaded_result->getResultId());
|
|
builder.setBuildPoint(current_build_point);
|
|
coop_mat_conv_ids.push_back({ input_type, output_type, func->getId(), HelperCall::CoopMatTransfer });
|
|
return func->getId();
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_coop_mat_saturate_fp8(SPIRVModule &module, spv::Id type_id)
|
|
{
|
|
for (auto &ops : coop_mat_conv_ids)
|
|
if (ops.output_type == type_id && ops.input_type == type_id && ops.call == HelperCall::CoopMatSaturateFP8)
|
|
return ops.func_id;
|
|
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
|
|
spv::Block *entry = nullptr;
|
|
spv::Id uint_type = builder.makeUintType(32);
|
|
spv::Id bool_type = builder.makeBoolType();
|
|
|
|
// RADV workaround. It fails to understand coopmat passed as value.
|
|
spv::Id f16_ptr_type = builder.makePointer(spv::StorageClassFunction, type_id);
|
|
|
|
auto *func = builder.makeFunctionEntry(spv::NoPrecision, type_id,
|
|
"CoopMatSaturateFP8",
|
|
{ f16_ptr_type }, {}, &entry);
|
|
|
|
spv::Id output_id = create_variable(spv::StorageClassFunction, type_id, "coop_output");
|
|
auto *len = builder.addInstruction(uint_type, spv::OpCooperativeMatrixLengthKHR);
|
|
len->addIdOperand(type_id);
|
|
|
|
auto *header = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *merge = new spv::Block(builder.getUniqueId(), *func);
|
|
builder.createBranch(header);
|
|
builder.setBuildPoint(header);
|
|
{
|
|
auto *phi = builder.addInstruction(uint_type, spv::OpPhi);
|
|
auto *iter = builder.addInstruction(uint_type, spv::OpIAdd);
|
|
iter->addIdOperand(phi->getResultId());
|
|
iter->addIdOperand(builder.makeUintConstant(1));
|
|
|
|
phi->addIdOperand(builder.makeUintConstant(0));
|
|
phi->addIdOperand(entry->getId());
|
|
phi->addIdOperand(iter->getResultId());
|
|
phi->addIdOperand(header->getId());
|
|
|
|
auto *input_chain = builder.addInstruction(
|
|
builder.makePointer(spv::StorageClassFunction, builder.makeFloatType(16)), spv::OpInBoundsAccessChain);
|
|
input_chain->addIdOperand(func->getParamId(0));
|
|
input_chain->addIdOperand(phi->getResultId());
|
|
|
|
auto *load = builder.addInstruction(builder.makeFloatType(16), spv::OpLoad);
|
|
load->addIdOperand(input_chain->getResultId());
|
|
|
|
spv::Id glsl450 = builder.import("GLSL.std.450");
|
|
auto *clamp = builder.addInstruction(builder.makeFloatType(16), spv::OpExtInst);
|
|
clamp->addIdOperand(glsl450);
|
|
clamp->addImmediateOperand(GLSLstd450NClamp);
|
|
clamp->addIdOperand(load->getResultId());
|
|
clamp->addIdOperand(builder.makeFloat16Constant(0x5f00 | 0x8000));
|
|
clamp->addIdOperand(builder.makeFloat16Constant(0x5f00)); // 448.0.
|
|
|
|
auto *output_chain = builder.addInstruction(
|
|
builder.makePointer(spv::StorageClassFunction, builder.makeFloatType(16)), spv::OpInBoundsAccessChain);
|
|
output_chain->addIdOperand(output_id);
|
|
output_chain->addIdOperand(phi->getResultId());
|
|
auto *store = builder.addInstruction(spv::OpStore);
|
|
store->addIdOperand(output_chain->getResultId());
|
|
store->addIdOperand(clamp->getResultId());
|
|
|
|
auto *cmp = builder.addInstruction(bool_type, spv::OpULessThan);
|
|
cmp->addIdOperand(iter->getResultId());
|
|
cmp->addIdOperand(len->getResultId());
|
|
builder.createLoopMerge(merge, header, 0);
|
|
builder.createConditionalBranch(cmp->getResultId(), header, merge);
|
|
}
|
|
|
|
builder.setBuildPoint(merge);
|
|
auto *loaded_result = builder.addInstruction(type_id, spv::OpLoad);
|
|
loaded_result->addIdOperand(output_id);
|
|
builder.makeReturn(false, loaded_result->getResultId());
|
|
builder.setBuildPoint(current_build_point);
|
|
coop_mat_conv_ids.push_back({ type_id, type_id, func->getId(), HelperCall::CoopMatSaturateFP8 });
|
|
return func->getId();
|
|
}
|
|
|
|
static spv::Id make_i16vec2_constant(spv::Builder &builder, int16_t val)
|
|
{
|
|
spv::Id constant = builder.makeInt16Constant(val);
|
|
spv::Id s16vec2_type = builder.makeVectorType(builder.makeIntType(16), 2);
|
|
return builder.makeCompositeConstant(s16vec2_type, { constant, constant });
|
|
}
|
|
|
|
static spv::Id make_u16vec2_constant(spv::Builder &builder, uint16_t val)
|
|
{
|
|
spv::Id constant = builder.makeUint16Constant(val);
|
|
spv::Id s16vec2_type = builder.makeVectorType(builder.makeUintType(16), 2);
|
|
return builder.makeCompositeConstant(s16vec2_type, { constant, constant });
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_coop_mat_fp16_to_fp8(SPIRVModule &module, const spv::Id *ids, uint32_t id_count)
|
|
{
|
|
if (id_count != 2)
|
|
return 0;
|
|
|
|
spv::Id u8mat_type = ids[0];
|
|
spv::Id f16mat_type = ids[1];
|
|
|
|
for (auto &ops : coop_mat_conv_ids)
|
|
if (ops.output_type == u8mat_type && ops.input_type == f16mat_type && ops.call == HelperCall::CoopMatFP16toFP8)
|
|
return ops.func_id;
|
|
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
|
|
spv::Block *entry = nullptr;
|
|
spv::Id uint_type = builder.makeUintType(32);
|
|
spv::Id bool_type = builder.makeBoolType();
|
|
spv::Id f16_type = builder.makeFloatType(16);
|
|
spv::Id s16_type = builder.makeIntType(16);
|
|
spv::Id u8_type = builder.makeUintType(8);
|
|
|
|
spv::Id bvec2_type = builder.makeVectorType(bool_type, 2);
|
|
spv::Id f16vec2_type = builder.makeVectorType(f16_type, 2);
|
|
spv::Id s16vec2_type = builder.makeVectorType(s16_type, 2);
|
|
spv::Id u8vec2_type = builder.makeVectorType(u8_type, 2);
|
|
|
|
// RADV workaround. It fails to understand coopmat passed as value.
|
|
spv::Id f16_ptr_type = builder.makePointer(spv::StorageClassFunction, f16mat_type);
|
|
|
|
auto *func =
|
|
builder.makeFunctionEntry(spv::NoPrecision, u8mat_type, "CoopMatFP16toFP8", { f16_ptr_type }, {}, &entry);
|
|
|
|
spv::Id output_id = create_variable(spv::StorageClassFunction, u8mat_type, "coop_output");
|
|
auto *len = builder.addInstruction(uint_type, spv::OpCooperativeMatrixLengthKHR);
|
|
len->addIdOperand(u8mat_type);
|
|
|
|
auto *header = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *merge = new spv::Block(builder.getUniqueId(), *func);
|
|
builder.createBranch(header);
|
|
builder.setBuildPoint(header);
|
|
{
|
|
auto *phi = builder.addInstruction(uint_type, spv::OpPhi);
|
|
auto *iter = builder.addInstruction(uint_type, spv::OpIAdd);
|
|
iter->addIdOperand(phi->getResultId());
|
|
// Process two elemts at once because AMD has i16vec2 instructions
|
|
// Assume coop mat length is always a multiple of 2.
|
|
iter->addIdOperand(builder.makeUintConstant(2));
|
|
|
|
phi->addIdOperand(builder.makeUintConstant(0));
|
|
phi->addIdOperand(entry->getId());
|
|
phi->addIdOperand(iter->getResultId());
|
|
phi->addIdOperand(header->getId());
|
|
|
|
auto *index1 = builder.addInstruction(uint_type, spv::OpIAdd);
|
|
index1->addIdOperand(phi->getResultId());
|
|
index1->addIdOperand(builder.makeUintConstant(1));
|
|
|
|
spv::Id components[2];
|
|
|
|
for (unsigned i = 0; i < 2; i++)
|
|
{
|
|
spv::Id index = i ? index1->getResultId() : phi->getResultId();
|
|
|
|
auto *input_chain = builder.addInstruction(builder.makePointer(spv::StorageClassFunction, f16_type),
|
|
spv::OpInBoundsAccessChain);
|
|
input_chain->addIdOperand(func->getParamId(0));
|
|
input_chain->addIdOperand(index);
|
|
|
|
auto *load = builder.addInstruction(f16_type, spv::OpLoad);
|
|
load->addIdOperand(input_chain->getResultId());
|
|
|
|
components[i] = load->getResultId();
|
|
}
|
|
|
|
auto *composite = builder.addInstruction(f16vec2_type, spv::OpCompositeConstruct);
|
|
composite->addIdOperand(components[0]);
|
|
composite->addIdOperand(components[1]);
|
|
|
|
auto *bitcast = builder.addInstruction(s16vec2_type, spv::OpBitcast);
|
|
bitcast->addIdOperand(composite->getResultId());
|
|
|
|
// Extract the sign bit.
|
|
auto *sign_in_lsb = builder.addInstruction(s16vec2_type, spv::OpShiftRightLogical);
|
|
sign_in_lsb->addIdOperand(bitcast->getResultId());
|
|
sign_in_lsb->addIdOperand(make_i16vec2_constant(builder, 15));
|
|
|
|
auto *sign_bit = builder.addInstruction(s16vec2_type, spv::OpShiftLeftLogical);
|
|
sign_bit->addIdOperand(sign_in_lsb->getResultId());
|
|
sign_bit->addIdOperand(make_i16vec2_constant(builder, 7));
|
|
///
|
|
|
|
// When the input is shifted like this the result is E5M3 in upper byte, which is very handy.
|
|
auto *unsigned_e5m11 = builder.addInstruction(s16vec2_type, spv::OpShiftLeftLogical);
|
|
unsigned_e5m11->addIdOperand(bitcast->getResultId());
|
|
unsigned_e5m11->addIdOperand(make_i16vec2_constant(builder, 1));
|
|
|
|
// Shift -15 bias to -7 bias.
|
|
auto *shift_exponent = builder.addInstruction(s16vec2_type, spv::OpISub);
|
|
shift_exponent->addIdOperand(unsigned_e5m11->getResultId());
|
|
shift_exponent->addIdOperand(make_i16vec2_constant(builder, 8 << 11));
|
|
unsigned_e5m11 = shift_exponent;
|
|
|
|
auto *exponent = builder.addInstruction(s16vec2_type, spv::OpShiftRightArithmetic);
|
|
exponent->addIdOperand(shift_exponent->getResultId());
|
|
exponent->addIdOperand(make_i16vec2_constant(builder, 11));
|
|
|
|
auto *exponent_minus1 = builder.addInstruction(s16vec2_type, spv::OpISub);
|
|
exponent_minus1->addIdOperand(exponent->getResultId());
|
|
exponent_minus1->addIdOperand(make_i16vec2_constant(builder, 1));
|
|
|
|
auto *denorm_shamt = builder.addInstruction(s16vec2_type, spv::OpSNegate);
|
|
denorm_shamt->addIdOperand(exponent_minus1->getResultId());
|
|
|
|
// Ensure we don't get negative shift.
|
|
spv::Id glsl450 = builder.import("GLSL.std.450");
|
|
auto *clamp = builder.addInstruction(s16vec2_type, spv::OpExtInst);
|
|
clamp->addIdOperand(glsl450);
|
|
clamp->addImmediateOperand(GLSLstd450SMax);
|
|
clamp->addIdOperand(denorm_shamt->getResultId());
|
|
clamp->addIdOperand(make_i16vec2_constant(builder, 0));
|
|
denorm_shamt = clamp;
|
|
|
|
// If we're denorm, the arith shift ensures the upper bits are all 1.
|
|
auto *denorm_mask = builder.addInstruction(s16vec2_type, spv::OpBitwiseAnd);
|
|
denorm_mask->addIdOperand(exponent_minus1->getResultId());
|
|
denorm_mask->addIdOperand(make_i16vec2_constant(builder, 1 << 11));
|
|
|
|
// Serves as a clamping function.
|
|
// If the exponent goes negative here, we need to emit a 0 exponent, marking that we're in denorm region.
|
|
auto *clear_exponent_neg_mask = builder.addInstruction(s16vec2_type, spv::OpBitwiseAnd);
|
|
clear_exponent_neg_mask->addIdOperand(exponent_minus1->getResultId());
|
|
clear_exponent_neg_mask->addIdOperand(make_i16vec2_constant(builder, int16_t(0x1f << 11)));
|
|
auto *clear_exponent_mask = builder.addInstruction(s16vec2_type, spv::OpBitwiseXor);
|
|
clear_exponent_mask->addIdOperand(clear_exponent_neg_mask->getResultId());
|
|
clear_exponent_mask->addIdOperand(make_i16vec2_constant(builder, -1));
|
|
|
|
// Clamp negative exponent to 0.
|
|
{
|
|
auto *mask = builder.addInstruction(s16vec2_type, spv::OpBitwiseAnd);
|
|
mask->addIdOperand(unsigned_e5m11->getResultId());
|
|
mask->addIdOperand(clear_exponent_mask->getResultId());
|
|
unsigned_e5m11 = mask;
|
|
}
|
|
|
|
// If denorm, add in the implicit 1.xxxx.
|
|
{
|
|
auto *mask = builder.addInstruction(s16vec2_type, spv::OpBitwiseOr);
|
|
mask->addIdOperand(unsigned_e5m11->getResultId());
|
|
mask->addIdOperand(denorm_mask->getResultId());
|
|
unsigned_e5m11 = mask;
|
|
}
|
|
|
|
// Before we shift, we need to capture any possible rounding bits.
|
|
// Only capture the lower 7 bits.
|
|
// If we shift more than 7 we've already exhausted all denorm bits on E4M3 anyway, so don't need to care.
|
|
// Cannot capture the top bit in lower half since we might get a false positive for 0.5 condition.
|
|
auto *pre_denorm_rounding_bits = builder.addInstruction(s16vec2_type, spv::OpBitwiseAnd);
|
|
pre_denorm_rounding_bits->addIdOperand(unsigned_e5m11->getResultId());
|
|
pre_denorm_rounding_bits->addIdOperand(make_i16vec2_constant(builder, 0x7f));
|
|
|
|
// Now we apply the denorm shift.
|
|
auto *denorm_shift = builder.addInstruction(s16vec2_type, spv::OpShiftRightArithmetic);
|
|
denorm_shift->addIdOperand(unsigned_e5m11->getResultId());
|
|
denorm_shift->addIdOperand(denorm_shamt->getResultId());
|
|
unsigned_e5m11 = denorm_shift;
|
|
|
|
// Capture any rounding bits.
|
|
// If we shift due to denorms,
|
|
// we have to know if we shifted away bits that are relevant to RTE.
|
|
auto *rounding_bits = builder.addInstruction(s16vec2_type, spv::OpBitwiseOr);
|
|
rounding_bits->addIdOperand(unsigned_e5m11->getResultId());
|
|
rounding_bits->addIdOperand(pre_denorm_rounding_bits->getResultId());
|
|
|
|
auto *unsigned_e5m3 = builder.addInstruction(s16vec2_type, spv::OpShiftRightLogical);
|
|
unsigned_e5m3->addIdOperand(unsigned_e5m11->getResultId());
|
|
unsigned_e5m3->addIdOperand(make_i16vec2_constant(builder, 8));
|
|
|
|
auto *is_odd = builder.addInstruction(s16vec2_type, spv::OpBitwiseAnd);
|
|
is_odd->addIdOperand(unsigned_e5m3->getResultId());
|
|
is_odd->addIdOperand(make_i16vec2_constant(builder, 1));
|
|
|
|
auto *or_rounding_bits = builder.addInstruction(s16vec2_type, spv::OpBitwiseOr);
|
|
or_rounding_bits->addIdOperand(is_odd->getResultId());
|
|
or_rounding_bits->addIdOperand(rounding_bits->getResultId());
|
|
|
|
// To get > 0x80 in lower bits either the fraction is > 0.5, or it's exactly 0.5 and the upper part is odd,
|
|
// which would make the result 0x81.
|
|
// We shift the high bits away and then compare with 0x8000
|
|
|
|
auto *rounding_bits_in_msb = builder.addInstruction(s16vec2_type, spv::OpShiftLeftLogical);
|
|
rounding_bits_in_msb->addIdOperand(or_rounding_bits->getResultId());
|
|
rounding_bits_in_msb->addIdOperand(make_i16vec2_constant(builder, 8));
|
|
|
|
auto *should_round = builder.addInstruction(bvec2_type, spv::OpUGreaterThan);
|
|
should_round->addIdOperand(rounding_bits_in_msb->getResultId());
|
|
should_round->addIdOperand(make_u16vec2_constant(builder, 0x8000));
|
|
|
|
// Compensate for exponent bias when dealing with denorms.
|
|
auto *rounding = builder.addInstruction(s16vec2_type, spv::OpSelect);
|
|
rounding->addIdOperand(should_round->getResultId());
|
|
rounding->addIdOperand(make_i16vec2_constant(builder, 1));
|
|
rounding->addIdOperand(make_i16vec2_constant(builder, 0));
|
|
|
|
// Add rounding.
|
|
auto *add_rounding = builder.addInstruction(s16vec2_type, spv::OpIAdd);
|
|
add_rounding->addIdOperand(unsigned_e5m3->getResultId());
|
|
add_rounding->addIdOperand(rounding->getResultId());
|
|
|
|
// Mask away the top exponent. We should be in range now anyway.
|
|
auto *e4m3 = builder.addInstruction(s16vec2_type, spv::OpBitwiseAnd);
|
|
e4m3->addIdOperand(add_rounding->getResultId());
|
|
e4m3->addIdOperand(make_i16vec2_constant(builder, 0x7f));
|
|
|
|
// OR in the sign bit.
|
|
auto *with_sign = builder.addInstruction(s16vec2_type, spv::OpBitwiseOr);
|
|
with_sign->addIdOperand(e4m3->getResultId());
|
|
with_sign->addIdOperand(sign_bit->getResultId());
|
|
|
|
auto *trunc = builder.addInstruction(u8vec2_type, spv::OpUConvert);
|
|
trunc->addIdOperand(with_sign->getResultId());
|
|
|
|
for (unsigned i = 0; i < 2; i++)
|
|
{
|
|
spv::Id index = i ? index1->getResultId() : phi->getResultId();
|
|
|
|
auto *output_chain = builder.addInstruction(builder.makePointer(spv::StorageClassFunction, u8_type),
|
|
spv::OpInBoundsAccessChain);
|
|
output_chain->addIdOperand(output_id);
|
|
output_chain->addIdOperand(index);
|
|
|
|
auto *component = builder.addInstruction(u8_type, spv::OpCompositeExtract);
|
|
component->addIdOperand(trunc->getResultId());
|
|
component->addImmediateOperand(i);
|
|
|
|
auto *store = builder.addInstruction(spv::OpStore);
|
|
store->addIdOperand(output_chain->getResultId());
|
|
store->addIdOperand(component->getResultId());
|
|
}
|
|
|
|
auto *cmp = builder.addInstruction(bool_type, spv::OpULessThan);
|
|
cmp->addIdOperand(iter->getResultId());
|
|
cmp->addIdOperand(len->getResultId());
|
|
builder.createLoopMerge(merge, header, 0);
|
|
builder.createConditionalBranch(cmp->getResultId(), header, merge);
|
|
}
|
|
|
|
builder.setBuildPoint(merge);
|
|
auto *loaded_result = builder.addInstruction(u8mat_type, spv::OpLoad);
|
|
loaded_result->addIdOperand(output_id);
|
|
builder.makeReturn(false, loaded_result->getResultId());
|
|
builder.setBuildPoint(current_build_point);
|
|
coop_mat_conv_ids.push_back({ f16mat_type, u8mat_type, func->getId(), HelperCall::CoopMatFP16toFP8 });
|
|
return func->getId();
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::build_coop_mat_fp8_to_fp16(SPIRVModule &module, const spv::Id *ids, uint32_t id_count)
|
|
{
|
|
if (id_count != 2)
|
|
return 0;
|
|
|
|
spv::Id u8_type = ids[0];
|
|
spv::Id f16_type = ids[1];
|
|
|
|
for (auto &ops : coop_mat_conv_ids)
|
|
if (ops.input_type == u8_type && ops.output_type == f16_type && ops.call == HelperCall::CoopMatFP8toFP16)
|
|
return ops.func_id;
|
|
|
|
auto *current_build_point = builder.getBuildPoint();
|
|
|
|
spv::Block *entry = nullptr;
|
|
spv::Id uint_type = builder.makeUintType(32);
|
|
spv::Id bool_type = builder.makeBoolType();
|
|
|
|
// RADV workaround. It fails to understand coopmat passed as value.
|
|
spv::Id u8_ptr_type = builder.makePointer(spv::StorageClassFunction, u8_type);
|
|
|
|
auto *func = builder.makeFunctionEntry(spv::NoPrecision, f16_type,
|
|
"CoopMatFP8toFP16",
|
|
{ u8_ptr_type }, {}, &entry);
|
|
|
|
spv::Id output_id = create_variable(spv::StorageClassFunction, f16_type, "coop_output");
|
|
|
|
auto *len = builder.addInstruction(uint_type, spv::OpCooperativeMatrixLengthKHR);
|
|
len->addIdOperand(u8_type);
|
|
|
|
auto *header = new spv::Block(builder.getUniqueId(), *func);
|
|
auto *merge = new spv::Block(builder.getUniqueId(), *func);
|
|
builder.createBranch(header);
|
|
builder.setBuildPoint(header);
|
|
{
|
|
auto *phi = builder.addInstruction(uint_type, spv::OpPhi);
|
|
auto *iter = builder.addInstruction(uint_type, spv::OpIAdd);
|
|
iter->addIdOperand(phi->getResultId());
|
|
iter->addIdOperand(builder.makeUintConstant(1));
|
|
|
|
phi->addIdOperand(builder.makeUintConstant(0));
|
|
phi->addIdOperand(entry->getId());
|
|
phi->addIdOperand(iter->getResultId());
|
|
phi->addIdOperand(header->getId());
|
|
|
|
auto *input_chain = builder.addInstruction(
|
|
builder.makePointer(spv::StorageClassFunction, builder.makeUintType(8)), spv::OpInBoundsAccessChain);
|
|
input_chain->addIdOperand(func->getParamId(0));
|
|
input_chain->addIdOperand(phi->getResultId());
|
|
|
|
auto *load = builder.addInstruction(builder.makeUintType(8), spv::OpLoad);
|
|
load->addIdOperand(input_chain->getResultId());
|
|
|
|
auto *sext = builder.addInstruction(builder.makeIntType(16), spv::OpSConvert);
|
|
sext->addIdOperand(load->getResultId());
|
|
|
|
auto *shift = builder.addInstruction(builder.makeIntType(16), spv::OpShiftLeftLogical);
|
|
shift->addIdOperand(sext->getResultId());
|
|
shift->addIdOperand(builder.makeInt16Constant(7));
|
|
|
|
auto *mask = builder.addInstruction(builder.makeIntType(16), spv::OpBitwiseAnd);
|
|
mask->addIdOperand(shift->getResultId());
|
|
mask->addIdOperand(builder.makeInt16Constant(int16_t(0xffff ^ 0x4000)));
|
|
|
|
auto *bitcast = builder.addInstruction(builder.makeFloatType(16), spv::OpBitcast);
|
|
bitcast->addIdOperand(mask->getResultId());
|
|
|
|
auto *output_chain = builder.addInstruction(
|
|
builder.makePointer(spv::StorageClassFunction, builder.makeFloatType(16)), spv::OpInBoundsAccessChain);
|
|
output_chain->addIdOperand(output_id);
|
|
output_chain->addIdOperand(phi->getResultId());
|
|
auto *store = builder.addInstruction(spv::OpStore);
|
|
store->addIdOperand(output_chain->getResultId());
|
|
store->addIdOperand(bitcast->getResultId());
|
|
|
|
auto *cmp = builder.addInstruction(bool_type, spv::OpULessThan);
|
|
cmp->addIdOperand(iter->getResultId());
|
|
cmp->addIdOperand(len->getResultId());
|
|
builder.createLoopMerge(merge, header, 0);
|
|
builder.createConditionalBranch(cmp->getResultId(), header, merge);
|
|
}
|
|
|
|
builder.setBuildPoint(merge);
|
|
auto *loaded_result = builder.addInstruction(f16_type, spv::OpLoad);
|
|
loaded_result->addIdOperand(output_id);
|
|
|
|
// Need post-scale to correctly deal with denorms.
|
|
auto *scale = builder.addInstruction(f16_type, spv::OpMatrixTimesScalar);
|
|
scale->addIdOperand(loaded_result->getResultId());
|
|
scale->addIdOperand(builder.makeFloat16Constant(0x5c00 /* 256.0 */));
|
|
|
|
builder.makeReturn(false, scale->getResultId());
|
|
|
|
builder.setBuildPoint(current_build_point);
|
|
coop_mat_conv_ids.push_back({ u8_type, f16_type, func->getId(), HelperCall::CoopMatFP8toFP16 });
|
|
return func->getId();
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::get_helper_call_id(SPIRVModule &module, HelperCall call,
|
|
const spv::Id *aux_ids, uint32_t aux_ids_count)
|
|
{
|
|
switch (call)
|
|
{
|
|
case HelperCall::NodeCoalescePayloadOffset:
|
|
return build_node_coalesce_payload_offset(module, aux_ids, aux_ids_count);
|
|
case HelperCall::CoopMatFP8toFP16:
|
|
return build_coop_mat_fp8_to_fp16(module, aux_ids, aux_ids_count);
|
|
case HelperCall::CoopMatFP16toFP8:
|
|
return build_coop_mat_fp16_to_fp8(module, aux_ids, aux_ids_count);
|
|
case HelperCall::CoopMatTransfer:
|
|
return build_coop_mat_transfer(module, aux_ids, aux_ids_count);
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::get_helper_call_id(SPIRVModule &module, HelperCall call, spv::Id type_id)
|
|
{
|
|
switch (call)
|
|
{
|
|
case HelperCall::DescriptorQACheck:
|
|
return build_descriptor_qa_check(module);
|
|
|
|
case HelperCall::WaveMatch:
|
|
return build_wave_match(module, type_id);
|
|
|
|
case HelperCall::WaveMultiPrefixCountBits:
|
|
return build_wave_multi_prefix_count_bits(module);
|
|
|
|
case HelperCall::WaveMultiPrefixFAdd:
|
|
return build_wave_multi_prefix_op(module, spv::OpGroupNonUniformFAdd, type_id);
|
|
case HelperCall::WaveMultiPrefixIAdd:
|
|
return build_wave_multi_prefix_op(module, spv::OpGroupNonUniformIAdd, type_id);
|
|
case HelperCall::WaveMultiPrefixFMul:
|
|
return build_wave_multi_prefix_op(module, spv::OpGroupNonUniformFMul, type_id);
|
|
case HelperCall::WaveMultiPrefixIMul:
|
|
return build_wave_multi_prefix_op(module, spv::OpGroupNonUniformIMul, type_id);
|
|
case HelperCall::WaveMultiPrefixBitOr:
|
|
return build_wave_multi_prefix_op(module, spv::OpGroupNonUniformBitwiseOr, type_id);
|
|
case HelperCall::WaveMultiPrefixBitAnd:
|
|
return build_wave_multi_prefix_op(module, spv::OpGroupNonUniformBitwiseAnd, type_id);
|
|
case HelperCall::WaveMultiPrefixBitXor:
|
|
return build_wave_multi_prefix_op(module, spv::OpGroupNonUniformBitwiseXor, type_id);
|
|
case HelperCall::WaveIsFirstLaneMasked:
|
|
return build_wave_is_first_lane_masked(module);
|
|
case HelperCall::WaveActiveAllEqualMasked:
|
|
return build_wave_active_all_equal_masked(module, type_id);
|
|
case HelperCall::WaveReadFirstLaneMasked:
|
|
return build_wave_read_first_lane_masked(module, type_id);
|
|
case HelperCall::RobustAtomicCounter:
|
|
return build_robust_atomic_counter_op(module);
|
|
case HelperCall::QuadAll:
|
|
return build_quad_all(module);
|
|
case HelperCall::QuadAny:
|
|
return build_quad_any(module);
|
|
case HelperCall::AtomicImageArrayR64Compact:
|
|
case HelperCall::AtomicImageR64Compact:
|
|
case HelperCall::AtomicImageArrayR64CompactNonUniform:
|
|
case HelperCall::AtomicImageR64CompactNonUniform:
|
|
return build_image_atomic_r64_compact(
|
|
module,
|
|
call == HelperCall::AtomicImageArrayR64Compact || call == HelperCall::AtomicImageArrayR64CompactNonUniform,
|
|
call == HelperCall::AtomicImageR64CompactNonUniform || call == HelperCall::AtomicImageArrayR64CompactNonUniform);
|
|
case HelperCall::FinishCrossGroupSharing:
|
|
return build_finish_cross_group_sharing(module);
|
|
case HelperCall::AllocateGroupNodeRecords:
|
|
return build_allocate_node_records(module, false);
|
|
case HelperCall::AllocateThreadNodeRecords:
|
|
return build_allocate_node_records(module, true);
|
|
case HelperCall::AllocateThreadNodeRecordsWaterfall:
|
|
return build_allocate_node_records_waterfall(module);
|
|
case HelperCall::GroupIncrementOutputCount:
|
|
return build_increment_node_count(module, false);
|
|
case HelperCall::ThreadIncrementOutputCount:
|
|
return build_increment_node_count(module, true);
|
|
case HelperCall::IsQuadUniformControlFlow:
|
|
return build_is_quad_uniform_control_flow(module);
|
|
case HelperCall::ValidateBDALoadStore:
|
|
return build_validate_bda_load_store(module);
|
|
case HelperCall::AllocateInvocationID:
|
|
return build_allocate_invocation_id(module);
|
|
case HelperCall::CoopMatSaturationFixup:
|
|
return build_coop_mat_saturation_fixup(module, type_id);
|
|
case HelperCall::CoopMatSaturateFP8:
|
|
return build_coop_mat_saturate_fp8(module, type_id);
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
SPIRVModule::SPIRVModule()
|
|
{
|
|
impl = std::make_unique<Impl>(*this);
|
|
}
|
|
|
|
void SPIRVModule::emit_entry_point(spv::ExecutionModel model, const char *name, bool physical_storage,
|
|
spv::MemoryModel memory_model)
|
|
{
|
|
if (memory_model == spv::MemoryModelVulkan)
|
|
set_override_spirv_version(0x10600);
|
|
impl->emit_entry_point(model, name, physical_storage, memory_model);
|
|
}
|
|
|
|
bool SPIRVModule::Impl::execution_model_is_ray_tracing() const
|
|
{
|
|
switch (execution_model)
|
|
{
|
|
case spv::ExecutionModelRayGenerationKHR:
|
|
case spv::ExecutionModelAnyHitKHR:
|
|
case spv::ExecutionModelIntersectionKHR:
|
|
case spv::ExecutionModelMissKHR:
|
|
case spv::ExecutionModelClosestHitKHR:
|
|
case spv::ExecutionModelCallableKHR:
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool SPIRVModule::Impl::spirv_requires_14() const
|
|
{
|
|
static const uint32_t Version_1_4 = 0x00010400;
|
|
if (override_spirv_version)
|
|
{
|
|
return override_spirv_version >= Version_1_4;
|
|
}
|
|
else
|
|
{
|
|
return execution_model_is_ray_tracing() || execution_model == spv::ExecutionModelMeshEXT ||
|
|
execution_model == spv::ExecutionModelTaskEXT;
|
|
}
|
|
}
|
|
|
|
bool SPIRVModule::Impl::finalize_spirv(Vector<uint32_t> &spirv)
|
|
{
|
|
spirv.clear();
|
|
|
|
mark_error = false;
|
|
builder.dump(spirv);
|
|
if (spirv.size() >= 2)
|
|
{
|
|
if (override_spirv_version)
|
|
{
|
|
spirv[1] = override_spirv_version;
|
|
}
|
|
else
|
|
{
|
|
static const uint32_t Version_1_3 = 0x00010300;
|
|
static const uint32_t Version_1_4 = 0x00010400;
|
|
spirv[1] = spirv_requires_14() ? Version_1_4 : Version_1_3;
|
|
}
|
|
}
|
|
return !mark_error;
|
|
}
|
|
|
|
void SPIRVModule::Impl::register_block(CFGNode *node)
|
|
{
|
|
if (!node->userdata || node->id == 0)
|
|
{
|
|
auto *bb = new spv::Block(builder.getUniqueId(), *active_function);
|
|
#if 0
|
|
if (!node->name.empty())
|
|
builder.addName(bb->getId(), node->name.c_str());
|
|
#endif
|
|
active_function->addBlock(bb);
|
|
node->id = bb->getId();
|
|
node->userdata = bb;
|
|
}
|
|
}
|
|
|
|
void SPIRVModule::Impl::add_instrumented_instruction(spv::Op op, spv::Block *bb, spv::Id id)
|
|
{
|
|
if (id == 0 || !instruction_instrumentation.info.enabled)
|
|
return;
|
|
|
|
spv::Id *call_id = nullptr;
|
|
spv::Id type_id = 0;
|
|
|
|
if (op == spv::OpAssumeTrueKHR)
|
|
{
|
|
call_id = &instruction_instrumentation.assume_true_call_id;
|
|
}
|
|
else
|
|
{
|
|
type_id = builder.getTypeId(id);
|
|
if (builder.getTypeClass(type_id) != spv::OpTypeFloat)
|
|
return;
|
|
|
|
switch (builder.getScalarTypeWidth(type_id))
|
|
{
|
|
case 16:
|
|
call_id = &instruction_instrumentation.nan_inf_instrument_fp16_call_id;
|
|
break;
|
|
|
|
case 32:
|
|
call_id = &instruction_instrumentation.nan_inf_instrument_fp32_call_id;
|
|
break;
|
|
|
|
case 64:
|
|
call_id = &instruction_instrumentation.nan_inf_instrument_fp64_call_id;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!instruction_instrumentation.should_report_instrumentation_id)
|
|
{
|
|
instruction_instrumentation.should_report_instrumentation_id = create_variable_with_initializer(
|
|
spv::StorageClassPrivate, builder.makeBoolType(),
|
|
builder.makeBoolConstant(true), "ShouldReportInstrumentation");
|
|
|
|
spv::Id u32_type = builder.makeUintType(32);
|
|
spv::Id uvec4_type = builder.makeVectorType(u32_type, 4);
|
|
spv::Id u32_array_type = builder.makeRuntimeArray(u32_type);
|
|
builder.addDecoration(u32_array_type, spv::DecorationArrayStride, 4);
|
|
spv::Id control_data = builder.makeStructType({ u32_array_type }, "InstrumentationControlDataSSBO");
|
|
builder.addMemberDecoration(control_data, 0, spv::DecorationOffset, 0);
|
|
builder.addMemberName(control_data, 0, "atomics");
|
|
builder.addDecoration(control_data, spv::DecorationBlock);
|
|
instruction_instrumentation.global_nan_inf_control_var_id =
|
|
create_variable(spv::StorageClassStorageBuffer, control_data, "InstrumentationControlData");
|
|
|
|
builder.addDecoration(instruction_instrumentation.global_nan_inf_control_var_id,
|
|
spv::DecorationDescriptorSet, instruction_instrumentation.info.control_desc_set);
|
|
|
|
builder.addDecoration(instruction_instrumentation.global_nan_inf_control_var_id,
|
|
spv::DecorationBinding,
|
|
instruction_instrumentation.info.control_binding);
|
|
|
|
spv::Id payload_data_array = builder.makeRuntimeArray(uvec4_type);
|
|
builder.addDecoration(payload_data_array, spv::DecorationArrayStride, 16);
|
|
|
|
spv::Id payload_block = builder.makeStructType({ payload_data_array }, "InstrumentationDataSSBO");
|
|
builder.addMemberName(payload_block, 0, "data");
|
|
builder.addMemberDecoration(payload_block, 0, spv::DecorationOffset, 0);
|
|
builder.addDecoration(payload_block, spv::DecorationBlock);
|
|
instruction_instrumentation.global_nan_inf_data_var_id =
|
|
create_variable(spv::StorageClassStorageBuffer, payload_block, "InstrumentationData");
|
|
|
|
builder.addDecoration(instruction_instrumentation.global_nan_inf_data_var_id,
|
|
spv::DecorationDescriptorSet, instruction_instrumentation.info.payload_desc_set);
|
|
|
|
builder.addDecoration(instruction_instrumentation.global_nan_inf_data_var_id,
|
|
spv::DecorationBinding,
|
|
instruction_instrumentation.info.payload_binding);
|
|
}
|
|
|
|
if (call_id && !*call_id)
|
|
{
|
|
if (op == spv::OpAssumeTrueKHR)
|
|
*call_id = build_assume_true_call_function(self, instruction_instrumentation);
|
|
else
|
|
*call_id = build_nan_inf_instrument_call_function(self, instruction_instrumentation, type_id);
|
|
}
|
|
|
|
auto call = std::make_unique<spv::Instruction>(builder.getUniqueId(), builder.makeVoidType(), spv::OpFunctionCall);
|
|
call->addIdOperand(*call_id);
|
|
call->addIdOperand(id);
|
|
call->addIdOperand(builder.makeUintConstant(++instruction_instrumentation.instruction_count));
|
|
bb->addInstruction(std::move(call));
|
|
}
|
|
|
|
void SPIRVModule::Impl::add_instruction(spv::Block *bb, std::unique_ptr<spv::Instruction> inst)
|
|
{
|
|
spv::Op op = inst->getOpCode();
|
|
spv::Id id = inst->getResultId();
|
|
spv::Id type_id = inst->getTypeId();
|
|
|
|
if (id != 0 && instruction_instrumentation.info.enabled &&
|
|
instruction_instrumentation.info.type == InstructionInstrumentationType::FlushNaNToZero &&
|
|
builder.getTypeClass(type_id) == spv::OpTypeFloat && op != spv::OpPhi)
|
|
{
|
|
// A bit special since we're rewriting the IDs.
|
|
spv::Id new_id = builder.getUniqueId();
|
|
spv::Id nan_id = builder.getUniqueId();
|
|
inst->setResultId(new_id);
|
|
|
|
spv::Id null_const = builder.makeNullConstant(type_id);
|
|
auto is_nan = std::make_unique<spv::Instruction>(nan_id, builder.makeBoolType(), spv::OpIsNan);
|
|
is_nan->addIdOperand(new_id);
|
|
auto replaced = std::make_unique<spv::Instruction>(id, type_id, spv::OpSelect);
|
|
replaced->addIdOperand(nan_id);
|
|
replaced->addIdOperand(null_const);
|
|
replaced->addIdOperand(new_id);
|
|
|
|
bb->addInstruction(std::move(inst));
|
|
bb->addInstruction(std::move(is_nan));
|
|
bb->addInstruction(std::move(replaced));
|
|
}
|
|
else
|
|
{
|
|
bb->addInstruction(std::move(inst));
|
|
|
|
// For Full instrumentation add everything, otherwise, we need specialized instrumentation.
|
|
if (instruction_instrumentation.info.enabled &&
|
|
instruction_instrumentation.info.type == InstructionInstrumentationType::FullNanInf &&
|
|
op != spv::OpPhi)
|
|
{
|
|
add_instrumented_instruction(op, bb, id);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SPIRVModule::Impl::emit_basic_block(CFGNode *node)
|
|
{
|
|
auto *bb = get_spv_block(node);
|
|
auto &ir = node->ir;
|
|
|
|
builder.setBuildPoint(bb);
|
|
|
|
spv::Block *fake_loop_block = nullptr;
|
|
|
|
// Break-like loops might not have a continue block.
|
|
// Infinite loops won't have merge blocks.
|
|
if (node->ir.merge_info.merge_type == MergeType::Loop &&
|
|
(int(node->ir.merge_info.merge_block != nullptr) +
|
|
int(node->ir.merge_info.continue_block != nullptr) == 1))
|
|
{
|
|
fake_loop_block = new spv::Block(builder.getUniqueId(), *active_function);
|
|
}
|
|
|
|
// Emit phi nodes.
|
|
for (auto &phi : ir.phi)
|
|
{
|
|
if (!phi.id)
|
|
continue;
|
|
|
|
auto phi_op = std::make_unique<spv::Instruction>(phi.id, phi.type_id, spv::OpPhi);
|
|
for (auto &incoming : phi.incoming)
|
|
{
|
|
phi_op->addIdOperand(incoming.id);
|
|
phi_op->addIdOperand(incoming.block->id);
|
|
}
|
|
|
|
if (fake_loop_block && !node->ir.merge_info.continue_block)
|
|
{
|
|
builder.setBuildPoint(fake_loop_block);
|
|
phi_op->addIdOperand(builder.createUndefined(phi.type_id));
|
|
builder.setBuildPoint(bb);
|
|
phi_op->addIdOperand(fake_loop_block->getId());
|
|
}
|
|
|
|
if (phi.relaxed)
|
|
builder.addDecoration(phi.id, spv::DecorationRelaxedPrecision);
|
|
|
|
add_instruction(bb, std::move(phi_op));
|
|
}
|
|
|
|
bool implicit_terminator = false;
|
|
spv::Id rewrite_phi_incoming_from = node->id;
|
|
spv::Id rewrite_phi_incoming_to = 0;
|
|
|
|
const auto emit_loop_header = [&](spv::Block *replacement_continue_bb) {
|
|
auto *continue_bb = ir.merge_info.continue_block ? get_spv_block(ir.merge_info.continue_block) : nullptr;
|
|
if (replacement_continue_bb)
|
|
continue_bb = replacement_continue_bb;
|
|
|
|
if (ir.merge_info.merge_block && ir.merge_info.continue_block)
|
|
{
|
|
builder.createLoopMerge(get_spv_block(ir.merge_info.merge_block),
|
|
continue_bb,
|
|
ir.merge_info.loop_control_mask);
|
|
}
|
|
else if (ir.merge_info.merge_block)
|
|
{
|
|
continue_bb = fake_loop_block;
|
|
active_function->addBlock(continue_bb);
|
|
builder.setBuildPoint(continue_bb);
|
|
builder.createBranch(get_spv_block(node));
|
|
builder.setBuildPoint(bb);
|
|
builder.createLoopMerge(get_spv_block(ir.merge_info.merge_block), continue_bb, 0);
|
|
}
|
|
else if (ir.merge_info.continue_block)
|
|
{
|
|
auto *merge_bb = fake_loop_block;
|
|
active_function->addBlock(merge_bb);
|
|
builder.setBuildPoint(merge_bb);
|
|
builder.createUnreachable();
|
|
builder.setBuildPoint(bb);
|
|
builder.createLoopMerge(merge_bb, continue_bb, 0);
|
|
}
|
|
};
|
|
|
|
// Emit opcodes.
|
|
for (auto *op : ir.operations)
|
|
{
|
|
if (implicit_terminator)
|
|
break;
|
|
|
|
if (op->flags & Operation::SubgroupSyncPre)
|
|
{
|
|
auto *pre = builder.addInstruction(spv::OpControlBarrier);
|
|
pre->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
pre->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
pre->addIdOperand(builder.getWorkgroupBarrierSemanticsId());
|
|
}
|
|
|
|
if (op->op == spv::OpIsHelperInvocationEXT && !caps.supports_demote)
|
|
{
|
|
spv::Id helper_var_id = get_builtin_shader_input(spv::BuiltInHelperInvocation);
|
|
|
|
if (discard_state_var_id)
|
|
{
|
|
auto is_helper = std::make_unique<spv::Instruction>(builder.getUniqueId(), builder.makeBoolType(), spv::OpLoad);
|
|
is_helper->addIdOperand(helper_var_id);
|
|
spv::Id is_helper_id = is_helper->getResultId();
|
|
add_instruction(bb, std::move(is_helper));
|
|
|
|
auto loaded_var = std::make_unique<spv::Instruction>(builder.getUniqueId(), builder.makeBoolType(), spv::OpLoad);
|
|
loaded_var->addIdOperand(discard_state_var_id);
|
|
spv::Id is_discard_id = loaded_var->getResultId();
|
|
add_instruction(bb, std::move(loaded_var));
|
|
|
|
auto or_inst = std::make_unique<spv::Instruction>(op->id, op->type_id, spv::OpLogicalOr);
|
|
or_inst->addIdOperand(is_helper_id);
|
|
or_inst->addIdOperand(is_discard_id);
|
|
add_instruction(bb, std::move(or_inst));
|
|
}
|
|
else
|
|
{
|
|
auto is_helper = std::make_unique<spv::Instruction>(op->id, op->type_id, spv::OpLoad);
|
|
is_helper->addIdOperand(helper_var_id);
|
|
add_instruction(bb, std::move(is_helper));
|
|
}
|
|
}
|
|
else if (op->op == spv::OpDemoteToHelperInvocationEXT && !caps.supports_demote)
|
|
{
|
|
if (op->num_arguments)
|
|
build_discard_call_early_cond(op->arguments[0]);
|
|
else
|
|
build_discard_call_early();
|
|
}
|
|
else if (op->op == spv::OpDemoteToHelperInvocationEXT && op->num_arguments)
|
|
{
|
|
builder.addExtension("SPV_EXT_demote_to_helper_invocation");
|
|
builder.addCapability(spv::CapabilityDemoteToHelperInvocationEXT);
|
|
build_demote_call_cond(op->arguments[0]);
|
|
}
|
|
else if (op->op == spv::PseudoOpInstrumentExternallyVisibleStore || op->op == spv::OpAssumeTrueKHR)
|
|
{
|
|
add_instrumented_instruction(op->op, bb, op->arguments[0]);
|
|
}
|
|
else if (op->op == spv::PseudoOpReturnCond ||
|
|
op->op == spv::PseudoOpMaskedLoad ||
|
|
op->op == spv::PseudoOpMaskedStore)
|
|
{
|
|
// Have to ensure we emit loop header before we replace block.
|
|
if (ir.merge_info.merge_type == MergeType::Loop && rewrite_phi_incoming_to == 0)
|
|
{
|
|
auto *direct_bb = new spv::Block(builder.getUniqueId(), *active_function);
|
|
|
|
// Handle post-domination rule.
|
|
if (ir.merge_info.continue_block && get_spv_block(ir.merge_info.continue_block) == bb)
|
|
emit_loop_header(direct_bb);
|
|
else
|
|
emit_loop_header(nullptr);
|
|
|
|
active_function->addBlock(direct_bb);
|
|
builder.createBranch(direct_bb);
|
|
bb = direct_bb;
|
|
builder.setBuildPoint(bb);
|
|
}
|
|
|
|
// Pseudo-op. Conditional return.
|
|
auto *inner_bb = new spv::Block(builder.getUniqueId(), *active_function);
|
|
auto *merge_bb = new spv::Block(builder.getUniqueId(), *active_function);
|
|
active_function->addBlock(inner_bb);
|
|
active_function->addBlock(merge_bb);
|
|
builder.createSelectionMerge(merge_bb, 0);
|
|
builder.createConditionalBranch(op->arguments[op->num_arguments - 1], inner_bb, merge_bb);
|
|
builder.setBuildPoint(inner_bb);
|
|
|
|
spv::Id inner_id = 0;
|
|
|
|
if (op->op == spv::PseudoOpReturnCond)
|
|
{
|
|
builder.makeReturn(false);
|
|
}
|
|
else
|
|
{
|
|
std::unique_ptr<spv::Instruction> inst;
|
|
if (op->op == spv::PseudoOpMaskedStore)
|
|
{
|
|
inst = std::make_unique<spv::Instruction>(spv::OpStore);
|
|
}
|
|
else
|
|
{
|
|
inner_id = builder.getUniqueId();
|
|
inst = std::make_unique<spv::Instruction>(inner_id, op->type_id, spv::OpLoad);
|
|
assert(op->type_id);
|
|
}
|
|
|
|
unsigned literal_mask = op->get_literal_mask();
|
|
for (unsigned i = 0; i < op->num_arguments - 1; i++)
|
|
{
|
|
spv::Id arg = op->arguments[i];
|
|
if (literal_mask & 1u)
|
|
inst->addImmediateOperand(arg);
|
|
else
|
|
{
|
|
assert(arg);
|
|
inst->addIdOperand(arg);
|
|
}
|
|
literal_mask >>= 1u;
|
|
}
|
|
add_instruction(inner_bb, std::move(inst));
|
|
builder.createBranch(merge_bb);
|
|
}
|
|
|
|
builder.setBuildPoint(merge_bb);
|
|
|
|
if (op->op == spv::PseudoOpMaskedLoad)
|
|
{
|
|
auto phi = std::make_unique<spv::Instruction>(op->id, op->type_id, spv::OpPhi);
|
|
phi->addIdOperand(inner_id);
|
|
phi->addIdOperand(inner_bb->getId());
|
|
phi->addIdOperand(builder.makeNullConstant(op->type_id));
|
|
phi->addIdOperand(bb->getId());
|
|
add_instruction(merge_bb, std::move(phi));
|
|
}
|
|
|
|
// If this is called in a continue block, the loop header might have a PHI incoming,
|
|
// and we'll have to rewrite that.
|
|
// For purposes of handling PHI later, the incoming block is this ID, i.e. the selection construct merge.
|
|
node->id = merge_bb->getId();
|
|
rewrite_phi_incoming_to = node->id;
|
|
bb = merge_bb;
|
|
}
|
|
else if (op->op != spv::OpNop)
|
|
{
|
|
if (op->op == spv::OpDemoteToHelperInvocationEXT || op->op == spv::OpIsHelperInvocationEXT)
|
|
{
|
|
builder.addExtension("SPV_EXT_demote_to_helper_invocation");
|
|
builder.addCapability(spv::CapabilityDemoteToHelperInvocationEXT);
|
|
}
|
|
else if (op->op == spv::OpTerminateRayKHR || op->op == spv::OpIgnoreIntersectionKHR ||
|
|
op->op == spv::OpEmitMeshTasksEXT)
|
|
{
|
|
// In DXIL, these must be by unreachable.
|
|
// There is no [[noreturn]] qualifier used for these intrinsics apparently.
|
|
// EmitMeshTasksEXT is similar, but ret void comes after.
|
|
implicit_terminator = true;
|
|
}
|
|
|
|
std::unique_ptr<spv::Instruction> inst;
|
|
if (op->id != 0)
|
|
{
|
|
assert(op->type_id);
|
|
inst = std::make_unique<spv::Instruction>(op->id, op->type_id, op->op);
|
|
}
|
|
else
|
|
inst = std::make_unique<spv::Instruction>(op->op);
|
|
|
|
unsigned literal_mask = op->get_literal_mask();
|
|
|
|
for (auto &arg : *op)
|
|
{
|
|
if (literal_mask & 1u)
|
|
inst->addImmediateOperand(arg);
|
|
else
|
|
{
|
|
assert(arg);
|
|
inst->addIdOperand(arg);
|
|
}
|
|
literal_mask >>= 1u;
|
|
}
|
|
add_instruction(bb, std::move(inst));
|
|
}
|
|
|
|
if (op->flags & Operation::SubgroupSyncPost)
|
|
{
|
|
auto *post = builder.addInstruction(spv::OpControlBarrier);
|
|
post->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
post->addIdOperand(builder.makeUintConstant(spv::ScopeSubgroup));
|
|
post->addIdOperand(builder.getWorkgroupBarrierSemanticsId());
|
|
}
|
|
}
|
|
|
|
if (implicit_terminator)
|
|
{
|
|
if (ir.merge_info.merge_type != MergeType::None)
|
|
{
|
|
LOGE("Basic block has implicit terminator, but attempts to merge execution?\n");
|
|
mark_error = true;
|
|
}
|
|
else if (ir.terminator.type != Terminator::Type::Unreachable && ir.terminator.type != Terminator::Type::Return)
|
|
{
|
|
LOGE("Implicitly terminated blocks must terminate with Unreachable or Return.\n");
|
|
mark_error = true;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Emit structured merge information.
|
|
switch (ir.merge_info.merge_type)
|
|
{
|
|
case MergeType::Selection:
|
|
if (ir.merge_info.merge_block)
|
|
{
|
|
builder.createSelectionMerge(get_spv_block(ir.merge_info.merge_block), ir.merge_info.selection_control_mask);
|
|
}
|
|
else
|
|
{
|
|
auto *unreachable_bb = new spv::Block(builder.getUniqueId(), *active_function);
|
|
active_function->addBlock(unreachable_bb);
|
|
builder.setBuildPoint(unreachable_bb);
|
|
builder.createUnreachable();
|
|
builder.setBuildPoint(bb);
|
|
builder.createSelectionMerge(unreachable_bb, 0);
|
|
}
|
|
break;
|
|
|
|
case MergeType::Loop:
|
|
if (rewrite_phi_incoming_to == 0)
|
|
emit_loop_header(nullptr);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Emit terminator.
|
|
switch (ir.terminator.type)
|
|
{
|
|
case Terminator::Type::Unreachable:
|
|
{
|
|
builder.createUnreachable();
|
|
break;
|
|
}
|
|
|
|
case Terminator::Type::Branch:
|
|
{
|
|
auto *direct_target = get_spv_block(ir.terminator.direct_block);
|
|
builder.createBranch(direct_target);
|
|
if (rewrite_phi_incoming_to)
|
|
direct_target->rewritePhiIncoming(rewrite_phi_incoming_from, rewrite_phi_incoming_to);
|
|
break;
|
|
}
|
|
|
|
case Terminator::Type::Condition:
|
|
{
|
|
auto *true_block = get_spv_block(ir.terminator.true_block);
|
|
auto *false_block = get_spv_block(ir.terminator.false_block);
|
|
|
|
// This used to pass validator, but latest SPIRV-Tools as of 2023-02 started caring about it.
|
|
// Patch this up late by rewriting loop header + conditional branch as
|
|
// loop header -> direct -> selection merge to unreachable -> conditional branch.
|
|
// It's easier to patch it up here than rewriting the CFG itself.
|
|
// We only need to rewrite PHI incoming blocks.
|
|
// A conditional terminator is fine if one of the branches target merge or continue block,
|
|
// since a selection merge is no longer required.
|
|
if (node->ir.merge_info.merge_type == MergeType::Loop &&
|
|
node->ir.terminator.type == Terminator::Type::Condition &&
|
|
node->ir.terminator.true_block != node->ir.merge_info.merge_block &&
|
|
node->ir.terminator.true_block != node->ir.merge_info.continue_block &&
|
|
node->ir.terminator.false_block != node->ir.merge_info.merge_block &&
|
|
node->ir.terminator.false_block != node->ir.merge_info.continue_block)
|
|
{
|
|
if (rewrite_phi_incoming_to == 0)
|
|
{
|
|
auto *fake_selection_bb = new spv::Block(builder.getUniqueId(), *active_function);
|
|
auto *unreachable_bb = new spv::Block(builder.getUniqueId(), *active_function);
|
|
active_function->addBlock(fake_selection_bb);
|
|
active_function->addBlock(unreachable_bb);
|
|
builder.createBranch(fake_selection_bb);
|
|
builder.setBuildPoint(fake_selection_bb);
|
|
builder.createSelectionMerge(unreachable_bb, 0);
|
|
builder.createConditionalBranch(ir.terminator.conditional_id, true_block, false_block);
|
|
builder.setBuildPoint(unreachable_bb);
|
|
builder.createUnreachable();
|
|
|
|
// For purposes of handling PHI later, the incoming block is this ID, i.e. the selection construct.
|
|
// Any branches that target the loop header have already been resolved since we emit SPIR-V blocks
|
|
// in traversal order.
|
|
// We don't need to consider single loop block constructs since those will never hit this
|
|
// code path. In that case, true or false block would have targeted continue block and
|
|
// avoided this workaround in the first place.
|
|
node->id = fake_selection_bb->getId();
|
|
}
|
|
else
|
|
{
|
|
// If we added blocks through masked ops, we have become a selection
|
|
// construction instead of a loop, and we have to add a merge target to make this valid.
|
|
// If we are directly branching to continue or merge target, we can omit a merge,
|
|
// since that case does not need a merge.
|
|
auto *unreachable_bb = new spv::Block(builder.getUniqueId(), *active_function);
|
|
active_function->addBlock(unreachable_bb);
|
|
builder.setBuildPoint(unreachable_bb);
|
|
builder.createUnreachable();
|
|
builder.setBuildPoint(bb);
|
|
builder.createSelectionMerge(unreachable_bb, 0);
|
|
builder.createConditionalBranch(ir.terminator.conditional_id,
|
|
true_block, false_block);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
builder.createConditionalBranch(ir.terminator.conditional_id,
|
|
true_block, false_block);
|
|
}
|
|
|
|
if (rewrite_phi_incoming_to)
|
|
{
|
|
true_block->rewritePhiIncoming(rewrite_phi_incoming_from, rewrite_phi_incoming_to);
|
|
false_block->rewritePhiIncoming(rewrite_phi_incoming_from, rewrite_phi_incoming_to);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Terminator::Type::Switch:
|
|
{
|
|
auto switch_op = std::make_unique<spv::Instruction>(spv::OpSwitch);
|
|
switch_op->addIdOperand(ir.terminator.conditional_id);
|
|
|
|
auto default_itr = std::find_if(ir.terminator.cases.begin(), ir.terminator.cases.end(),
|
|
[](const Terminator::Case &c) { return c.is_default; });
|
|
assert(default_itr != ir.terminator.cases.end());
|
|
switch_op->addIdOperand(default_itr->node->id);
|
|
|
|
auto *default_block = get_spv_block(default_itr->node);
|
|
default_block->addPredecessor(bb);
|
|
if (rewrite_phi_incoming_to)
|
|
default_block->rewritePhiIncoming(rewrite_phi_incoming_from, rewrite_phi_incoming_to);
|
|
for (auto &switch_case : ir.terminator.cases)
|
|
{
|
|
if (switch_case.is_default)
|
|
continue;
|
|
switch_op->addImmediateOperand(switch_case.value);
|
|
switch_op->addIdOperand(switch_case.node->id);
|
|
|
|
auto *case_block = get_spv_block(switch_case.node);
|
|
case_block->addPredecessor(bb);
|
|
if (rewrite_phi_incoming_to)
|
|
case_block->rewritePhiIncoming(rewrite_phi_incoming_from, rewrite_phi_incoming_to);
|
|
}
|
|
add_instruction(bb, std::move(switch_op));
|
|
break;
|
|
}
|
|
|
|
case Terminator::Type::Kill:
|
|
{
|
|
auto kill_op = std::make_unique<spv::Instruction>(spv::OpKill);
|
|
add_instruction(bb, std::move(kill_op));
|
|
break;
|
|
}
|
|
|
|
case Terminator::Type::Return:
|
|
{
|
|
if (discard_state_var_id)
|
|
build_discard_call_exit();
|
|
builder.makeReturn(false, ir.terminator.return_value);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool SPIRVModule::finalize_spirv(Vector<uint32_t> &spirv) const
|
|
{
|
|
return impl->finalize_spirv(spirv);
|
|
}
|
|
|
|
void SPIRVModule::Impl::emit_entry_point_function_body(CFGStructurizer &structurizer)
|
|
{
|
|
active_function = entry_function;
|
|
{
|
|
structurizer.traverse(*this);
|
|
builder.setBuildPoint(active_function->getEntryBlock());
|
|
builder.createBranch(get_spv_block(structurizer.get_entry_block()));
|
|
builder.leaveFunction();
|
|
}
|
|
active_function = nullptr;
|
|
}
|
|
|
|
void SPIRVModule::Impl::emit_leaf_function_body(spv::Function *func, CFGStructurizer &structurizer)
|
|
{
|
|
active_function = func;
|
|
{
|
|
structurizer.traverse(*this);
|
|
builder.setBuildPoint(active_function->getEntryBlock());
|
|
builder.createBranch(get_spv_block(structurizer.get_entry_block()));
|
|
builder.leaveFunction();
|
|
}
|
|
active_function = nullptr;
|
|
}
|
|
|
|
void SPIRVModule::Impl::register_active_variable(spv::StorageClass storage, spv::Id id)
|
|
{
|
|
bool register_entry_point;
|
|
// In SPIR-V 1.4, any global variable is part of the interface.
|
|
if (spirv_requires_14())
|
|
register_entry_point = storage != spv::StorageClassFunction;
|
|
else
|
|
register_entry_point = storage == spv::StorageClassOutput || storage == spv::StorageClassInput;
|
|
|
|
if (register_entry_point)
|
|
entry_point->addIdOperand(id);
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::create_variable(spv::StorageClass storage, spv::Id type, const char *name)
|
|
{
|
|
spv::Id id = builder.createVariable(storage, type, name);
|
|
register_active_variable(storage, id);
|
|
return id;
|
|
}
|
|
|
|
spv::Id SPIRVModule::Impl::create_variable_with_initializer(spv::StorageClass storage, spv::Id type,
|
|
spv::Id initializer, const char *name)
|
|
{
|
|
spv::Id id = builder.createVariableWithInitializer(storage, type, initializer, name);
|
|
register_active_variable(storage, id);
|
|
return id;
|
|
}
|
|
|
|
void SPIRVModule::emit_entry_point_function_body(CFGStructurizer &structurizer)
|
|
{
|
|
impl->emit_entry_point_function_body(structurizer);
|
|
}
|
|
|
|
void SPIRVModule::emit_leaf_function_body(spv::Function *func, CFGStructurizer &structurizer)
|
|
{
|
|
impl->emit_leaf_function_body(func, structurizer);
|
|
}
|
|
|
|
spv::Builder &SPIRVModule::get_builder()
|
|
{
|
|
return impl->builder;
|
|
}
|
|
|
|
spv::Instruction *SPIRVModule::get_entry_point()
|
|
{
|
|
return impl->entry_point;
|
|
}
|
|
|
|
spv::Function *SPIRVModule::get_entry_function()
|
|
{
|
|
return impl->entry_function;
|
|
}
|
|
|
|
uint32_t SPIRVModule::allocate_id()
|
|
{
|
|
return impl->builder.getUniqueId();
|
|
}
|
|
|
|
uint32_t SPIRVModule::allocate_ids(uint32_t count)
|
|
{
|
|
return impl->builder.getUniqueIds(count);
|
|
}
|
|
|
|
void SPIRVModule::enable_shader_discard(bool supports_demote)
|
|
{
|
|
impl->enable_shader_discard(supports_demote);
|
|
}
|
|
|
|
spv::Id SPIRVModule::get_builtin_shader_input(spv::BuiltIn builtin)
|
|
{
|
|
return impl->get_builtin_shader_input(builtin);
|
|
}
|
|
|
|
spv::Id SPIRVModule::get_builtin_shader_output(spv::BuiltIn builtin)
|
|
{
|
|
return impl->get_builtin_shader_output(builtin);
|
|
}
|
|
|
|
bool SPIRVModule::has_builtin_shader_input(spv::BuiltIn builtin) const
|
|
{
|
|
return impl->has_builtin_shader_input(builtin);
|
|
}
|
|
|
|
bool SPIRVModule::has_builtin_shader_output(spv::BuiltIn builtin) const
|
|
{
|
|
return impl->has_builtin_shader_output(builtin);
|
|
}
|
|
|
|
void SPIRVModule::register_builtin_shader_input(spv::Id id, spv::BuiltIn builtin)
|
|
{
|
|
impl->register_builtin_shader_input(id, builtin);
|
|
}
|
|
|
|
void SPIRVModule::register_builtin_shader_output(spv::Id id, spv::BuiltIn builtin)
|
|
{
|
|
impl->register_builtin_shader_output(id, builtin);
|
|
}
|
|
|
|
bool SPIRVModule::query_builtin_shader_input(spv::Id id, spv::BuiltIn *builtin) const
|
|
{
|
|
return impl->query_builtin_shader_input(id, builtin);
|
|
}
|
|
|
|
bool SPIRVModule::builtin_requires_volatile(spv::BuiltIn builtin) const
|
|
{
|
|
return impl->builtin_requires_volatile(builtin);
|
|
}
|
|
|
|
bool SPIRVModule::query_builtin_shader_output(spv::Id id, spv::BuiltIn *builtin) const
|
|
{
|
|
return impl->query_builtin_shader_output(id, builtin);
|
|
}
|
|
|
|
Operation *SPIRVModule::allocate_op()
|
|
{
|
|
return impl->operation_pool.allocate();
|
|
}
|
|
|
|
Operation *SPIRVModule::allocate_op(spv::Op op)
|
|
{
|
|
return impl->operation_pool.allocate(op);
|
|
}
|
|
|
|
Operation *SPIRVModule::allocate_op(spv::Op op, spv::Id id, spv::Id type_id)
|
|
{
|
|
return impl->operation_pool.allocate(op, id, type_id);
|
|
}
|
|
|
|
spv::Id SPIRVModule::create_variable(spv::StorageClass storage, spv::Id type, const char *name)
|
|
{
|
|
return impl->create_variable(storage, type, name);
|
|
}
|
|
|
|
spv::Id SPIRVModule::create_variable_with_initializer(spv::StorageClass storage, spv::Id type,
|
|
spv::Id initializer, const char *name)
|
|
{
|
|
return impl->create_variable_with_initializer(storage, type, initializer, name);
|
|
}
|
|
|
|
spv::Id SPIRVModule::get_helper_call_id(HelperCall call, spv::Id type_id)
|
|
{
|
|
return impl->get_helper_call_id(*this, call, type_id);
|
|
}
|
|
|
|
spv::Id SPIRVModule::get_helper_call_id(HelperCall call, const spv::Id *aux_ids, uint32_t aux_id_count)
|
|
{
|
|
return impl->get_helper_call_id(*this, call, aux_ids, aux_id_count);
|
|
}
|
|
|
|
spv::Id SPIRVModule::get_robust_physical_cbv_load_call_id(spv::Id type_id, spv::Id ptr_type_id, unsigned alignment)
|
|
{
|
|
return impl->build_robust_physical_cbv_load(*this, type_id, ptr_type_id, alignment);
|
|
}
|
|
|
|
void SPIRVModule::set_descriptor_qa_info(const DescriptorQAInfo &info)
|
|
{
|
|
impl->descriptor_qa_info = info;
|
|
}
|
|
|
|
void SPIRVModule::set_instruction_instrumentation_info(const InstructionInstrumentationInfo &info)
|
|
{
|
|
impl->instruction_instrumentation.info = info;
|
|
}
|
|
|
|
const DescriptorQAInfo &SPIRVModule::get_descriptor_qa_info() const
|
|
{
|
|
return impl->descriptor_qa_info;
|
|
}
|
|
|
|
void SPIRVModule::set_override_spirv_version(uint32_t version)
|
|
{
|
|
impl->override_spirv_version = std::max<uint32_t>(impl->override_spirv_version, version);
|
|
}
|
|
|
|
void SPIRVModule::set_helper_lanes_participate_in_wave_ops(bool enable)
|
|
{
|
|
impl->helper_lanes_participate_in_wave_ops = enable;
|
|
}
|
|
|
|
void SPIRVModule::set_entry_build_point(spv::Function *func)
|
|
{
|
|
get_builder().setBuildPoint(func->getEntryBlock());
|
|
}
|
|
|
|
bool SPIRVModule::opcode_has_side_effect_and_result(spv::Op opcode)
|
|
{
|
|
switch (opcode)
|
|
{
|
|
case spv::OpAtomicIAdd:
|
|
case spv::OpAtomicIDecrement:
|
|
case spv::OpAtomicIIncrement:
|
|
case spv::OpAtomicISub:
|
|
case spv::OpAtomicAnd:
|
|
case spv::OpAtomicOr:
|
|
case spv::OpAtomicXor:
|
|
case spv::OpAtomicUMax:
|
|
case spv::OpAtomicUMin:
|
|
case spv::OpAtomicSMax:
|
|
case spv::OpAtomicSMin:
|
|
case spv::OpAtomicFAddEXT:
|
|
case spv::OpAtomicFMaxEXT:
|
|
case spv::OpAtomicFMinEXT:
|
|
case spv::OpAtomicCompareExchange:
|
|
case spv::OpAtomicCompareExchangeWeak:
|
|
case spv::OpAtomicExchange:
|
|
case spv::OpAtomicStore:
|
|
case spv::OpFunctionCall: // This depends, but we have to assume it might.
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool SPIRVModule::opcode_is_control_dependent(spv::Op opcode)
|
|
{
|
|
// An opcode is considered control dependent if it is affected by other invocations in the subgroup.
|
|
switch (opcode)
|
|
{
|
|
// Anything derivatives
|
|
case spv::OpDPdx:
|
|
case spv::OpDPdxCoarse:
|
|
case spv::OpDPdxFine:
|
|
case spv::OpDPdy:
|
|
case spv::OpDPdyCoarse:
|
|
case spv::OpDPdyFine:
|
|
case spv::OpFwidth:
|
|
case spv::OpFwidthCoarse:
|
|
case spv::OpFwidthFine:
|
|
|
|
// Anything implicit LOD
|
|
case spv::OpImageSampleImplicitLod:
|
|
case spv::OpImageSampleDrefImplicitLod:
|
|
case spv::OpImageSampleProjImplicitLod:
|
|
case spv::OpImageSampleProjDrefImplicitLod:
|
|
case spv::OpImageSparseSampleImplicitLod:
|
|
case spv::OpImageSparseSampleDrefImplicitLod:
|
|
case spv::OpImageSparseSampleProjImplicitLod:
|
|
case spv::OpImageSparseSampleProjDrefImplicitLod:
|
|
case spv::OpImageQueryLod:
|
|
case spv::OpImageDrefGather:
|
|
case spv::OpImageGather:
|
|
case spv::OpImageSparseDrefGather:
|
|
case spv::OpImageSparseGather:
|
|
|
|
// Anything subgroups
|
|
case spv::OpGroupNonUniformElect:
|
|
case spv::OpGroupNonUniformAll:
|
|
case spv::OpGroupNonUniformAny:
|
|
case spv::OpGroupNonUniformAllEqual:
|
|
case spv::OpGroupNonUniformBroadcast:
|
|
case spv::OpGroupNonUniformBroadcastFirst:
|
|
case spv::OpGroupNonUniformBallot:
|
|
case spv::OpGroupNonUniformInverseBallot:
|
|
case spv::OpGroupNonUniformBallotBitExtract:
|
|
case spv::OpGroupNonUniformBallotBitCount:
|
|
case spv::OpGroupNonUniformBallotFindLSB:
|
|
case spv::OpGroupNonUniformBallotFindMSB:
|
|
case spv::OpGroupNonUniformShuffle:
|
|
case spv::OpGroupNonUniformShuffleXor:
|
|
case spv::OpGroupNonUniformShuffleUp:
|
|
case spv::OpGroupNonUniformShuffleDown:
|
|
case spv::OpGroupNonUniformIAdd:
|
|
case spv::OpGroupNonUniformFAdd:
|
|
case spv::OpGroupNonUniformIMul:
|
|
case spv::OpGroupNonUniformFMul:
|
|
case spv::OpGroupNonUniformSMin:
|
|
case spv::OpGroupNonUniformUMin:
|
|
case spv::OpGroupNonUniformFMin:
|
|
case spv::OpGroupNonUniformSMax:
|
|
case spv::OpGroupNonUniformUMax:
|
|
case spv::OpGroupNonUniformFMax:
|
|
case spv::OpGroupNonUniformBitwiseAnd:
|
|
case spv::OpGroupNonUniformBitwiseOr:
|
|
case spv::OpGroupNonUniformBitwiseXor:
|
|
case spv::OpGroupNonUniformLogicalAnd:
|
|
case spv::OpGroupNonUniformLogicalOr:
|
|
case spv::OpGroupNonUniformLogicalXor:
|
|
case spv::OpGroupNonUniformQuadBroadcast:
|
|
case spv::OpGroupNonUniformQuadSwap:
|
|
|
|
// Control barriers
|
|
case spv::OpControlBarrier:
|
|
case spv::OpMemoryBarrier:
|
|
|
|
// Internal helpers function calls may or may not include control dependent ops.
|
|
case spv::OpFunctionCall:
|
|
|
|
// Cooperative matrix is control sensitive.
|
|
case spv::OpCooperativeMatrixLoadKHR:
|
|
case spv::OpCooperativeMatrixStoreKHR:
|
|
case spv::OpCooperativeMatrixMulAddKHR:
|
|
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
SPIRVModule::~SPIRVModule()
|
|
{
|
|
}
|
|
} // namespace dxil_spv
|