def run_shader_job( shader_compiler_device: DeviceShaderCompiler, spirv_shader_job_path: Path, output_dir: Path, binary_manager: binaries_util.BinaryManager, ) -> List[Path]: compiler_path = binary_manager.get_binary_path_by_name( shader_compiler_device.binary).path log(f"Running {str(compiler_path)} on shader job {str(spirv_shader_job_path)}" ) shader_paths = shader_job_util.get_related_files( spirv_shader_job_path, language_suffix=[shader_job_util.SUFFIX_SPIRV]) log(f"Running {str(compiler_path)} on shaders: {shader_paths}") result = [] for shader_path in shader_paths: result.append( run_shader( shader_compiler_device, compiler_path=compiler_path, shader_path=shader_path, output_dir=output_dir, )) return result
def run_glslang_glsl_to_spirv_job( glsl_shader_job_json_file_path: pathlib.Path, spirv_shader_job_json_file_path: pathlib.Path, glslang_validator_file_path: Optional[pathlib.Path] = None, ) -> pathlib.Path: if not glslang_validator_file_path: glslang_validator_file_path = util.tool_on_path( binaries_util.GLSLANG_VALIDATOR_NAME ) glsl_shader_files = shader_job_util.get_related_files( glsl_shader_job_json_file_path ) util.copy_file(glsl_shader_job_json_file_path, spirv_shader_job_json_file_path) for glsl_shader_file in glsl_shader_files: run_glslang_glsl_shader_to_spirv_shader( glsl_shader_file, spirv_shader_job_json_file_path.parent, glslang_validator_file_path, ) return spirv_shader_job_json_file_path
def run_spirv_opt_on_spirv_shader_job( input_spirv_shader_job_json_file_path: pathlib.Path, output_spirv_shader_job_json_file_path: pathlib.Path, spirv_opt_args: List[str], spirv_opt_file_path: Optional[pathlib.Path] = None, spirv_opt_no_validate_after_all: bool = False, preprocessor_cache: Optional[util.CommandCache] = None, ) -> pathlib.Path: if not spirv_opt_file_path: spirv_opt_file_path = util.tool_on_path(binaries_util.SPIRV_OPT_NAME) shader_files = shader_job_util.get_related_files( input_spirv_shader_job_json_file_path, language_suffix=[shader_job_util.SUFFIX_SPIRV], ) util.copy_file(input_spirv_shader_job_json_file_path, output_spirv_shader_job_json_file_path) for shader_file in shader_files: run_spirv_opt_on_spirv_shader( shader_file, output_spirv_shader_job_json_file_path.parent, spirv_opt_args, spirv_opt_file_path, spirv_opt_no_validate_after_all, preprocessor_cache=preprocessor_cache, ) return output_spirv_shader_job_json_file_path
def validate_spirv_shader_job_helper( input_json: Path, spirv_val_path: Path, extra_args: Optional[List[str]] = None, ) -> None: shader_paths = shader_job_util.get_related_files( input_json, shader_job_util.EXT_ALL, [shader_job_util.SUFFIX_SPIRV] ) for shader_path in shader_paths: run_spirv_val_on_shader(shader_path, spirv_val_path, extra_args=extra_args)
def is_compute_job(input_asm_spirv_job_json_path: pathlib.Path) -> bool: comp_files = shader_job_util.get_related_files( input_asm_spirv_job_json_path, [shader_job_util.EXT_COMP], [shader_job_util.SUFFIX_ASM_SPIRV], ) check( len(comp_files) <= 1, AssertionError(f"Expected 1 or 0 compute shader files: {comp_files}"), ) return len(comp_files) == 1
def run_spirv_shader_job_to_spirv_asm_shader_job( input_spirv_job_json_file_path: pathlib.Path, output_spirv_job_json_file_path: pathlib.Path, spirv_dis_file_path: Optional[pathlib.Path] = None, ) -> pathlib.Path: if not spirv_dis_file_path: spirv_dis_file_path = util.tool_on_path(binaries_util.SPIRV_DIS_NAME) shader_files = shader_job_util.get_related_files( input_spirv_job_json_file_path, language_suffix=[shader_job_util.SUFFIX_SPIRV]) util.copy_file(input_spirv_job_json_file_path, output_spirv_job_json_file_path) for shader_file in shader_files: run_spirv_dis_on_spirv_shader(shader_file, output_spirv_job_json_file_path.parent, spirv_dis_file_path) return output_spirv_job_json_file_path
def main_helper( # pylint: disable=too-many-locals, too-many-branches, too-many-statements; settings_path: Path, iteration_seed_override: Optional[int] = None, fuzzing_tool_pattern: Optional[List[FuzzingTool]] = None, allow_no_stack_traces: bool = False, override_sigint: bool = True, use_amber_vulkan_loader: bool = False, active_device_names: Optional[List[str]] = None, update_ignored_crash_signatures_gerrit_cookie: Optional[str] = None, ) -> None: if not fuzzing_tool_pattern: fuzzing_tool_pattern = [FuzzingTool.GLSL_FUZZ] util.update_gcov_environment_variable_if_needed() if override_sigint: interrupt_util.override_sigint() try_get_root_file() settings = settings_util.read_or_create(settings_path) binary_manager = binaries_util.get_default_binary_manager( settings=settings) temp_dir = Path() / "temp" # Note: we use "is not None" so that if the user passes an empty Gerrit cookie, we still try to execute this code. if update_ignored_crash_signatures_gerrit_cookie is not None: git_tool = util.tool_on_path("git") downloaded_graphicsfuzz_tests_dir = ( temp_dir / f"graphicsfuzz_cts_tests_{get_random_name()[:8]}") work_dir = temp_dir / f"graphicsfuzz_cts_run_{get_random_name()[:8]}" download_cts_gf_tests.download_cts_graphicsfuzz_tests( git_tool=git_tool, cookie=update_ignored_crash_signatures_gerrit_cookie, output_tests_dir=downloaded_graphicsfuzz_tests_dir, ) download_cts_gf_tests.extract_shaders( tests_dir=downloaded_graphicsfuzz_tests_dir, binaries=binary_manager) with util.file_open_text(work_dir / "results.csv", "w") as results_out_handle: run_cts_gf_tests.main_helper( tests_dir=downloaded_graphicsfuzz_tests_dir, work_dir=work_dir, binaries=binary_manager, settings=settings, active_devices=devices_util.get_active_devices( settings.device_list), results_out_handle=results_out_handle, updated_settings_output_path=settings_path, ) return active_devices = devices_util.get_active_devices( settings.device_list, active_device_names=active_device_names) # Add host_preprocessor device from device list if it is missing. if not active_devices[0].HasField("preprocess"): for device in settings.device_list.devices: if device.HasField("preprocess"): active_devices.insert(0, device) break # Add host_preprocessor device (from scratch) if it is still missing. if not active_devices[0].HasField("preprocess"): active_devices.insert( 0, Device(name="host_preprocessor", preprocess=DevicePreprocess())) reports_dir = Path() / "reports" fuzz_failures_dir = reports_dir / FUZZ_FAILURES_DIR_NAME references_dir = Path() / REFERENCES_DIR donors_dir = Path() / DONORS_DIR spirv_fuzz_shaders_dir = Path() / "spirv_fuzz_shaders" # Log a warning if there is no tool on the PATH for printing stack traces. prepended = util.prepend_catchsegv_if_available([], log_warning=True) if not allow_no_stack_traces and not prepended: raise AssertionError("Stopping because we cannot get stack traces.") spirv_fuzz_shaders: List[Path] = [] references: List[Path] = [] if FuzzingTool.SPIRV_FUZZ in fuzzing_tool_pattern: check_dir_exists(spirv_fuzz_shaders_dir) spirv_fuzz_shaders = sorted(spirv_fuzz_shaders_dir.rglob("*.json")) if FuzzingTool.GLSL_FUZZ in fuzzing_tool_pattern: check_dir_exists(references_dir) check_dir_exists(donors_dir) # TODO: make GraphicsFuzz find donors recursively. references = sorted(references_dir.rglob("*.json")) # Filter to only include .json files that have at least one shader (.frag, .vert, .comp) file. references = [ ref for ref in references if shader_job_util.get_related_files(ref) ] if use_amber_vulkan_loader: library_path = binary_manager.get_binary_path_by_name( binaries_util.AMBER_VULKAN_LOADER_NAME).path.parent util.add_library_paths_to_environ([library_path], os.environ) fuzzing_tool_index = 0 while True: interrupt_util.interrupt_if_needed() # We have to use "is not None" because the seed could be 0. if iteration_seed_override is not None: iteration_seed = iteration_seed_override else: iteration_seed = secrets.randbits(ITERATION_SEED_BITS) log(f"Iteration seed: {iteration_seed}") random.seed(iteration_seed) staging_name = get_random_name()[:8] staging_dir = temp_dir / staging_name try: util.mkdir_p_new(staging_dir) except FileExistsError: if iteration_seed_override is not None: raise log(f"Staging directory already exists: {str(staging_dir)}") log("Starting new iteration.") continue # Pseudocode: # - Create test_dir(s) in staging directory. # - Run test_dir(s) on all active devices (stop early if appropriate). # - For each test failure on each device, copy the test to reports_dir, adding the device and crash signature. # - Reduce each report (on the given device). # - Produce a summary for each report. fuzzing_tool = fuzzing_tool_pattern[fuzzing_tool_index] fuzzing_tool_index = (fuzzing_tool_index + 1) % len(fuzzing_tool_pattern) if fuzzing_tool == FuzzingTool.SPIRV_FUZZ: fuzz_spirv_test.fuzz_spirv( staging_dir, reports_dir, fuzz_failures_dir, active_devices, spirv_fuzz_shaders, settings, binary_manager, ) elif fuzzing_tool == FuzzingTool.GLSL_FUZZ: fuzz_glsl_test.fuzz_glsl( staging_dir, reports_dir, fuzz_failures_dir, active_devices, references, donors_dir, settings, binary_manager, ) else: raise AssertionError(f"Unknown fuzzing tool: {fuzzing_tool}") if iteration_seed_override is not None: log("Stopping due to iteration_seed") break shutil.rmtree(staging_dir)
def tool_crash_summary_bug_report_dir( # pylint: disable=too-many-locals; reduced_glsl_source_dir: Path, variant_reduced_glsl_result_dir: Path, output_dir: Path, binary_manager: binaries_util.BinaryManager, ) -> Optional[Path]: # Create a simple script and README. shader_job = reduced_glsl_source_dir / test_util.VARIANT_DIR / test_util.SHADER_JOB if not shader_job.is_file(): return None test_metadata: Test = test_util.metadata_read_from_path( reduced_glsl_source_dir / test_util.TEST_METADATA) shader_files = shader_job_util.get_related_files( shader_job, shader_job_util.EXT_ALL, (shader_job_util.SUFFIX_GLSL, shader_job_util.SUFFIX_SPIRV), ) check( len(shader_files) > 0, AssertionError(f"Need at least one shader for {shader_job}"), ) shader_extension = shader_files[0].suffix bug_report_dir = util.copy_dir(variant_reduced_glsl_result_dir, output_dir / "bug_report") shader_files = sorted(bug_report_dir.rglob("shader.*")) glsl_files = [ shader_file for shader_file in shader_files if shader_file.suffix == shader_extension ] asm_files = [ shader_file for shader_file in shader_files if shader_file.name.endswith(shader_extension + shader_job_util.SUFFIX_ASM_SPIRV) ] spv_files = [ shader_file for shader_file in shader_files if shader_file.name.endswith(shader_extension + shader_job_util.SUFFIX_SPIRV) ] readme = "\n\n" readme += ( "Issue found using [GraphicsFuzz](https://github.com/google/graphicsfuzz).\n\n" ) readme += "Tool versions:\n\n" # noinspection PyTypeChecker if test_metadata.HasField("glsl"): readme += f"* glslangValidator commit hash: {binary_manager.get_binary_by_name(binaries_util.GLSLANG_VALIDATOR_NAME).version}\n" if test_metadata.glsl.spirv_opt_args or test_metadata.spirv_fuzz.spirv_opt_args: readme += f"* spirv-opt commit hash: {binary_manager.get_binary_by_name(binaries_util.SPIRV_OPT_NAME).version}\n" readme += "\nTo reproduce:\n\n" readme += f"`glslangValidator -V shader{shader_extension} -o shader{shader_extension}.spv`\n\n" if (test_metadata.HasField("glsl") and spv_files and not test_metadata.glsl.spirv_opt_args): # GLSL was converted to SPIR-V, and spirv-opt was not run, so indicate that we should validate the SPIR-V. readme += f"`spirv-val shader{shader_extension}.spv`\n\n" if test_metadata.glsl.spirv_opt_args or test_metadata.spirv_fuzz.spirv_opt_args: readme += f"`spirv-opt shader{shader_extension}.spv -o temp.spv --validate-after-all {' '.join(test_metadata.glsl.spirv_opt_args)}`\n\n" files_to_list = glsl_files + spv_files + asm_files files_to_list.sort() files_to_show = glsl_files + asm_files files_to_show.sort() readme += "The following shader files are included in the attached archive, some of which are also shown inline below:\n\n" for file_to_list in files_to_list: short_path = file_to_list.relative_to(bug_report_dir).as_posix() readme += f"* {short_path}\n" for file_to_show in files_to_show: short_path = file_to_show.relative_to(bug_report_dir).as_posix() file_contents = util.file_read_text(file_to_show) readme += f"\n{short_path}:\n\n" readme += f"```\n{file_contents}\n```\n" util.file_write_text(output_dir / "README.md", readme) return bug_report_dir
def run_shader_job( # pylint: disable=too-many-return-statements,too-many-branches, too-many-locals, too-many-statements; source_dir: Path, output_dir: Path, binary_manager: binaries_util.BinaryManager, test: Optional[Test] = None, device: Optional[Device] = None, ignore_test_and_device_binaries: bool = False, shader_job_overrides: Iterable[tool.NameAndShaderJob] = (), shader_job_shader_overrides: Optional[ tool.ShaderJobNameToShaderOverridesMap] = None, ) -> Path: if not shader_job_shader_overrides: shader_job_shader_overrides = {} with util.file_open_text(output_dir / "log.txt", "w") as log_file: try: gflogging.push_stream_for_logging(log_file) # TODO: Find amber path. NDK or host. # TODO: If Amber is going to be used, check if Amber can use Vulkan debug layers now, and if not, pass that # info down via a bool. if not test: test = test_util.metadata_read_from_path( source_dir / test_util.TEST_METADATA) if not device: device = test.device log(f"Running test on device:\n{device.name}") # We will create a binary_manager child with a restricted set of binaries so that we only use the binaries # specified in the test and by the device; if some required binaries are not specified by the test nor the # device, there will be an error instead of falling back to our default binaries. But we keep a reference to # the parent so we can still access certain "test-independent" binaries like Amber. binary_manager_parent = binary_manager if not ignore_test_and_device_binaries: binary_manager = binary_manager.get_child_binary_manager( list(device.binaries) + list(test.binaries)) spirv_opt_hash: Optional[str] = None spirv_opt_args: Optional[List[str]] = None if test.glsl.spirv_opt_args or test.spirv_fuzz.spirv_opt_args: spirv_opt_hash = binary_manager.get_binary_by_name( binaries_util.SPIRV_OPT_NAME).version spirv_opt_args = (list(test.glsl.spirv_opt_args) if test.glsl.spirv_opt_args else list( test.spirv_fuzz.spirv_opt_args)) shader_jobs = tool.get_shader_jobs(source_dir, overrides=shader_job_overrides) combined_spirv_shader_jobs: List[tool.SpirvCombinedShaderJob] = [] for shader_job in shader_jobs: try: shader_overrides = shader_job_shader_overrides.get( shader_job.name, None) combined_spirv_shader_jobs.append( tool.compile_shader_job( name=shader_job.name, input_json=shader_job.shader_job, work_dir=output_dir / shader_job.name, binary_paths=binary_manager, spirv_opt_args=spirv_opt_args, shader_overrides=shader_overrides, )) except subprocess.CalledProcessError: result_util.write_status(output_dir, fuzz.STATUS_TOOL_CRASH, shader_job.name) return output_dir except subprocess.TimeoutExpired: result_util.write_status(output_dir, fuzz.STATUS_TOOL_TIMEOUT, shader_job.name) return output_dir # Device types: |preprocess| and |shader_compiler| don't need an AmberScript file. # noinspection PyTypeChecker if device.HasField("preprocess"): # The "preprocess" device type just needs to get this far, so this is a success. result_util.write_status(output_dir, fuzz.STATUS_SUCCESS) return output_dir # noinspection PyTypeChecker if device.HasField("shader_compiler"): for combined_spirv_shader_job in combined_spirv_shader_jobs: try: shader_compiler_util.run_shader_job( device.shader_compiler, combined_spirv_shader_job.spirv_shader_job, output_dir, binary_manager=binary_manager, ) except subprocess.CalledProcessError: result_util.write_status( output_dir, fuzz.STATUS_CRASH, combined_spirv_shader_job.name, ) return output_dir except subprocess.TimeoutExpired: result_util.write_status( output_dir, fuzz.STATUS_TIMEOUT, combined_spirv_shader_job.name, ) return output_dir # The shader compiler succeeded on all files; this is a success. result_util.write_status(output_dir, fuzz.STATUS_SUCCESS) return output_dir # Other device types need an AmberScript file. amber_converter_shader_job_files = [ amber_converter.ShaderJobFile( name_prefix=combined_spirv_shader_job.name, asm_spirv_shader_job_json=combined_spirv_shader_job. spirv_asm_shader_job, glsl_source_json=combined_spirv_shader_job. glsl_source_shader_job, processing_info="", ) for combined_spirv_shader_job in combined_spirv_shader_jobs ] # Check if the first is the reference shader; if so, pull it out into its own variable. reference: Optional[amber_converter.ShaderJobFile] = None variants = amber_converter_shader_job_files if (amber_converter_shader_job_files[0].name_prefix == test_util.REFERENCE_DIR): reference = amber_converter_shader_job_files[0] variants = variants[1:] elif len(variants) > 1: raise AssertionError( "More than one variant, but no reference. This is unexpected." ) amber_script_file = amber_converter.spirv_asm_shader_job_to_amber_script( shader_job_file_amber_test=amber_converter. ShaderJobFileBasedAmberTest(reference_asm_spirv_job=reference, variants_asm_spirv_job=variants), output_amber_script_file_path=output_dir / "test.amber", amberfy_settings=amber_converter.AmberfySettings( spirv_opt_args=spirv_opt_args, spirv_opt_hash=spirv_opt_hash), ) is_compute = bool( shader_job_util.get_related_files( combined_spirv_shader_jobs[0].spirv_shader_job, [shader_job_util.EXT_COMP], )) # noinspection PyTypeChecker if device.HasField("host") or device.HasField("swift_shader"): icd: Optional[Path] = None # noinspection PyTypeChecker if device.HasField("swift_shader"): icd = binary_manager.get_binary_path_by_name( binaries_util.SWIFT_SHADER_NAME).path # Run the test on the host using Amber. host_device_util.run_amber( amber_script_file, output_dir, amber_path=binary_manager_parent.get_binary_path_by_name( binaries_util.AMBER_NAME).path, dump_image=(not is_compute), dump_buffer=is_compute, icd=icd, ) return output_dir # noinspection PyTypeChecker if device.HasField("android"): android_device.run_amber_on_device( amber_script_file, output_dir, dump_image=(not is_compute), dump_buffer=is_compute, serial=device.android.serial, ) return output_dir # TODO: For a remote device (which we will probably need to support), use log_a_file to output the # "amber_log.txt" file. raise AssertionError(f"Unhandled device type:\n{str(device)}") finally: gflogging.pop_stream_for_logging()
def validate_spirv_shader_job_helper(input_json: Path, spirv_val_path: Path) -> None: shader_paths = shader_job_util.get_related_files( input_json, shader_job_util.EXT_ALL, [shader_job_util.SUFFIX_SPIRV]) for shader_path in shader_paths: run_spirv_val_on_shader(shader_path, spirv_val_path)
def main_helper( # pylint: disable=too-many-locals, too-many-branches, too-many-statements; settings_path: Path, iteration_seed_override: Optional[int], use_spirv_fuzz: bool, force_no_stack_traces: bool, ) -> None: util.update_gcov_environment_variable_if_needed() try: artifact_util.artifact_path_get_root() except FileNotFoundError: log( "Could not find ROOT file (in the current directory or above) to mark where binaries should be stored. " "Creating a ROOT file in the current directory." ) util.file_write_text(Path(artifact_util.ARTIFACT_ROOT_FILE_NAME), "") settings = settings_util.read_or_create(settings_path) active_devices = devices_util.get_active_devices(settings.device_list) reports_dir = Path() / "reports" fuzz_failures_dir = reports_dir / FUZZ_FAILURES_DIR_NAME temp_dir = Path() / "temp" references_dir = Path() / "references" donors_dir = Path() / "donors" spirv_fuzz_shaders_dir = Path() / "spirv_fuzz_shaders" # Log a warning if there is no tool on the PATH for printing stack traces. prepended = util.prepend_catchsegv_if_available([], log_warning=True) if not force_no_stack_traces and not prepended: raise AssertionError("Stopping because we cannot get stack traces.") spirv_fuzz_shaders: List[Path] = [] references: List[Path] = [] if use_spirv_fuzz: check_dir_exists(spirv_fuzz_shaders_dir) spirv_fuzz_shaders = sorted(spirv_fuzz_shaders_dir.rglob("*.json")) else: check_dir_exists(references_dir) check_dir_exists(donors_dir) # TODO: make GraphicsFuzz find donors recursively. references = sorted(references_dir.rglob("*.json")) # Filter to only include .json files that have at least one shader (.frag, .vert, .comp) file. references = [ ref for ref in references if shader_job_util.get_related_files(ref) ] binary_manager = binaries_util.get_default_binary_manager( settings=settings ).get_child_binary_manager(list(settings.custom_binaries), prepend=True) while True: # We have to use "is not None" because the seed could be 0. if iteration_seed_override is not None: iteration_seed = iteration_seed_override else: iteration_seed = secrets.randbits(ITERATION_SEED_BITS) log(f"Iteration seed: {iteration_seed}") random.seed(iteration_seed) staging_name = get_random_name()[:8] staging_dir = temp_dir / staging_name try: util.mkdir_p_new(staging_dir) except FileExistsError: if iteration_seed_override is not None: raise log(f"Staging directory already exists: {str(staging_dir)}") log(f"Starting new iteration.") continue # Pseudocode: # - Create test_dir(s) in staging directory. # - Run test_dir(s) on all active devices (stop early if appropriate). # - For each test failure on each device, copy the test to reports_dir, adding the device and crash signature. # - Reduce each report (on the given device). # - Produce a summary for each report. if use_spirv_fuzz: fuzz_spirv_test.fuzz_spirv( staging_dir, reports_dir, fuzz_failures_dir, active_devices, spirv_fuzz_shaders, settings, binary_manager, ) else: fuzz_glsl_test.fuzz_glsl( staging_dir, reports_dir, fuzz_failures_dir, active_devices, references, donors_dir, settings, binary_manager, ) shutil.rmtree(staging_dir) if iteration_seed_override is not None: log("Stopping due to iteration_seed") break