From fc16266e516d69b5ab06c426e809cd0a170f3a54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D1=80=D0=B0=D0=BD=D0=B8=D0=BC=D0=B8=D1=80=20=D0=9A?= =?UTF-8?q?=D0=B0=D1=80=D0=B0=D1=9F=D0=B8=D1=9B?= Date: Sat, 5 Oct 2019 09:53:49 -0700 Subject: [PATCH] Updated spirv-tools. --- 3rdparty/spirv-tools/DEPS | 4 +- 3rdparty/spirv-tools/README.md | 620 ++++++++++++++++++ .../include/generated/build-version.inc | 2 +- .../spirv-tools/source/fuzz/CMakeLists.txt | 4 +- .../source/fuzz/force_render_red.cpp | 368 +++++++++++ .../source/fuzz/force_render_red.h | 48 ++ 3rdparty/spirv-tools/source/opt/cfg.h | 5 +- .../spirv-tools/source/opt/inline_pass.cpp | 18 +- 3rdparty/spirv-tools/source/opt/inline_pass.h | 7 + .../source/opt/inst_buff_addr_check_pass.cpp | 24 +- 3rdparty/spirv-tools/source/opt/ir_context.h | 7 +- 3rdparty/spirv-tools/source/opt/ir_loader.cpp | 3 + 3rdparty/spirv-tools/source/opt/module.cpp | 3 + 3rdparty/spirv-tools/source/opt/module.h | 33 +- .../source/opt/strip_debug_info_pass.cpp | 23 +- .../source/opt/struct_cfg_analysis.cpp | 65 ++ .../source/opt/struct_cfg_analysis.h | 34 +- .../spirv-tools/source/opt/wrap_opkill.cpp | 7 +- .../source/val/validate_constants.cpp | 6 +- .../source/val/validate_instruction.cpp | 113 ---- .../source/val/validate_memory.cpp | 27 +- .../spirv-tools/source/val/validate_misc.cpp | 34 + .../source/val/validate_mode_setting.cpp | 41 ++ .../spirv-tools/source/val/validate_type.cpp | 17 + .../source/val/validation_state.cpp | 55 +- .../spirv-tools/source/val/validation_state.h | 6 +- 3rdparty/spirv-tools/test/opt/inline_test.cpp | 56 +- .../test/opt/inst_buff_addr_check_test.cpp | 33 +- 3rdparty/spirv-tools/test/opt/module_test.cpp | 67 ++ .../test/opt/strip_debug_info_test.cpp | 78 +++ .../test/opt/struct_cfg_analysis_test.cpp | 534 ++++++++++++++- .../spirv-tools/test/opt/wrap_opkill_test.cpp | 195 +++++- .../spirv-tools/test/val/val_cfg_test.cpp | 9 +- .../test/val/val_constants_test.cpp | 20 + .../spirv-tools/test/val/val_memory_test.cpp | 90 +++ .../spirv-tools/test/val/val_misc_test.cpp | 81 +++ .../spirv-tools/test/val/val_opencl_test.cpp | 11 +- .../spirv-tools/test/val/val_storage_test.cpp | 14 +- .../spirv-tools/test/val/val_webgpu_test.cpp | 17 - 39 files changed, 2527 insertions(+), 252 deletions(-) create mode 100644 3rdparty/spirv-tools/README.md create mode 100644 3rdparty/spirv-tools/source/fuzz/force_render_red.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/force_render_red.h diff --git a/3rdparty/spirv-tools/DEPS b/3rdparty/spirv-tools/DEPS index fd626f600..8a61f227d 100644 --- a/3rdparty/spirv-tools/DEPS +++ b/3rdparty/spirv-tools/DEPS @@ -3,10 +3,10 @@ use_relative_paths = True vars = { 'github': 'https://github.com', - 'effcee_revision': '6fa2a03cebb4fb18fbad086d53d1054928bef54e', + 'effcee_revision': 'cd25ec17e9382f99a895b9ef53ff3c277464d07d', 'googletest_revision': 'f2fb48c3b3d79a75a88a99fba6576b25d42ec528', 're2_revision': '5bd613749fd530b576b890283bfb6bc6ea6246cb', - 'spirv_headers_revision': '601d738723ac381741311c6c98c36d6170be14a2', + 'spirv_headers_revision': '842ec90674627ed2ffef609e3cd79d1562eded01', } deps = { diff --git a/3rdparty/spirv-tools/README.md b/3rdparty/spirv-tools/README.md new file mode 100644 index 000000000..3bebbf2d4 --- /dev/null +++ b/3rdparty/spirv-tools/README.md @@ -0,0 +1,620 @@ +# SPIR-V Tools + +## Overview + +The SPIR-V Tools project provides an API and commands for processing SPIR-V +modules. + +The project includes an assembler, binary module parser, disassembler, +validator, and optimizer for SPIR-V. Except for the optimizer, all are based +on a common static library. The library contains all of the implementation +details, and is used in the standalone tools whilst also enabling integration +into other code bases directly. The optimizer implementation resides in its +own library, which depends on the core library. + +The interfaces have stabilized: +We don't anticipate making a breaking change for existing features. + +SPIR-V is defined by the Khronos Group Inc. +See the [SPIR-V Registry][spirv-registry] for the SPIR-V specification, +headers, and XML registry. + +## Downloads + +[![Build status](https://ci.appveyor.com/api/projects/status/gpue87cesrx3pi0d/branch/master?svg=true)](https://ci.appveyor.com/project/Khronoswebmaster/spirv-tools/branch/master) +Linux[![Linux Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_linux_clang_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_clang_release.html) +MacOS[![MacOS Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_macos_clang_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_macos_clang_release.html) +Windows[![Windows Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_windows_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_windows_vs2017_release.html) + +[More downloads](docs/downloads.md) + +## Versioning SPIRV-Tools + +See [`CHANGES`](CHANGES) for a high level summary of recent changes, by version. + +SPIRV-Tools project version numbers are of the form `v`*year*`.`*index* and with +an optional `-dev` suffix to indicate work in progress. For exampe, the +following versions are ordered from oldest to newest: + +* `v2016.0` +* `v2016.1-dev` +* `v2016.1` +* `v2016.2-dev` +* `v2016.2` + +Use the `--version` option on each command line tool to see the software +version. An API call reports the software version as a C-style string. + +## Supported features + +### Assembler, binary parser, and disassembler + +* Support for SPIR-V 1.0, 1.1, 1.2, and 1.3 + * Based on SPIR-V syntax described by JSON grammar files in the + [SPIRV-Headers](https://github.com/KhronosGroup/SPIRV-Headers) repository. +* Support for extended instruction sets: + * GLSL std450 version 1.0 Rev 3 + * OpenCL version 1.0 Rev 2 +* Assembler only does basic syntax checking. No cross validation of + IDs or types is performed, except to check literal arguments to + `OpConstant`, `OpSpecConstant`, and `OpSwitch`. + +See [`docs/syntax.md`](docs/syntax.md) for the assembly language syntax. + +### Validator + +The validator checks validation rules described by the SPIR-V specification. + +Khronos recommends that tools that create or transform SPIR-V modules use the +validator to ensure their outputs are valid, and that tools that consume SPIR-V +modules optionally use the validator to protect themselves from bad inputs. +This is especially encouraged for debug and development scenarios. + +The validator has one-sided error: it will only return an error when it has +implemented a rule check and the module violates that rule. + +The validator is incomplete. +See the [CHANGES](CHANGES) file for reports on completed work, and +the [Validator +sub-project](https://github.com/KhronosGroup/SPIRV-Tools/projects/1) for planned +and in-progress work. + +*Note*: The validator checks some Universal Limits, from section 2.17 of the SPIR-V spec. +The validator will fail on a module that exceeds those minimum upper bound limits. +It is [future work](https://github.com/KhronosGroup/SPIRV-Tools/projects/1#card-1052403) +to parameterize the validator to allow larger +limits accepted by a more than minimally capable SPIR-V consumer. + + +### Optimizer + +*Note:* The optimizer is still under development. + +Currently supported optimizations: +* General + * Strip debug info +* Specialization Constants + * Set spec constant default value + * Freeze spec constant + * Fold `OpSpecConstantOp` and `OpSpecConstantComposite` + * Unify constants + * Eliminate dead constant +* Code Reduction + * Inline all function calls exhaustively + * Convert local access chains to inserts/extracts + * Eliminate local load/store in single block + * Eliminate local load/store with single store + * Eliminate local load/store with multiple stores + * Eliminate local extract from insert + * Eliminate dead instructions (aggressive) + * Eliminate dead branches + * Merge single successor / single predecessor block pairs + * Eliminate common uniform loads + * Remove duplicates: Capabilities, extended instruction imports, types, and + decorations. + +For the latest list with detailed documentation, please refer to +[`include/spirv-tools/optimizer.hpp`](include/spirv-tools/optimizer.hpp). + +For suggestions on using the code reduction options, please refer to this [white paper](https://www.lunarg.com/shader-compiler-technologies/white-paper-spirv-opt/). + + +### Linker + +*Note:* The linker is still under development. + +Current features: +* Combine multiple SPIR-V binary modules together. +* Combine into a library (exports are retained) or an executable (no symbols + are exported). + +See the [CHANGES](CHANGES) file for reports on completed work, and the [General +sub-project](https://github.com/KhronosGroup/SPIRV-Tools/projects/2) for +planned and in-progress work. + + +### Reducer + +*Note:* The reducer is still under development. + +The reducer simplifies and shrinks a SPIR-V module with respect to a +user-supplied *interestingness function*. For example, given a large +SPIR-V module that cause some SPIR-V compiler to fail with a given +fatal error message, the reducer could be used to look for a smaller +version of the module that causes the compiler to fail with the same +fatal error message. + +To suggest an additional capability for the reducer, [file an +issue](https://github.com/KhronosGroup/SPIRV-Tools/issues]) with +"Reducer:" as the start of its title. + + +### Extras + +* [Utility filters](#utility-filters) +* Build target `spirv-tools-vimsyntax` generates file `spvasm.vim`. + Copy that file into your `$HOME/.vim/syntax` directory to get SPIR-V assembly syntax + highlighting in Vim. This build target is not built by default. + +## Contributing + +The SPIR-V Tools project is maintained by members of the The Khronos Group Inc., +and is hosted at https://github.com/KhronosGroup/SPIRV-Tools. + +Consider joining the `public_spirv_tools_dev@khronos.org` mailing list, via +[https://www.khronos.org/spir/spirv-tools-mailing-list/](https://www.khronos.org/spir/spirv-tools-mailing-list/). +The mailing list is used to discuss development plans for the SPIRV-Tools as an open source project. +Once discussion is resolved, +specific work is tracked via issues and sometimes in one of the +[projects][spirv-tools-projects]. + +(To provide feedback on the SPIR-V _specification_, file an issue on the +[SPIRV-Headers][spirv-headers] GitHub repository.) + +See [`docs/projects.md`](docs/projects.md) to see how we use the +[GitHub Project +feature](https://help.github.com/articles/tracking-the-progress-of-your-work-with-projects/) +to organize planned and in-progress work. + +Contributions via merge request are welcome. Changes should: +* Be provided under the [Apache 2.0](#license). +* You'll be prompted with a one-time "click-through" + [Khronos Open Source Contributor License Agreement][spirv-tools-cla] + (CLA) dialog as part of submitting your pull request or + other contribution to GitHub. +* Include tests to cover updated functionality. +* C++ code should follow the [Google C++ Style Guide][cpp-style-guide]. +* Code should be formatted with `clang-format`. + [kokoro/check-format/build.sh](kokoro/check-format/build.sh) + shows how to download it. Note that we currently use + `clang-format version 5.0.0` for SPIRV-Tools. Settings are defined by + the included [.clang-format](.clang-format) file. + +We intend to maintain a linear history on the GitHub `master` branch. + +### Source code organization + +* `example`: demo code of using SPIRV-Tools APIs +* `external/googletest`: Intended location for the + [googletest][googletest] sources, not provided +* `external/effcee`: Location of [Effcee][effcee] sources, if the `effcee` library + is not already configured by an enclosing project. +* `external/re2`: Location of [RE2][re2] sources, if the `re2` library is not already + configured by an enclosing project. + (The Effcee project already requires RE2.) +* `include/`: API clients should add this directory to the include search path +* `external/spirv-headers`: Intended location for + [SPIR-V headers][spirv-headers], not provided +* `include/spirv-tools/libspirv.h`: C API public interface +* `source/`: API implementation +* `test/`: Tests, using the [googletest][googletest] framework +* `tools/`: Command line executables + +Example of getting sources, assuming SPIRV-Tools is configured as a standalone project: + + git clone https://github.com/KhronosGroup/SPIRV-Tools.git spirv-tools + git clone https://github.com/KhronosGroup/SPIRV-Headers.git spirv-tools/external/spirv-headers + git clone https://github.com/google/googletest.git spirv-tools/external/googletest + git clone https://github.com/google/effcee.git spirv-tools/external/effcee + git clone https://github.com/google/re2.git spirv-tools/external/re2 + +### Tests + +The project contains a number of tests, used to drive development +and ensure correctness. The tests are written using the +[googletest][googletest] framework. The `googletest` +source is not provided with this project. There are two ways to enable +tests: +* If SPIR-V Tools is configured as part of an enclosing project, then the + enclosing project should configure `googletest` before configuring SPIR-V Tools. +* If SPIR-V Tools is configured as a standalone project, then download the + `googletest` source into the `/external/googletest` directory before + configuring and building the project. + +*Note*: You must use a version of googletest that includes +[a fix][googletest-pull-612] for [googletest issue 610][googletest-issue-610]. +The fix is included on the googletest master branch any time after 2015-11-10. +In particular, googletest must be newer than version 1.7.0. + +### Dependency on Effcee + +Some tests depend on the [Effcee][effcee] library for stateful matching. +Effcee itself depends on [RE2][re2]. + +* If SPIRV-Tools is configured as part of a larger project that already uses + Effcee, then that project should include Effcee before SPIRV-Tools. +* Otherwise, SPIRV-Tools expects Effcee sources to appear in `external/effcee` + and RE2 sources to appear in `external/re2`. + + +## Build + +Instead of building manually, you can also download the binaries for your +platform directly from the [master-tot release][master-tot-release] on GitHub. +Those binaries are automatically uploaded by the buildbots after successful +testing and they always reflect the current top of the tree of the master +branch. + +In order to build the code, you first need to sync the external repositories +that it depends on. Assume that `` is the root directory of the +checked out code: + +```sh +cd +git clone https://github.com/KhronosGroup/SPIRV-Headers.git external/spirv-headers +git clone https://github.com/google/effcee.git external/effcee +git clone https://github.com/google/re2.git external/re2 +git clone https://github.com/google/googletest.git external/googletest # optional + +``` + +*Note*: +The script `utils/git-sync-deps` can be used to checkout and/or update the +contents of the repos under `external/` instead of manually maintaining them. + +### Build using CMake +You can build The project using [CMake][cmake] to generate platform-specific +build configurations. + +```sh +cd +mkdir build && cd build +cmake [-G ] +``` + +Once the build files have been generated, build using your preferred +development environment. + +### Build using Bazel +You can also use [Bazel](https://bazel.build/) to build the project. +```sh +cd +bazel build :all +``` + +### Tools you'll need + +For building and testing SPIRV-Tools, the following tools should be +installed regardless of your OS: + +- [CMake](http://www.cmake.org/): if using CMake for generating compilation +targets, you need to install CMake Version 2.8.12 or later. +- [Python 3](http://www.python.org/): for utility scripts and running the test +suite. +- [Bazel](https://baze.build/) (optional): if building the source with Bazel, +you need to install Bazel Version 0.29.1 on your machine. Other versions may +also work, but are not verified. + +SPIRV-Tools is regularly tested with the the following compilers: + +On Linux +- GCC version 4.8.5 +- Clang version 3.8 + +On MacOS +- AppleClang 10.0 + +On Windows +- Visual Studio 2015 +- Visual Studio 2017 + +Other compilers or later versions may work, but they are not tested. + +### CMake options + +The following CMake options are supported: + +* `SPIRV_COLOR_TERMINAL={ON|OFF}`, default `ON` - Enables color console output. +* `SPIRV_SKIP_TESTS={ON|OFF}`, default `OFF`- Build only the library and + the command line tools. This will prevent the tests from being built. +* `SPIRV_SKIP_EXECUTABLES={ON|OFF}`, default `OFF`- Build only the library, not + the command line tools and tests. +* `SPIRV_USE_SANITIZER=`, default is no sanitizing - On UNIX + platforms with an appropriate version of `clang` this option enables the use + of the sanitizers documented [here][clang-sanitizers]. + This should only be used with a debug build. +* `SPIRV_WARN_EVERYTHING={ON|OFF}`, default `OFF` - On UNIX platforms enable + more strict warnings. The code might not compile with this option enabled. + For Clang, enables `-Weverything`. For GCC, enables `-Wpedantic`. + See [`CMakeLists.txt`](CMakeLists.txt) for details. +* `SPIRV_WERROR={ON|OFF}`, default `ON` - Forces a compilation error on any + warnings encountered by enabling the compiler-specific compiler front-end + option. No compiler front-end options are enabled when this option is OFF. + +Additionally, you can pass additional C preprocessor definitions to SPIRV-Tools +via setting `SPIRV_TOOLS_EXTRA_DEFINITIONS`. For example, by setting it to +`/D_ITERATOR_DEBUG_LEVEL=0` on Windows, you can disable checked iterators and +iterator debugging. + +### Android + +SPIR-V Tools supports building static libraries `libSPIRV-Tools.a` and +`libSPIRV-Tools-opt.a` for Android: + +``` +cd + +export ANDROID_NDK=/path/to/your/ndk + +mkdir build && cd build +mkdir libs +mkdir app + +$ANDROID_NDK/ndk-build -C ../android_test \ + NDK_PROJECT_PATH=. \ + NDK_LIBS_OUT=`pwd`/libs \ + NDK_APP_OUT=`pwd`/app +``` + +### Updating DEPS +Occasionally the entries in DEPS will need to be updated. This is done on demand +when there is a request to do this, often due to downstream breakages. There is +a script `utils/roll_deps.sh` provided, which will generate a patch with the +updated DEPS values. This will still need to be tested in your checkout to +confirm that there are no integration issues that need to be resolved. + +## Library + +### Usage + +The internals of the library use C++11 features, and are exposed via both a C +and C++ API. + +In order to use the library from an application, the include path should point +to `/include`, which will enable the application to include the +header `/include/spirv-tools/libspirv.h{|pp}` then linking against +the static library in `/source/libSPIRV-Tools.a` or +`/source/SPIRV-Tools.lib`. +For optimization, the header file is +`/include/spirv-tools/optimizer.hpp`, and the static library is +`/source/libSPIRV-Tools-opt.a` or +`/source/SPIRV-Tools-opt.lib`. + +* `SPIRV-Tools` CMake target: Creates the static library: + * `/source/libSPIRV-Tools.a` on Linux and OS X. + * `/source/libSPIRV-Tools.lib` on Windows. +* `SPIRV-Tools-opt` CMake target: Creates the static library: + * `/source/libSPIRV-Tools-opt.a` on Linux and OS X. + * `/source/libSPIRV-Tools-opt.lib` on Windows. + +#### Entry points + +The interfaces are still under development, and are expected to change. + +There are five main entry points into the library in the C interface: + +* `spvTextToBinary`: An assembler, translating text to a binary SPIR-V module. +* `spvBinaryToText`: A disassembler, translating a binary SPIR-V module to + text. +* `spvBinaryParse`: The entry point to a binary parser API. It issues callbacks + for the header and each parsed instruction. The disassembler is implemented + as a client of `spvBinaryParse`. +* `spvValidate` implements the validator functionality. *Incomplete* +* `spvValidateBinary` implements the validator functionality. *Incomplete* + +The C++ interface is comprised of three classes, `SpirvTools`, `Optimizer` and +`Linker`, all in the `spvtools` namespace. +* `SpirvTools` provides `Assemble`, `Disassemble`, and `Validate` methods. +* `Optimizer` provides methods for registering and running optimization passes. +* `Linker` provides methods for combining together multiple binaries. + +## Command line tools + +Command line tools, which wrap the above library functions, are provided to +assemble or disassemble shader files. It's a convention to name SPIR-V +assembly and binary files with suffix `.spvasm` and `.spv`, respectively. + +### Assembler tool + +The assembler reads the assembly language text, and emits the binary form. + +The standalone assembler is the exectuable called `spirv-as`, and is located in +`/tools/spirv-as`. The functionality of the assembler is implemented +by the `spvTextToBinary` library function. + +* `spirv-as` - the standalone assembler + * `/tools/as` + +Use option `-h` to print help. + +### Disassembler tool + +The disassembler reads the binary form, and emits assembly language text. + +The standalone disassembler is the executable called `spirv-dis`, and is located in +`/tools/spirv-dis`. The functionality of the disassembler is implemented +by the `spvBinaryToText` library function. + +* `spirv-dis` - the standalone disassembler + * `/tools/dis` + +Use option `-h` to print help. + +The output includes syntax colouring when printing to the standard output stream, +on Linux, Windows, and OS X. + +### Linker tool + +The linker combines multiple SPIR-V binary modules together, resulting in a single +binary module as output. + +This is a work in progress. +The linker does not support OpenCL program linking options related to math +flags. (See section 5.6.5.2 in OpenCL 1.2) + +* `spirv-link` - the standalone linker + * `/tools/link` + +### Optimizer tool + +The optimizer processes a SPIR-V binary module, applying transformations +in the specified order. + +This is a work in progress, with initially only few available transformations. + +* `spirv-opt` - the standalone optimizer + * `/tools/opt` + +### Validator tool + +*Warning:* This functionality is under development, and is incomplete. + +The standalone validator is the executable called `spirv-val`, and is located in +`/tools/spirv-val`. The functionality of the validator is implemented +by the `spvValidate` library function. + +The validator operates on the binary form. + +* `spirv-val` - the standalone validator + * `/tools/val` + +### Reducer tool + +The reducer shrinks a SPIR-V binary module, guided by a user-supplied +*interestingness test*. + +This is a work in progress, with initially only shrinks a module in a few ways. + +* `spirv-reduce` - the standalone reducer + * `/tools/reduce` + +Run `spirv-reduce --help` to see how to specify interestingness. + +### Control flow dumper tool + +The control flow dumper prints the control flow graph for a SPIR-V module as a +[GraphViz](http://www.graphviz.org/) graph. + +This is experimental. + +* `spirv-cfg` - the control flow graph dumper + * `/tools/cfg` + +### Utility filters + +* `spirv-lesspipe.sh` - Automatically disassembles `.spv` binary files for the + `less` program, on compatible systems. For example, set the `LESSOPEN` + environment variable as follows, assuming both `spirv-lesspipe.sh` and + `spirv-dis` are on your executable search path: + ``` + export LESSOPEN='| spirv-lesspipe.sh "%s"' + ``` + Then you page through a disassembled module as follows: + ``` + less foo.spv + ``` + * The `spirv-lesspipe.sh` script will pass through any extra arguments to + `spirv-dis`. So, for example, you can turn off colours and friendly ID + naming as follows: + ``` + export LESSOPEN='| spirv-lesspipe.sh "%s" --no-color --raw-id' + ``` + +* [vim-spirv](https://github.com/kbenzie/vim-spirv) - A vim plugin which + supports automatic disassembly of `.spv` files using the `:edit` command and + assembly using the `:write` command. The plugin also provides additional + features which include; syntax highlighting; highlighting of all ID's matching + the ID under the cursor; and highlighting errors where the `Instruction` + operand of `OpExtInst` is used without an appropriate `OpExtInstImport`. + +* `50spirv-tools.el` - Automatically disassembles '.spv' binary files when + loaded into the emacs text editor, and re-assembles them when saved, + provided any modifications to the file are valid. This functionality + must be explicitly requested by defining the symbol + SPIRV_TOOLS_INSTALL_EMACS_HELPERS as follows: + ``` + cmake -DSPIRV_TOOLS_INSTALL_EMACS_HELPERS=true ... + ``` + + In addition, this helper is only installed if the directory /etc/emacs/site-start.d + exists, which is typically true if emacs is installed on the system. + + Note that symbol IDs are not currently preserved through a load/edit/save operation. + This may change if the ability is added to spirv-as. + + +### Tests + +Tests are only built when googletest is found. Use `ctest` to run all the +tests. + +## Future Work + + +_See the [projects pages](https://github.com/KhronosGroup/SPIRV-Tools/projects) +for more information._ + +### Assembler and disassembler + +* The disassembler could emit helpful annotations in comments. For example: + * Use variable name information from debug instructions to annotate + key operations on variables. + * Show control flow information by annotating `OpLabel` instructions with + that basic block's predecessors. +* Error messages could be improved. + +### Validator + +This is a work in progress. + +### Linker + +* The linker could accept math transformations such as allowing MADs, or other + math flags passed at linking-time in OpenCL. +* Linkage attributes can not be applied through a group. +* Check decorations of linked functions attributes. +* Remove dead instructions, such as OpName targeting imported symbols. + +## Licence + +Full license terms are in [LICENSE](LICENSE) +``` +Copyright (c) 2015-2016 The Khronos Group Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` + +[spirv-tools-cla]: https://cla-assistant.io/KhronosGroup/SPIRV-Tools +[spirv-tools-projects]: https://github.com/KhronosGroup/SPIRV-Tools/projects +[spirv-tools-mailing-list]: https://www.khronos.org/spir/spirv-tools-mailing-list +[spirv-registry]: https://www.khronos.org/registry/spir-v/ +[spirv-headers]: https://github.com/KhronosGroup/SPIRV-Headers +[googletest]: https://github.com/google/googletest +[googletest-pull-612]: https://github.com/google/googletest/pull/612 +[googletest-issue-610]: https://github.com/google/googletest/issues/610 +[effcee]: https://github.com/google/effcee +[re2]: https://github.com/google/re2 +[CMake]: https://cmake.org/ +[cpp-style-guide]: https://google.github.io/styleguide/cppguide.html +[clang-sanitizers]: http://clang.llvm.org/docs/UsersManual.html#controlling-code-generation +[master-tot-release]: https://github.com/KhronosGroup/SPIRV-Tools/releases/tag/master-tot diff --git a/3rdparty/spirv-tools/include/generated/build-version.inc b/3rdparty/spirv-tools/include/generated/build-version.inc index 06a28c38b..115b30e9d 100644 --- a/3rdparty/spirv-tools/include/generated/build-version.inc +++ b/3rdparty/spirv-tools/include/generated/build-version.inc @@ -1 +1 @@ -"v2019.5-dev", "SPIRV-Tools v2019.5-dev v2019.4-89-g84b19760" +"v2019.5-dev", "SPIRV-Tools v2019.5-dev v2019.4-102-gc18c9ff6" diff --git a/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt b/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt index ac8c002b3..b21d210d0 100644 --- a/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt +++ b/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt @@ -31,6 +31,7 @@ if(SPIRV_BUILD_FUZZER) set(SPIRV_TOOLS_FUZZ_SOURCES data_descriptor.h fact_manager.h + force_render_red.h fuzzer.h fuzzer_context.h fuzzer_pass.h @@ -62,13 +63,14 @@ if(SPIRV_BUILD_FUZZER) transformation_move_block_down.h transformation_replace_boolean_constant_with_constant_binary.h transformation_replace_constant_with_uniform.h - transformation_replace_id_with_synonym.h + transformation_replace_id_with_synonym.h transformation_split_block.h uniform_buffer_element_descriptor.h ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h data_descriptor.cpp fact_manager.cpp + force_render_red.cpp fuzzer.cpp fuzzer_context.cpp fuzzer_pass.cpp diff --git a/3rdparty/spirv-tools/source/fuzz/force_render_red.cpp b/3rdparty/spirv-tools/source/fuzz/force_render_red.cpp new file mode 100644 index 000000000..3bc117249 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/force_render_red.cpp @@ -0,0 +1,368 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/force_render_red.h" + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation_replace_constant_with_uniform.h" +#include "source/fuzz/uniform_buffer_element_descriptor.h" +#include "source/opt/build_module.h" +#include "source/opt/ir_context.h" +#include "source/opt/types.h" +#include "source/util/make_unique.h" +#include "tools/util/cli_consumer.h" + +#include +#include + +namespace spvtools { +namespace fuzz { + +namespace { + +// Helper method to find the fragment shader entry point, complaining if there +// is no shader or if there is no fragment entry point. +opt::Function* FindFragmentShaderEntryPoint(opt::IRContext* ir_context, + MessageConsumer message_consumer) { + // Check that this is a fragment shader + bool found_capability_shader = false; + for (auto& capability : ir_context->capabilities()) { + assert(capability.opcode() == SpvOpCapability); + if (capability.GetSingleWordInOperand(0) == SpvCapabilityShader) { + found_capability_shader = true; + break; + } + } + if (!found_capability_shader) { + message_consumer( + SPV_MSG_ERROR, nullptr, {}, + "Forcing of red rendering requires the Shader capability."); + return nullptr; + } + + opt::Instruction* fragment_entry_point = nullptr; + for (auto& entry_point : ir_context->module()->entry_points()) { + if (entry_point.GetSingleWordInOperand(0) == SpvExecutionModelFragment) { + fragment_entry_point = &entry_point; + break; + } + } + if (fragment_entry_point == nullptr) { + message_consumer(SPV_MSG_ERROR, nullptr, {}, + "Forcing of red rendering requires an entry point with " + "the Fragment execution model."); + return nullptr; + } + + for (auto& function : *ir_context->module()) { + if (function.result_id() == + fragment_entry_point->GetSingleWordInOperand(1)) { + return &function; + } + } + assert( + false && + "A valid module must have a function associate with each entry point."); + return nullptr; +} + +// Helper method to check that there is a single vec4 output variable and get a +// pointer to it. +opt::Instruction* FindVec4OutputVariable(opt::IRContext* ir_context, + MessageConsumer message_consumer) { + opt::Instruction* output_variable = nullptr; + for (auto& inst : ir_context->types_values()) { + if (inst.opcode() == SpvOpVariable && + inst.GetSingleWordInOperand(0) == SpvStorageClassOutput) { + if (output_variable != nullptr) { + message_consumer(SPV_MSG_ERROR, nullptr, {}, + "Only one output variable can be handled at present; " + "found multiple."); + return nullptr; + } + output_variable = &inst; + // Do not break, as we want to check for multiple output variables. + } + } + if (output_variable == nullptr) { + message_consumer(SPV_MSG_ERROR, nullptr, {}, + "No output variable to which to write red was found."); + return nullptr; + } + + auto output_variable_base_type = ir_context->get_type_mgr() + ->GetType(output_variable->type_id()) + ->AsPointer() + ->pointee_type() + ->AsVector(); + if (!output_variable_base_type || + output_variable_base_type->element_count() != 4 || + !output_variable_base_type->element_type()->AsFloat()) { + message_consumer(SPV_MSG_ERROR, nullptr, {}, + "The output variable must have type vec4."); + return nullptr; + } + + return output_variable; +} + +// Helper to get the ids of float constants 0.0 and 1.0, creating them if +// necessary. +std::pair FindOrCreateFloatZeroAndOne( + opt::IRContext* ir_context, opt::analysis::Float* float_type) { + float one = 1.0; + uint32_t one_as_uint; + memcpy(&one_as_uint, &one, sizeof(float)); + std::vector zero_bytes = {0}; + std::vector one_bytes = {one_as_uint}; + auto constant_zero = ir_context->get_constant_mgr()->RegisterConstant( + MakeUnique(float_type, zero_bytes)); + auto constant_one = ir_context->get_constant_mgr()->RegisterConstant( + MakeUnique(float_type, one_bytes)); + auto constant_zero_id = ir_context->get_constant_mgr() + ->GetDefiningInstruction(constant_zero) + ->result_id(); + auto constant_one_id = ir_context->get_constant_mgr() + ->GetDefiningInstruction(constant_one) + ->result_id(); + return std::pair(constant_zero_id, constant_one_id); +} + +std::unique_ptr +MakeConstantUniformReplacement(opt::IRContext* ir_context, + const FactManager& fact_manager, + uint32_t constant_id, + uint32_t greater_than_instruction, + uint32_t in_operand_index) { + return MakeUnique( + transformation::MakeIdUseDescriptor(constant_id, SpvOpFOrdGreaterThan, + in_operand_index, + greater_than_instruction, 0), + fact_manager.GetUniformDescriptorsForConstant(ir_context, constant_id)[0], + ir_context->TakeNextId(), ir_context->TakeNextId()); +} + +} // namespace + +bool ForceRenderRed( + const spv_target_env& target_env, const std::vector& binary_in, + const spvtools::fuzz::protobufs::FactSequence& initial_facts, + std::vector* binary_out) { + auto message_consumer = spvtools::utils::CLIMessageConsumer; + spvtools::SpirvTools tools(target_env); + if (!tools.IsValid()) { + message_consumer(SPV_MSG_ERROR, nullptr, {}, + "Failed to create SPIRV-Tools interface; stopping."); + return false; + } + + // Initial binary should be valid. + if (!tools.Validate(&binary_in[0], binary_in.size())) { + message_consumer(SPV_MSG_ERROR, nullptr, {}, + "Initial binary is invalid; stopping."); + return false; + } + + // Build the module from the input binary. + std::unique_ptr ir_context = BuildModule( + target_env, message_consumer, binary_in.data(), binary_in.size()); + assert(ir_context); + + // Set up a fact manager with any given initial facts. + FactManager fact_manager; + for (auto& fact : initial_facts.fact()) { + fact_manager.AddFact(fact, ir_context.get()); + } + + auto entry_point_function = + FindFragmentShaderEntryPoint(ir_context.get(), message_consumer); + auto output_variable = + FindVec4OutputVariable(ir_context.get(), message_consumer); + if (entry_point_function == nullptr || output_variable == nullptr) { + return false; + } + + opt::analysis::Float temp_float_type(32); + opt::analysis::Float* float_type = ir_context->get_type_mgr() + ->GetRegisteredType(&temp_float_type) + ->AsFloat(); + std::pair zero_one_float_ids = + FindOrCreateFloatZeroAndOne(ir_context.get(), float_type); + + // Make the new exit block + auto new_exit_block_id = ir_context->TakeNextId(); + { + auto label = MakeUnique(ir_context.get(), SpvOpLabel, 0, + new_exit_block_id, + opt::Instruction::OperandList()); + auto new_exit_block = MakeUnique(std::move(label)); + new_exit_block->AddInstruction(MakeUnique( + ir_context.get(), SpvOpReturn, 0, 0, opt::Instruction::OperandList())); + entry_point_function->AddBasicBlock(std::move(new_exit_block)); + } + + // Make the new entry block + { + auto label = MakeUnique(ir_context.get(), SpvOpLabel, 0, + ir_context->TakeNextId(), + opt::Instruction::OperandList()); + auto new_entry_block = MakeUnique(std::move(label)); + + // Make an instruction to construct vec4(1.0, 0.0, 0.0, 1.0), representing + // the colour red. + opt::Operand zero_float = {SPV_OPERAND_TYPE_ID, {zero_one_float_ids.first}}; + opt::Operand one_float = {SPV_OPERAND_TYPE_ID, {zero_one_float_ids.second}}; + opt::Instruction::OperandList op_composite_construct_operands = { + one_float, zero_float, zero_float, one_float}; + auto temp_vec4 = opt::analysis::Vector(float_type, 4); + auto vec4_id = ir_context->get_type_mgr()->GetId(&temp_vec4); + auto red = MakeUnique( + ir_context.get(), SpvOpCompositeConstruct, vec4_id, + ir_context->TakeNextId(), op_composite_construct_operands); + auto red_id = red->result_id(); + new_entry_block->AddInstruction(std::move(red)); + + // Make an instruction to store red into the output color. + opt::Operand variable_to_store_into = {SPV_OPERAND_TYPE_ID, + {output_variable->result_id()}}; + opt::Operand value_to_be_stored = {SPV_OPERAND_TYPE_ID, {red_id}}; + opt::Instruction::OperandList op_store_operands = {variable_to_store_into, + value_to_be_stored}; + new_entry_block->AddInstruction(MakeUnique( + ir_context.get(), SpvOpStore, 0, 0, op_store_operands)); + + // We are going to attempt to construct 'false' as an expression of the form + // 'literal1 > literal2'. If we succeed, we will later replace each literal + // with a uniform of the same value - we can only do that replacement once + // we have added the entry block to the module. + std::unique_ptr + first_greater_then_operand_replacement = nullptr; + std::unique_ptr + second_greater_then_operand_replacement = nullptr; + uint32_t id_guaranteed_to_be_false = 0; + + opt::analysis::Bool temp_bool_type; + opt::analysis::Bool* registered_bool_type = + ir_context->get_type_mgr() + ->GetRegisteredType(&temp_bool_type) + ->AsBool(); + + auto float_type_id = ir_context->get_type_mgr()->GetId(float_type); + auto types_for_which_uniforms_are_known = + fact_manager.GetTypesForWhichUniformValuesAreKnown(); + + // Check whether we have any float uniforms. + if (std::find(types_for_which_uniforms_are_known.begin(), + types_for_which_uniforms_are_known.end(), + float_type_id) != types_for_which_uniforms_are_known.end()) { + // We have at least one float uniform; let's see whether we have at least + // two. + auto available_constants = + fact_manager.GetConstantsAvailableFromUniformsForType( + ir_context.get(), float_type_id); + if (available_constants.size() > 1) { + // Grab the float constants associated with the first two known float + // uniforms. + auto first_constant = + ir_context->get_constant_mgr() + ->GetConstantFromInst(ir_context->get_def_use_mgr()->GetDef( + available_constants[0])) + ->AsFloatConstant(); + auto second_constant = + ir_context->get_constant_mgr() + ->GetConstantFromInst(ir_context->get_def_use_mgr()->GetDef( + available_constants[1])) + ->AsFloatConstant(); + + // Now work out which of the two constants is larger than the other. + uint32_t larger_constant_index = 0; + uint32_t smaller_constant_index = 0; + if (first_constant->GetFloat() > second_constant->GetFloat()) { + larger_constant_index = 0; + smaller_constant_index = 1; + } else if (first_constant->GetFloat() < second_constant->GetFloat()) { + larger_constant_index = 1; + smaller_constant_index = 0; + } + + // Only proceed with these constants if they have turned out to be + // distinct. + if (larger_constant_index != smaller_constant_index) { + // We are in a position to create 'false' as 'literal1 > literal2', so + // reserve an id for this computation; this id will end up being + // guaranteed to be 'false'. + id_guaranteed_to_be_false = ir_context->TakeNextId(); + + auto smaller_constant = available_constants[smaller_constant_index]; + auto larger_constant = available_constants[larger_constant_index]; + + opt::Instruction::OperandList greater_than_operands = { + {SPV_OPERAND_TYPE_ID, {smaller_constant}}, + {SPV_OPERAND_TYPE_ID, {larger_constant}}}; + new_entry_block->AddInstruction(MakeUnique( + ir_context.get(), SpvOpFOrdGreaterThan, + ir_context->get_type_mgr()->GetId(registered_bool_type), + id_guaranteed_to_be_false, greater_than_operands)); + + first_greater_then_operand_replacement = + MakeConstantUniformReplacement(ir_context.get(), fact_manager, + smaller_constant, + id_guaranteed_to_be_false, 0); + second_greater_then_operand_replacement = + MakeConstantUniformReplacement(ir_context.get(), fact_manager, + larger_constant, + id_guaranteed_to_be_false, 1); + } + } + } + + if (id_guaranteed_to_be_false == 0) { + auto constant_false = ir_context->get_constant_mgr()->RegisterConstant( + MakeUnique(registered_bool_type, false)); + id_guaranteed_to_be_false = ir_context->get_constant_mgr() + ->GetDefiningInstruction(constant_false) + ->result_id(); + } + + opt::Operand false_condition = {SPV_OPERAND_TYPE_ID, + {id_guaranteed_to_be_false}}; + opt::Operand then_block = {SPV_OPERAND_TYPE_ID, + {entry_point_function->entry()->id()}}; + opt::Operand else_block = {SPV_OPERAND_TYPE_ID, {new_exit_block_id}}; + opt::Instruction::OperandList op_branch_conditional_operands = { + false_condition, then_block, else_block}; + new_entry_block->AddInstruction( + MakeUnique(ir_context.get(), SpvOpBranchConditional, + 0, 0, op_branch_conditional_operands)); + + entry_point_function->InsertBasicBlockBefore( + std::move(new_entry_block), entry_point_function->entry().get()); + + for (auto& replacement : {first_greater_then_operand_replacement.get(), + second_greater_then_operand_replacement.get()}) { + if (replacement) { + assert(replacement->IsApplicable(ir_context.get(), fact_manager)); + replacement->Apply(ir_context.get(), &fact_manager); + } + } + } + + // Write out the module as a binary. + ir_context->module()->ToBinary(binary_out, false); + return true; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/force_render_red.h b/3rdparty/spirv-tools/source/fuzz/force_render_red.h new file mode 100644 index 000000000..2484d2788 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/force_render_red.h @@ -0,0 +1,48 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FORCE_RENDER_RED_H_ +#define SOURCE_FORCE_RENDER_RED_H_ + +#include + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "spirv-tools/libspirv.hpp" + +namespace spvtools { +namespace fuzz { + +// Requires |binary_in| to be a valid SPIR-V module with Shader capability, +// containing an entry point with the Fragment execution model, and a single +// output variable of type vec4. +// +// Turns the body of this entry point into effectively: +// +// output_variable = vec4(1.0, 0.0, 0.0, 1.0); +// if (false) { +// original_body +// } +// +// If suitable facts about values of uniforms are available, the 'false' will +// instead become: 'u > v', where 'u' and 'v' are pieces of uniform data for +// which it is known that 'u < v' holds. +bool ForceRenderRed( + const spv_target_env& target_env, const std::vector& binary_in, + const spvtools::fuzz::protobufs::FactSequence& initial_facts, + std::vector* binary_out); + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FORCE_RENDER_RED_H_ diff --git a/3rdparty/spirv-tools/source/opt/cfg.h b/3rdparty/spirv-tools/source/opt/cfg.h index 4c7e936ca..f28068229 100644 --- a/3rdparty/spirv-tools/source/opt/cfg.h +++ b/3rdparty/spirv-tools/source/opt/cfg.h @@ -60,8 +60,9 @@ class CFG { // Compute structured block order into |order| for |func| starting at |root|. // This order has the property that dominators come before all blocks they - // dominate and merge blocks come after all blocks that are in the control - // constructs of their header. + // dominate, merge blocks come after all blocks that are in the control + // constructs of their header, and continue blocks come after all of the + // blocks in the body of their loop. void ComputeStructuredOrder(Function* func, BasicBlock* root, std::list* order); diff --git a/3rdparty/spirv-tools/source/opt/inline_pass.cpp b/3rdparty/spirv-tools/source/opt/inline_pass.cpp index 98daaf43c..36991ec56 100644 --- a/3rdparty/spirv-tools/source/opt/inline_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/inline_pass.cpp @@ -720,18 +720,24 @@ bool InlinePass::IsInlinableFunction(Function* func) { return false; } - // Do not inline functions with an OpKill because they may be inlined into a - // continue construct. - bool has_opkill = !func->WhileEachInst( - [](Instruction* inst) { return inst->opcode() != SpvOpKill; }); + // Do not inline functions with an OpKill if they are called from a continue + // construct. If it is inlined into a continue construct it will generate + // invalid code. + bool func_is_called_from_continue = + funcs_called_from_continue_.count(func->result_id()) != 0; - if (has_opkill) { + if (func_is_called_from_continue && ContainsKill(func)) { return false; } return true; } +bool InlinePass::ContainsKill(Function* func) const { + return !func->WhileEachInst( + [](Instruction* inst) { return inst->opcode() != SpvOpKill; }); +} + void InlinePass::InitializeInline() { false_id_ = 0; @@ -741,6 +747,8 @@ void InlinePass::InitializeInline() { inlinable_.clear(); no_return_in_loop_.clear(); early_return_funcs_.clear(); + funcs_called_from_continue_ = + context()->GetStructuredCFGAnalysis()->FindFuncsCalledFromContinue(); for (auto& fn : *get_module()) { // Initialize function and block maps. diff --git a/3rdparty/spirv-tools/source/opt/inline_pass.h b/3rdparty/spirv-tools/source/opt/inline_pass.h index ecfe964f1..bc5f78127 100644 --- a/3rdparty/spirv-tools/source/opt/inline_pass.h +++ b/3rdparty/spirv-tools/source/opt/inline_pass.h @@ -139,6 +139,9 @@ class InlinePass : public Pass { // Return true if |func| is a function that can be inlined. bool IsInlinableFunction(Function* func); + // Returns true if |func| contains an OpKill instruction. + bool ContainsKill(Function* func) const; + // Update phis in succeeding blocks to point to new last block void UpdateSucceedingPhis( std::vector>& new_blocks); @@ -164,6 +167,10 @@ class InlinePass : public Pass { // result id for OpConstantFalse uint32_t false_id_; + + // Set of functions that are originally called directly or indirectly from a + // continue construct. + std::unordered_set funcs_called_from_continue_; }; } // namespace opt diff --git a/3rdparty/spirv-tools/source/opt/inst_buff_addr_check_pass.cpp b/3rdparty/spirv-tools/source/opt/inst_buff_addr_check_pass.cpp index ef29ce552..fa6c2c6a0 100644 --- a/3rdparty/spirv-tools/source/opt/inst_buff_addr_check_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/inst_buff_addr_check_pass.cpp @@ -96,8 +96,22 @@ void InstBuffAddrCheckPass::GenCheckCode( uid2offset_[ref_inst->unique_id()], stage_idx, {error_id, lo_uptr_inst->result_id(), hi_uptr_inst->result_id()}, &builder); - // Gen zero for invalid reference - uint32_t ref_type_id = ref_inst->type_id(); + // Gen zero for invalid load. If pointer type, need to convert uint64 + // zero to pointer; cannot create ConstantNull of pointer type. + uint32_t null_id = 0; + if (new_ref_id != 0) { + uint32_t ref_type_id = ref_inst->type_id(); + analysis::TypeManager* type_mgr = context()->get_type_mgr(); + analysis::Type* ref_type = type_mgr->GetType(ref_type_id); + if (ref_type->AsPointer() != nullptr) { + uint32_t null_u64_id = GetNullId(GetUint64Id()); + Instruction* null_ptr_inst = + builder.AddUnaryOp(ref_type_id, SpvOpConvertUToPtr, null_u64_id); + null_id = null_ptr_inst->result_id(); + } else { + null_id = GetNullId(ref_type_id); + } + } (void)builder.AddBranch(merge_blk_id); new_blocks->push_back(std::move(new_blk_ptr)); // Gen merge block @@ -107,9 +121,9 @@ void InstBuffAddrCheckPass::GenCheckCode( // result id of the original reference with that of the Phi. Kill original // reference. if (new_ref_id != 0) { - Instruction* phi_inst = builder.AddPhi( - ref_type_id, - {new_ref_id, valid_blk_id, GetNullId(ref_type_id), invalid_blk_id}); + Instruction* phi_inst = + builder.AddPhi(ref_inst->type_id(), + {new_ref_id, valid_blk_id, null_id, invalid_blk_id}); context()->ReplaceAllUsesWith(ref_inst->result_id(), phi_inst->result_id()); } new_blocks->push_back(std::move(new_blk_ptr)); diff --git a/3rdparty/spirv-tools/source/opt/ir_context.h b/3rdparty/spirv-tools/source/opt/ir_context.h index 3bbf18091..45bf1290e 100644 --- a/3rdparty/spirv-tools/source/opt/ir_context.h +++ b/3rdparty/spirv-tools/source/opt/ir_context.h @@ -187,9 +187,6 @@ class IRContext { inline IteratorRange debugs3(); inline IteratorRange debugs3() const; - // Clears all debug instructions (excluding OpLine & OpNoLine). - inline void debug_clear(); - // Add |capability| to the module, if it is not already enabled. inline void AddCapability(SpvCapability capability); @@ -543,7 +540,7 @@ class IRContext { return GetFunction(inst->result_id()); } - // Add to |todo| all ids of functions called in |func|. + // Add to |todo| all ids of functions called directly from |func|. void AddCalls(const Function* func, std::queue* todo); // Applies |pfn| to every function in the call trees that are rooted at the @@ -928,8 +925,6 @@ IteratorRange IRContext::debugs3() const { return ((const Module*)module_.get())->debugs3(); } -void IRContext::debug_clear() { module_->debug_clear(); } - void IRContext::AddCapability(SpvCapability capability) { if (!get_feature_mgr()->HasCapability(capability)) { std::unique_ptr capability_inst(new Instruction( diff --git a/3rdparty/spirv-tools/source/opt/ir_loader.cpp b/3rdparty/spirv-tools/source/opt/ir_loader.cpp index edd4832d9..c68b3e26b 100644 --- a/3rdparty/spirv-tools/source/opt/ir_loader.cpp +++ b/3rdparty/spirv-tools/source/opt/ir_loader.cpp @@ -159,6 +159,9 @@ void IrLoader::EndModule() { for (auto& function : *module_) { for (auto& bb : function) bb.SetParent(&function); } + + // Copy any trailing Op*Line instruction into the module + module_->SetTrailingDbgLineInfo(std::move(dbg_line_info_)); } } // namespace opt diff --git a/3rdparty/spirv-tools/source/opt/module.cpp b/3rdparty/spirv-tools/source/opt/module.cpp index 04e4e9733..c7fc2473d 100644 --- a/3rdparty/spirv-tools/source/opt/module.cpp +++ b/3rdparty/spirv-tools/source/opt/module.cpp @@ -121,6 +121,9 @@ void Module::ForEachInst(const std::function& f, static_cast(i.get())->ForEachInst(f, run_on_debug_line_insts); } + if (run_on_debug_line_insts) { + for (auto& i : trailing_dbg_line_info_) DELEGATE(i); + } #undef DELEGATE } diff --git a/3rdparty/spirv-tools/source/opt/module.h b/3rdparty/spirv-tools/source/opt/module.h index cf7c274de..aefa2a548 100644 --- a/3rdparty/spirv-tools/source/opt/module.h +++ b/3rdparty/spirv-tools/source/opt/module.h @@ -192,22 +192,6 @@ class Module { inline IteratorRange execution_modes(); inline IteratorRange execution_modes() const; - // Clears all debug instructions (excluding OpLine & OpNoLine). - void debug_clear() { - debug1_clear(); - debug2_clear(); - debug3_clear(); - } - - // Clears all debug 1 instructions (excluding OpLine & OpNoLine). - void debug1_clear() { debugs1_.clear(); } - - // Clears all debug 2 instructions (excluding OpLine & OpNoLine). - void debug2_clear() { debugs2_.clear(); } - - // Clears all debug 3 instructions (excluding OpLine & OpNoLine). - void debug3_clear() { debugs3_.clear(); } - // Iterators for annotation instructions contained in this module. inline inst_iterator annotation_begin(); inline inst_iterator annotation_end(); @@ -261,6 +245,19 @@ class Module { // Gets the associated context for this module IRContext* context() const { return context_; } + // Sets the trailing debug line info to |dbg_line_info|. + void SetTrailingDbgLineInfo(std::vector&& dbg_line_info) { + trailing_dbg_line_info_ = std::move(dbg_line_info); + } + + std::vector& trailing_dbg_line_info() { + return trailing_dbg_line_info_; + } + + const std::vector& trailing_dbg_line_info() const { + return trailing_dbg_line_info_; + } + private: ModuleHeader header_; // Module header @@ -281,6 +278,10 @@ class Module { // Type declarations, constants, and global variable declarations. InstructionList types_values_; std::vector> functions_; + + // If the module ends with Op*Line instruction, they will not be attached to + // any instruction. We record them here, so they will not be lost. + std::vector trailing_dbg_line_info_; }; // Pretty-prints |module| to |str|. Returns |str|. diff --git a/3rdparty/spirv-tools/source/opt/strip_debug_info_pass.cpp b/3rdparty/spirv-tools/source/opt/strip_debug_info_pass.cpp index 5d9c5fec8..9e7fad0b2 100644 --- a/3rdparty/spirv-tools/source/opt/strip_debug_info_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/strip_debug_info_pass.cpp @@ -22,13 +22,34 @@ Pass::Status StripDebugInfoPass::Process() { bool modified = !context()->debugs1().empty() || !context()->debugs2().empty() || !context()->debugs3().empty(); - context()->debug_clear(); + + std::vector to_kill; + for (auto& dbg : context()->debugs1()) to_kill.push_back(&dbg); + for (auto& dbg : context()->debugs2()) to_kill.push_back(&dbg); + for (auto& dbg : context()->debugs3()) to_kill.push_back(&dbg); + + // OpName must come first, since they may refer to other debug instructions. + // If they are after the instructions that refer to, then they will be killed + // when that instruction is killed, which will lead to a double kill. + std::sort(to_kill.begin(), to_kill.end(), + [](Instruction* lhs, Instruction* rhs) -> bool { + if (lhs->opcode() == SpvOpName && rhs->opcode() != SpvOpName) + return true; + return false; + }); + + for (auto* inst : to_kill) context()->KillInst(inst); context()->module()->ForEachInst([&modified](Instruction* inst) { modified |= !inst->dbg_line_insts().empty(); inst->dbg_line_insts().clear(); }); + if (!get_module()->trailing_dbg_line_info().empty()) { + modified = true; + get_module()->trailing_dbg_line_info().clear(); + } + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; } diff --git a/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.cpp b/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.cpp index 152ded508..b16322c1f 100644 --- a/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.cpp +++ b/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.cpp @@ -45,6 +45,7 @@ void StructuredCFGAnalysis::AddBlocksInFunction(Function* func) { struct TraversalInfo { ConstructInfo cinfo; uint32_t merge_node; + uint32_t continue_node; }; // Set up a stack to keep track of currently active constructs. @@ -53,7 +54,9 @@ void StructuredCFGAnalysis::AddBlocksInFunction(Function* func) { state[0].cinfo.containing_construct = 0; state[0].cinfo.containing_loop = 0; state[0].cinfo.containing_switch = 0; + state[0].cinfo.in_continue = false; state[0].merge_node = 0; + state[0].continue_node = 0; for (BasicBlock* block : order) { if (context_->cfg()->IsPseudoEntryBlock(block) || @@ -65,6 +68,12 @@ void StructuredCFGAnalysis::AddBlocksInFunction(Function* func) { state.pop_back(); } + // This works because the structured order is designed to keep the blocks in + // the continue construct between the continue header and the merge node. + if (block->id() == state.back().continue_node) { + state.back().cinfo.in_continue = true; + } + bb_to_construct_.emplace(std::make_pair(block->id(), state.back().cinfo)); if (Instruction* merge_inst = block->GetMergeInst()) { @@ -76,8 +85,14 @@ void StructuredCFGAnalysis::AddBlocksInFunction(Function* func) { if (merge_inst->opcode() == SpvOpLoopMerge) { new_state.cinfo.containing_loop = block->id(); new_state.cinfo.containing_switch = 0; + new_state.cinfo.in_continue = false; + new_state.continue_node = + merge_inst->GetSingleWordInOperand(kContinueNodeIndex); } else { new_state.cinfo.containing_loop = state.back().cinfo.containing_loop; + new_state.cinfo.in_continue = state.back().cinfo.in_continue; + new_state.continue_node = state.back().continue_node; + if (merge_inst->NextNode()->opcode() == SpvOpSwitch) { new_state.cinfo.containing_switch = block->id(); } else { @@ -146,9 +161,59 @@ bool StructuredCFGAnalysis::IsContinueBlock(uint32_t bb_id) { return LoopContinueBlock(bb_id) == bb_id; } +bool StructuredCFGAnalysis::IsInContainingLoopsContinueConstruct( + uint32_t bb_id) { + auto it = bb_to_construct_.find(bb_id); + if (it == bb_to_construct_.end()) { + return false; + } + return it->second.in_continue; +} + +bool StructuredCFGAnalysis::IsInContinueConstruct(uint32_t bb_id) { + while (bb_id != 0) { + if (IsInContainingLoopsContinueConstruct(bb_id)) { + return true; + } + bb_id = ContainingLoop(bb_id); + } + return false; +} + bool StructuredCFGAnalysis::IsMergeBlock(uint32_t bb_id) { return merge_blocks_.Get(bb_id); } +std::unordered_set +StructuredCFGAnalysis::FindFuncsCalledFromContinue() { + std::unordered_set called_from_continue; + std::queue funcs_to_process; + + // First collect the functions that are called directly from a continue + // construct. + for (Function& func : *context_->module()) { + for (auto& bb : func) { + if (IsInContainingLoopsContinueConstruct(bb.id())) { + for (const Instruction& inst : bb) { + if (inst.opcode() == SpvOpFunctionCall) { + funcs_to_process.push(inst.GetSingleWordInOperand(0)); + } + } + } + } + } + + // Now collect all of the functions that are indirectly called as well. + while (!funcs_to_process.empty()) { + uint32_t func_id = funcs_to_process.front(); + funcs_to_process.pop(); + Function* func = context_->GetFunction(func_id); + if (called_from_continue.insert(func_id).second) { + context_->AddCalls(func, &funcs_to_process); + } + } + return called_from_continue; +} + } // namespace opt } // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.h b/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.h index f25266a8f..dfae6d4f2 100644 --- a/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.h +++ b/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.h @@ -16,6 +16,7 @@ #define SOURCE_OPT_STRUCT_CFG_ANALYSIS_H_ #include +#include #include "source/opt/function.h" #include "source/util/bit_vector.h" @@ -88,21 +89,46 @@ class StructuredCFGAnalysis { // if no such block exists. uint32_t SwitchMergeBlock(uint32_t bb_id); + // Returns true if |bb_id| is the continue block for a loop. bool IsContinueBlock(uint32_t bb_id); + + // Returns true if |bb_id| is in the continue construct for its inner most + // containing loop. + bool IsInContainingLoopsContinueConstruct(uint32_t bb_id); + + // Returns true if |bb_id| is in the continue construct for any loop in its + // function. + bool IsInContinueConstruct(uint32_t bb_id); + + // Return true if |bb_id| is the merge block for a construct. bool IsMergeBlock(uint32_t bb_id); + // Returns the set of function ids that are called directly or indirectly from + // a continue construct. + std::unordered_set FindFuncsCalledFromContinue(); + private: // Struct used to hold the information for a basic block. // |containing_construct| is the header for the innermost containing // construct, or 0 if no such construct exists. It could be a selection - // construct or a loop construct. |containing_loop| is the innermost - // containing loop construct, or 0 if the basic bloc is not in a loop. If the - // basic block is in a selection construct that is contained in a loop - // construct, then these two values will not be the same. + // construct or a loop construct. + // + // |containing_loop| is the innermost containing loop construct, or 0 if the + // basic bloc is not in a loop. If the basic block is in a selection + // construct that is contained in a loop construct, then these two values will + // not be the same. + // + // |containing_switch| is the innermost contain selection construct with an + // |OpSwitch| for the branch, as long as there is not intervening loop. This + // is used to identify the selection construct from which it can break. + // + // |in_continue| is true of the block is in the continue construct for its + // innermost containing loop. struct ConstructInfo { uint32_t containing_construct; uint32_t containing_loop; uint32_t containing_switch; + bool in_continue; }; // Populates |bb_to_construct_| with the innermost containing merge and loop diff --git a/3rdparty/spirv-tools/source/opt/wrap_opkill.cpp b/3rdparty/spirv-tools/source/opt/wrap_opkill.cpp index 76714d630..ffd7a1050 100644 --- a/3rdparty/spirv-tools/source/opt/wrap_opkill.cpp +++ b/3rdparty/spirv-tools/source/opt/wrap_opkill.cpp @@ -22,8 +22,11 @@ namespace opt { Pass::Status WrapOpKill::Process() { bool modified = false; - for (auto& func : *get_module()) { - bool successful = func.WhileEachInst([this, &modified](Instruction* inst) { + auto func_to_process = + context()->GetStructuredCFGAnalysis()->FindFuncsCalledFromContinue(); + for (uint32_t func_id : func_to_process) { + Function* func = context()->GetFunction(func_id); + bool successful = func->WhileEachInst([this, &modified](Instruction* inst) { if (inst->opcode() == SpvOpKill) { modified = true; if (!ReplaceWithFunctionCall(inst)) { diff --git a/3rdparty/spirv-tools/source/val/validate_constants.cpp b/3rdparty/spirv-tools/source/val/validate_constants.cpp index 95ec1680d..dea95c8a2 100644 --- a/3rdparty/spirv-tools/source/val/validate_constants.cpp +++ b/3rdparty/spirv-tools/source/val/validate_constants.cpp @@ -304,7 +304,6 @@ bool IsTypeNullable(const std::vector& instruction, case SpvOpTypeBool: case SpvOpTypeInt: case SpvOpTypeFloat: - case SpvOpTypePointer: case SpvOpTypeEvent: case SpvOpTypeDeviceEvent: case SpvOpTypeReserveId: @@ -325,6 +324,11 @@ bool IsTypeNullable(const std::vector& instruction, } return true; } + case SpvOpTypePointer: + if (instruction[2] == SpvStorageClassPhysicalStorageBuffer) { + return false; + } + return true; default: return false; } diff --git a/3rdparty/spirv-tools/source/val/validate_instruction.cpp b/3rdparty/spirv-tools/source/val/validate_instruction.cpp index fecc35199..6478b3cb6 100644 --- a/3rdparty/spirv-tools/source/val/validate_instruction.cpp +++ b/3rdparty/spirv-tools/source/val/validate_instruction.cpp @@ -55,18 +55,6 @@ std::string ToString(const CapabilitySet& capabilities, return ss.str(); } -bool IsValidWebGPUStorageClass(SpvStorageClass storage_class) { - return storage_class == SpvStorageClassUniformConstant || - storage_class == SpvStorageClassUniform || - storage_class == SpvStorageClassStorageBuffer || - storage_class == SpvStorageClassInput || - storage_class == SpvStorageClassOutput || - storage_class == SpvStorageClassImage || - storage_class == SpvStorageClassWorkgroup || - storage_class == SpvStorageClassPrivate || - storage_class == SpvStorageClassFunction; -} - // Returns capabilities that enable an opcode. An empty result is interpreted // as no prohibition of use of the opcode. If the result is non-empty, then // the opcode may only be used if at least one of the capabilities is specified @@ -249,23 +237,6 @@ spv_result_t ReservedCheck(ValidationState_t& _, const Instruction* inst) { return SPV_SUCCESS; } -// Returns SPV_ERROR_INVALID_BINARY and emits a diagnostic if the instruction -// is invalid because of an execution environment constraint. -spv_result_t EnvironmentCheck(ValidationState_t& _, const Instruction* inst) { - const SpvOp opcode = inst->opcode(); - switch (opcode) { - case SpvOpUndef: - if (_.features().bans_op_undef) { - return _.diag(SPV_ERROR_INVALID_BINARY, inst) - << "OpUndef is disallowed"; - } - break; - default: - break; - } - return SPV_SUCCESS; -} - // Returns SPV_ERROR_INVALID_CAPABILITY and emits a diagnostic if the // instruction is invalid because the required capability isn't declared // in the module. @@ -499,38 +470,6 @@ spv_result_t InstructionPass(ValidationState_t& _, const Instruction* inst) { } _.set_addressing_model(inst->GetOperandAs(0)); _.set_memory_model(inst->GetOperandAs(1)); - - if (_.memory_model() != SpvMemoryModelVulkanKHR && - _.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) { - return _.diag(SPV_ERROR_INVALID_DATA, inst) - << "VulkanMemoryModelKHR capability must only be specified if " - "the " - "VulkanKHR memory model is used."; - } - - if (spvIsWebGPUEnv(_.context()->target_env)) { - if (_.addressing_model() != SpvAddressingModelLogical) { - return _.diag(SPV_ERROR_INVALID_DATA, inst) - << "Addressing model must be Logical for WebGPU environment."; - } - if (_.memory_model() != SpvMemoryModelVulkanKHR) { - return _.diag(SPV_ERROR_INVALID_DATA, inst) - << "Memory model must be VulkanKHR for WebGPU environment."; - } - } - - if (spvIsOpenCLEnv(_.context()->target_env)) { - if ((_.addressing_model() != SpvAddressingModelPhysical32) && - (_.addressing_model() != SpvAddressingModelPhysical64)) { - return _.diag(SPV_ERROR_INVALID_DATA, inst) - << "Addressing model must be Physical32 or Physical64 " - << "in the OpenCL environment."; - } - if (_.memory_model() != SpvMemoryModelOpenCL) { - return _.diag(SPV_ERROR_INVALID_DATA, inst) - << "Memory model must be OpenCL in the OpenCL environment."; - } - } } else if (opcode == SpvOpExecutionMode) { const uint32_t entry_point = inst->word(1); _.RegisterExecutionModeForEntryPoint(entry_point, @@ -540,61 +479,9 @@ spv_result_t InstructionPass(ValidationState_t& _, const Instruction* inst) { if (auto error = LimitCheckNumVars(_, inst->id(), storage_class)) { return error; } - - if (spvIsWebGPUEnv(_.context()->target_env) && - !IsValidWebGPUStorageClass(storage_class)) { - return _.diag(SPV_ERROR_INVALID_BINARY, inst) - << "For WebGPU, OpVariable storage class must be one of " - "UniformConstant, Uniform, StorageBuffer, Input, Output, " - "Image, Workgroup, Private, Function for WebGPU"; - } - - if (storage_class == SpvStorageClassGeneric) - return _.diag(SPV_ERROR_INVALID_BINARY, inst) - << "OpVariable storage class cannot be Generic"; - if (_.current_layout_section() == kLayoutFunctionDefinitions) { - if (storage_class != SpvStorageClassFunction) { - return _.diag(SPV_ERROR_INVALID_LAYOUT, inst) - << "Variables must have a function[7] storage class inside" - " of a function"; - } - if (_.current_function().IsFirstBlock( - _.current_function().current_block()->id()) == false) { - return _.diag(SPV_ERROR_INVALID_CFG, inst) - << "Variables can only be defined " - "in the first block of a " - "function"; - } - } else { - if (storage_class == SpvStorageClassFunction) { - return _.diag(SPV_ERROR_INVALID_LAYOUT, inst) - << "Variables can not have a function[7] storage class " - "outside of a function"; - } - } - } else if (opcode == SpvOpTypePointer) { - const auto storage_class = inst->GetOperandAs(1); - if (spvIsWebGPUEnv(_.context()->target_env) && - !IsValidWebGPUStorageClass(storage_class)) { - return _.diag(SPV_ERROR_INVALID_BINARY, inst) - << "For WebGPU, OpTypePointer storage class must be one of " - "UniformConstant, Uniform, StorageBuffer, Input, Output, " - "Image, Workgroup, Private, Function"; - } - } - - // SPIR-V Spec 2.16.3: Validation Rules for Kernel Capabilities: The - // Signedness in OpTypeInt must always be 0. - if (SpvOpTypeInt == inst->opcode() && _.HasCapability(SpvCapabilityKernel) && - inst->GetOperandAs(2) != 0u) { - return _.diag(SPV_ERROR_INVALID_BINARY, inst) - << "The Signedness in OpTypeInt " - "must always be 0 when Kernel " - "capability is used."; } if (auto error = ReservedCheck(_, inst)) return error; - if (auto error = EnvironmentCheck(_, inst)) return error; if (auto error = CapabilityCheck(_, inst)) return error; if (auto error = LimitCheckIdBound(_, inst)) return error; if (auto error = LimitCheckStruct(_, inst)) return error; diff --git a/3rdparty/spirv-tools/source/val/validate_memory.cpp b/3rdparty/spirv-tools/source/val/validate_memory.cpp index 8e220971c..bff8b20ee 100644 --- a/3rdparty/spirv-tools/source/val/validate_memory.cpp +++ b/3rdparty/spirv-tools/source/val/validate_memory.cpp @@ -461,6 +461,28 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) { } } + if (!_.IsValidStorageClass(storage_class)) { + return _.diag(SPV_ERROR_INVALID_BINARY, inst) + << "Invalid storage class for target environment"; + } + + if (storage_class == SpvStorageClassGeneric) { + return _.diag(SPV_ERROR_INVALID_BINARY, inst) + << "OpVariable storage class cannot be Generic"; + } + + if (inst->function() && storage_class != SpvStorageClassFunction) { + return _.diag(SPV_ERROR_INVALID_LAYOUT, inst) + << "Variables must have a function[7] storage class inside" + " of a function"; + } + + if (!inst->function() && storage_class == SpvStorageClassFunction) { + return _.diag(SPV_ERROR_INVALID_LAYOUT, inst) + << "Variables can not have a function[7] storage class " + "outside of a function"; + } + // SPIR-V 3.32.8: Check that pointer type and variable type have the same // storage class. const auto result_storage_class_index = 1; @@ -1568,8 +1590,8 @@ spv_result_t ValidatePtrComparison(ValidationState_t& _, << "Operand type must be a pointer"; } + SpvStorageClass sc = op1_type->GetOperandAs(1u); if (_.addressing_model() == SpvAddressingModelLogical) { - SpvStorageClass sc = op1_type->GetOperandAs(1u); if (sc != SpvStorageClassWorkgroup && sc != SpvStorageClassStorageBuffer) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "Invalid pointer storage class"; @@ -1580,6 +1602,9 @@ spv_result_t ValidatePtrComparison(ValidationState_t& _, << "Workgroup storage class pointer requires VariablePointers " "capability to be specified"; } + } else if (sc == SpvStorageClassPhysicalStorageBuffer) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Cannot use a pointer in the PhysicalStorageBuffer storage class"; } return SPV_SUCCESS; diff --git a/3rdparty/spirv-tools/source/val/validate_misc.cpp b/3rdparty/spirv-tools/source/val/validate_misc.cpp index 28e3fc684..7542f32c5 100644 --- a/3rdparty/spirv-tools/source/val/validate_misc.cpp +++ b/3rdparty/spirv-tools/source/val/validate_misc.cpp @@ -18,6 +18,7 @@ #include "source/opcode.h" #include "source/spirv_target_env.h" #include "source/val/instruction.h" +#include "source/val/validate_scopes.h" #include "source/val/validation_state.h" namespace spvtools { @@ -32,6 +33,34 @@ spv_result_t ValidateUndef(ValidationState_t& _, const Instruction* inst) { << "Cannot create undefined values with 8- or 16-bit types"; } + if (spvIsWebGPUEnv(_.context()->target_env)) { + return _.diag(SPV_ERROR_INVALID_BINARY, inst) << "OpUndef is disallowed"; + } + + return SPV_SUCCESS; +} + +spv_result_t ValidateShaderClock(ValidationState_t& _, + const Instruction* inst) { + const uint32_t execution_scope = inst->word(3); + if (auto error = ValidateExecutionScope(_, inst, execution_scope)) { + return error; + } + + // Result Type must be a 64 - bit unsigned integer type or + // a vector of two - components of 32 - + // bit unsigned integer type + const uint32_t result_type = inst->type_id(); + if (!(_.IsUnsignedIntScalarType(result_type) && + _.GetBitWidth(result_type) == 64) && + !(_.IsUnsignedIntVectorType(result_type) && + _.GetDimension(result_type) == 2 && _.GetBitWidth(result_type) == 32)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Expected Value to be a " + "vector of two components" + " of unsigned integer" + " or 64bit unsigned integer"; + } + return SPV_SUCCESS; } @@ -110,6 +139,11 @@ spv_result_t MiscPass(ValidationState_t& _, const Instruction* inst) { << spvOpcodeString(inst->opcode()); break; } + case SpvOpReadClockKHR: + if (auto error = ValidateShaderClock(_, inst)) { + return error; + } + break; default: break; } diff --git a/3rdparty/spirv-tools/source/val/validate_mode_setting.cpp b/3rdparty/spirv-tools/source/val/validate_mode_setting.cpp index cbcf11a3f..e020f5ae6 100644 --- a/3rdparty/spirv-tools/source/val/validate_mode_setting.cpp +++ b/3rdparty/spirv-tools/source/val/validate_mode_setting.cpp @@ -485,6 +485,44 @@ spv_result_t ValidateExecutionMode(ValidationState_t& _, return SPV_SUCCESS; } +spv_result_t ValidateMemoryModel(ValidationState_t& _, + const Instruction* inst) { + // Already produced an error if multiple memory model instructions are + // present. + if (_.memory_model() != SpvMemoryModelVulkanKHR && + _.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "VulkanMemoryModelKHR capability must only be specified if " + "the VulkanKHR memory model is used."; + } + + if (spvIsWebGPUEnv(_.context()->target_env)) { + if (_.addressing_model() != SpvAddressingModelLogical) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Addressing model must be Logical for WebGPU environment."; + } + if (_.memory_model() != SpvMemoryModelVulkanKHR) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Memory model must be VulkanKHR for WebGPU environment."; + } + } + + if (spvIsOpenCLEnv(_.context()->target_env)) { + if ((_.addressing_model() != SpvAddressingModelPhysical32) && + (_.addressing_model() != SpvAddressingModelPhysical64)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Addressing model must be Physical32 or Physical64 " + << "in the OpenCL environment."; + } + if (_.memory_model() != SpvMemoryModelOpenCL) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Memory model must be OpenCL in the OpenCL environment."; + } + } + + return SPV_SUCCESS; +} + } // namespace spv_result_t ModeSettingPass(ValidationState_t& _, const Instruction* inst) { @@ -496,6 +534,9 @@ spv_result_t ModeSettingPass(ValidationState_t& _, const Instruction* inst) { case SpvOpExecutionModeId: if (auto error = ValidateExecutionMode(_, inst)) return error; break; + case SpvOpMemoryModel: + if (auto error = ValidateMemoryModel(_, inst)) return error; + break; default: break; } diff --git a/3rdparty/spirv-tools/source/val/validate_type.cpp b/3rdparty/spirv-tools/source/val/validate_type.cpp index d3872da78..4d673b4a7 100644 --- a/3rdparty/spirv-tools/source/val/validate_type.cpp +++ b/3rdparty/spirv-tools/source/val/validate_type.cpp @@ -106,6 +106,17 @@ spv_result_t ValidateTypeInt(ValidationState_t& _, const Instruction* inst) { return _.diag(SPV_ERROR_INVALID_VALUE, inst) << "OpTypeInt has invalid signedness:"; } + + // SPIR-V Spec 2.16.3: Validation Rules for Kernel Capabilities: The + // Signedness in OpTypeInt must always be 0. + if (SpvOpTypeInt == inst->opcode() && _.HasCapability(SpvCapabilityKernel) && + inst->GetOperandAs(2) != 0u) { + return _.diag(SPV_ERROR_INVALID_BINARY, inst) + << "The Signedness in OpTypeInt " + "must always be 0 when Kernel " + "capability is used."; + } + return SPV_SUCCESS; } @@ -445,6 +456,12 @@ spv_result_t ValidateTypePointer(ValidationState_t& _, if (sampled == 2) _.RegisterPointerToStorageImage(inst->id()); } } + + if (!_.IsValidStorageClass(storage_class)) { + return _.diag(SPV_ERROR_INVALID_BINARY, inst) + << "Invalid storage class for target environment"; + } + return SPV_SUCCESS; } diff --git a/3rdparty/spirv-tools/source/val/validation_state.cpp b/3rdparty/spirv-tools/source/val/validation_state.cpp index 794d0f7b8..20eaf88e4 100644 --- a/3rdparty/spirv-tools/source/val/validation_state.cpp +++ b/3rdparty/spirv-tools/source/val/validation_state.cpp @@ -212,14 +212,6 @@ ValidationState_t::ValidationState_t(const spv_const_context ctx, } } - switch (env) { - case SPV_ENV_WEBGPU_0: - features_.bans_op_undef = true; - break; - default: - break; - } - // Only attempt to count if we have words, otherwise let the other validation // fail and generate an error. if (num_words > 0) { @@ -1277,5 +1269,52 @@ bool ValidationState_t::ContainsLimitedUseIntOrFloatType(uint32_t id) const { return false; } +bool ValidationState_t::IsValidStorageClass( + SpvStorageClass storage_class) const { + if (spvIsWebGPUEnv(context()->target_env)) { + switch (storage_class) { + case SpvStorageClassUniformConstant: + case SpvStorageClassUniform: + case SpvStorageClassStorageBuffer: + case SpvStorageClassInput: + case SpvStorageClassOutput: + case SpvStorageClassImage: + case SpvStorageClassWorkgroup: + case SpvStorageClassPrivate: + case SpvStorageClassFunction: + return true; + default: + return false; + } + } + + if (spvIsVulkanEnv(context()->target_env)) { + switch (storage_class) { + case SpvStorageClassUniformConstant: + case SpvStorageClassUniform: + case SpvStorageClassStorageBuffer: + case SpvStorageClassInput: + case SpvStorageClassOutput: + case SpvStorageClassImage: + case SpvStorageClassWorkgroup: + case SpvStorageClassPrivate: + case SpvStorageClassFunction: + case SpvStorageClassPushConstant: + case SpvStorageClassPhysicalStorageBuffer: + case SpvStorageClassRayPayloadNV: + case SpvStorageClassIncomingRayPayloadNV: + case SpvStorageClassHitAttributeNV: + case SpvStorageClassCallableDataNV: + case SpvStorageClassIncomingCallableDataNV: + case SpvStorageClassShaderRecordBufferNV: + return true; + default: + return false; + } + } + + return true; +} + } // namespace val } // namespace spvtools diff --git a/3rdparty/spirv-tools/source/val/validation_state.h b/3rdparty/spirv-tools/source/val/validation_state.h index e650d2ea0..e5d31acf6 100644 --- a/3rdparty/spirv-tools/source/val/validation_state.h +++ b/3rdparty/spirv-tools/source/val/validation_state.h @@ -79,9 +79,6 @@ class ValidationState_t { // Permit group oerations Reduce, InclusiveScan, ExclusiveScan bool group_ops_reduce_and_scans = false; - // Disallows the use of OpUndef - bool bans_op_undef = false; - // Allow OpTypeInt with 8 bit width? bool declare_int8_type = false; @@ -707,6 +704,9 @@ class ValidationState_t { // * OpCopyObject const Instruction* TracePointer(const Instruction* inst) const; + // Validates the storage class for the target environment. + bool IsValidStorageClass(SpvStorageClass storage_class) const; + private: ValidationState_t(const ValidationState_t&); diff --git a/3rdparty/spirv-tools/test/opt/inline_test.cpp b/3rdparty/spirv-tools/test/opt/inline_test.cpp index 1fb0e7210..fac49ca6a 100644 --- a/3rdparty/spirv-tools/test/opt/inline_test.cpp +++ b/3rdparty/spirv-tools/test/opt/inline_test.cpp @@ -2936,7 +2936,7 @@ OpFunctionEnd SinglePassRunAndCheck(test, test, false, true); } -TEST_F(InlineTest, DontInlineFuncWithOpKill) { +TEST_F(InlineTest, DontInlineFuncWithOpKillInContinue) { const std::string test = R"(OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" @@ -2976,6 +2976,60 @@ OpFunctionEnd SinglePassRunAndCheck(test, test, false, true); } +TEST_F(InlineTest, InlineFuncWithOpKillNotInContinue) { + const std::string before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 330 +OpName %main "main" +OpName %kill_ "kill(" +%void = OpTypeVoid +%3 = OpTypeFunction %void +%bool = OpTypeBool +%true = OpConstantTrue %bool +%main = OpFunction %void None %3 +%5 = OpLabel +%16 = OpFunctionCall %void %kill_ +OpReturn +OpFunctionEnd +%kill_ = OpFunction %void None %3 +%7 = OpLabel +OpKill +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 330 +OpName %main "main" +OpName %kill_ "kill(" +%void = OpTypeVoid +%3 = OpTypeFunction %void +%bool = OpTypeBool +%true = OpConstantTrue %bool +%main = OpFunction %void None %3 +%5 = OpLabel +OpKill +%17 = OpLabel +OpReturn +OpFunctionEnd +%kill_ = OpFunction %void None %3 +%7 = OpLabel +OpKill +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(before, after, false, true); +} + // TODO(greg-lunarg): Add tests to verify handling of these cases: // // Empty modules diff --git a/3rdparty/spirv-tools/test/opt/inst_buff_addr_check_test.cpp b/3rdparty/spirv-tools/test/opt/inst_buff_addr_check_test.cpp index 837c8166b..f8448a98f 100644 --- a/3rdparty/spirv-tools/test/opt/inst_buff_addr_check_test.cpp +++ b/3rdparty/spirv-tools/test/opt/inst_buff_addr_check_test.cpp @@ -464,7 +464,7 @@ OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_blockType PhysicalStorageBuffer %uint_7 = OpConstant %uint 7 %uint_9 = OpConstant %uint 9 %uint_44 = OpConstant %uint 44 -%132 = OpConstantNull %_ptr_PhysicalStorageBuffer_blockType +%132 = OpConstantNull %ulong %uint_46 = OpConstant %uint 46 )"; @@ -499,24 +499,25 @@ OpBranch %68 %74 = OpShiftRightLogical %ulong %30 %uint_32 %75 = OpUConvert %uint %74 %131 = OpFunctionCall %void %76 %uint_44 %uint_2 %72 %75 +%133 = OpConvertUToPtr %_ptr_PhysicalStorageBuffer_blockType %132 OpBranch %68 %68 = OpLabel -%133 = OpPhi %_ptr_PhysicalStorageBuffer_blockType %71 %69 %132 %70 -%26 = OpAccessChain %_ptr_PhysicalStorageBuffer_int %133 %int_0 -%134 = OpConvertPtrToU %ulong %26 -%135 = OpFunctionCall %bool %32 %134 %uint_4 -OpSelectionMerge %136 None -OpBranchConditional %135 %137 %138 -%137 = OpLabel -OpStore %26 %int_531 Aligned 16 -OpBranch %136 +%134 = OpPhi %_ptr_PhysicalStorageBuffer_blockType %71 %69 %133 %70 +%26 = OpAccessChain %_ptr_PhysicalStorageBuffer_int %134 %int_0 +%135 = OpConvertPtrToU %ulong %26 +%136 = OpFunctionCall %bool %32 %135 %uint_4 +OpSelectionMerge %137 None +OpBranchConditional %136 %138 %139 %138 = OpLabel -%139 = OpUConvert %uint %134 -%140 = OpShiftRightLogical %ulong %134 %uint_32 -%141 = OpUConvert %uint %140 -%143 = OpFunctionCall %void %76 %uint_46 %uint_2 %139 %141 -OpBranch %136 -%136 = OpLabel +OpStore %26 %int_531 Aligned 16 +OpBranch %137 +%139 = OpLabel +%140 = OpUConvert %uint %135 +%141 = OpShiftRightLogical %ulong %135 %uint_32 +%142 = OpUConvert %uint %141 +%144 = OpFunctionCall %void %76 %uint_46 %uint_2 %140 %142 +OpBranch %137 +%137 = OpLabel OpReturn OpFunctionEnd )"; diff --git a/3rdparty/spirv-tools/test/opt/module_test.cpp b/3rdparty/spirv-tools/test/opt/module_test.cpp index 569cf9bcd..406da0939 100644 --- a/3rdparty/spirv-tools/test/opt/module_test.cpp +++ b/3rdparty/spirv-tools/test/opt/module_test.cpp @@ -21,6 +21,7 @@ #include "gtest/gtest.h" #include "source/opt/build_module.h" #include "source/opt/module.h" +#include "source/opt/pass.h" #include "spirv-tools/libspirv.hpp" #include "test/opt/module_utils.h" @@ -228,6 +229,72 @@ OpFunctionEnd)"; EXPECT_EQ(next_id_bound, 0); EXPECT_EQ(current_bound, context->module()->id_bound()); } + +// Tests that "text" does not change when it is assembled, converted into a +// module, converted back to a binary, and then disassembled. +void AssembleAndDisassemble(const std::string& text) { + std::unique_ptr context = BuildModule(text); + std::vector binary; + + context->module()->ToBinary(&binary, false); + + SpirvTools tools(SPV_ENV_UNIVERSAL_1_1); + std::string s; + tools.Disassemble(binary, &s); + EXPECT_EQ(s, text); +} + +TEST(ModuleTest, TrailingOpLine) { + const std::string text = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%5 = OpString "file.ext" +%void = OpTypeVoid +%2 = OpTypeFunction %void +%3 = OpFunction %void None %2 +%4 = OpLabel +OpReturn +OpFunctionEnd +OpLine %5 1 0 +)"; + + AssembleAndDisassemble(text); +} + +TEST(ModuleTest, TrailingOpNoLine) { + const std::string text = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%void = OpTypeVoid +%2 = OpTypeFunction %void +%3 = OpFunction %void None %2 +%4 = OpLabel +OpReturn +OpFunctionEnd +OpNoLine +)"; + + AssembleAndDisassemble(text); +} + +TEST(ModuleTest, MulitpleTrailingOpLine) { + const std::string text = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%5 = OpString "file.ext" +%void = OpTypeVoid +%2 = OpTypeFunction %void +%3 = OpFunction %void None %2 +%4 = OpLabel +OpReturn +OpFunctionEnd +OpLine %5 1 0 +OpNoLine +OpLine %5 1 1 +)"; + + AssembleAndDisassemble(text); +} } // namespace } // namespace opt } // namespace spvtools diff --git a/3rdparty/spirv-tools/test/opt/strip_debug_info_test.cpp b/3rdparty/spirv-tools/test/opt/strip_debug_info_test.cpp index 25cf7d8ac..2f2ff4670 100644 --- a/3rdparty/spirv-tools/test/opt/strip_debug_info_test.cpp +++ b/3rdparty/spirv-tools/test/opt/strip_debug_info_test.cpp @@ -51,6 +51,8 @@ TEST_F(StripLineDebugInfoTest, LineNoLine) { "OpLine %3 4 4", "OpNoLine", "OpFunctionEnd", + "OpNoLine", + "OpLine %3 4 5" // clang-format on }; SinglePassRunAndCheck(JoinAllInsts(text), @@ -74,6 +76,82 @@ TEST_F(StripLineDebugInfoTest, LineNoLine) { /* skip_nop = */ false); } +using StripDebugStringTest = PassTest<::testing::Test>; + +TEST_F(StripDebugStringTest, OpDecorateRemoved) { + std::vector input{ + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %2 \"main\"", + "%3 = OpString \"minimal.vert\"", + "OpDecorate %3 Location 1337", + "%void = OpTypeVoid", + "%5 = OpTypeFunction %void", + "%2 = OpFunction %void None %5", + "%6 = OpLabel", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + std::vector output{ + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %2 \"main\"", + "%void = OpTypeVoid", + "%5 = OpTypeFunction %void", + "%2 = OpFunction %void None %5", + "%6 = OpLabel", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + SinglePassRunAndCheck(JoinAllInsts(input), + JoinAllInsts(output), + /* skip_nop = */ false, + /* do_validation */ true); +} + +TEST_F(StripDebugStringTest, OpNameRemoved) { + std::vector input{ + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %2 \"main\"", + "%3 = OpString \"minimal.vert\"", + "OpName %3 \"bob\"", + "%void = OpTypeVoid", + "%5 = OpTypeFunction %void", + "%2 = OpFunction %void None %5", + "%6 = OpLabel", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + std::vector output{ + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %2 \"main\"", + "%void = OpTypeVoid", + "%5 = OpTypeFunction %void", + "%2 = OpFunction %void None %5", + "%6 = OpLabel", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + SinglePassRunAndCheck(JoinAllInsts(input), + JoinAllInsts(output), + /* skip_nop = */ false, + /* do_validation */ true); +} + using StripDebugInfoTest = PassTest<::testing::TestWithParam>; TEST_P(StripDebugInfoTest, Kind) { diff --git a/3rdparty/spirv-tools/test/opt/struct_cfg_analysis_test.cpp b/3rdparty/spirv-tools/test/opt/struct_cfg_analysis_test.cpp index 7fb378432..0451a8b46 100644 --- a/3rdparty/spirv-tools/test/opt/struct_cfg_analysis_test.cpp +++ b/3rdparty/spirv-tools/test/opt/struct_cfg_analysis_test.cpp @@ -26,6 +26,7 @@ namespace opt { namespace { using StructCFGAnalysisTest = PassTest<::testing::Test>; +using ::testing::UnorderedElementsAre; TEST_F(StructCFGAnalysisTest, BBInSelection) { const std::string text = R"( @@ -62,6 +63,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); + EXPECT_FALSE(analysis.IsContinueBlock(1)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(1)); + EXPECT_FALSE(analysis.IsInContinueConstruct(1)); + EXPECT_FALSE(analysis.IsMergeBlock(1)); // BB2 is in the construct. EXPECT_EQ(analysis.ContainingConstruct(2), 1); @@ -70,6 +75,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(2), 0); EXPECT_EQ(analysis.ContainingSwitch(2), 0); EXPECT_EQ(analysis.SwitchMergeBlock(2), 0); + EXPECT_FALSE(analysis.IsContinueBlock(2)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(2)); + EXPECT_FALSE(analysis.IsInContinueConstruct(2)); + EXPECT_FALSE(analysis.IsMergeBlock(2)); // The merge node is not in the construct. EXPECT_EQ(analysis.ContainingConstruct(3), 0); @@ -78,6 +87,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); + EXPECT_FALSE(analysis.IsContinueBlock(3)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(3)); + EXPECT_FALSE(analysis.IsInContinueConstruct(3)); + EXPECT_TRUE(analysis.IsMergeBlock(3)); } TEST_F(StructCFGAnalysisTest, BBInLoop) { @@ -119,6 +132,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); + EXPECT_FALSE(analysis.IsContinueBlock(1)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(1)); + EXPECT_FALSE(analysis.IsInContinueConstruct(1)); + EXPECT_FALSE(analysis.IsMergeBlock(1)); // BB2 is in the construct. EXPECT_EQ(analysis.ContainingConstruct(2), 1); @@ -127,6 +144,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(2), 3); EXPECT_EQ(analysis.ContainingSwitch(2), 0); EXPECT_EQ(analysis.SwitchMergeBlock(2), 0); + EXPECT_FALSE(analysis.IsContinueBlock(2)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(2)); + EXPECT_FALSE(analysis.IsInContinueConstruct(2)); + EXPECT_FALSE(analysis.IsMergeBlock(2)); // The merge node is not in the construct. EXPECT_EQ(analysis.ContainingConstruct(3), 0); @@ -135,6 +156,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); + EXPECT_FALSE(analysis.IsContinueBlock(3)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(3)); + EXPECT_FALSE(analysis.IsInContinueConstruct(3)); + EXPECT_TRUE(analysis.IsMergeBlock(3)); // The continue block is in the construct. EXPECT_EQ(analysis.ContainingConstruct(4), 1); @@ -143,6 +168,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(4), 3); EXPECT_EQ(analysis.ContainingSwitch(4), 0); EXPECT_EQ(analysis.SwitchMergeBlock(4), 0); + EXPECT_TRUE(analysis.IsContinueBlock(4)); + EXPECT_TRUE(analysis.IsInContainingLoopsContinueConstruct(4)); + EXPECT_TRUE(analysis.IsInContinueConstruct(4)); + EXPECT_FALSE(analysis.IsMergeBlock(4)); } TEST_F(StructCFGAnalysisTest, SelectionInLoop) { @@ -189,6 +218,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); + EXPECT_FALSE(analysis.IsContinueBlock(1)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(1)); + EXPECT_FALSE(analysis.IsInContinueConstruct(1)); + EXPECT_FALSE(analysis.IsMergeBlock(1)); // Selection header is in the loop only. EXPECT_EQ(analysis.ContainingConstruct(2), 1); @@ -197,6 +230,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(2), 3); EXPECT_EQ(analysis.ContainingSwitch(2), 0); EXPECT_EQ(analysis.SwitchMergeBlock(2), 0); + EXPECT_FALSE(analysis.IsContinueBlock(2)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(2)); + EXPECT_FALSE(analysis.IsInContinueConstruct(2)); + EXPECT_FALSE(analysis.IsMergeBlock(2)); // The loop merge node is not in either construct. EXPECT_EQ(analysis.ContainingConstruct(3), 0); @@ -205,6 +242,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); + EXPECT_FALSE(analysis.IsContinueBlock(3)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(3)); + EXPECT_FALSE(analysis.IsInContinueConstruct(3)); + EXPECT_TRUE(analysis.IsMergeBlock(3)); // The continue block is in the loop only. EXPECT_EQ(analysis.ContainingConstruct(4), 1); @@ -213,14 +254,22 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(4), 3); EXPECT_EQ(analysis.ContainingSwitch(4), 0); EXPECT_EQ(analysis.SwitchMergeBlock(4), 0); + EXPECT_TRUE(analysis.IsContinueBlock(4)); + EXPECT_TRUE(analysis.IsInContainingLoopsContinueConstruct(4)); + EXPECT_TRUE(analysis.IsInContinueConstruct(4)); + EXPECT_FALSE(analysis.IsMergeBlock(4)); - // BB5 is in the selection fist and the loop. + // BB5 is in the selection and the loop. EXPECT_EQ(analysis.ContainingConstruct(5), 2); EXPECT_EQ(analysis.ContainingLoop(5), 1); EXPECT_EQ(analysis.MergeBlock(5), 6); EXPECT_EQ(analysis.LoopMergeBlock(5), 3); EXPECT_EQ(analysis.ContainingSwitch(5), 0); EXPECT_EQ(analysis.SwitchMergeBlock(5), 0); + EXPECT_FALSE(analysis.IsContinueBlock(5)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(5)); + EXPECT_FALSE(analysis.IsInContinueConstruct(5)); + EXPECT_FALSE(analysis.IsMergeBlock(5)); // The selection merge is in the loop only. EXPECT_EQ(analysis.ContainingConstruct(6), 1); @@ -229,6 +278,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(6), 3); EXPECT_EQ(analysis.ContainingSwitch(6), 0); EXPECT_EQ(analysis.SwitchMergeBlock(6), 0); + EXPECT_FALSE(analysis.IsContinueBlock(6)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(6)); + EXPECT_FALSE(analysis.IsInContinueConstruct(6)); + EXPECT_TRUE(analysis.IsMergeBlock(6)); } TEST_F(StructCFGAnalysisTest, LoopInSelection) { @@ -275,6 +328,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); + EXPECT_FALSE(analysis.IsContinueBlock(1)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(1)); + EXPECT_FALSE(analysis.IsInContinueConstruct(1)); + EXPECT_FALSE(analysis.IsMergeBlock(1)); // Loop header is in the selection only. EXPECT_EQ(analysis.ContainingConstruct(2), 1); @@ -283,6 +340,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(2), 0); EXPECT_EQ(analysis.ContainingSwitch(2), 0); EXPECT_EQ(analysis.SwitchMergeBlock(2), 0); + EXPECT_FALSE(analysis.IsContinueBlock(2)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(2)); + EXPECT_FALSE(analysis.IsInContinueConstruct(2)); + EXPECT_FALSE(analysis.IsMergeBlock(2)); // The selection merge node is not in either construct. EXPECT_EQ(analysis.ContainingConstruct(3), 0); @@ -291,6 +352,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); + EXPECT_FALSE(analysis.IsContinueBlock(3)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(3)); + EXPECT_FALSE(analysis.IsInContinueConstruct(3)); + EXPECT_TRUE(analysis.IsMergeBlock(3)); // The loop merge is in the selection only. EXPECT_EQ(analysis.ContainingConstruct(4), 1); @@ -299,6 +364,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(4), 0); EXPECT_EQ(analysis.ContainingSwitch(4), 0); EXPECT_EQ(analysis.SwitchMergeBlock(4), 0); + EXPECT_FALSE(analysis.IsContinueBlock(4)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(4)); + EXPECT_FALSE(analysis.IsInContinueConstruct(4)); + EXPECT_TRUE(analysis.IsMergeBlock(4)); // The loop continue target is in the loop. EXPECT_EQ(analysis.ContainingConstruct(5), 2); @@ -307,6 +376,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(5), 4); EXPECT_EQ(analysis.ContainingSwitch(5), 0); EXPECT_EQ(analysis.SwitchMergeBlock(5), 0); + EXPECT_TRUE(analysis.IsContinueBlock(5)); + EXPECT_TRUE(analysis.IsInContainingLoopsContinueConstruct(5)); + EXPECT_TRUE(analysis.IsInContinueConstruct(5)); + EXPECT_FALSE(analysis.IsMergeBlock(5)); // BB6 is in the loop. EXPECT_EQ(analysis.ContainingConstruct(6), 2); @@ -315,6 +388,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(6), 4); EXPECT_EQ(analysis.ContainingSwitch(6), 0); EXPECT_EQ(analysis.SwitchMergeBlock(6), 0); + EXPECT_FALSE(analysis.IsContinueBlock(6)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(6)); + EXPECT_FALSE(analysis.IsInContinueConstruct(6)); + EXPECT_FALSE(analysis.IsMergeBlock(6)); } TEST_F(StructCFGAnalysisTest, SelectionInSelection) { @@ -359,6 +436,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); + EXPECT_FALSE(analysis.IsContinueBlock(1)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(1)); + EXPECT_FALSE(analysis.IsInContinueConstruct(1)); + EXPECT_FALSE(analysis.IsMergeBlock(1)); // The inner header is in the outer selection. EXPECT_EQ(analysis.ContainingConstruct(2), 1); @@ -367,6 +448,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(2), 0); EXPECT_EQ(analysis.ContainingSwitch(2), 0); EXPECT_EQ(analysis.SwitchMergeBlock(2), 0); + EXPECT_FALSE(analysis.IsContinueBlock(2)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(2)); + EXPECT_FALSE(analysis.IsInContinueConstruct(2)); + EXPECT_FALSE(analysis.IsMergeBlock(2)); // The outer merge node is not in either construct. EXPECT_EQ(analysis.ContainingConstruct(3), 0); @@ -375,6 +460,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); + EXPECT_FALSE(analysis.IsContinueBlock(3)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(3)); + EXPECT_FALSE(analysis.IsInContinueConstruct(3)); + EXPECT_TRUE(analysis.IsMergeBlock(3)); // The inner merge is in the outer selection. EXPECT_EQ(analysis.ContainingConstruct(4), 1); @@ -383,6 +472,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(4), 0); EXPECT_EQ(analysis.ContainingSwitch(4), 0); EXPECT_EQ(analysis.SwitchMergeBlock(4), 0); + EXPECT_FALSE(analysis.IsContinueBlock(4)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(4)); + EXPECT_FALSE(analysis.IsInContinueConstruct(4)); + EXPECT_TRUE(analysis.IsMergeBlock(4)); // BB5 is in the inner selection. EXPECT_EQ(analysis.ContainingConstruct(5), 2); @@ -391,6 +484,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(5), 0); EXPECT_EQ(analysis.ContainingSwitch(5), 0); EXPECT_EQ(analysis.SwitchMergeBlock(5), 0); + EXPECT_FALSE(analysis.IsContinueBlock(5)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(5)); + EXPECT_FALSE(analysis.IsInContinueConstruct(5)); + EXPECT_FALSE(analysis.IsMergeBlock(5)); } TEST_F(StructCFGAnalysisTest, LoopInLoop) { @@ -439,6 +536,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); + EXPECT_FALSE(analysis.IsContinueBlock(1)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(1)); + EXPECT_FALSE(analysis.IsInContinueConstruct(1)); + EXPECT_FALSE(analysis.IsMergeBlock(1)); // The inner loop header is in the outer loop. EXPECT_EQ(analysis.ContainingConstruct(2), 1); @@ -447,6 +548,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(2), 3); EXPECT_EQ(analysis.ContainingSwitch(2), 0); EXPECT_EQ(analysis.SwitchMergeBlock(2), 0); + EXPECT_FALSE(analysis.IsContinueBlock(2)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(2)); + EXPECT_FALSE(analysis.IsInContinueConstruct(2)); + EXPECT_FALSE(analysis.IsMergeBlock(2)); // The outer merge node is not in either construct. EXPECT_EQ(analysis.ContainingConstruct(3), 0); @@ -455,6 +560,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); + EXPECT_FALSE(analysis.IsContinueBlock(3)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(3)); + EXPECT_FALSE(analysis.IsInContinueConstruct(3)); + EXPECT_TRUE(analysis.IsMergeBlock(3)); // The inner merge is in the outer loop. EXPECT_EQ(analysis.ContainingConstruct(4), 1); @@ -463,6 +572,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(4), 3); EXPECT_EQ(analysis.ContainingSwitch(4), 0); EXPECT_EQ(analysis.SwitchMergeBlock(4), 0); + EXPECT_FALSE(analysis.IsContinueBlock(4)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(4)); + EXPECT_FALSE(analysis.IsInContinueConstruct(4)); + EXPECT_TRUE(analysis.IsMergeBlock(4)); // The inner continue target is in the inner loop. EXPECT_EQ(analysis.ContainingConstruct(5), 2); @@ -471,6 +584,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(5), 4); EXPECT_EQ(analysis.ContainingSwitch(5), 0); EXPECT_EQ(analysis.SwitchMergeBlock(5), 0); + EXPECT_TRUE(analysis.IsContinueBlock(5)); + EXPECT_TRUE(analysis.IsInContainingLoopsContinueConstruct(5)); + EXPECT_TRUE(analysis.IsInContinueConstruct(5)); + EXPECT_FALSE(analysis.IsMergeBlock(5)); // BB6 is in the loop. EXPECT_EQ(analysis.ContainingConstruct(6), 2); @@ -479,6 +596,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(6), 4); EXPECT_EQ(analysis.ContainingSwitch(6), 0); EXPECT_EQ(analysis.SwitchMergeBlock(6), 0); + EXPECT_FALSE(analysis.IsContinueBlock(6)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(6)); + EXPECT_FALSE(analysis.IsInContinueConstruct(6)); + EXPECT_FALSE(analysis.IsMergeBlock(6)); // The outer continue target is in the outer loop. EXPECT_EQ(analysis.ContainingConstruct(7), 1); @@ -487,6 +608,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(7), 3); EXPECT_EQ(analysis.ContainingSwitch(7), 0); EXPECT_EQ(analysis.SwitchMergeBlock(7), 0); + EXPECT_TRUE(analysis.IsContinueBlock(7)); + EXPECT_TRUE(analysis.IsInContainingLoopsContinueConstruct(7)); + EXPECT_TRUE(analysis.IsInContinueConstruct(7)); + EXPECT_FALSE(analysis.IsMergeBlock(7)); } TEST_F(StructCFGAnalysisTest, KernelTest) { @@ -523,6 +648,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(i), 0); EXPECT_EQ(analysis.ContainingSwitch(i), 0); EXPECT_EQ(analysis.SwitchMergeBlock(i), 0); + EXPECT_FALSE(analysis.IsContinueBlock(i)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(i)); + EXPECT_FALSE(analysis.IsInContinueConstruct(i)); + EXPECT_FALSE(analysis.IsMergeBlock(i)); } } @@ -581,6 +710,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); + EXPECT_FALSE(analysis.IsContinueBlock(1)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(1)); + EXPECT_FALSE(analysis.IsInContinueConstruct(1)); + EXPECT_FALSE(analysis.IsMergeBlock(1)); // BB2 is in the construct. EXPECT_EQ(analysis.ContainingConstruct(2), 1); @@ -589,6 +722,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(2), 0); EXPECT_EQ(analysis.ContainingSwitch(2), 1); EXPECT_EQ(analysis.SwitchMergeBlock(2), 3); + EXPECT_FALSE(analysis.IsContinueBlock(2)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(2)); + EXPECT_FALSE(analysis.IsInContinueConstruct(2)); + EXPECT_FALSE(analysis.IsMergeBlock(2)); // The merge node is not in the construct. EXPECT_EQ(analysis.ContainingConstruct(3), 0); @@ -597,6 +734,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); + EXPECT_FALSE(analysis.IsContinueBlock(3)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(3)); + EXPECT_FALSE(analysis.IsInContinueConstruct(3)); + EXPECT_TRUE(analysis.IsMergeBlock(3)); } TEST_F(StructCFGAnalysisTest, LoopInSwitch) { @@ -643,6 +784,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); + EXPECT_FALSE(analysis.IsContinueBlock(1)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(1)); + EXPECT_FALSE(analysis.IsInContinueConstruct(1)); + EXPECT_FALSE(analysis.IsMergeBlock(1)); // Loop header is in the selection only. EXPECT_EQ(analysis.ContainingConstruct(2), 1); @@ -651,6 +796,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(2), 0); EXPECT_EQ(analysis.ContainingSwitch(2), 1); EXPECT_EQ(analysis.SwitchMergeBlock(2), 3); + EXPECT_FALSE(analysis.IsContinueBlock(2)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(2)); + EXPECT_FALSE(analysis.IsInContinueConstruct(2)); + EXPECT_FALSE(analysis.IsMergeBlock(2)); // The selection merge node is not in either construct. EXPECT_EQ(analysis.ContainingConstruct(3), 0); @@ -659,6 +808,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); + EXPECT_FALSE(analysis.IsContinueBlock(3)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(3)); + EXPECT_FALSE(analysis.IsInContinueConstruct(3)); + EXPECT_TRUE(analysis.IsMergeBlock(3)); // The loop merge is in the selection only. EXPECT_EQ(analysis.ContainingConstruct(4), 1); @@ -667,6 +820,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(4), 0); EXPECT_EQ(analysis.ContainingSwitch(4), 1); EXPECT_EQ(analysis.SwitchMergeBlock(4), 3); + EXPECT_FALSE(analysis.IsContinueBlock(4)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(4)); + EXPECT_FALSE(analysis.IsInContinueConstruct(4)); + EXPECT_TRUE(analysis.IsMergeBlock(4)); // The loop continue target is in the loop. EXPECT_EQ(analysis.ContainingConstruct(5), 2); @@ -675,6 +832,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(5), 4); EXPECT_EQ(analysis.ContainingSwitch(5), 0); EXPECT_EQ(analysis.SwitchMergeBlock(5), 0); + EXPECT_TRUE(analysis.IsContinueBlock(5)); + EXPECT_TRUE(analysis.IsInContainingLoopsContinueConstruct(5)); + EXPECT_TRUE(analysis.IsInContinueConstruct(5)); + EXPECT_FALSE(analysis.IsMergeBlock(5)); // BB6 is in the loop. EXPECT_EQ(analysis.ContainingConstruct(6), 2); @@ -683,6 +844,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(6), 4); EXPECT_EQ(analysis.ContainingSwitch(6), 0); EXPECT_EQ(analysis.SwitchMergeBlock(6), 0); + EXPECT_FALSE(analysis.IsContinueBlock(6)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(6)); + EXPECT_FALSE(analysis.IsInContinueConstruct(6)); + EXPECT_FALSE(analysis.IsMergeBlock(6)); } TEST_F(StructCFGAnalysisTest, SelectionInSwitch) { @@ -727,6 +892,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); + EXPECT_FALSE(analysis.IsContinueBlock(1)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(1)); + EXPECT_FALSE(analysis.IsInContinueConstruct(1)); + EXPECT_FALSE(analysis.IsMergeBlock(1)); // The inner header is in the outer selection. EXPECT_EQ(analysis.ContainingConstruct(2), 1); @@ -735,6 +904,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(2), 0); EXPECT_EQ(analysis.ContainingSwitch(2), 1); EXPECT_EQ(analysis.SwitchMergeBlock(2), 3); + EXPECT_FALSE(analysis.IsContinueBlock(2)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(2)); + EXPECT_FALSE(analysis.IsInContinueConstruct(2)); + EXPECT_FALSE(analysis.IsMergeBlock(2)); // The outer merge node is not in either construct. EXPECT_EQ(analysis.ContainingConstruct(3), 0); @@ -743,6 +916,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); + EXPECT_FALSE(analysis.IsContinueBlock(3)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(3)); + EXPECT_FALSE(analysis.IsInContinueConstruct(3)); + EXPECT_TRUE(analysis.IsMergeBlock(3)); // The inner merge is in the outer selection. EXPECT_EQ(analysis.ContainingConstruct(4), 1); @@ -751,6 +928,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(4), 0); EXPECT_EQ(analysis.ContainingSwitch(4), 1); EXPECT_EQ(analysis.SwitchMergeBlock(4), 3); + EXPECT_FALSE(analysis.IsContinueBlock(4)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(4)); + EXPECT_FALSE(analysis.IsInContinueConstruct(4)); + EXPECT_TRUE(analysis.IsMergeBlock(4)); // BB5 is in the inner selection. EXPECT_EQ(analysis.ContainingConstruct(5), 2); @@ -759,6 +940,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(5), 0); EXPECT_EQ(analysis.ContainingSwitch(5), 1); EXPECT_EQ(analysis.SwitchMergeBlock(5), 3); + EXPECT_FALSE(analysis.IsContinueBlock(5)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(5)); + EXPECT_FALSE(analysis.IsInContinueConstruct(5)); + EXPECT_FALSE(analysis.IsMergeBlock(5)); } TEST_F(StructCFGAnalysisTest, SwitchInSelection) { @@ -803,6 +988,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); + EXPECT_FALSE(analysis.IsContinueBlock(1)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(1)); + EXPECT_FALSE(analysis.IsInContinueConstruct(1)); + EXPECT_FALSE(analysis.IsMergeBlock(1)); // The inner header is in the outer selection. EXPECT_EQ(analysis.ContainingConstruct(2), 1); @@ -811,6 +1000,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(2), 0); EXPECT_EQ(analysis.ContainingSwitch(2), 0); EXPECT_EQ(analysis.SwitchMergeBlock(2), 0); + EXPECT_FALSE(analysis.IsContinueBlock(2)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(2)); + EXPECT_FALSE(analysis.IsInContinueConstruct(2)); + EXPECT_FALSE(analysis.IsMergeBlock(2)); // The outer merge node is not in either construct. EXPECT_EQ(analysis.ContainingConstruct(3), 0); @@ -819,6 +1012,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); + EXPECT_FALSE(analysis.IsContinueBlock(3)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(3)); + EXPECT_FALSE(analysis.IsInContinueConstruct(3)); + EXPECT_TRUE(analysis.IsMergeBlock(3)); // The inner merge is in the outer selection. EXPECT_EQ(analysis.ContainingConstruct(4), 1); @@ -827,6 +1024,10 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(4), 0); EXPECT_EQ(analysis.ContainingSwitch(4), 0); EXPECT_EQ(analysis.SwitchMergeBlock(4), 0); + EXPECT_FALSE(analysis.IsContinueBlock(4)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(4)); + EXPECT_FALSE(analysis.IsInContinueConstruct(4)); + EXPECT_TRUE(analysis.IsMergeBlock(4)); // BB5 is in the inner selection. EXPECT_EQ(analysis.ContainingConstruct(5), 2); @@ -835,8 +1036,339 @@ OpFunctionEnd EXPECT_EQ(analysis.LoopMergeBlock(5), 0); EXPECT_EQ(analysis.ContainingSwitch(5), 2); EXPECT_EQ(analysis.SwitchMergeBlock(5), 4); + EXPECT_FALSE(analysis.IsContinueBlock(5)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(5)); + EXPECT_FALSE(analysis.IsInContinueConstruct(5)); + EXPECT_FALSE(analysis.IsMergeBlock(5)); } +TEST_F(StructCFGAnalysisTest, SelectionInContinue) { + const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +%void = OpTypeVoid +%bool = OpTypeBool +%bool_undef = OpUndef %bool +%uint = OpTypeInt 32 0 +%uint_undef = OpUndef %uint +%void_func = OpTypeFunction %void +%main = OpFunction %void None %void_func +%entry_lab = OpLabel +OpBranch %1 +%1 = OpLabel +OpLoopMerge %3 %4 None +OpBranchConditional %undef_bool %2 %3 +%2 = OpLabel +OpBranch %3 +%4 = OpLabel +OpSelectionMerge %6 None +OpBranchConditional %undef_bool %5 %6 +%5 = OpLabel +OpBranch %6 +%6 = OpLabel +OpBranch %1 +%3 = OpLabel +OpReturn +OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + + StructuredCFGAnalysis analysis(context.get()); + + // The loop header is not in either construct. + EXPECT_EQ(analysis.ContainingConstruct(1), 0); + EXPECT_EQ(analysis.ContainingLoop(1), 0); + EXPECT_EQ(analysis.MergeBlock(1), 0); + EXPECT_EQ(analysis.LoopMergeBlock(1), 0); + EXPECT_EQ(analysis.ContainingSwitch(1), 0); + EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); + EXPECT_FALSE(analysis.IsContinueBlock(1)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(1)); + EXPECT_FALSE(analysis.IsInContinueConstruct(1)); + EXPECT_FALSE(analysis.IsMergeBlock(1)); + + // Selection header is in the loop only. + EXPECT_EQ(analysis.ContainingConstruct(2), 1); + EXPECT_EQ(analysis.ContainingLoop(2), 1); + EXPECT_EQ(analysis.MergeBlock(2), 3); + EXPECT_EQ(analysis.LoopMergeBlock(2), 3); + EXPECT_EQ(analysis.ContainingSwitch(2), 0); + EXPECT_EQ(analysis.SwitchMergeBlock(2), 0); + EXPECT_FALSE(analysis.IsContinueBlock(2)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(2)); + EXPECT_FALSE(analysis.IsInContinueConstruct(2)); + EXPECT_FALSE(analysis.IsMergeBlock(2)); + + // The loop merge node is not in either construct. + EXPECT_EQ(analysis.ContainingConstruct(3), 0); + EXPECT_EQ(analysis.ContainingLoop(3), 0); + EXPECT_EQ(analysis.MergeBlock(3), 0); + EXPECT_EQ(analysis.LoopMergeBlock(3), 0); + EXPECT_EQ(analysis.ContainingSwitch(3), 0); + EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); + EXPECT_FALSE(analysis.IsContinueBlock(3)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(3)); + EXPECT_FALSE(analysis.IsInContinueConstruct(3)); + EXPECT_TRUE(analysis.IsMergeBlock(3)); + + // The continue block is in the loop only. + EXPECT_EQ(analysis.ContainingConstruct(4), 1); + EXPECT_EQ(analysis.ContainingLoop(4), 1); + EXPECT_EQ(analysis.MergeBlock(4), 3); + EXPECT_EQ(analysis.LoopMergeBlock(4), 3); + EXPECT_EQ(analysis.ContainingSwitch(4), 0); + EXPECT_EQ(analysis.SwitchMergeBlock(4), 0); + EXPECT_TRUE(analysis.IsContinueBlock(4)); + EXPECT_TRUE(analysis.IsInContainingLoopsContinueConstruct(4)); + EXPECT_TRUE(analysis.IsInContinueConstruct(4)); + EXPECT_FALSE(analysis.IsMergeBlock(4)); + + // BB5 is in the selection and the continue for the loop. + EXPECT_EQ(analysis.ContainingConstruct(5), 4); + EXPECT_EQ(analysis.ContainingLoop(5), 1); + EXPECT_EQ(analysis.MergeBlock(5), 6); + EXPECT_EQ(analysis.LoopMergeBlock(5), 3); + EXPECT_EQ(analysis.ContainingSwitch(5), 0); + EXPECT_EQ(analysis.SwitchMergeBlock(5), 0); + EXPECT_FALSE(analysis.IsContinueBlock(5)); + EXPECT_TRUE(analysis.IsInContainingLoopsContinueConstruct(5)); + EXPECT_TRUE(analysis.IsInContinueConstruct(5)); + EXPECT_FALSE(analysis.IsMergeBlock(5)); + + // BB5 is in the continue for the loop. + EXPECT_EQ(analysis.ContainingConstruct(6), 1); + EXPECT_EQ(analysis.ContainingLoop(6), 1); + EXPECT_EQ(analysis.MergeBlock(6), 3); + EXPECT_EQ(analysis.LoopMergeBlock(6), 3); + EXPECT_EQ(analysis.ContainingSwitch(6), 0); + EXPECT_EQ(analysis.SwitchMergeBlock(6), 0); + EXPECT_FALSE(analysis.IsContinueBlock(6)); + EXPECT_TRUE(analysis.IsInContainingLoopsContinueConstruct(6)); + EXPECT_TRUE(analysis.IsInContinueConstruct(6)); + EXPECT_TRUE(analysis.IsMergeBlock(6)); +} + +TEST_F(StructCFGAnalysisTest, LoopInContinue) { + const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +%void = OpTypeVoid +%bool = OpTypeBool +%bool_undef = OpUndef %bool +%uint = OpTypeInt 32 0 +%uint_undef = OpUndef %uint +%void_func = OpTypeFunction %void +%main = OpFunction %void None %void_func +%entry_lab = OpLabel +OpBranch %1 +%1 = OpLabel +OpLoopMerge %3 %7 None +OpBranchConditional %undef_bool %2 %3 +%2 = OpLabel +OpBranchConditional %undef_bool %3 %7 +%7 = OpLabel +OpLoopMerge %4 %5 None +OpBranchConditional %undef_bool %4 %6 +%5 = OpLabel +OpBranch %7 +%6 = OpLabel +OpBranch %4 +%4 = OpLabel +OpBranch %1 +%3 = OpLabel +OpReturn +OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + + StructuredCFGAnalysis analysis(context.get()); + + // The outer loop header is not in either construct. + EXPECT_EQ(analysis.ContainingConstruct(1), 0); + EXPECT_EQ(analysis.ContainingLoop(1), 0); + EXPECT_EQ(analysis.MergeBlock(1), 0); + EXPECT_EQ(analysis.LoopMergeBlock(1), 0); + EXPECT_EQ(analysis.ContainingSwitch(1), 0); + EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); + EXPECT_FALSE(analysis.IsContinueBlock(1)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(1)); + EXPECT_FALSE(analysis.IsInContinueConstruct(1)); + EXPECT_FALSE(analysis.IsMergeBlock(1)); + + // BB2 is a regular block in the inner loop. + EXPECT_EQ(analysis.ContainingConstruct(2), 1); + EXPECT_EQ(analysis.ContainingLoop(2), 1); + EXPECT_EQ(analysis.MergeBlock(2), 3); + EXPECT_EQ(analysis.LoopMergeBlock(2), 3); + EXPECT_EQ(analysis.ContainingSwitch(2), 0); + EXPECT_EQ(analysis.SwitchMergeBlock(2), 0); + EXPECT_FALSE(analysis.IsContinueBlock(2)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(2)); + EXPECT_FALSE(analysis.IsInContinueConstruct(2)); + EXPECT_FALSE(analysis.IsMergeBlock(2)); + + // The outer merge node is not in either construct. + EXPECT_EQ(analysis.ContainingConstruct(3), 0); + EXPECT_EQ(analysis.ContainingLoop(3), 0); + EXPECT_EQ(analysis.MergeBlock(3), 0); + EXPECT_EQ(analysis.LoopMergeBlock(3), 0); + EXPECT_EQ(analysis.ContainingSwitch(3), 0); + EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); + EXPECT_FALSE(analysis.IsContinueBlock(3)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(3)); + EXPECT_FALSE(analysis.IsInContinueConstruct(3)); + EXPECT_TRUE(analysis.IsMergeBlock(3)); + + // The inner merge is in the continue of the outer loop. + EXPECT_EQ(analysis.ContainingConstruct(4), 1); + EXPECT_EQ(analysis.ContainingLoop(4), 1); + EXPECT_EQ(analysis.MergeBlock(4), 3); + EXPECT_EQ(analysis.LoopMergeBlock(4), 3); + EXPECT_EQ(analysis.ContainingSwitch(4), 0); + EXPECT_EQ(analysis.SwitchMergeBlock(4), 0); + EXPECT_FALSE(analysis.IsContinueBlock(4)); + EXPECT_TRUE(analysis.IsInContainingLoopsContinueConstruct(4)); + EXPECT_TRUE(analysis.IsInContinueConstruct(4)); + EXPECT_TRUE(analysis.IsMergeBlock(4)); + + // The inner continue target is in the inner loop. + EXPECT_EQ(analysis.ContainingConstruct(5), 7); + EXPECT_EQ(analysis.ContainingLoop(5), 7); + EXPECT_EQ(analysis.MergeBlock(5), 4); + EXPECT_EQ(analysis.LoopMergeBlock(5), 4); + EXPECT_EQ(analysis.ContainingSwitch(5), 0); + EXPECT_EQ(analysis.SwitchMergeBlock(5), 0); + EXPECT_TRUE(analysis.IsContinueBlock(5)); + EXPECT_TRUE(analysis.IsInContainingLoopsContinueConstruct(5)); + EXPECT_TRUE(analysis.IsInContinueConstruct(5)); + EXPECT_FALSE(analysis.IsMergeBlock(5)); + + // BB6 is a regular block in the inner loop. + EXPECT_EQ(analysis.ContainingConstruct(6), 7); + EXPECT_EQ(analysis.ContainingLoop(6), 7); + EXPECT_EQ(analysis.MergeBlock(6), 4); + EXPECT_EQ(analysis.LoopMergeBlock(6), 4); + EXPECT_EQ(analysis.ContainingSwitch(6), 0); + EXPECT_EQ(analysis.SwitchMergeBlock(6), 0); + EXPECT_FALSE(analysis.IsContinueBlock(6)); + EXPECT_FALSE(analysis.IsInContainingLoopsContinueConstruct(6)); + EXPECT_TRUE(analysis.IsInContinueConstruct(6)); + EXPECT_FALSE(analysis.IsMergeBlock(6)); + + // The outer continue target is in the outer loop. + EXPECT_EQ(analysis.ContainingConstruct(7), 1); + EXPECT_EQ(analysis.ContainingLoop(7), 1); + EXPECT_EQ(analysis.MergeBlock(7), 3); + EXPECT_EQ(analysis.LoopMergeBlock(7), 3); + EXPECT_EQ(analysis.ContainingSwitch(7), 0); + EXPECT_EQ(analysis.SwitchMergeBlock(7), 0); + EXPECT_TRUE(analysis.IsContinueBlock(7)); + EXPECT_TRUE(analysis.IsInContainingLoopsContinueConstruct(7)); + EXPECT_TRUE(analysis.IsInContinueConstruct(7)); + EXPECT_FALSE(analysis.IsMergeBlock(7)); +} + +TEST_F(StructCFGAnalysisTest, FuncCallInContinueDirect) { + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %1 "main" + %void = OpTypeVoid + %bool = OpTypeBool + %4 = OpUndef %bool + %uint = OpTypeInt 32 0 + %6 = OpUndef %uint + %7 = OpTypeFunction %void + %1 = OpFunction %void None %7 + %8 = OpLabel + OpBranch %9 + %9 = OpLabel + OpLoopMerge %10 %11 None + OpBranchConditional %12 %10 %11 + %11 = OpLabel + %13 = OpFunctionCall %void %14 + OpBranch %9 + %10 = OpLabel + %15 = OpFunctionCall %void %16 + OpReturn + OpFunctionEnd + %14 = OpFunction %void None %7 + %17 = OpLabel + OpReturn + OpFunctionEnd + %16 = OpFunction %void None %7 + %18 = OpLabel + OpReturn + OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + + StructuredCFGAnalysis analysis(context.get()); + + auto c = analysis.FindFuncsCalledFromContinue(); + EXPECT_THAT(c, UnorderedElementsAre(14u)); +} + +TEST_F(StructCFGAnalysisTest, FuncCallInContinueIndirect) { + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %1 "main" + %void = OpTypeVoid + %bool = OpTypeBool + %4 = OpUndef %bool + %uint = OpTypeInt 32 0 + %6 = OpUndef %uint + %7 = OpTypeFunction %void + %1 = OpFunction %void None %7 + %8 = OpLabel + OpBranch %9 + %9 = OpLabel + OpLoopMerge %10 %11 None + OpBranchConditional %12 %10 %11 + %11 = OpLabel + %13 = OpFunctionCall %void %14 + OpBranch %9 + %10 = OpLabel + %15 = OpFunctionCall %void %16 + OpReturn + OpFunctionEnd + %14 = OpFunction %void None %7 + %17 = OpLabel + %19 = OpFunctionCall %void %16 + OpReturn + OpFunctionEnd + %16 = OpFunction %void None %7 + %18 = OpLabel + %20 = OpFunctionCall %void %21 + OpReturn + OpFunctionEnd + %21 = OpFunction %void None %7 + %22 = OpLabel + OpReturn + OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + + StructuredCFGAnalysis analysis(context.get()); + + auto c = analysis.FindFuncsCalledFromContinue(); + EXPECT_THAT(c, UnorderedElementsAre(14u, 16u, 21u)); +} } // namespace } // namespace opt } // namespace spvtools diff --git a/3rdparty/spirv-tools/test/opt/wrap_opkill_test.cpp b/3rdparty/spirv-tools/test/opt/wrap_opkill_test.cpp index 89022ae8c..d50af28a1 100644 --- a/3rdparty/spirv-tools/test/opt/wrap_opkill_test.cpp +++ b/3rdparty/spirv-tools/test/opt/wrap_opkill_test.cpp @@ -249,15 +249,33 @@ TEST_F(WrapOpKillTest, IdBoundOverflow1) { const std::string text = R"( OpCapability GeometryStreams OpMemoryModel Logical GLSL450 -OpEntryPoint Fragment %4 "main" -OpExecutionMode %4 OriginUpperLeft +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft %2 = OpTypeVoid %3 = OpTypeFunction %2 -%4 = OpFunction %2 Pure|Const %3 +%bool = OpTypeBool +%true = OpConstantTrue %bool +%main = OpFunction %2 None %3 +%8 = OpLabel +OpBranch %9 +%9 = OpLabel +OpLoopMerge %10 %11 None +OpBranch %12 +%12 = OpLabel +OpBranchConditional %true %13 %10 +%13 = OpLabel +OpBranch %11 +%11 = OpLabel +%14 = OpFunctionCall %void %kill_ +OpBranch %9 +%10 = OpLabel +OpReturn +OpFunctionEnd +%kill_ = OpFunction %2 Pure|Const %3 %4194302 = OpLabel OpKill OpFunctionEnd - )"; +)"; SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); @@ -272,15 +290,33 @@ TEST_F(WrapOpKillTest, IdBoundOverflow2) { const std::string text = R"( OpCapability GeometryStreams OpMemoryModel Logical GLSL450 -OpEntryPoint Fragment %4 "main" -OpExecutionMode %4 OriginUpperLeft +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft %2 = OpTypeVoid %3 = OpTypeFunction %2 -%4 = OpFunction %2 Pure|Const %3 +%bool = OpTypeBool +%true = OpConstantTrue %bool +%main = OpFunction %2 None %3 +%8 = OpLabel +OpBranch %9 +%9 = OpLabel +OpLoopMerge %10 %11 None +OpBranch %12 +%12 = OpLabel +OpBranchConditional %true %13 %10 +%13 = OpLabel +OpBranch %11 +%11 = OpLabel +%14 = OpFunctionCall %void %kill_ +OpBranch %9 +%10 = OpLabel +OpReturn +OpFunctionEnd +%kill_ = OpFunction %2 Pure|Const %3 %4194301 = OpLabel OpKill OpFunctionEnd - )"; +)"; SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); @@ -295,15 +331,33 @@ TEST_F(WrapOpKillTest, IdBoundOverflow3) { const std::string text = R"( OpCapability GeometryStreams OpMemoryModel Logical GLSL450 -OpEntryPoint Fragment %4 "main" -OpExecutionMode %4 OriginUpperLeft +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft %2 = OpTypeVoid %3 = OpTypeFunction %2 -%4 = OpFunction %2 Pure|Const %3 +%bool = OpTypeBool +%true = OpConstantTrue %bool +%main = OpFunction %2 None %3 +%8 = OpLabel +OpBranch %9 +%9 = OpLabel +OpLoopMerge %10 %11 None +OpBranch %12 +%12 = OpLabel +OpBranchConditional %true %13 %10 +%13 = OpLabel +OpBranch %11 +%11 = OpLabel +%14 = OpFunctionCall %void %kill_ +OpBranch %9 +%10 = OpLabel +OpReturn +OpFunctionEnd +%kill_ = OpFunction %2 Pure|Const %3 %4194300 = OpLabel OpKill OpFunctionEnd - )"; +)"; SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); @@ -318,16 +372,34 @@ TEST_F(WrapOpKillTest, IdBoundOverflow4) { const std::string text = R"( OpCapability DerivativeControl OpMemoryModel Logical GLSL450 -OpEntryPoint Fragment %4 "main" -OpExecutionMode %4 OriginUpperLeft +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft OpDecorate %2 Location 539091968 %2 = OpTypeVoid %3 = OpTypeFunction %2 -%4 = OpFunction %2 Inline|Pure|Const %3 +%bool = OpTypeBool +%true = OpConstantTrue %bool +%main = OpFunction %2 None %3 +%8 = OpLabel +OpBranch %9 +%9 = OpLabel +OpLoopMerge %10 %11 None +OpBranch %12 +%12 = OpLabel +OpBranchConditional %true %13 %10 +%13 = OpLabel +OpBranch %11 +%11 = OpLabel +%14 = OpFunctionCall %void %kill_ +OpBranch %9 +%10 = OpLabel +OpReturn +OpFunctionEnd +%kill_ = OpFunction %2 Inline|Pure|Const %3 %4194302 = OpLabel OpKill OpFunctionEnd - )"; +)"; SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); @@ -342,32 +414,52 @@ TEST_F(WrapOpKillTest, IdBoundOverflow5) { const std::string text = R"( OpCapability Shader OpMemoryModel Logical GLSL450 - OpEntryPoint Fragment %4 "main" - OpExecutionMode %4 OriginUpperLeft + OpEntryPoint Fragment %1 "main" + OpExecutionMode %1 OriginUpperLeft OpDecorate %void Location 539091968 %void = OpTypeVoid %3 = OpTypeFunction %void %float = OpTypeFloat 32 - %_struct_7 = OpTypeStruct %float %float - %_struct_8 = OpTypeStruct %_struct_7 -%_ptr_Function__struct_8 = OpTypePointer Function %_struct_8 + %_struct_5 = OpTypeStruct %float %float + %_struct_6 = OpTypeStruct %_struct_5 +%_ptr_Function__struct_6 = OpTypePointer Function %_struct_6 %_ptr_Output_float = OpTypePointer Output %float - %18 = OpTypeFunction %_struct_7 %_ptr_Function__struct_8 - %4 = OpFunction %void Inline|Pure|Const %3 - %850212 = OpLabel - %10 = OpVariable %_ptr_Function__struct_8 Function - %1441807 = OpFunctionCall %_struct_7 %32257 %10 + %9 = OpTypeFunction %_struct_5 %_ptr_Function__struct_6 + %bool = OpTypeBool + %true = OpConstantTrue %bool + %1 = OpFunction %void None %3 + %12 = OpLabel + %13 = OpVariable %_ptr_Function__struct_6 Function + OpBranch %14 + %14 = OpLabel + OpLoopMerge %15 %16 None + OpBranch %17 + %17 = OpLabel + OpBranchConditional %true %18 %15 + %18 = OpLabel + OpBranch %16 + %16 = OpLabel + %19 = OpFunctionCall %void %20 + %21 = OpFunctionCall %_struct_5 %22 %13 + OpBranch %14 + %15 = OpLabel + OpReturn + OpFunctionEnd + %20 = OpFunction %void Inline|Pure|Const %3 + %23 = OpLabel + %24 = OpVariable %_ptr_Function__struct_6 Function + %25 = OpFunctionCall %_struct_5 %26 %24 OpKill OpFunctionEnd - %32257 = OpFunction %_struct_7 None %18 - %28 = OpLabel + %26 = OpFunction %_struct_5 None %9 + %27 = OpLabel OpUnreachable OpFunctionEnd - %64821 = OpFunction %_struct_7 Inline %18 + %22 = OpFunction %_struct_5 Inline %9 %4194295 = OpLabel OpKill OpFunctionEnd - )"; +)"; SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); @@ -378,6 +470,49 @@ TEST_F(WrapOpKillTest, IdBoundOverflow5) { EXPECT_EQ(Pass::Status::Failure, std::get<1>(result)); } +TEST_F(WrapOpKillTest, SkipEntryPoint) { + const std::string text = R"( +OpCapability GeometryStreams +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %4 "main" +OpExecutionMode %4 OriginUpperLeft +%2 = OpTypeVoid +%3 = OpTypeFunction %2 +%4 = OpFunction %2 Pure|Const %3 +%5 = OpLabel +OpKill +OpFunctionEnd +)"; + + auto result = SinglePassRunToBinary(text, true); + EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(WrapOpKillTest, SkipFunctionNotInContinue) { + const std::string text = R"( +OpCapability GeometryStreams +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +%2 = OpTypeVoid +%3 = OpTypeFunction %2 +%bool = OpTypeBool +%true = OpConstantTrue %bool +%main = OpFunction %2 None %3 +%6 = OpLabel +%7 = OpFunctionCall %void %4 +OpReturn +OpFunctionEnd +%4 = OpFunction %2 Pure|Const %3 +%5 = OpLabel +OpKill +OpFunctionEnd +)"; + + auto result = SinglePassRunToBinary(text, true); + EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/3rdparty/spirv-tools/test/val/val_cfg_test.cpp b/3rdparty/spirv-tools/test/val/val_cfg_test.cpp index b22db0655..547eb578f 100644 --- a/3rdparty/spirv-tools/test/val/val_cfg_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_cfg_test.cpp @@ -342,11 +342,10 @@ TEST_P(ValidateCFG, VariableNotInFirstBlockBad) { str += "OpFunctionEnd\n"; CompileSuccessfully(str); - ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); - EXPECT_THAT( - getDiagnosticString(), - HasSubstr( - "Variables can only be defined in the first block of a function")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("All OpVariable instructions in a function must be the " + "first instructions in the first block")); } TEST_P(ValidateCFG, BlockSelfLoopIsOk) { diff --git a/3rdparty/spirv-tools/test/val/val_constants_test.cpp b/3rdparty/spirv-tools/test/val/val_constants_test.cpp index 2499f5ca4..301539d98 100644 --- a/3rdparty/spirv-tools/test/val/val_constants_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_constants_test.cpp @@ -458,6 +458,26 @@ OpMemoryModel Logical GLSL450 EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); } +TEST_F(ValidateConstant, NullPhysicalStorageBuffer) { + std::string spirv = R"( +OpCapability Shader +OpCapability PhysicalStorageBufferAddresses +OpCapability Linkage +OpExtension "SPV_KHR_physical_storage_buffer" +OpMemoryModel PhysicalStorageBuffer64 GLSL450 +OpName %ptr "ptr" +%int = OpTypeInt 32 0 +%ptr = OpTypePointer PhysicalStorageBuffer %int +%null = OpConstantNull %ptr +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpConstantNull Result Type '1[%ptr]' cannot have " + "a null value")); +} + } // namespace } // namespace val } // namespace spvtools diff --git a/3rdparty/spirv-tools/test/val/val_memory_test.cpp b/3rdparty/spirv-tools/test/val/val_memory_test.cpp index 858ef8ddc..17f26eec3 100644 --- a/3rdparty/spirv-tools/test/val/val_memory_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_memory_test.cpp @@ -4386,6 +4386,96 @@ OpFunctionEnd "typed as OpTypeStruct, or an array of this type")); } +TEST_F(ValidateMemory, PhysicalStorageBufferPtrEqual) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Int64 +OpCapability PhysicalStorageBufferAddresses +OpMemoryModel PhysicalStorageBuffer64 GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +%void = OpTypeVoid +%bool = OpTypeBool +%long = OpTypeInt 64 0 +%long_0 = OpConstant %long 0 +%ptr_pssbo_long = OpTypePointer PhysicalStorageBuffer %long +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%conv = OpConvertUToPtr %ptr_pssbo_long %long_0 +%eq = OpPtrEqual %bool %conv %conv +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Cannot use a pointer in the PhysicalStorageBuffer storage class")); +} + +TEST_F(ValidateMemory, PhysicalStorageBufferPtrNotEqual) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Int64 +OpCapability PhysicalStorageBufferAddresses +OpMemoryModel PhysicalStorageBuffer64 GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +%void = OpTypeVoid +%bool = OpTypeBool +%long = OpTypeInt 64 0 +%long_0 = OpConstant %long 0 +%ptr_pssbo_long = OpTypePointer PhysicalStorageBuffer %long +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%conv = OpConvertUToPtr %ptr_pssbo_long %long_0 +%neq = OpPtrNotEqual %bool %conv %conv +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Cannot use a pointer in the PhysicalStorageBuffer storage class")); +} + +TEST_F(ValidateMemory, PhysicalStorageBufferPtrDiff) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Int64 +OpCapability PhysicalStorageBufferAddresses +OpCapability VariablePointers +OpMemoryModel PhysicalStorageBuffer64 GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +%void = OpTypeVoid +%long = OpTypeInt 64 0 +%long_0 = OpConstant %long 0 +%ptr_pssbo_long = OpTypePointer PhysicalStorageBuffer %long +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%conv = OpConvertUToPtr %ptr_pssbo_long %long_0 +%diff = OpPtrDiff %long %conv %conv +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Cannot use a pointer in the PhysicalStorageBuffer storage class")); +} + } // namespace } // namespace val } // namespace spvtools diff --git a/3rdparty/spirv-tools/test/val/val_misc_test.cpp b/3rdparty/spirv-tools/test/val/val_misc_test.cpp index 350f5616b..d266653e3 100644 --- a/3rdparty/spirv-tools/test/val/val_misc_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_misc_test.cpp @@ -83,6 +83,87 @@ OpMemoryModel Logical GLSL450 getDiagnosticString(), HasSubstr("Cannot create undefined values with 8- or 16-bit types")); } + +const std::string ShaderClockSpriv = R"( +OpCapability Shader +OpCapability Int64 +OpCapability ShaderClockKHR +OpExtension "SPV_KHR_shader_clock" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpSourceExtension "GL_ARB_gpu_shader_int64" +OpSourceExtension "GL_ARB_shader_clock" +OpSourceExtension "GL_EXT_shader_realtime_clock" +OpName %main "main" +OpName %time1 "time1" +%void = OpTypeVoid +)"; + +TEST_F(ValidateMisc, ShaderClockInt64) { + const std::string spirv = ShaderClockSpriv + R"( +%3 = OpTypeFunction %void +%uint = OpTypeInt 32 0 +%_ptr_Function_uint = OpTypePointer Function %uint +%uint_3 = OpConstant %uint 3 +%uint_1 = OpConstant %uint 1 +%main = OpFunction %void None %3 +%5 = OpLabel +%time1 = OpVariable %_ptr_Function_uint Function +%11 = OpReadClockKHR %uint %uint_3 +OpStore %time1 %11 +OpReturn +OpFunctionEnd)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("or 64bit unsigned integer")); +} + +TEST_F(ValidateMisc, ShaderClockVec2) { + const std::string spirv = ShaderClockSpriv + R"( +%3 = OpTypeFunction %void +%ulong = OpTypeInt 64 0 +%_ptr_Function_ulong = OpTypePointer Function %ulong +%uint = OpTypeInt 32 0 +%uint_3 = OpConstant %uint 3 +%v2uint = OpTypeVector %ulong 2 +%_ptr_Function_v2uint = OpTypePointer Function %v2uint +%main = OpFunction %void None %3 +%5 = OpLabel +%time1 = OpVariable %_ptr_Function_v2uint Function +%15 = OpReadClockKHR %v2uint %uint_3 +OpStore %time1 %15 +OpReturn +OpFunctionEnd)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("vector of two components")); +} + +TEST_F(ValidateMisc, ShaderClockExecutionScope) { + const std::string spirv = ShaderClockSpriv + R"( +%3 = OpTypeFunction %void +%ulong = OpTypeInt 64 0 +%uint = OpTypeInt 32 0 +%_ptr_Function_ulong = OpTypePointer Function %ulong +%uint_3 = OpConstant %uint 10 +%uint_1 = OpConstant %uint 1 +%main = OpFunction %void None %3 +%5 = OpLabel +%time1 = OpVariable %_ptr_Function_ulong Function +%11 = OpReadClockKHR %ulong %uint_3 +OpStore %time1 %11 +OpReturn +OpFunctionEnd)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("Invalid scope value")); +} } // namespace } // namespace val } // namespace spvtools diff --git a/3rdparty/spirv-tools/test/val/val_opencl_test.cpp b/3rdparty/spirv-tools/test/val/val_opencl_test.cpp index 18b2f715e..10641587f 100644 --- a/3rdparty/spirv-tools/test/val/val_opencl_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_opencl_test.cpp @@ -45,15 +45,18 @@ TEST_F(ValidateOpenCL, NonPhysicalAddressingModelBad) { TEST_F(ValidateOpenCL, NonOpenCLMemoryModelBad) { std::string spirv = R"( OpCapability Kernel - OpMemoryModel Physical32 GLSL450 + OpCapability Addresses + OpCapability VulkanMemoryModelKHR + OpExtension "SPV_KHR_vulkan_memory_model" + OpMemoryModel Physical32 VulkanKHR )"; CompileSuccessfully(spirv); EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_OPENCL_1_2)); - EXPECT_THAT(getDiagnosticString(), - HasSubstr("Memory model must be OpenCL in the OpenCL environment." - "\n OpMemoryModel Physical32 GLSL450\n")); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Memory model must be OpenCL in the OpenCL environment.")); } TEST_F(ValidateOpenCL, NonVoidSampledTypeImageBad) { diff --git a/3rdparty/spirv-tools/test/val/val_storage_test.cpp b/3rdparty/spirv-tools/test/val/val_storage_test.cpp index f54b425ea..fe37a93c6 100644 --- a/3rdparty/spirv-tools/test/val/val_storage_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_storage_test.cpp @@ -157,6 +157,7 @@ TEST_F(ValidateStorage, GenericVariableOutsideFunction) { const auto str = R"( OpCapability Kernel OpCapability Linkage + OpCapability GenericPointer OpMemoryModel Logical OpenCL %intt = OpTypeInt 32 0 %ptrt = OpTypePointer Function %intt @@ -172,6 +173,7 @@ TEST_F(ValidateStorage, GenericVariableInsideFunction) { const auto str = R"( OpCapability Shader OpCapability Linkage + OpCapability GenericPointer OpMemoryModel Logical GLSL450 %intt = OpTypeInt 32 1 %voidt = OpTypeVoid @@ -184,7 +186,7 @@ TEST_F(ValidateStorage, GenericVariableInsideFunction) { OpFunctionEnd )"; CompileSuccessfully(str); - ASSERT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); EXPECT_THAT(getDiagnosticString(), HasSubstr("OpVariable storage class cannot be Generic")); } @@ -307,12 +309,10 @@ INSTANTIATE_TEST_SUITE_P( std::make_tuple("Workgroup", false, true, ""), std::make_tuple("Private", false, true, ""), std::make_tuple("Function", true, true, ""), - std::make_tuple( - "CrossWorkgroup", false, false, - "For WebGPU, OpTypePointer storage class must be one of"), - std::make_tuple( - "PushConstant", false, false, - "For WebGPU, OpTypePointer storage class must be one of"))); + std::make_tuple("CrossWorkgroup", false, false, + "Invalid storage class for target environment"), + std::make_tuple("PushConstant", false, false, + "Invalid storage class for target environment"))); } // namespace } // namespace val diff --git a/3rdparty/spirv-tools/test/val/val_webgpu_test.cpp b/3rdparty/spirv-tools/test/val/val_webgpu_test.cpp index 1eae0d39c..8f625551b 100644 --- a/3rdparty/spirv-tools/test/val/val_webgpu_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_webgpu_test.cpp @@ -187,23 +187,6 @@ TEST_F(ValidateWebGPU, LogicalAddressingVulkanKHRMemoryGood) { EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0)); } -TEST_F(ValidateWebGPU, NonLogicalAddressingModelBad) { - std::string spirv = R"( - OpCapability Shader - OpCapability VulkanMemoryModelKHR - OpExtension "SPV_KHR_vulkan_memory_model" - OpMemoryModel Physical32 VulkanKHR -)"; - - CompileSuccessfully(spirv); - - EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); - EXPECT_THAT(getDiagnosticString(), - HasSubstr("Addressing model must be Logical for WebGPU " - "environment.\n OpMemoryModel Physical32 " - "Vulkan\n")); -} - TEST_F(ValidateWebGPU, NonVulkanKHRMemoryModelBad) { std::string spirv = R"( OpCapability Shader