blob: 4f469cca21d972d4b81cd0a589017e02d75e6274 [file]
// Copyright 2023 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <span>
#include <string>
#include <vector>
#include "src/tint/api/helpers/generate_bindings.h"
#include "src/tint/cmd/fuzz/ir/fuzz.h"
#include "src/tint/lang/core/ir/disassembler.h"
#include "src/tint/lang/core/ir/referenced_module_vars.h"
#include "src/tint/lang/spirv/validate/validate.h"
#include "src/tint/lang/spirv/writer/printer/printer.h"
#include "src/tint/lang/spirv/writer/writer.h"
#include "src/tint/utils/macros/defer.h"
#if TINT_BUILD_FUZZER_VULKAN_SUPPORT
#include <vulkan/vulkan.h>
// One of the indirectly included graphics headers here #defines
// Success, which causes build issues
#undef Success
#endif // TINT_BUILD_FUZZER_VULKAN_SUPPORT
namespace tint::spirv::writer {
// Fuzzed options used to init tint::spirv::writer::Options
struct FuzzedOptions {
///////////////////////////////////////////////////////////////////////////////////////////////
// NOTE: These options should not be reordered or removed as it will change the operation of //
// pre-existing fuzzer cases. Always append new options to the end of the list. //
///////////////////////////////////////////////////////////////////////////////////////////////
bool strip_all_names;
bool disable_robustness;
bool disable_workgroup_init;
bool disable_polyfill_integer_div_mod;
bool enable_integer_range_analysis;
bool emit_vertex_point_size;
bool polyfill_pixel_center;
bool polyfill_case_switch;
bool scalarize_max_min_clamp;
bool dva_transform_handle;
bool polyfill_pack_unpack_4x8_norm;
bool subgroup_shuffle_clamped;
bool polyfill_subgroup_broadcast_f16;
bool pass_matrix_by_pointer;
bool polyfill_unary_f32_negation;
bool polyfill_f32_abs;
bool use_demote_to_helper_invocation;
bool use_storage_input_output_16;
bool use_zero_initialize_workgroup_memory;
bool use_vulkan_memory_model;
bool disable_image_robustness;
bool disable_runtime_sized_array_index_clamping;
bool dot_4x8_packed;
std::optional<Options::RangeOffsets> depth_range_offsets;
SpvVersion spirv_version;
SubstituteOverridesConfig substitute_overrides_config;
bool texture_sample_compare_depth_cube_array;
bool polyfill_saturate_as_min_max_f16;
bool multisampled_framebuffer_fetch;
bool cooperative_matrix_stride_is_matrix_elements;
bool polyfill_length_scalar_f32;
bool polyfill_distance_scalar_f32;
/// Reflect the fields of this class so that it can be used by tint::ForeachField()
TINT_REFLECT(FuzzedOptions,
strip_all_names,
disable_robustness,
disable_workgroup_init,
disable_polyfill_integer_div_mod,
enable_integer_range_analysis,
emit_vertex_point_size,
polyfill_pixel_center,
polyfill_case_switch,
scalarize_max_min_clamp,
dva_transform_handle,
polyfill_pack_unpack_4x8_norm,
subgroup_shuffle_clamped,
polyfill_subgroup_broadcast_f16,
pass_matrix_by_pointer,
polyfill_unary_f32_negation,
polyfill_f32_abs,
use_demote_to_helper_invocation,
use_storage_input_output_16,
use_zero_initialize_workgroup_memory,
use_vulkan_memory_model,
disable_image_robustness,
disable_runtime_sized_array_index_clamping,
dot_4x8_packed,
depth_range_offsets,
spirv_version,
substitute_overrides_config,
texture_sample_compare_depth_cube_array,
polyfill_saturate_as_min_max_f16,
multisampled_framebuffer_fetch,
cooperative_matrix_stride_is_matrix_elements,
polyfill_length_scalar_f32,
polyfill_distance_scalar_f32);
TINT_REFLECT_HASH_CODE(FuzzedOptions);
};
namespace {
#if TINT_BUILD_FUZZER_VULKAN_SUPPORT
Result<SuccessType> ValidateUsingVulkan(const std::string& vk_icd_path,
std::span<const uint32_t> spirv) {
#if TINT_BUILD_IS_WIN
#error "TINT_BUILD_FUZZER_VULKAN_SUPPORT is not supported on Windows"
#endif // TINT_BUILD_IS_WIN
// This setenv call is why this works on Linux/Mac but not Windows
setenv("VK_ICD_FILENAMES", vk_icd_path.c_str(), 1);
VkApplicationInfo app_info = {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.apiVersion = VK_API_VERSION_1_1,
};
VkInstanceCreateInfo create_info = {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pApplicationInfo = &app_info,
};
VkInstance instance;
if (vkCreateInstance(&create_info, nullptr, &instance) != VK_SUCCESS) {
return Failure{"Failed to create Vulkan instance"};
}
TINT_DEFER(vkDestroyInstance(instance, nullptr));
uint32_t count = 0;
vkEnumeratePhysicalDevices(instance, &count, nullptr);
if (count == 0) {
return Failure{"Failed to enumerate physical devices"};
}
std::vector<VkPhysicalDevice> physical_devices(count);
VkResult res = vkEnumeratePhysicalDevices(instance, &count, physical_devices.data());
if ((res != VK_SUCCESS && res != VK_INCOMPLETE) || count == 0) {
return Failure{"Failed to enumerate physical devices"};
}
VkPhysicalDevice physical_device = physical_devices[0];
float queue_priority = 1.0f;
VkDeviceQueueCreateInfo queue_create_info = {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = 0,
.queueCount = 1,
.pQueuePriorities = &queue_priority,
};
VkDeviceCreateInfo device_create_info = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &queue_create_info,
};
VkDevice device;
if (vkCreateDevice(physical_device, &device_create_info, nullptr, &device) != VK_SUCCESS) {
return Failure{"Failed to create Vulkan device"};
}
TINT_DEFER(vkDestroyDevice(device, nullptr));
VkShaderModuleCreateInfo shader_module_create_info = {
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = spirv.size() * sizeof(uint32_t),
.pCode = spirv.data(),
};
VkShaderModule shader_module;
if (vkCreateShaderModule(device, &shader_module_create_info, nullptr, &shader_module) !=
VK_SUCCESS) {
return Failure{"Failed to create shader module"};
}
vkDestroyShaderModule(device, shader_module, nullptr);
return Success;
}
#endif // TINT_BUILD_FUZZER_VULKAN_SUPPORT
std::unordered_map<uint32_t, tint::BindingPoint> GenerateColourBindings(core::ir::Module& mod,
std::string_view ep_name) {
std::unordered_map<uint32_t, tint::BindingPoint> bindings;
core::ir::Function* ep_func = nullptr;
for (auto* f : mod.functions) {
if (!f->IsEntryPoint()) {
continue;
}
// Colour only applies to fragment.
if (f->Stage() != core::ir::Function::PipelineStage::kFragment) {
continue;
}
if (mod.NameOf(f).NameView() == ep_name) {
ep_func = f;
break;
}
}
// No entrypoint, so no bindings needed
if (!ep_func) {
return bindings;
}
uint32_t group = 66;
uint32_t binding = 0;
auto check_attrs = [&](const core::IOAttributes& attrs) {
if (attrs.color.has_value()) {
bindings.emplace(attrs.color.value(),
tint::BindingPoint{.group = group, .binding = binding++});
return true;
}
return false;
};
std::function<void(const core::type::Struct*)> check_struct =
[&](const core::type::Struct* str) {
if (!str) {
return;
}
for (auto& mem : str->Members()) {
if (check_attrs(mem->Attributes())) {
continue;
}
check_struct(mem->Type()->As<core::type::Struct>());
}
};
for (auto& p : ep_func->Params()) {
if (check_attrs(p->Attributes())) {
continue;
}
check_struct(p->Type()->As<core::type::Struct>());
}
core::ir::ReferencedModuleVars<const core::ir::Module> referenced_module_vars{mod};
auto& refs = referenced_module_vars.TransitiveReferences(ep_func);
for (auto& r : refs) {
if (check_attrs(r->Attributes())) {
continue;
}
check_struct(r->Result()->Type()->As<core::type::Struct>());
}
return bindings;
}
Result<SuccessType> IRFuzzer(core::ir::Module& module,
const fuzz::ir::Context& context,
FuzzedOptions fuzzed_options) {
// TODO(375388101): We cannot run the backend for every entry point in the module unless we
// clone the whole module each time, so for now we just generate the first entry point.
// Strip the module down to a single entry point.
core::ir::Function* entry_point = nullptr;
for (auto& func : module.functions) {
if (func->IsEntryPoint()) {
entry_point = func;
break;
}
}
std::string ep_name;
if (entry_point) {
ep_name = module.NameOf(entry_point).NameView();
}
if (ep_name.empty()) {
// No entry point, just return success
return Success;
}
// We fuzz options that Dawn will vary depending on the platform and provided toggles.
// Options that are entirely controlled by Dawn (e.g. binding points) are not fuzzed.
Options options;
options.entry_point_name = ep_name;
options.bindings = GenerateBindings(module, ep_name, false, false);
options.colour_index_to_binding_point = GenerateColourBindings(module, ep_name);
options.strip_all_names = fuzzed_options.strip_all_names;
options.disable_robustness = fuzzed_options.disable_robustness;
options.disable_workgroup_init = fuzzed_options.disable_workgroup_init;
options.disable_polyfill_integer_div_mod = fuzzed_options.disable_polyfill_integer_div_mod;
options.disable_integer_range_analysis = !fuzzed_options.enable_integer_range_analysis;
options.emit_vertex_point_size = fuzzed_options.emit_vertex_point_size;
options.polyfill_pixel_center = fuzzed_options.polyfill_pixel_center;
options.workarounds.polyfill_case_switch = fuzzed_options.polyfill_case_switch;
options.workarounds.scalarize_max_min_clamp = fuzzed_options.scalarize_max_min_clamp;
options.workarounds.dva_transform_handle = fuzzed_options.dva_transform_handle;
options.workarounds.polyfill_pack_unpack_4x8_norm =
fuzzed_options.polyfill_pack_unpack_4x8_norm;
options.workarounds.subgroup_shuffle_clamped = fuzzed_options.subgroup_shuffle_clamped;
options.workarounds.polyfill_subgroup_broadcast_f16 =
fuzzed_options.polyfill_subgroup_broadcast_f16;
options.workarounds.pass_matrix_by_pointer = fuzzed_options.pass_matrix_by_pointer;
options.workarounds.polyfill_unary_f32_negation = fuzzed_options.polyfill_unary_f32_negation;
options.workarounds.polyfill_f32_abs = fuzzed_options.polyfill_f32_abs;
options.extensions.use_demote_to_helper_invocation =
fuzzed_options.use_demote_to_helper_invocation;
options.extensions.use_storage_input_output_16 = fuzzed_options.use_storage_input_output_16;
options.extensions.use_zero_initialize_workgroup_memory =
fuzzed_options.use_zero_initialize_workgroup_memory;
options.extensions.use_vulkan_memory_model = fuzzed_options.use_vulkan_memory_model;
options.extensions.disable_image_robustness = fuzzed_options.disable_image_robustness;
options.extensions.disable_runtime_sized_array_index_clamping =
fuzzed_options.disable_runtime_sized_array_index_clamping;
options.extensions.dot_4x8_packed = fuzzed_options.dot_4x8_packed;
options.depth_range_offsets = fuzzed_options.depth_range_offsets;
options.spirv_version = fuzzed_options.spirv_version;
options.substitute_overrides_config = fuzzed_options.substitute_overrides_config;
options.workarounds.texture_sample_compare_depth_cube_array =
fuzzed_options.texture_sample_compare_depth_cube_array;
options.workarounds.polyfill_saturate_as_min_max_f16 =
fuzzed_options.polyfill_saturate_as_min_max_f16;
options.workarounds.polyfill_length_scalar_f32 = fuzzed_options.polyfill_length_scalar_f32;
options.workarounds.polyfill_distance_scalar_f32 = fuzzed_options.polyfill_distance_scalar_f32;
options.workarounds.cooperative_matrix_stride_is_matrix_elements =
fuzzed_options.cooperative_matrix_stride_is_matrix_elements;
options.multisampled_framebuffer_fetch = fuzzed_options.multisampled_framebuffer_fetch;
TINT_CHECK_RESULT_UNWRAP(output, Generate(module, options));
spv_target_env target_env = SPV_ENV_VULKAN_1_1;
switch (options.spirv_version) {
case SpvVersion::kSpv13:
target_env = SPV_ENV_VULKAN_1_1;
break;
case SpvVersion::kSpv14:
target_env = SPV_ENV_VULKAN_1_1_SPIRV_1_4;
break;
case SpvVersion::kSpv15:
target_env = SPV_ENV_VULKAN_1_2;
break;
default:
TINT_ICE() << "unsupported SPIR-V version";
}
auto& spirv = output.spirv;
auto res = validate::Validate(spirv, target_env);
TINT_ASSERT(res == Success) << "output of SPIR-V writer failed to validate with SPIR-V Tools\n"
<< res.Failure() << "\n\n"
<< "IR:\n"
<< core::ir::Disassembler(module).Plain();
#if TINT_BUILD_FUZZER_VULKAN_SUPPORT
if (!context.options.vk_icd.empty()) {
TINT_CHECK_RESULT(ValidateUsingVulkan(context.options.vk_icd, spirv));
}
#endif
return Success;
}
} // namespace
} // namespace tint::spirv::writer
TINT_IR_MODULE_FUZZER(tint::spirv::writer::IRFuzzer,
tint::core::ir::Capabilities{},
tint::spirv::writer::kPrinterCapabilities);