Updated spirv-tools.

This commit is contained in:
Бранимир Караџић
2019-09-26 12:57:29 -07:00
parent f7ed7df55c
commit 03c99d50c7
96 changed files with 1745 additions and 13216 deletions

View File

@@ -16,6 +16,11 @@ compile_commands.json
/testing
/tools/clang/
/utils/clang-format-diff.py
bazel-bin
bazel-genfiles
bazel-out
bazel-spirv-tools
bazel-testlogs
# Vim
[._]*.s[a-w][a-z]

View File

@@ -49,7 +49,6 @@ SPVTOOLS_SRC_FILES := \
source/val/validate_composites.cpp \
source/val/validate_constants.cpp \
source/val/validate_conversion.cpp \
source/val/validate_datarules.cpp \
source/val/validate_debug.cpp \
source/val/validate_decorations.cpp \
source/val/validate_derivatives.cpp \

View File

@@ -410,7 +410,6 @@ static_library("spvtools_val") {
"source/val/validate_composites.cpp",
"source/val/validate_constants.cpp",
"source/val/validate_conversion.cpp",
"source/val/validate_datarules.cpp",
"source/val/validate_debug.cpp",
"source/val/validate_decorations.cpp",
"source/val/validate_derivatives.cpp",
@@ -877,6 +876,7 @@ source_set("spvtools_util_cli_consumer") {
"tools/util/cli_consumer.cpp",
"tools/util/cli_consumer.h",
]
deps = [ ":spvtools_headers" ]
configs += [ ":spvtools_internal_config" ]
}

View File

@@ -1,313 +0,0 @@
# 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.
cmake_minimum_required(VERSION 2.8.12)
if (POLICY CMP0048)
cmake_policy(SET CMP0048 NEW)
endif()
if (POLICY CMP0054)
# Avoid dereferencing variables or interpret keywords that have been
# quoted or bracketed.
# https://cmake.org/cmake/help/v3.1/policy/CMP0054.html
cmake_policy(SET CMP0054 NEW)
endif()
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
project(spirv-tools)
enable_testing()
set(SPIRV_TOOLS "SPIRV-Tools")
include(GNUInstallDirs)
include(cmake/setup_build.cmake)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_CXX_STANDARD 11)
option(SPIRV_ALLOW_TIMERS "Allow timers via clock_gettime on supported platforms" ON)
if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux")
add_definitions(-DSPIRV_LINUX)
set(SPIRV_TIMER_ENABLED ${SPIRV_ALLOW_TIMERS})
elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
add_definitions(-DSPIRV_WINDOWS)
elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "CYGWIN")
add_definitions(-DSPIRV_WINDOWS)
elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin")
add_definitions(-DSPIRV_MAC)
elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Android")
add_definitions(-DSPIRV_ANDROID)
set(SPIRV_TIMER_ENABLED ${SPIRV_ALLOW_TIMERS})
elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "FreeBSD")
add_definitions(-DSPIRV_FREEBSD)
else()
message(FATAL_ERROR "Your platform '${CMAKE_SYSTEM_NAME}' is not supported!")
endif()
if (${SPIRV_TIMER_ENABLED})
add_definitions(-DSPIRV_TIMER_ENABLED)
endif()
if ("${CMAKE_BUILD_TYPE}" STREQUAL "")
message(STATUS "No build type selected, default to Debug")
set(CMAKE_BUILD_TYPE "Debug")
endif()
option(SKIP_SPIRV_TOOLS_INSTALL "Skip installation" ${SKIP_SPIRV_TOOLS_INSTALL})
if(NOT ${SKIP_SPIRV_TOOLS_INSTALL})
set(ENABLE_SPIRV_TOOLS_INSTALL ON)
endif()
option(SPIRV_BUILD_COMPRESSION "Build SPIR-V compressing codec" OFF)
if(SPIRV_BUILD_COMPRESSION)
message(FATAL_ERROR "SPIR-V compression codec has been removed from SPIR-V tools. "
"Please remove SPIRV_BUILD_COMPRESSION from your build options.")
endif(SPIRV_BUILD_COMPRESSION)
option(SPIRV_WERROR "Enable error on warning" ON)
if(("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR (("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") AND (NOT CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC")))
set(COMPILER_IS_LIKE_GNU TRUE)
endif()
if(${COMPILER_IS_LIKE_GNU})
set(SPIRV_WARNINGS -Wall -Wextra -Wnon-virtual-dtor -Wno-missing-field-initializers)
if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
set(SPIRV_WARNINGS ${SPIRV_WARNINGS} -Wno-self-assign)
endif()
option(SPIRV_WARN_EVERYTHING "Enable -Weverything" ${SPIRV_WARN_EVERYTHING})
if(${SPIRV_WARN_EVERYTHING})
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
set(SPIRV_WARNINGS ${SPIRV_WARNINGS}
-Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded)
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
set(SPIRV_WARNINGS ${SPIRV_WARNINGS} -Wpedantic -pedantic-errors)
else()
message(STATUS "Unknown compiler ${CMAKE_CXX_COMPILER_ID}, "
"so SPIRV_WARN_EVERYTHING has no effect")
endif()
endif()
if(${SPIRV_WERROR})
set(SPIRV_WARNINGS ${SPIRV_WARNINGS} -Werror)
endif()
elseif(MSVC)
set(SPIRV_WARNINGS -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS /wd4800)
if(${SPIRV_WERROR})
set(SPIRV_WARNINGS ${SPIRV_WARNINGS} /WX)
endif()
endif()
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/)
option(SPIRV_COLOR_TERMINAL "Enable color terminal output" ON)
if(${SPIRV_COLOR_TERMINAL})
add_definitions(-DSPIRV_COLOR_TERMINAL)
endif()
option(SPIRV_LOG_DEBUG "Enable excessive debug output" OFF)
if(${SPIRV_LOG_DEBUG})
add_definitions(-DSPIRV_LOG_DEBUG)
endif()
if (DEFINED SPIRV_TOOLS_EXTRA_DEFINITIONS)
add_definitions(${SPIRV_TOOLS_EXTRA_DEFINITIONS})
endif()
function(spvtools_default_compile_options TARGET)
target_compile_options(${TARGET} PRIVATE ${SPIRV_WARNINGS})
if (${COMPILER_IS_LIKE_GNU})
target_compile_options(${TARGET} PRIVATE
-std=c++11 -fno-exceptions -fno-rtti)
target_compile_options(${TARGET} PRIVATE
-Wall -Wextra -Wno-long-long -Wshadow -Wundef -Wconversion
-Wno-sign-conversion)
# For good call stacks in profiles, keep the frame pointers.
if(NOT "${SPIRV_PERF}" STREQUAL "")
target_compile_options(${TARGET} PRIVATE -fno-omit-frame-pointer)
endif()
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
set(SPIRV_USE_SANITIZER "" CACHE STRING
"Use the clang sanitizer [address|memory|thread|...]")
if(NOT "${SPIRV_USE_SANITIZER}" STREQUAL "")
target_compile_options(${TARGET} PRIVATE
-fsanitize=${SPIRV_USE_SANITIZER})
set_target_properties(${TARGET} PROPERTIES
LINK_FLAGS -fsanitize=${SPIRV_USE_SANITIZER})
endif()
target_compile_options(${TARGET} PRIVATE
-ftemplate-depth=1024)
else()
target_compile_options(${TARGET} PRIVATE
-Wno-missing-field-initializers)
endif()
endif()
if (MSVC)
# Specify /EHs for exception handling. This makes using SPIRV-Tools as
# dependencies in other projects easier.
target_compile_options(${TARGET} PRIVATE /EHs)
endif()
# For MinGW cross compile, statically link to the C++ runtime.
# But it still depends on MSVCRT.dll.
if (${CMAKE_SYSTEM_NAME} MATCHES "Windows")
if (${CMAKE_CXX_COMPILER_ID} MATCHES "GNU")
set_target_properties(${TARGET} PROPERTIES
LINK_FLAGS -static -static-libgcc -static-libstdc++)
endif()
endif()
endfunction()
if(NOT COMMAND find_host_package)
macro(find_host_package)
find_package(${ARGN})
endmacro()
endif()
if(NOT COMMAND find_host_program)
macro(find_host_program)
find_program(${ARGN})
endmacro()
endif()
# Tests require Python3
find_host_package(PythonInterp 3 REQUIRED)
# Check for symbol exports on Linux.
# At the moment, this check will fail on the OSX build machines for the Android NDK.
# It appears they don't have objdump.
if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux")
macro(spvtools_check_symbol_exports TARGET)
if (NOT "${SPIRV_SKIP_TESTS}")
add_test(NAME spirv-tools-symbol-exports-${TARGET}
COMMAND ${PYTHON_EXECUTABLE}
${spirv-tools_SOURCE_DIR}/utils/check_symbol_exports.py "$<TARGET_FILE:${TARGET}>")
endif()
endmacro()
else()
macro(spvtools_check_symbol_exports TARGET)
if (NOT "${SPIRV_SKIP_TESTS}")
message("Skipping symbol exports test for ${TARGET}")
endif()
endmacro()
endif()
# Defaults to OFF if the user didn't set it.
option(SPIRV_SKIP_EXECUTABLES
"Skip building the executable and tests along with the library"
${SPIRV_SKIP_EXECUTABLES})
option(SPIRV_SKIP_TESTS
"Skip building tests along with the library" ${SPIRV_SKIP_TESTS})
if ("${SPIRV_SKIP_EXECUTABLES}")
set(SPIRV_SKIP_TESTS ON)
endif()
# Defaults to ON. The checks can be time consuming.
# Turn off if they take too long.
option(SPIRV_CHECK_CONTEXT "In a debug build, check if the IR context is in a valid state." ON)
if (${SPIRV_CHECK_CONTEXT})
add_definitions(-DSPIRV_CHECK_CONTEXT)
endif()
# Precompiled header macro. Parameters are source file list and filename for pch cpp file.
macro(spvtools_pch SRCS PCHPREFIX)
if(MSVC AND CMAKE_GENERATOR MATCHES "^Visual Studio")
set(PCH_NAME "$(IntDir)\\${PCHPREFIX}.pch")
# make source files use/depend on PCH_NAME
set_source_files_properties(${${SRCS}} PROPERTIES COMPILE_FLAGS "/Yu${PCHPREFIX}.h /FI${PCHPREFIX}.h /Fp${PCH_NAME} /Zm300" OBJECT_DEPENDS "${PCH_NAME}")
# make PCHPREFIX.cpp file compile and generate PCH_NAME
set_source_files_properties("${PCHPREFIX}.cpp" PROPERTIES COMPILE_FLAGS "/Yc${PCHPREFIX}.h /Fp${PCH_NAME} /Zm300" OBJECT_OUTPUTS "${PCH_NAME}")
list(APPEND ${SRCS} "${PCHPREFIX}.cpp")
endif()
endmacro(spvtools_pch)
add_subdirectory(external)
# Warning about extra semi-colons.
#
# This is not supported on all compilers/versions. so enabling only
# for clang, since that works for all versions that our bots run.
#
# This is intentionally done after adding the external subdirectory,
# so we don't enforce this flag on our dependencies, some of which do
# not pass it.
#
# If the minimum version of CMake supported is updated to 3.0 or
# later, then check_cxx_compiler_flag could be used instead.
if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
add_compile_options("-Wextra-semi")
endif()
add_subdirectory(source)
add_subdirectory(tools)
add_subdirectory(test)
add_subdirectory(examples)
if(ENABLE_SPIRV_TOOLS_INSTALL)
install(
FILES
${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/libspirv.h
${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/libspirv.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/optimizer.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/linker.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/instrument.hpp
DESTINATION
${CMAKE_INSTALL_INCLUDEDIR}/spirv-tools/)
endif(ENABLE_SPIRV_TOOLS_INSTALL)
if (NOT "${SPIRV_SKIP_TESTS}")
add_test(NAME spirv-tools-copyrights
COMMAND ${PYTHON_EXECUTABLE} utils/check_copyright.py
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
endif()
set(SPIRV_LIBRARIES "-lSPIRV-Tools -lSPIRV-Tools-link -lSPIRV-Tools-opt")
set(SPIRV_SHARED_LIBRARIES "-lSPIRV-Tools-shared")
# Build pkg-config file
# Use a first-class target so it's regenerated when relevant files are updated.
add_custom_target(spirv-tools-pkg-config ALL
COMMAND ${CMAKE_COMMAND}
-DCHANGES_FILE=${CMAKE_CURRENT_SOURCE_DIR}/CHANGES
-DTEMPLATE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/cmake/SPIRV-Tools.pc.in
-DOUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/SPIRV-Tools.pc
-DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}
-DCMAKE_INSTALL_LIBDIR=${CMAKE_INSTALL_LIBDIR}
-DCMAKE_INSTALL_INCLUDEDIR=${CMAKE_INSTALL_INCLUDEDIR}
-DSPIRV_LIBRARIES=${SPIRV_LIBRARIES}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/write_pkg_config.cmake
DEPENDS "CHANGES" "cmake/SPIRV-Tools.pc.in" "cmake/write_pkg_config.cmake")
add_custom_target(spirv-tools-shared-pkg-config ALL
COMMAND ${CMAKE_COMMAND}
-DCHANGES_FILE=${CMAKE_CURRENT_SOURCE_DIR}/CHANGES
-DTEMPLATE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/cmake/SPIRV-Tools-shared.pc.in
-DOUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/SPIRV-Tools-shared.pc
-DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}
-DCMAKE_INSTALL_LIBDIR=${CMAKE_INSTALL_LIBDIR}
-DCMAKE_INSTALL_INCLUDEDIR=${CMAKE_INSTALL_INCLUDEDIR}
-DSPIRV_SHARED_LIBRARIES=${SPIRV_SHARED_LIBRARIES}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/write_pkg_config.cmake
DEPENDS "CHANGES" "cmake/SPIRV-Tools-shared.pc.in" "cmake/write_pkg_config.cmake")
# Install pkg-config file
if (ENABLE_SPIRV_TOOLS_INSTALL)
install(
FILES
${CMAKE_CURRENT_BINARY_DIR}/SPIRV-Tools.pc
${CMAKE_CURRENT_BINARY_DIR}/SPIRV-Tools-shared.pc
DESTINATION
${CMAKE_INSTALL_LIBDIR}/pkgconfig)
endif()

View File

@@ -1 +0,0 @@
A reminder that this issue tracker is managed by the Khronos Group. Interactions here should follow the Khronos Code of Conduct (https://www.khronos.org/developers/code-of-conduct), which prohibits aggressive or derogatory language. Please keep the discussion friendly and civil.

View File

@@ -3,9 +3,9 @@ use_relative_paths = True
vars = {
'github': 'https://github.com',
'effcee_revision': 'b83b58d177b797edd1f94c5f10837f2cc2863f0a',
'googletest_revision': '2f42d769ad1b08742f7ccb5ad4dd357fc5ff248c',
're2_revision': 'e356bd3f80e0c15c1050323bb5a2d0f8ea4845f4',
'effcee_revision': '6fa2a03cebb4fb18fbad086d53d1054928bef54e',
'googletest_revision': 'f2fb48c3b3d79a75a88a99fba6576b25d42ec528',
're2_revision': '5bd613749fd530b576b890283bfb6bc6ea6246cb',
'spirv_headers_revision': '601d738723ac381741311c6c98c36d6170be14a2',
}

View File

@@ -1,602 +0,0 @@
# 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)
<img alt="Linux" src="kokoro/img/linux.png" width="20px" height="20px" hspace="2px"/>[![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)
<img alt="MacOS" src="kokoro/img/macos.png" width="20px" height="20px" hspace="2px"/>[![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)
<img alt="Windows" src="kokoro/img/windows.png" width="20px" height="20px" hspace="2px"/>[![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 `<spirv-dir>/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.
The project uses [CMake][cmake] to generate platform-specific build
configurations. Assume that `<spirv-dir>` is the root directory of the checked
out code:
```sh
cd <spirv-dir>
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
mkdir build && cd build
cmake [-G <platform-generator>] <spirv-dir>
```
*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.
Once the build files have been generated, build using your preferred
development environment.
### 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/): for generating compilation targets. Version
2.8.12 or later.
- [Python 3](http://www.python.org/): for utility scripts and running the test
suite.
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=<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 <spirv-dir>
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 `<spirv-dir>/include`, which will enable the application to include the
header `<spirv-dir>/include/spirv-tools/libspirv.h{|pp}` then linking against
the static library in `<spirv-build-dir>/source/libSPIRV-Tools.a` or
`<spirv-build-dir>/source/SPIRV-Tools.lib`.
For optimization, the header file is
`<spirv-dir>/include/spirv-tools/optimizer.hpp`, and the static library is
`<spirv-build-dir>/source/libSPIRV-Tools-opt.a` or
`<spirv-build-dir>/source/SPIRV-Tools-opt.lib`.
* `SPIRV-Tools` CMake target: Creates the static library:
* `<spirv-build-dir>/source/libSPIRV-Tools.a` on Linux and OS X.
* `<spirv-build-dir>/source/libSPIRV-Tools.lib` on Windows.
* `SPIRV-Tools-opt` CMake target: Creates the static library:
* `<spirv-build-dir>/source/libSPIRV-Tools-opt.a` on Linux and OS X.
* `<spirv-build-dir>/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
`<spirv-build-dir>/tools/spirv-as`. The functionality of the assembler is implemented
by the `spvTextToBinary` library function.
* `spirv-as` - the standalone assembler
* `<spirv-dir>/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
`<spirv-build-dir>/tools/spirv-dis`. The functionality of the disassembler is implemented
by the `spvBinaryToText` library function.
* `spirv-dis` - the standalone disassembler
* `<spirv-dir>/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
* `<spirv-dir>/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
* `<spirv-dir>/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
`<spirv-build-dir>/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
* `<spirv-dir>/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
* `<spirv-dir>/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
* `<spirv-dir>/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
<a name="future"></a>
_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
<a name="license"></a>
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

View File

@@ -1,12 +0,0 @@
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=${prefix}
libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
Name: SPIRV-Tools
Description: Tools for SPIR-V
Version: @CURRENT_VERSION@
URL: https://github.com/KhronosGroup/SPIRV-Tools
Libs: -L${libdir} @SPIRV_SHARED_LIBRARIES@
Cflags: -I${includedir}

View File

@@ -1,12 +0,0 @@
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=${prefix}
libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
Name: SPIRV-Tools
Description: Tools for SPIR-V
Version: @CURRENT_VERSION@
URL: https://github.com/KhronosGroup/SPIRV-Tools
Libs: -L${libdir} @SPIRV_LIBRARIES@
Cflags: -I${includedir}

View File

@@ -1,20 +0,0 @@
# Find nosetests; see spirv_add_nosetests() for opting in to nosetests in a
# specific directory.
find_program(NOSETESTS_EXE NAMES nosetests PATHS $ENV{PYTHON_PACKAGE_PATH})
if (NOT NOSETESTS_EXE)
message(STATUS "SPIRV-Tools: nosetests was not found - python support code will not be tested")
else()
message(STATUS "SPIRV-Tools: nosetests found - python support code will be tested")
endif()
# Run nosetests on file ${PREFIX}_nosetest.py. Nosetests will look for classes
# and functions whose names start with "nosetest". The test name will be
# ${PREFIX}_nosetests.
function(spirv_add_nosetests PREFIX)
if(NOT "${SPIRV_SKIP_TESTS}" AND NOSETESTS_EXE)
add_test(
NAME ${PREFIX}_nosetests
COMMAND ${NOSETESTS_EXE} -m "^[Nn]ose[Tt]est" -v
${CMAKE_CURRENT_SOURCE_DIR}/${PREFIX}_nosetest.py)
endif()
endfunction()

View File

@@ -1,31 +0,0 @@
# Copyright (c) 2017 Pierre Moreau
#
# 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.
# First, retrieve the current version from CHANGES
file(STRINGS ${CHANGES_FILE} CHANGES_CONTENT)
string(
REGEX
MATCH "v[0-9]+(.[0-9]+)?(-dev)? [0-9]+-[0-9]+-[0-9]+"
FIRST_VERSION_LINE
${CHANGES_CONTENT})
string(
REGEX
REPLACE "^v([^ ]+) .+$" "\\1"
CURRENT_VERSION
"${FIRST_VERSION_LINE}")
# If this is a development version, replace "-dev" by ".0" as pkg-config nor
# CMake support "-dev" in the version.
# If it's not a "-dev" version then ensure it ends with ".1"
string(REGEX REPLACE "-dev.1" ".0" CURRENT_VERSION "${CURRENT_VERSION}.1")
configure_file(${TEMPLATE_FILE} ${OUT_FILE} @ONLY)

View File

@@ -1 +1 @@
"v2019.5-dev", "SPIRV-Tools v2019.5-dev v2019.4-71-g6b072126"
"v2019.5-dev", "SPIRV-Tools v2019.5-dev v2019.4-89-g84b19760"

View File

@@ -283,7 +283,6 @@ set(SPIRV_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/val/validate_composites.cpp
${CMAKE_CURRENT_SOURCE_DIR}/val/validate_constants.cpp
${CMAKE_CURRENT_SOURCE_DIR}/val/validate_conversion.cpp
${CMAKE_CURRENT_SOURCE_DIR}/val/validate_datarules.cpp
${CMAKE_CURRENT_SOURCE_DIR}/val/validate_debug.cpp
${CMAKE_CURRENT_SOURCE_DIR}/val/validate_decorations.cpp
${CMAKE_CURRENT_SOURCE_DIR}/val/validate_derivatives.cpp

View File

@@ -15,6 +15,7 @@
#include "source/fuzz/fuzzer.h"
#include <cassert>
#include <memory>
#include <sstream>
#include "source/fuzz/fact_manager.h"
@@ -38,8 +39,25 @@ namespace fuzz {
namespace {
const uint32_t kIdBoundGap = 100;
const uint32_t kTransformationLimit = 500;
const uint32_t kChanceOfApplyingAnotherPass = 85;
template <typename T>
void MaybeAddPass(
std::vector<std::unique_ptr<FuzzerPass>>* passes,
opt::IRContext* ir_context, FactManager* fact_manager,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformation_sequence_out) {
if (fuzzer_context->ChooseEven()) {
passes->push_back(MakeUnique<T>(ir_context, fact_manager, fuzzer_context,
transformation_sequence_out));
}
}
} // namespace
struct Fuzzer::Impl {
explicit Impl(spv_target_env env) : target_env(env) {}
@@ -109,29 +127,40 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
.Apply();
// Apply some semantics-preserving passes.
FuzzerPassCopyObjects(ir_context.get(), &fact_manager, &fuzzer_context,
transformation_sequence_out)
.Apply();
FuzzerPassApplyIdSynonyms(ir_context.get(), &fact_manager, &fuzzer_context,
transformation_sequence_out)
.Apply();
FuzzerPassSplitBlocks(ir_context.get(), &fact_manager, &fuzzer_context,
transformation_sequence_out)
.Apply();
FuzzerPassAddDeadBreaks(ir_context.get(), &fact_manager, &fuzzer_context,
transformation_sequence_out)
.Apply();
FuzzerPassAddDeadContinues(ir_context.get(), &fact_manager, &fuzzer_context,
transformation_sequence_out)
.Apply();
FuzzerPassObfuscateConstants(ir_context.get(), &fact_manager, &fuzzer_context,
transformation_sequence_out)
.Apply();
std::vector<std::unique_ptr<FuzzerPass>> passes;
while (passes.empty()) {
MaybeAddPass<FuzzerPassAddDeadBreaks>(&passes, ir_context.get(),
&fact_manager, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddDeadContinues>(&passes, ir_context.get(),
&fact_manager, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassApplyIdSynonyms>(&passes, ir_context.get(),
&fact_manager, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassCopyObjects>(&passes, ir_context.get(),
&fact_manager, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassObfuscateConstants>(&passes, ir_context.get(),
&fact_manager, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassPermuteBlocks>(&passes, ir_context.get(),
&fact_manager, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassSplitBlocks>(&passes, ir_context.get(),
&fact_manager, &fuzzer_context,
transformation_sequence_out);
}
// Finally, give the blocks in the module a good shake-up.
FuzzerPassPermuteBlocks(ir_context.get(), &fact_manager, &fuzzer_context,
transformation_sequence_out)
.Apply();
bool is_first = true;
while (static_cast<uint32_t>(
transformation_sequence_out->transformation_size()) <
kTransformationLimit &&
(is_first ||
fuzzer_context.ChoosePercentage(kChanceOfApplyingAnotherPass))) {
is_first = false;
passes[fuzzer_context.RandomIndex(passes)]->Apply();
}
// Encode the module as a binary.
ir_context->module()->ToBinary(binary_out, false);

View File

@@ -20,17 +20,16 @@ namespace spvtools {
namespace fuzz {
namespace {
// Default probabilities for applying various transformations.
// All values are percentages.
// Keep them in alphabetical order.
// Default <minimum, maximum> pairs of probabilities for applying various
// transformations. All values are percentages. Keep them in alphabetical order.
const uint32_t kDefaultChanceOfAddingDeadBreak = 20;
const uint32_t kDefaultChanceOfAddingDeadContinue = 20;
const uint32_t kDefaultChanceOfCopyingObject = 20;
const uint32_t kDefaultChanceOfMovingBlockDown = 25;
const uint32_t kDefaultChanceOfObfuscatingConstant = 20;
const uint32_t kDefaultChanceOfReplacingIdWithSynonym = 20;
const uint32_t kDefaultChanceOfSplittingBlock = 20;
const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBreak = {5, 80};
const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadContinue = {5, 80};
const std::pair<uint32_t, uint32_t> kChanceOfCopyingObject = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfMovingBlockDown = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfObfuscatingConstant = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdWithSynonym = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfSplittingBlock = {40, 95};
// Default functions for controlling how deep to go during recursive
// generation/transformation. Keep them in alphabetical order.
@@ -48,16 +47,21 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
uint32_t min_fresh_id)
: random_generator_(random_generator),
next_fresh_id_(min_fresh_id),
chance_of_adding_dead_break_(kDefaultChanceOfAddingDeadBreak),
chance_of_adding_dead_continue_(kDefaultChanceOfAddingDeadContinue),
chance_of_copying_object_(kDefaultChanceOfCopyingObject),
chance_of_moving_block_down_(kDefaultChanceOfMovingBlockDown),
chance_of_obfuscating_constant_(kDefaultChanceOfObfuscatingConstant),
chance_of_replacing_id_with_synonym_(
kDefaultChanceOfReplacingIdWithSynonym),
chance_of_splitting_block_(kDefaultChanceOfSplittingBlock),
go_deeper_in_constant_obfuscation_(
kDefaultGoDeeperInConstantObfuscation) {}
kDefaultGoDeeperInConstantObfuscation) {
chance_of_adding_dead_break_ =
ChooseBetweenMinAndMax(kChanceOfAddingDeadBreak);
chance_of_adding_dead_continue_ =
ChooseBetweenMinAndMax(kChanceOfAddingDeadContinue);
chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject);
chance_of_moving_block_down_ =
ChooseBetweenMinAndMax(kChanceOfMovingBlockDown);
chance_of_obfuscating_constant_ =
ChooseBetweenMinAndMax(kChanceOfObfuscatingConstant);
chance_of_replacing_id_with_synonym_ =
ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym);
chance_of_splitting_block_ = ChooseBetweenMinAndMax(kChanceOfSplittingBlock);
}
FuzzerContext::~FuzzerContext() = default;
@@ -70,5 +74,12 @@ bool FuzzerContext::ChoosePercentage(uint32_t percentage_chance) {
return random_generator_->RandomPercentage() < percentage_chance;
}
uint32_t FuzzerContext::ChooseBetweenMinAndMax(
const std::pair<uint32_t, uint32_t>& min_max) {
assert(min_max.first <= min_max.second);
return min_max.first +
random_generator_->RandomUint32(min_max.second - min_max.first + 1);
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -16,6 +16,7 @@
#define SOURCE_FUZZ_FUZZER_CONTEXT_H_
#include <functional>
#include <utility>
#include "source/fuzz/random_generator.h"
#include "source/opt/function.h"
@@ -45,7 +46,7 @@ class FuzzerContext {
// method, and which must be non-empty. Typically 'HasSizeMethod' will be an
// std::vector.
template <typename HasSizeMethod>
uint32_t RandomIndex(HasSizeMethod sequence) {
uint32_t RandomIndex(const HasSizeMethod& sequence) {
assert(sequence.size() > 0);
return random_generator_->RandomUint32(
static_cast<uint32_t>(sequence.size()));
@@ -97,6 +98,10 @@ class FuzzerContext {
// or mutating constructs recursively.
const std::function<bool(uint32_t, RandomGenerator*)>&
go_deeper_in_constant_obfuscation_;
// Requires |min_max.first| <= |min_max.second|, and returns a value in the
// range [ |min_max.first|, |min_max.second| ]
uint32_t ChooseBetweenMinAndMax(const std::pair<uint32_t, uint32_t>& min_max);
};
} // namespace fuzz

View File

@@ -47,6 +47,12 @@ void FuzzerPassApplyIdSynonyms::Apply() {
return;
}
// |use_index| is the absolute index of the operand. We require
// the index of the operand restricted to input operands only, so
// we subtract the number of non-input operands from |use_index|.
uint32_t use_in_operand_index =
use_index - use_inst->NumOperands() + use_inst->NumInOperands();
std::vector<const protobufs::DataDescriptor*> synonyms_to_try;
for (auto& data_descriptor :
GetFactManager()->GetSynonymsForId(id_with_known_synonyms)) {
@@ -63,19 +69,14 @@ void FuzzerPassApplyIdSynonyms::Apply() {
if (!TransformationReplaceIdWithSynonym::
ReplacingUseWithSynonymIsOk(GetIRContext(), use_inst,
use_index, *synonym_to_try)) {
use_in_operand_index,
*synonym_to_try)) {
continue;
}
// |use_index| is the absolute index of the operand. We require
// the index of the operand restricted to input operands only, so
// we subtract the number of non-input operands from |use_index|.
uint32_t number_of_non_input_operands =
use_inst->NumOperands() - use_inst->NumInOperands();
TransformationReplaceIdWithSynonym replace_id_transformation(
transformation::MakeIdUseDescriptorFromUse(
GetIRContext(), use_inst,
use_index - number_of_non_input_operands),
GetIRContext(), use_inst, use_in_operand_index),
*synonym_to_try, 0);
// The transformation should be applicable by construction.
assert(replace_id_transformation.IsApplicable(GetIRContext(),

View File

@@ -205,156 +205,71 @@ opt::BasicBlock::iterator GetIteratorForBaseInstructionAndOffset(
return nullptr;
}
// Returns the ids of all successors of |block|
std::vector<uint32_t> GetSuccessors(opt::BasicBlock* block) {
std::vector<uint32_t> result;
switch (block->terminator()->opcode()) {
case SpvOpBranch:
result.push_back(block->terminator()->GetSingleWordInOperand(0));
break;
case SpvOpBranchConditional:
result.push_back(block->terminator()->GetSingleWordInOperand(1));
result.push_back(block->terminator()->GetSingleWordInOperand(2));
break;
case SpvOpSwitch:
for (uint32_t i = 1; i < block->terminator()->NumInOperands(); i += 2) {
result.push_back(block->terminator()->GetSingleWordInOperand(i));
}
break;
default:
break;
bool NewEdgeRespectsUseDefDominance(opt::IRContext* context,
opt::BasicBlock* bb_from,
opt::BasicBlock* bb_to) {
assert(bb_from->terminator()->opcode() == SpvOpBranch);
// If there is *already* an edge from |bb_from| to |bb_to|, then adding
// another edge is fine from a dominance point of view.
if (bb_from->terminator()->GetSingleWordInOperand(0) == bb_to->id()) {
return true;
}
return result;
}
// The FindBypassedBlocks method and its helpers perform a depth-first search;
// this struct represents an element of the stack used during depth-first
// search.
struct FindBypassedBlocksDfsStackNode {
opt::BasicBlock* block; // The block that is being explored
bool handled_merge; // We visit merge blocks before successors; this field
// tracks whether we have yet processed the merge block
// (if any) associated with the block
uint32_t next_successor; // The next as-yet unexplored successor of this
// block; exploration of a block is complete when
// this field's value reaches the successor count
};
// Helper method for the depth-first-search routine that collects blocks that a
// new break or continue control flow graph edge will bypass.
void HandleSuccessorDuringSearchForBypassedBlocks(
opt::BasicBlock* successor, bool new_blocks_will_be_bypassed,
std::set<uint32_t>* already_visited,
std::set<opt::BasicBlock*>* bypassed_blocks,
std::vector<FindBypassedBlocksDfsStackNode>* dfs_stack) {
if (already_visited->count(successor->id()) == 0) {
// This is a new block; mark it as visited so that we don't regard it as new
// in the future, and push it on to the stack for exploration.
already_visited->insert(successor->id());
dfs_stack->push_back({successor, false, 0});
if (new_blocks_will_be_bypassed) {
// We are in the region of the control-flow graph consisting of blocks
// that the new edge will bypass, so grab this block.
bypassed_blocks->insert(successor);
}
}
}
// Determines those block that will be bypassed by a break or continue edge from
// |bb_from| to |bb_to|.
void FindBypassedBlocks(opt::IRContext* context, opt::BasicBlock* bb_from,
opt::BasicBlock* bb_to,
std::set<opt::BasicBlock*>* bypassed_blocks) {
// This algorithm finds all blocks different from |bb_from| that:
// - are in the innermost structured control flow construct containing
// |bb_from|
// - can be reached from |bb_from| without traversing a back-edge or going
// through |bb_to|
// Let us assume that the module being manipulated is valid according to the
// rules of the SPIR-V language.
//
// This is achieved by doing a depth-first search of the function's CFG,
// exploring merge blocks before successors, and grabbing all blocks that are
// visited in the sub-search rooted at |bb_from|. (As an optimization, the
// search terminates as soon as exploration of |bb_from| has completed.)
// Suppose that some block Y is dominated by |bb_to| (which includes the case
// where Y = |bb_to|).
//
// Suppose that Y uses an id i that is defined in some other block X.
//
// Because the module is valid, X must dominate Y. We are concerned about
// whether an edge from |bb_from| to |bb_to| could *stop* X from dominating
// Y.
//
// Because |bb_to| dominates Y, a new edge from |bb_from| to |bb_to| can
// only affect whether X dominates Y if X dominates |bb_to|.
//
// So let us assume that X does dominate |bb_to|, so that we have:
//
// (X defines i) dominates |bb_to| dominates (Y uses i)
//
// The new edge from |bb_from| to |bb_to| will stop the definition of i in X
// from dominating the use of i in Y exactly when the new edge will stop X
// from dominating |bb_to|.
//
// Now, the block X that we are worried about cannot dominate |bb_from|,
// because in that case X would still dominate |bb_to| after we add an edge
// from |bb_from| to |bb_to|.
//
// Also, it cannot be that X = |bb_to|, because nothing can stop a block
// from dominating itself.
//
// So we are looking for a block X such that:
//
// - X strictly dominates |bb_to|
// - X does not dominate |bb_from|
// - X defines an id i
// - i is used in some block Y
// - |bb_to| dominates Y
auto enclosing_function = bb_from->GetParent();
// The set of block ids already visited during search. We put |bb_to| in
// there initially so that search automatically backtracks when this block is
// reached.
std::set<uint32_t> already_visited;
already_visited.insert(bb_to->id());
// Tracks when we are in the region of blocks that the new edge would bypass;
// we flip this to 'true' once we reach |bb_from| and have finished searching
// its merge block (in the case that it happens to be a header.
bool new_blocks_will_be_bypassed = false;
std::vector<FindBypassedBlocksDfsStackNode> dfs_stack;
opt::BasicBlock* entry_block = enclosing_function->entry().get();
dfs_stack.push_back({entry_block, false, 0});
while (!dfs_stack.empty()) {
auto node_index = dfs_stack.size() - 1;
// First make sure we search the merge block associated ith this block, if
// there is one.
if (!dfs_stack[node_index].handled_merge) {
dfs_stack[node_index].handled_merge = true;
if (dfs_stack[node_index].block->MergeBlockIdIfAny()) {
opt::BasicBlock* merge_block = context->cfg()->block(
dfs_stack[node_index].block->MergeBlockIdIfAny());
// A block can only be the merge block for one header, so this block
// should only be in |visited| if it is |bb_to|, which we put into
// |visited| in advance.
assert(already_visited.count(merge_block->id()) == 0 ||
merge_block == bb_to);
HandleSuccessorDuringSearchForBypassedBlocks(
merge_block, new_blocks_will_be_bypassed, &already_visited,
bypassed_blocks, &dfs_stack);
}
continue;
}
// If we find |bb_from|, we are interested in grabbing previously unseen
// successor blocks (by this point we will have already searched the merge
// block associated with |bb_from|, if there is one.
if (dfs_stack[node_index].block == bb_from) {
new_blocks_will_be_bypassed = true;
}
// Consider the next unexplored successor.
auto successors = GetSuccessors(dfs_stack[node_index].block);
if (dfs_stack[node_index].next_successor < successors.size()) {
HandleSuccessorDuringSearchForBypassedBlocks(
context->cfg()->block(
successors[dfs_stack[node_index].next_successor]),
new_blocks_will_be_bypassed, &already_visited, bypassed_blocks,
&dfs_stack);
dfs_stack[node_index].next_successor++;
} else {
// We have finished exploring |node|. If it is |bb_from|, we can
// terminate search -- we have grabbed all the relevant blocks.
if (dfs_stack[node_index].block == bb_from) {
break;
}
dfs_stack.pop_back();
}
}
}
bool NewEdgeLeavingConstructBodyRespectsUseDefDominance(
opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to) {
// Find those blocks that the edge from |bb_from| to |bb_to| might bypass.
std::set<opt::BasicBlock*> bypassed_blocks;
FindBypassedBlocks(context, bb_from, bb_to, &bypassed_blocks);
// For each bypassed block, check whether it contains a definition that is
// used by some non-bypassed block - that would be problematic.
for (auto defining_block : bypassed_blocks) {
for (auto& inst : *defining_block) {
// Walk the dominator tree backwards, starting from the immediate dominator
// of |bb_to|. We can stop when we find a block that also dominates
// |bb_from|.
auto dominator_analysis = context->GetDominatorAnalysis(bb_from->GetParent());
for (auto dominator = dominator_analysis->ImmediateDominator(bb_to);
dominator != nullptr &&
!dominator_analysis->Dominates(dominator, bb_from);
dominator = dominator_analysis->ImmediateDominator(dominator)) {
// |dominator| is a candidate for block X in the above description.
// We now look through the instructions for a candidate instruction i.
for (auto& inst : *dominator) {
// Consider all the uses of this instruction.
if (!context->get_def_use_mgr()->WhileEachUse(
&inst,
[context, &bypassed_blocks](opt::Instruction* user,
uint32_t operand_index) -> bool {
[bb_to, context, dominator_analysis](
opt::Instruction* user, uint32_t operand_index) -> bool {
// If this use is in an OpPhi, we need to check that dominance
// of the relevant *parent* block is not spoiled. Otherwise we
// need to check that dominance of the block containing the use
@@ -371,13 +286,12 @@ bool NewEdgeLeavingConstructBodyRespectsUseDefDominance(
return true;
}
// If the use-block is not in |bypassed_blocks| then we have
// found a block in the construct that is reachable from
// |from_block|, and which defines an id that is used outside of
// the construct. Adding an edge from |from_block| to
// |to_block| would prevent this use being dominated.
return bypassed_blocks.find(use_block_or_phi_parent) !=
bypassed_blocks.end();
// With reference to the above discussion,
// |use_block_or_phi_parent| is a candidate for the block Y.
// If |bb_to| dominates this block, the new edge would be
// problematic.
return !dominator_analysis->Dominates(bb_to,
use_block_or_phi_parent);
})) {
return false;
}
@@ -386,6 +300,13 @@ bool NewEdgeLeavingConstructBodyRespectsUseDefDominance(
return true;
}
bool BlockIsReachableInItsFunction(opt::IRContext* context,
opt::BasicBlock* bb) {
auto enclosing_function = bb->GetParent();
return context->GetDominatorAnalysis(enclosing_function)
->Dominates(enclosing_function->entry().get(), bb);
}
} // namespace fuzzerutil
} // namespace fuzz

View File

@@ -74,18 +74,18 @@ bool BlockIsInLoopContinueConstruct(opt::IRContext* context, uint32_t block_id,
opt::BasicBlock::iterator GetIteratorForBaseInstructionAndOffset(
opt::BasicBlock* block, const opt::Instruction* base_inst, uint32_t offset);
// Block |bb_from| is assumed to be in a structured control flow construct, and
// block |bb_to| is assumed to be either the merge bock for that construct (in
// the case of a loop, conditional or switch) or the continue target for that
// construct (in the case of a loop only).
//
// The function determines whether adding an edge from |bb_from| to |bb_to| -
// i.e. a break or continue for the construct
// - is legitimate with respect to the SPIR-V rule that a definition must
// is legitimate with respect to the SPIR-V rule that a definition must
// dominate all of its uses. This is because adding such an edge can change
// dominance in the control flow graph, potentially making the module invalid.
bool NewEdgeLeavingConstructBodyRespectsUseDefDominance(
opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to);
bool NewEdgeRespectsUseDefDominance(opt::IRContext* context,
opt::BasicBlock* bb_from,
opt::BasicBlock* bb_to);
// Returns true if and only if there is a path to |bb| from the entry block of
// the function that contains |bb|.
bool BlockIsReachableInItsFunction(opt::IRContext* context,
opt::BasicBlock* bb);
} // namespace fuzzerutil

View File

@@ -138,6 +138,13 @@ bool TransformationAddDeadBreak::IsApplicable(
return false;
}
if (!fuzzerutil::BlockIsReachableInItsFunction(context, bb_to)) {
// If the target of the break is unreachable, we conservatively do not
// allow adding a dead break, to avoid the compilations that arise due to
// the lack of sensible dominance information for unreachable blocks.
return false;
}
// Check that |message_.from_block| ends with an unconditional branch.
if (bb_from->terminator()->opcode() != SpvOpBranch) {
// The block associated with the id does not end with an unconditional
@@ -170,8 +177,7 @@ bool TransformationAddDeadBreak::IsApplicable(
// Check that adding the break would not violate the property that a
// definition must dominate all of its uses.
return fuzzerutil::NewEdgeLeavingConstructBodyRespectsUseDefDominance(
context, bb_from, bb_to);
return fuzzerutil::NewEdgeRespectsUseDefDominance(context, bb_from, bb_to);
}
void TransformationAddDeadBreak::Apply(opt::IRContext* context,

View File

@@ -83,14 +83,22 @@ bool TransformationAddDeadContinue::IsApplicable(
return false;
}
auto continue_block = context->cfg()->block(loop_header)->ContinueBlockId();
if (!fuzzerutil::BlockIsReachableInItsFunction(
context, context->cfg()->block(continue_block))) {
// If the loop's continue block is unreachable, we conservatively do not
// allow adding a dead continue, to avoid the compilations that arise due to
// the lack of sensible dominance information for unreachable blocks.
return false;
}
if (fuzzerutil::BlockIsInLoopContinueConstruct(context, message_.from_block(),
loop_header)) {
// We cannot jump to the continue target from the continue construct.
return false;
}
auto continue_block = context->cfg()->block(loop_header)->ContinueBlockId();
if (context->GetStructuredCFGAnalysis()->IsMergeBlock(continue_block)) {
// A branch straight to the continue target that is also a merge block might
// break the property that a construct header must dominate its merge block
@@ -100,7 +108,7 @@ bool TransformationAddDeadContinue::IsApplicable(
// Check that adding the continue would not violate the property that a
// definition must dominate all of its uses.
if (!fuzzerutil::NewEdgeLeavingConstructBodyRespectsUseDefDominance(
if (!fuzzerutil::NewEdgeRespectsUseDefDominance(
context, bb_from, context->cfg()->block(continue_block))) {
return false;
}

View File

@@ -36,6 +36,8 @@ class TransformationAddDeadContinue : public Transformation {
// - |message_.from_block| must be the id of a block a in the given module.
// - a must be contained in a loop with continue target b
// - The continue target b must be dominated by the head of the loop in which
// it is contained
// - b must not be the merge block of a selection construct
// - if |message_.continue_condition_value| holds (does not hold) then
// OpConstantTrue (OpConstantFalse) must be present in the module

View File

@@ -84,12 +84,12 @@ void TransformationMoveBlockDown::Apply(opt::IRContext* context,
"To be able to move a block down, it needs to have a "
"program-order successor.");
function.MoveBasicBlockToAfter(message_.block_id(), &*block_it);
// It is prudent to invalidate analyses after changing block ordering in
// case any of them depend on it, but the ones that definitely do not
// depend on ordering can be preserved. These include the following,
// which can likely be extended.
// For performance, it is vital to keep the dominator analysis valid
// (which due to https://github.com/KhronosGroup/SPIRV-Tools/issues/2889
// requires keeping the CFG analysis valid).
context->InvalidateAnalysesExceptFor(
opt::IRContext::Analysis::kAnalysisDefUse |
opt::IRContext::Analysis::kAnalysisCFG |
opt::IRContext::Analysis::kAnalysisDominatorAnalysis);
return;

View File

@@ -178,7 +178,7 @@ bool TransformationReplaceBooleanConstantWithConstantBinary::IsApplicable(
context->get_constant_mgr()->FindDeclaredConstant(message_.rhs_id());
bool expected_result = (boolean_constant->opcode() == SpvOpConstantTrue);
const SpvOp binary_opcode = static_cast<SpvOp>(message_.opcode());
const auto binary_opcode = static_cast<SpvOp>(message_.opcode());
// We consider the floating point, signed and unsigned integer cases
// separately. In each case the logic is very similar.
@@ -237,8 +237,17 @@ bool TransformationReplaceBooleanConstantWithConstantBinary::IsApplicable(
}
// The id use descriptor must identify some instruction
return transformation::FindInstruction(message_.id_use_descriptor(),
context) != nullptr;
auto instruction =
transformation::FindInstruction(message_.id_use_descriptor(), context);
if (instruction == nullptr) {
return false;
}
// The instruction must not be an OpPhi, as we cannot insert a binary
// operator instruction before an OpPhi.
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2902): there is
// scope for being less conservative.
return instruction->opcode() != SpvOpPhi;
}
void TransformationReplaceBooleanConstantWithConstantBinary::Apply(

View File

@@ -43,6 +43,12 @@ class TransformationReplaceBooleanConstantWithConstantBinary
// - |message_.opcode| must be suitable for applying to |message.lhs_id| and
// |message_.rhs_id|, and the result must evaluate to the boolean constant
// c.
// - The boolean constant usage must not be an argument to OpPhi, because in
// this case it is not legal to insert a binary operator instruction right
// before the OpPhi.
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2902): consider
// replacing a boolean in an OpPhi by adding a binary operator instruction
// to the parent block for the OpPhi.
bool IsApplicable(opt::IRContext* context,
const FactManager& fact_manager) const override;

View File

@@ -19,6 +19,7 @@
#include "source/fuzz/data_descriptor.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/id_use_descriptor.h"
#include "source/opt/types.h"
namespace spvtools {
namespace fuzz {
@@ -116,15 +117,74 @@ bool TransformationReplaceIdWithSynonym::ReplacingUseWithSynonymIsOk(
if (use_instruction->opcode() == SpvOpAccessChain &&
use_in_operand_index > 0) {
// This is an access chain index. If the object being accessed has
// pointer-to-struct type then we cannot replace the use with a synonym, as
// the use needs to be an OpConstant.
// This is an access chain index. If the (sub-)object being accessed by the
// given index has struct type then we cannot replace the use with a
// synonym, as the use needs to be an OpConstant.
// Get the top-level composite type that is being accessed.
auto object_being_accessed = context->get_def_use_mgr()->GetDef(
use_instruction->GetSingleWordInOperand(0));
auto pointer_type =
context->get_type_mgr()->GetType(object_being_accessed->type_id());
assert(pointer_type->AsPointer());
if (pointer_type->AsPointer()->pointee_type()->AsStruct()) {
auto composite_type_being_accessed =
pointer_type->AsPointer()->pointee_type();
// Now walk the access chain, tracking the type of each sub-object of the
// composite that is traversed, until the index of interest is reached.
for (uint32_t index_in_operand = 1; index_in_operand < use_in_operand_index;
index_in_operand++) {
// For vectors, matrices and arrays, getting the type of the sub-object is
// trivial. For the struct case, the sub-object type is field-sensitive,
// and depends on the constant index that is used.
if (composite_type_being_accessed->AsVector()) {
composite_type_being_accessed =
composite_type_being_accessed->AsVector()->element_type();
} else if (composite_type_being_accessed->AsMatrix()) {
composite_type_being_accessed =
composite_type_being_accessed->AsMatrix()->element_type();
} else if (composite_type_being_accessed->AsArray()) {
composite_type_being_accessed =
composite_type_being_accessed->AsArray()->element_type();
} else {
assert(composite_type_being_accessed->AsStruct());
auto constant_index_instruction = context->get_def_use_mgr()->GetDef(
use_instruction->GetSingleWordInOperand(index_in_operand));
assert(constant_index_instruction->opcode() == SpvOpConstant);
uint32_t member_index =
constant_index_instruction->GetSingleWordInOperand(0);
composite_type_being_accessed =
composite_type_being_accessed->AsStruct()
->element_types()[member_index];
}
}
// We have found the composite type being accessed by the index we are
// considering replacing. If it is a struct, then we cannot do the
// replacement as struct indices must be constants.
if (composite_type_being_accessed->AsStruct()) {
return false;
}
}
if (use_instruction->opcode() == SpvOpFunctionCall &&
use_in_operand_index > 0) {
// This is a function call argument. It is not allowed to have pointer
// type.
// Get the definition of the function being called.
auto function = context->get_def_use_mgr()->GetDef(
use_instruction->GetSingleWordInOperand(0));
// From the function definition, get the function type.
auto function_type =
context->get_def_use_mgr()->GetDef(function->GetSingleWordInOperand(1));
// OpTypeFunction's 0-th input operand is the function return type, and the
// function argument types follow. Because the arguments to OpFunctionCall
// start from input operand 1, we can use |use_in_operand_index| to get the
// type associated with this function argument.
auto parameter_type = context->get_type_mgr()->GetType(
function_type->GetSingleWordInOperand(use_in_operand_index));
if (parameter_type->AsPointer()) {
return false;
}
}

View File

@@ -41,6 +41,8 @@ class TransformationReplaceIdWithSynonym : public Transformation {
// dominated by their definitions.
// - The id must not be an index into an access chain whose base object has
// struct type, as such indices must be constants.
// - The id must not be a pointer argument to a function call (because the
// synonym might not be a memory object declaration).
// - |fresh_id_for_temporary| must be 0.
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2855): the
// motivation for the temporary is to support the case where an id is

View File

@@ -1274,6 +1274,12 @@ FoldingRule CompositeConstructFeedingExtract() {
"Wrong opcode. Should be OpCompositeExtract.");
analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
analysis::TypeManager* type_mgr = context->get_type_mgr();
// If there are no index operands, then this rule cannot do anything.
if (inst->NumInOperands() <= 1) {
return false;
}
uint32_t cid = inst->GetSingleWordInOperand(kExtractCompositeIdInIdx);
Instruction* cinst = def_use_mgr->GetDef(cid);
@@ -1401,6 +1407,20 @@ FoldingRule CompositeExtractFeedingConstruct() {
};
}
// Folds an OpCompositeExtract instruction with no indexes into an OpCopyObject.
bool ExtractWithNoIndexes(IRContext*, Instruction* inst,
const std::vector<const analysis::Constant*>&) {
assert(inst->opcode() == SpvOpCompositeExtract &&
"Wrong opcode. Should be OpCompositeExtract.");
if (inst->NumInOperands() > 1) {
return false;
}
inst->SetOpcode(SpvOpCopyObject);
return true;
}
FoldingRule InsertFeedingExtract() {
return [](IRContext* context, Instruction* inst,
const std::vector<const analysis::Constant*>&) {
@@ -2207,6 +2227,7 @@ void FoldingRules::AddFoldingRules() {
// Take that into consideration.
rules_[SpvOpCompositeConstruct].push_back(CompositeExtractFeedingConstruct());
rules_[SpvOpCompositeExtract].push_back(ExtractWithNoIndexes);
rules_[SpvOpCompositeExtract].push_back(InsertFeedingExtract());
rules_[SpvOpCompositeExtract].push_back(CompositeConstructFeedingExtract());
rules_[SpvOpCompositeExtract].push_back(VectorShuffleFeedingExtract());

View File

@@ -104,19 +104,24 @@ class Function {
});
}
// Runs the given function |f| on each instruction in this function, and
// optionally on debug line instructions that might precede them.
// Runs the given function |f| on instructions in this function, in order,
// and optionally on debug line instructions that might precede them.
void ForEachInst(const std::function<void(Instruction*)>& f,
bool run_on_debug_line_insts = false);
void ForEachInst(const std::function<void(const Instruction*)>& f,
bool run_on_debug_line_insts = false) const;
// Runs the given function |f| on instructions in this function, in order,
// and optionally on debug line instructions that might precede them.
// If |f| returns false, iteration is terminated and this function returns
// false.
bool WhileEachInst(const std::function<bool(Instruction*)>& f,
bool run_on_debug_line_insts = false);
bool WhileEachInst(const std::function<bool(const Instruction*)>& f,
bool run_on_debug_line_insts = false) const;
// Runs the given function |f| on each parameter instruction in this function,
// and optionally on debug line instructions that might precede them.
// in order, and optionally on debug line instructions that might precede
// them.
void ForEachParam(const std::function<void(const Instruction*)>& f,
bool run_on_debug_line_insts = false) const;
void ForEachParam(const std::function<void(Instruction*)>& f,

View File

@@ -46,6 +46,10 @@ uint32_t LocalAccessChainConvertPass::BuildAndAppendVarLoad(
const Instruction* ptrInst, uint32_t* varId, uint32_t* varPteTypeId,
std::vector<std::unique_ptr<Instruction>>* newInsts) {
const uint32_t ldResultId = TakeNextId();
if (ldResultId == 0) {
return 0;
}
*varId = ptrInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx);
const Instruction* varInst = get_def_use_mgr()->GetDef(*varId);
assert(varInst->opcode() == SpvOpVariable);
@@ -70,7 +74,7 @@ void LocalAccessChainConvertPass::AppendConstantOperands(
});
}
void LocalAccessChainConvertPass::ReplaceAccessChainLoad(
bool LocalAccessChainConvertPass::ReplaceAccessChainLoad(
const Instruction* address_inst, Instruction* original_load) {
// Build and append load of variable in ptrInst
std::vector<std::unique_ptr<Instruction>> new_inst;
@@ -78,6 +82,10 @@ void LocalAccessChainConvertPass::ReplaceAccessChainLoad(
uint32_t varPteTypeId;
const uint32_t ldResultId =
BuildAndAppendVarLoad(address_inst, &varId, &varPteTypeId, &new_inst);
if (ldResultId == 0) {
return false;
}
context()->get_decoration_mgr()->CloneDecorations(
original_load->result_id(), ldResultId, {SpvDecorationRelaxedPrecision});
original_load->InsertBefore(std::move(new_inst));
@@ -95,9 +103,10 @@ void LocalAccessChainConvertPass::ReplaceAccessChainLoad(
original_load->SetOpcode(SpvOpCompositeExtract);
original_load->ReplaceOperands(new_operands);
context()->UpdateDefUse(original_load);
return true;
}
void LocalAccessChainConvertPass::GenAccessChainStoreReplacement(
bool LocalAccessChainConvertPass::GenAccessChainStoreReplacement(
const Instruction* ptrInst, uint32_t valId,
std::vector<std::unique_ptr<Instruction>>* newInsts) {
// Build and append load of variable in ptrInst
@@ -105,11 +114,18 @@ void LocalAccessChainConvertPass::GenAccessChainStoreReplacement(
uint32_t varPteTypeId;
const uint32_t ldResultId =
BuildAndAppendVarLoad(ptrInst, &varId, &varPteTypeId, newInsts);
if (ldResultId == 0) {
return false;
}
context()->get_decoration_mgr()->CloneDecorations(
varId, ldResultId, {SpvDecorationRelaxedPrecision});
// Build and append Insert
const uint32_t insResultId = TakeNextId();
if (insResultId == 0) {
return false;
}
std::vector<Operand> ins_in_opnds = {
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {valId}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ldResultId}}};
@@ -125,6 +141,7 @@ void LocalAccessChainConvertPass::GenAccessChainStoreReplacement(
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {varId}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {insResultId}}},
newInsts);
return true;
}
bool LocalAccessChainConvertPass::IsConstantIndexAccessChain(
@@ -198,7 +215,8 @@ void LocalAccessChainConvertPass::FindTargetVars(Function* func) {
}
}
bool LocalAccessChainConvertPass::ConvertLocalAccessChains(Function* func) {
Pass::Status LocalAccessChainConvertPass::ConvertLocalAccessChains(
Function* func) {
FindTargetVars(func);
// Replace access chains of all targeted variables with equivalent
// extract and insert sequences
@@ -213,7 +231,9 @@ bool LocalAccessChainConvertPass::ConvertLocalAccessChains(Function* func) {
if (!IsNonPtrAccessChain(ptrInst->opcode())) break;
if (!IsTargetVar(varId)) break;
std::vector<std::unique_ptr<Instruction>> newInsts;
ReplaceAccessChainLoad(ptrInst, &*ii);
if (!ReplaceAccessChainLoad(ptrInst, &*ii)) {
return Status::Failure;
}
modified = true;
} break;
case SpvOpStore: {
@@ -223,7 +243,9 @@ bool LocalAccessChainConvertPass::ConvertLocalAccessChains(Function* func) {
if (!IsTargetVar(varId)) break;
std::vector<std::unique_ptr<Instruction>> newInsts;
uint32_t valId = ii->GetSingleWordInOperand(kStoreValIdInIdx);
GenAccessChainStoreReplacement(ptrInst, valId, &newInsts);
if (!GenAccessChainStoreReplacement(ptrInst, valId, &newInsts)) {
return Status::Failure;
}
dead_instructions.push_back(&*ii);
++ii;
ii = ii.InsertBefore(std::move(newInsts));
@@ -248,7 +270,7 @@ bool LocalAccessChainConvertPass::ConvertLocalAccessChains(Function* func) {
});
}
}
return modified;
return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
}
void LocalAccessChainConvertPass::Initialize() {
@@ -294,12 +316,16 @@ Pass::Status LocalAccessChainConvertPass::ProcessImpl() {
if (ai.opcode() == SpvOpGroupDecorate) return Status::SuccessWithoutChange;
// Do not process if any disallowed extensions are enabled
if (!AllExtensionsSupported()) return Status::SuccessWithoutChange;
// Process all entry point functions.
ProcessFunction pfn = [this](Function* fp) {
return ConvertLocalAccessChains(fp);
};
bool modified = context()->ProcessEntryPointCallTree(pfn);
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
// Process all functions in the module.
Status status = Status::SuccessWithoutChange;
for (Function& func : *get_module()) {
status = CombineStatus(status, ConvertLocalAccessChains(&func));
if (status == Status::Failure) {
break;
}
}
return status;
}
LocalAccessChainConvertPass::LocalAccessChainConvertPass() {}

View File

@@ -82,16 +82,16 @@ class LocalAccessChainConvertPass : public MemPass {
// Create a load/insert/store equivalent to a store of
// |valId| through (constant index) access chaing |ptrInst|.
// Append to |newInsts|.
void GenAccessChainStoreReplacement(
// Append to |newInsts|. Returns true if successful.
bool GenAccessChainStoreReplacement(
const Instruction* ptrInst, uint32_t valId,
std::vector<std::unique_ptr<Instruction>>* newInsts);
// For the (constant index) access chain |address_inst|, create an
// equivalent load and extract that replaces |original_load|. The result id
// of the extract will be the same as the original result id of
// |original_load|.
void ReplaceAccessChainLoad(const Instruction* address_inst,
// |original_load|. Returns true if successful.
bool ReplaceAccessChainLoad(const Instruction* address_inst,
Instruction* original_load);
// Return true if all indices of access chain |acp| are OpConstant integers
@@ -106,7 +106,9 @@ class LocalAccessChainConvertPass : public MemPass {
//
// Nested access chains and pointer access chains are not currently
// converted.
bool ConvertLocalAccessChains(Function* func);
//
// Returns a status to indicate success or failure, and change or no change.
Status ConvertLocalAccessChains(Function* func);
// Initialize extensions whitelist
void InitExtensions();

View File

@@ -820,7 +820,8 @@ ScalarReplacementPass::GetUsedComponents(Instruction* inst) {
// Look for extract from the load.
std::vector<uint32_t> t;
if (def_use_mgr->WhileEachUser(use, [&t](Instruction* use2) {
if (use2->opcode() != SpvOpCompositeExtract) {
if (use2->opcode() != SpvOpCompositeExtract ||
use2->NumInOperands() <= 1) {
return false;
}
t.push_back(use2->GetSingleWordInOperand(1));

View File

@@ -60,13 +60,23 @@ bool WrapOpKill::ReplaceWithFunctionCall(Instruction* inst) {
return false;
}
Instruction* return_inst = nullptr;
uint32_t return_type_id = GetOwningFunctionsReturnType(inst);
if (return_type_id != GetVoidTypeId()) {
Instruction* undef = ir_builder.AddNullaryOp(return_type_id, SpvOpUndef);
ir_builder.AddUnaryOp(0, SpvOpReturnValue, undef->result_id());
if (undef == nullptr) {
return false;
}
return_inst =
ir_builder.AddUnaryOp(0, SpvOpReturnValue, undef->result_id());
} else {
ir_builder.AddNullaryOp(0, SpvOpReturn);
return_inst = ir_builder.AddNullaryOp(0, SpvOpReturn);
}
if (return_inst == nullptr) {
return false;
}
context()->KillInst(inst);
return true;
}

View File

@@ -343,7 +343,6 @@ spv_result_t ValidateBinaryUsingContextAndValidationState(
}
if (auto error = CapabilityPass(*vstate, &instruction)) return error;
if (auto error = DataRulesPass(*vstate, &instruction)) return error;
if (auto error = ModuleLayoutPass(*vstate, &instruction)) return error;
if (auto error = CfgPass(*vstate, &instruction)) return error;
if (auto error = InstructionPass(*vstate, &instruction)) return error;
@@ -352,6 +351,9 @@ spv_result_t ValidateBinaryUsingContextAndValidationState(
{
Instruction* inst = const_cast<Instruction*>(&instruction);
vstate->RegisterInstruction(inst);
if (inst->opcode() == SpvOpTypeForwardPointer) {
vstate->RegisterForwardPointer(inst->GetOperandAs<uint32_t>(0));
}
}
}

View File

@@ -123,11 +123,6 @@ spv_result_t ControlFlowPass(ValidationState_t& _, const Instruction* inst);
/// Performs Id and SSA validation of a module
spv_result_t IdPass(ValidationState_t& _, Instruction* inst);
/// Performs validation of the Data Rules subsection of 2.16.1 Universal
/// Validation Rules.
/// TODO(ehsann): add more comments here as more validation code is added.
spv_result_t DataRulesPass(ValidationState_t& _, const Instruction* inst);
/// Performs instruction validation.
spv_result_t InstructionPass(ValidationState_t& _, const Instruction* inst);

View File

@@ -2504,20 +2504,20 @@ spv_result_t BuiltInsValidator::ValidateLayerOrViewportIndexAtReference(
switch (execution_model) {
case SpvExecutionModelGeometry:
case SpvExecutionModelFragment:
case SpvExecutionModelMeshNV: {
case SpvExecutionModelMeshNV:
// Ok.
break;
case SpvExecutionModelVertex:
case SpvExecutionModelTessellationEvaluation:
if (!_.HasCapability(SpvCapabilityShaderViewportIndexLayerEXT)) {
return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
<< "Using BuiltIn "
<< _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
decoration.params()[0])
<< " in Vertex or Tessellation execution model requires "
"the ShaderViewportIndexLayerEXT capability.";
}
break;
case SpvExecutionModelVertex:
case SpvExecutionModelTessellationEvaluation: {
if (!_.HasCapability(SpvCapabilityShaderViewportIndexLayerEXT)) {
return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
<< "Using BuiltIn "
<< _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
decoration.params()[0])
<< " in Vertex or Tessellation execution model requires "
"the ShaderViewportIndexLayerEXT capability.";
}
break;
}
default: {
return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)

View File

@@ -342,6 +342,21 @@ spv_result_t ValidateConstantNull(ValidationState_t& _,
return SPV_SUCCESS;
}
// Validates that OpSpecConstant specializes to either int or float type.
spv_result_t ValidateSpecConstant(ValidationState_t& _,
const Instruction* inst) {
// Operand 0 is the <id> of the type that we're specializing to.
auto type_id = inst->GetOperandAs<const uint32_t>(0);
auto type_instruction = _.FindDef(type_id);
auto type_opcode = type_instruction->opcode();
if (type_opcode != SpvOpTypeInt && type_opcode != SpvOpTypeFloat) {
return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Specialization constant "
"must be an integer or "
"floating-point number.";
}
return SPV_SUCCESS;
}
spv_result_t ValidateSpecConstantOp(ValidationState_t& _,
const Instruction* inst) {
const auto op = inst->GetOperandAs<SpvOp>(2);
@@ -422,6 +437,9 @@ spv_result_t ConstantPass(ValidationState_t& _, const Instruction* inst) {
case SpvOpConstantNull:
if (auto error = ValidateConstantNull(_, inst)) return error;
break;
case SpvOpSpecConstant:
if (auto error = ValidateSpecConstant(_, inst)) return error;
break;
case SpvOpSpecConstantOp:
if (auto error = ValidateSpecConstantOp(_, inst)) return error;
break;

View File

@@ -1,286 +0,0 @@
// Copyright (c) 2016 Google 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.
// Ensures Data Rules are followed according to the specifications.
#include "source/val/validate.h"
#include <cassert>
#include <sstream>
#include <string>
#include "source/diagnostic.h"
#include "source/opcode.h"
#include "source/operand.h"
#include "source/val/instruction.h"
#include "source/val/validation_state.h"
namespace spvtools {
namespace val {
namespace {
// Validates that the number of components in the vector is valid.
// Vector types can only be parameterized as having 2, 3, or 4 components.
// If the Vector16 capability is added, 8 and 16 components are also allowed.
spv_result_t ValidateVecNumComponents(ValidationState_t& _,
const Instruction* inst) {
// Operand 2 specifies the number of components in the vector.
auto num_components = inst->GetOperandAs<const uint32_t>(2);
if (num_components == 2 || num_components == 3 || num_components == 4) {
return SPV_SUCCESS;
}
if (num_components == 8 || num_components == 16) {
if (_.HasCapability(SpvCapabilityVector16)) {
return SPV_SUCCESS;
}
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Having " << num_components << " components for "
<< spvOpcodeString(inst->opcode())
<< " requires the Vector16 capability";
}
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Illegal number of components (" << num_components << ") for "
<< spvOpcodeString(inst->opcode());
}
// Validates that the number of bits specifed for a float type is valid.
// Scalar floating-point types can be parameterized only with 32-bits.
// Float16 capability allows using a 16-bit OpTypeFloat.
// Float16Buffer capability allows creation of a 16-bit OpTypeFloat.
// Float64 capability allows using a 64-bit OpTypeFloat.
spv_result_t ValidateFloatSize(ValidationState_t& _, const Instruction* inst) {
// Operand 1 is the number of bits for this float
auto num_bits = inst->GetOperandAs<const uint32_t>(1);
if (num_bits == 32) {
return SPV_SUCCESS;
}
if (num_bits == 16) {
if (_.features().declare_float16_type) {
return SPV_SUCCESS;
}
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Using a 16-bit floating point "
<< "type requires the Float16 or Float16Buffer capability,"
" or an extension that explicitly enables 16-bit floating point.";
}
if (num_bits == 64) {
if (_.HasCapability(SpvCapabilityFloat64)) {
return SPV_SUCCESS;
}
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Using a 64-bit floating point "
<< "type requires the Float64 capability.";
}
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Invalid number of bits (" << num_bits << ") used for OpTypeFloat.";
}
// Validates that the number of bits specified for an Int type is valid.
// Scalar integer types can be parameterized only with 32-bits.
// Int8, Int16, and Int64 capabilities allow using 8-bit, 16-bit, and 64-bit
// integers, respectively.
spv_result_t ValidateIntSize(ValidationState_t& _, const Instruction* inst) {
// Operand 1 is the number of bits for this integer.
auto num_bits = inst->GetOperandAs<const uint32_t>(1);
if (num_bits == 32) {
return SPV_SUCCESS;
}
if (num_bits == 8) {
if (_.features().declare_int8_type) {
return SPV_SUCCESS;
}
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Using an 8-bit integer type requires the Int8 capability,"
" or an extension that explicitly enables 8-bit integers.";
}
if (num_bits == 16) {
if (_.features().declare_int16_type) {
return SPV_SUCCESS;
}
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Using a 16-bit integer type requires the Int16 capability,"
" or an extension that explicitly enables 16-bit integers.";
}
if (num_bits == 64) {
if (_.HasCapability(SpvCapabilityInt64)) {
return SPV_SUCCESS;
}
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Using a 64-bit integer type requires the Int64 capability.";
}
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Invalid number of bits (" << num_bits << ") used for OpTypeInt.";
}
// Validates that the matrix is parameterized with floating-point types.
spv_result_t ValidateMatrixColumnType(ValidationState_t& _,
const Instruction* inst) {
// Find the component type of matrix columns (must be vector).
// Operand 1 is the <id> of the type specified for matrix columns.
auto type_id = inst->GetOperandAs<const uint32_t>(1);
auto col_type_instr = _.FindDef(type_id);
if (col_type_instr->opcode() != SpvOpTypeVector) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Columns in a matrix must be of type vector.";
}
// Trace back once more to find out the type of components in the vector.
// Operand 1 is the <id> of the type of data in the vector.
auto comp_type_id =
col_type_instr->words()[col_type_instr->operands()[1].offset];
auto comp_type_instruction = _.FindDef(comp_type_id);
if (comp_type_instruction->opcode() != SpvOpTypeFloat) {
return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Matrix types can only be "
"parameterized with "
"floating-point types.";
}
return SPV_SUCCESS;
}
// Validates that the matrix has 2,3, or 4 columns.
spv_result_t ValidateMatrixNumCols(ValidationState_t& _,
const Instruction* inst) {
// Operand 2 is the number of columns in the matrix.
auto num_cols = inst->GetOperandAs<const uint32_t>(2);
if (num_cols != 2 && num_cols != 3 && num_cols != 4) {
return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Matrix types can only be "
"parameterized as having "
"only 2, 3, or 4 columns.";
}
return SPV_SUCCESS;
}
// Validates that OpSpecConstant specializes to either int or float type.
spv_result_t ValidateSpecConstNumerical(ValidationState_t& _,
const Instruction* inst) {
// Operand 0 is the <id> of the type that we're specializing to.
auto type_id = inst->GetOperandAs<const uint32_t>(0);
auto type_instruction = _.FindDef(type_id);
auto type_opcode = type_instruction->opcode();
if (type_opcode != SpvOpTypeInt && type_opcode != SpvOpTypeFloat) {
return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Specialization constant "
"must be an integer or "
"floating-point number.";
}
return SPV_SUCCESS;
}
// Validates that OpSpecConstantTrue and OpSpecConstantFalse specialize to bool.
spv_result_t ValidateSpecConstBoolean(ValidationState_t& _,
const Instruction* inst) {
// Find out the type that we're specializing to.
auto type_instruction = _.FindDef(inst->type_id());
if (type_instruction->opcode() != SpvOpTypeBool) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Specialization constant must be a boolean type.";
}
return SPV_SUCCESS;
}
// Records the <id> of the forward pointer to be used for validation.
spv_result_t ValidateForwardPointer(ValidationState_t& _,
const Instruction* inst) {
// Record the <id> (which is operand 0) to ensure it's used properly.
// OpTypeStruct can only include undefined pointers that are
// previously declared as a ForwardPointer
return (_.RegisterForwardPointer(inst->GetOperandAs<uint32_t>(0)));
}
// Validates that any undefined component of the struct is a forward pointer.
// It is valid to declare a forward pointer, and use its <id> as one of the
// components of a struct.
spv_result_t ValidateStruct(ValidationState_t& _, const Instruction* inst) {
// Struct components are operands 1, 2, etc.
for (unsigned i = 1; i < inst->operands().size(); i++) {
auto type_id = inst->GetOperandAs<const uint32_t>(i);
auto type_instruction = _.FindDef(type_id);
if (type_instruction == nullptr && !_.IsForwardPointer(type_id)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Forward reference operands in an OpTypeStruct must first be "
"declared using OpTypeForwardPointer.";
}
}
return SPV_SUCCESS;
}
// Validates that any undefined type of the array is a forward pointer.
// It is valid to declare a forward pointer, and use its <id> as the element
// type of the array.
spv_result_t ValidateArray(ValidationState_t& _, const Instruction* inst) {
auto element_type_id = inst->GetOperandAs<const uint32_t>(1);
auto element_type_instruction = _.FindDef(element_type_id);
if (element_type_instruction == nullptr &&
!_.IsForwardPointer(element_type_id)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Forward reference operands in an OpTypeArray must first be "
"declared using OpTypeForwardPointer.";
}
return SPV_SUCCESS;
}
} // namespace
// Validates that Data Rules are followed according to the specifications.
// (Data Rules subsection of 2.16.1 Universal Validation Rules)
spv_result_t DataRulesPass(ValidationState_t& _, const Instruction* inst) {
switch (inst->opcode()) {
case SpvOpTypeVector: {
if (auto error = ValidateVecNumComponents(_, inst)) return error;
break;
}
case SpvOpTypeFloat: {
if (auto error = ValidateFloatSize(_, inst)) return error;
break;
}
case SpvOpTypeInt: {
if (auto error = ValidateIntSize(_, inst)) return error;
break;
}
case SpvOpTypeMatrix: {
if (auto error = ValidateMatrixColumnType(_, inst)) return error;
if (auto error = ValidateMatrixNumCols(_, inst)) return error;
break;
}
// TODO(ehsan): Add OpSpecConstantComposite validation code.
// TODO(ehsan): Add OpSpecConstantOp validation code (if any).
case SpvOpSpecConstant: {
if (auto error = ValidateSpecConstNumerical(_, inst)) return error;
break;
}
case SpvOpSpecConstantFalse:
case SpvOpSpecConstantTrue: {
if (auto error = ValidateSpecConstBoolean(_, inst)) return error;
break;
}
case SpvOpTypeForwardPointer: {
if (auto error = ValidateForwardPointer(_, inst)) return error;
break;
}
case SpvOpTypeStruct: {
if (auto error = ValidateStruct(_, inst)) return error;
break;
}
case SpvOpTypeArray: {
if (auto error = ValidateArray(_, inst)) return error;
break;
}
// TODO(ehsan): add more data rules validation here.
default: { break; }
}
return SPV_SUCCESS;
}
} // namespace val
} // namespace spvtools

View File

@@ -191,7 +191,14 @@ spv_result_t IdPass(ValidationState_t& _, Instruction* inst) {
ret = SPV_SUCCESS;
}
} else if (can_have_forward_declared_ids(i)) {
ret = _.ForwardDeclareId(operand_word);
if (inst->opcode() == SpvOpTypeStruct &&
!_.IsForwardPointer(operand_word)) {
ret = _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Operand " << _.getIdName(operand_word)
<< " requires a previous definition";
} else {
ret = _.ForwardDeclareId(operand_word);
}
} else {
ret = _.diag(SPV_ERROR_INVALID_ID, inst)
<< "ID " << _.getIdName(operand_word)

View File

@@ -67,6 +67,39 @@ spv_result_t ValidateUniqueness(ValidationState_t& _, const Instruction* inst) {
}
spv_result_t ValidateTypeInt(ValidationState_t& _, const Instruction* inst) {
// Validates that the number of bits specified for an Int type is valid.
// Scalar integer types can be parameterized only with 32-bits.
// Int8, Int16, and Int64 capabilities allow using 8-bit, 16-bit, and 64-bit
// integers, respectively.
auto num_bits = inst->GetOperandAs<const uint32_t>(1);
if (num_bits != 32) {
if (num_bits == 8) {
if (_.features().declare_int8_type) {
return SPV_SUCCESS;
}
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Using an 8-bit integer type requires the Int8 capability,"
" or an extension that explicitly enables 8-bit integers.";
} else if (num_bits == 16) {
if (_.features().declare_int16_type) {
return SPV_SUCCESS;
}
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Using a 16-bit integer type requires the Int16 capability,"
" or an extension that explicitly enables 16-bit integers.";
} else if (num_bits == 64) {
if (_.HasCapability(SpvCapabilityInt64)) {
return SPV_SUCCESS;
}
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Using a 64-bit integer type requires the Int64 capability.";
} else {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Invalid number of bits (" << num_bits
<< ") used for OpTypeInt.";
}
}
const auto signedness_index = 2;
const auto signedness = inst->GetOperandAs<uint32_t>(signedness_index);
if (signedness != 0 && signedness != 1) {
@@ -76,6 +109,36 @@ spv_result_t ValidateTypeInt(ValidationState_t& _, const Instruction* inst) {
return SPV_SUCCESS;
}
spv_result_t ValidateTypeFloat(ValidationState_t& _, const Instruction* inst) {
// Validates that the number of bits specified for an Int type is valid.
// Scalar integer types can be parameterized only with 32-bits.
// Int8, Int16, and Int64 capabilities allow using 8-bit, 16-bit, and 64-bit
// integers, respectively.
auto num_bits = inst->GetOperandAs<const uint32_t>(1);
if (num_bits == 32) {
return SPV_SUCCESS;
}
if (num_bits == 16) {
if (_.features().declare_float16_type) {
return SPV_SUCCESS;
}
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Using a 16-bit floating point "
<< "type requires the Float16 or Float16Buffer capability,"
" or an extension that explicitly enables 16-bit floating point.";
}
if (num_bits == 64) {
if (_.HasCapability(SpvCapabilityFloat64)) {
return SPV_SUCCESS;
}
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Using a 64-bit floating point "
<< "type requires the Float64 capability.";
}
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Invalid number of bits (" << num_bits << ") used for OpTypeFloat.";
}
spv_result_t ValidateTypeVector(ValidationState_t& _, const Instruction* inst) {
const auto component_index = 1;
const auto component_id = inst->GetOperandAs<uint32_t>(component_index);
@@ -85,6 +148,27 @@ spv_result_t ValidateTypeVector(ValidationState_t& _, const Instruction* inst) {
<< "OpTypeVector Component Type <id> '" << _.getIdName(component_id)
<< "' is not a scalar type.";
}
// Validates that the number of components in the vector is valid.
// Vector types can only be parameterized as having 2, 3, or 4 components.
// If the Vector16 capability is added, 8 and 16 components are also allowed.
auto num_components = inst->GetOperandAs<const uint32_t>(2);
if (num_components == 2 || num_components == 3 || num_components == 4) {
return SPV_SUCCESS;
} else if (num_components == 8 || num_components == 16) {
if (_.HasCapability(SpvCapabilityVector16)) {
return SPV_SUCCESS;
}
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Having " << num_components << " components for "
<< spvOpcodeString(inst->opcode())
<< " requires the Vector16 capability";
} else {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Illegal number of components (" << num_components << ") for "
<< spvOpcodeString(inst->opcode());
}
return SPV_SUCCESS;
}
@@ -94,9 +178,27 @@ spv_result_t ValidateTypeMatrix(ValidationState_t& _, const Instruction* inst) {
const auto column_type = _.FindDef(column_type_id);
if (!column_type || SpvOpTypeVector != column_type->opcode()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpTypeMatrix Column Type <id> '" << _.getIdName(column_type_id)
<< "' is not a vector.";
<< "Columns in a matrix must be of type vector.";
}
// Trace back once more to find out the type of components in the vector.
// Operand 1 is the <id> of the type of data in the vector.
const auto comp_type_id = column_type->GetOperandAs<uint32_t>(1);
auto comp_type_instruction = _.FindDef(comp_type_id);
if (comp_type_instruction->opcode() != SpvOpTypeFloat) {
return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Matrix types can only be "
"parameterized with "
"floating-point types.";
}
// Validates that the matrix has 2,3, or 4 columns.
auto num_cols = inst->GetOperandAs<const uint32_t>(2);
if (num_cols != 2 && num_cols != 3 && num_cols != 4) {
return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Matrix types can only be "
"parameterized as having "
"only 2, 3, or 4 columns.";
}
return SPV_SUCCESS;
}
@@ -224,6 +326,11 @@ spv_result_t ValidateTypeStruct(ValidationState_t& _, const Instruction* inst) {
for (size_t member_type_index = 1;
member_type_index < inst->operands().size(); ++member_type_index) {
auto member_type_id = inst->GetOperandAs<uint32_t>(member_type_index);
if (member_type_id == inst->id()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Structure members may not be self references";
}
auto member_type = _.FindDef(member_type_id);
if (!member_type || !spvOpcodeGeneratesType(member_type->opcode())) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
@@ -245,22 +352,6 @@ spv_result_t ValidateTypeStruct(ValidationState_t& _, const Instruction* inst) {
<< " contains structure <id> " << _.getIdName(member_type_id)
<< ".";
}
if (_.IsForwardPointer(member_type_id)) {
// If we're dealing with a forward pointer:
// Find out the type that the pointer is pointing to (must be struct)
// word 3 is the <id> of the type being pointed to.
auto type_pointing_to = _.FindDef(member_type->words()[3]);
if (type_pointing_to && type_pointing_to->opcode() != SpvOpTypeStruct) {
// Forward declared operands of a struct may only point to a struct.
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "A forward reference operand in an OpTypeStruct must be an "
"OpTypePointer that points to an OpTypeStruct. "
"Found OpTypePointer that points to Op"
<< spvOpcodeString(
static_cast<SpvOp>(type_pointing_to->opcode()))
<< ".";
}
}
if (spvIsVulkanOrWebGPUEnv(_.context()->target_env) &&
member_type->opcode() == SpvOpTypeRuntimeArray) {
@@ -356,7 +447,6 @@ spv_result_t ValidateTypePointer(ValidationState_t& _,
}
return SPV_SUCCESS;
}
} // namespace
spv_result_t ValidateTypeFunction(ValidationState_t& _,
const Instruction* inst) {
@@ -425,6 +515,13 @@ spv_result_t ValidateTypeForwardPointer(ValidationState_t& _,
<< "pointer definition.";
}
const auto pointee_type_id = pointer_type_inst->GetOperandAs<uint32_t>(2);
const auto pointee_type = _.FindDef(pointee_type_id);
if (!pointee_type || pointee_type->opcode() != SpvOpTypeStruct) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Forward pointers must point to a structure";
}
return SPV_SUCCESS;
}
@@ -474,6 +571,7 @@ spv_result_t ValidateTypeCooperativeMatrixNV(ValidationState_t& _,
return SPV_SUCCESS;
}
} // namespace
spv_result_t TypePass(ValidationState_t& _, const Instruction* inst) {
if (!spvOpcodeGeneratesType(inst->opcode()) &&
@@ -487,6 +585,9 @@ spv_result_t TypePass(ValidationState_t& _, const Instruction* inst) {
case SpvOpTypeInt:
if (auto error = ValidateTypeInt(_, inst)) return error;
break;
case SpvOpTypeFloat:
if (auto error = ValidateTypeFloat(_, inst)) return error;
break;
case SpvOpTypeVector:
if (auto error = ValidateTypeVector(_, inst)) return error;
break;

View File

@@ -54,7 +54,7 @@ void RunFuzzerAndReplayer(const std::string& shader,
std::vector<uint32_t> replayer_binary_out;
protobufs::TransformationSequence replayer_transformation_sequence_out;
Replayer replayer(env, true);
Replayer replayer(env, false);
replayer.SetMessageConsumer(kSilentConsumer);
auto replayer_result_status = replayer.Run(
binary_in, initial_facts, fuzzer_transformation_sequence_out,

View File

@@ -125,7 +125,7 @@ void RunAndCheckShrinker(
const std::vector<uint32_t>& expected_binary_out,
uint32_t expected_transformations_out_size, uint32_t step_limit) {
// Run the shrinker.
Shrinker shrinker(target_env, step_limit, true);
Shrinker shrinker(target_env, step_limit, false);
shrinker.SetMessageConsumer(kSilentConsumer);
std::vector<uint32_t> binary_out;

View File

@@ -2559,6 +2559,55 @@ TEST(TransformationAddDeadBreakTest, RespectDominanceRules8) {
ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
}
TEST(TransformationAddDeadBreakTest,
BreakWouldDisobeyDominanceBlockOrderingRules) {
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeBool
%9 = OpConstantTrue %6
%4 = OpFunction %2 None %3
%5 = OpLabel
OpBranch %10
%10 = OpLabel
OpLoopMerge %16 %15 None
OpBranch %11
%11 = OpLabel
OpSelectionMerge %14 None
OpBranchConditional %9 %12 %13
%14 = OpLabel
OpBranch %15
%12 = OpLabel
OpBranch %16
%13 = OpLabel
OpBranch %16
%15 = OpLabel
OpBranch %10
%16 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, context.get()));
FactManager fact_manager;
// Bad because 14 comes before 12 in the module, and 14 has no predecessors.
// This means that an edge from 12 to 14 will lead to 12 dominating 14, which
// is illegal if 12 appears after 14.
auto bad_transformation = TransformationAddDeadBreak(12, 14, true, {});
ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
}
} // namespace
} // namespace fuzz
} // namespace spvtools

View File

@@ -1388,6 +1388,244 @@ TEST(TransformationAddDeadContinueTest, Miscellaneous1) {
ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
}
TEST(TransformationAddDeadContinueTest, Miscellaneous2) {
// A miscellaneous test that exposed a bug in spirv-fuzz.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%51 = OpTypeBool
%395 = OpConstantTrue %51
%4 = OpFunction %2 None %3
%5 = OpLabel
OpBranch %389
%389 = OpLabel
OpLoopMerge %388 %391 None
OpBranch %339
%339 = OpLabel
OpSelectionMerge %396 None
OpBranchConditional %395 %388 %396
%396 = OpLabel
OpBranch %1552
%1552 = OpLabel
OpLoopMerge %1553 %1554 None
OpBranch %1556
%1556 = OpLabel
OpLoopMerge %1557 %1570 None
OpBranchConditional %395 %1562 %1557
%1562 = OpLabel
OpSelectionMerge %1570 None
OpBranchConditional %395 %1571 %1570
%1571 = OpLabel
OpBranch %1557
%1570 = OpLabel
OpBranch %1556
%1557 = OpLabel
OpSelectionMerge %1586 None
OpBranchConditional %395 %1553 %1586
%1586 = OpLabel
OpBranch %1553
%1554 = OpLabel
OpBranch %1552
%1553 = OpLabel
OpBranch %388
%391 = OpLabel
OpBranch %389
%388 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, context.get()));
FactManager fact_manager;
// This transformation would introduce a branch from a continue target to
// itself.
auto bad_transformation = TransformationAddDeadContinue(1554, true, {});
ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
}
TEST(TransformationAddDeadContinueTest, Miscellaneous3) {
// A miscellaneous test that exposed a bug in spirv-fuzz.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%85 = OpTypeBool
%434 = OpConstantFalse %85
%4 = OpFunction %2 None %3
%5 = OpLabel
OpBranch %234
%234 = OpLabel
OpLoopMerge %235 %236 None
OpBranch %259
%259 = OpLabel
OpLoopMerge %260 %274 None
OpBranchConditional %434 %265 %260
%265 = OpLabel
OpBranch %275
%275 = OpLabel
OpBranch %260
%274 = OpLabel
OpBranch %259
%260 = OpLabel
OpSelectionMerge %298 None
OpBranchConditional %434 %299 %300
%300 = OpLabel
OpBranch %235
%298 = OpLabel
OpUnreachable
%236 = OpLabel
OpBranch %234
%299 = OpLabel
OpBranch %235
%235 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, context.get()));
FactManager fact_manager;
auto bad_transformation = TransformationAddDeadContinue(299, false, {});
// The continue edge would connect %299 to the previously-unreachable %236,
// making %299 dominate %236, and breaking the rule that block ordering must
// respect dominance.
ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
}
TEST(TransformationAddDeadContinueTest, Miscellaneous4) {
// A miscellaneous test that exposed a bug in spirv-fuzz.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %8 "i"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypePointer Function %6
%9 = OpConstant %6 0
%16 = OpConstant %6 100
%17 = OpTypeBool
%100 = OpConstantFalse %17
%21 = OpConstant %6 1
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
OpStore %8 %9
OpBranch %10
%13 = OpLabel
%20 = OpLoad %6 %8
%22 = OpIAdd %6 %20 %21
OpStore %8 %22
OpBranch %10
%10 = OpLabel
OpLoopMerge %12 %13 None
OpBranch %14
%14 = OpLabel
%15 = OpLoad %6 %8
%18 = OpSLessThan %17 %15 %16
OpBranchConditional %18 %11 %12
%11 = OpLabel
OpBranch %12
%12 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, context.get()));
FactManager fact_manager;
auto bad_transformation = TransformationAddDeadContinue(10, false, {});
// The continue edge would connect %10 to the previously-unreachable %13,
// making %10 dominate %13, and breaking the rule that block ordering must
// respect dominance.
ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
}
TEST(TransformationAddDeadContinueTest, Miscellaneous5) {
// A miscellaneous test that exposed a bug in spirv-fuzz.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeBool
%7 = OpTypePointer Function %6
%9 = OpConstantTrue %6
%4 = OpFunction %2 None %3
%5 = OpLabel
OpBranch %98
%98 = OpLabel
OpLoopMerge %100 %101 None
OpBranch %99
%99 = OpLabel
OpSelectionMerge %111 None
OpBranchConditional %9 %110 %111
%110 = OpLabel
OpBranch %100
%111 = OpLabel
%200 = OpCopyObject %6 %9
OpBranch %101
%101 = OpLabel
%201 = OpCopyObject %6 %200
OpBranchConditional %9 %98 %100
%100 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, context.get()));
FactManager fact_manager;
auto bad_transformation = TransformationAddDeadContinue(110, true, {});
// The continue edge would lead to the use of %200 in block %101 no longer
// being dominated by its definition in block %111.
ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
}
} // namespace
} // namespace fuzz
} // namespace spvtools

View File

@@ -597,6 +597,56 @@ TEST(TransformationReplaceBooleanConstantWithConstantBinaryTest,
ASSERT_TRUE(IsEqual(env, after, context.get()));
}
TEST(TransformationReplaceBooleanConstantWithConstantBinaryTest, OpPhi) {
// Hand-written SPIR-V to check applicability of the transformation on an
// OpPhi argument.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeBool
%7 = OpTypePointer Function %6
%9 = OpConstantTrue %6
%16 = OpConstantFalse %6
%10 = OpTypeInt 32 1
%11 = OpTypePointer Function %10
%13 = OpConstant %10 0
%15 = OpConstant %10 1
%4 = OpFunction %2 None %3
%5 = OpLabel
OpSelectionMerge %20 None
OpBranchConditional %9 %21 %22
%21 = OpLabel
OpBranch %20
%22 = OpLabel
OpBranch %20
%20 = OpLabel
%23 = OpPhi %6 %9 %21 %16 %22
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, context.get()));
FactManager fact_manager;
auto replacement = TransformationReplaceBooleanConstantWithConstantBinary(
transformation::MakeIdUseDescriptor(9, SpvOpPhi, 0, 23, 0), 13, 15,
SpvOpSLessThan, 100);
ASSERT_FALSE(replacement.IsApplicable(context.get(), fact_manager));
}
} // namespace
} // namespace fuzz
} // namespace spvtools

View File

@@ -564,6 +564,613 @@ TEST(TransformationReplaceIdWithSynonymTest, SynonymsOfVariables) {
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}
TEST(TransformationReplaceIdWithSynonymTest,
SynonymOfVariableNoGoodInFunctionCall) {
// The following SPIR-V comes from this GLSL, with an object copy added:
//
// #version 310 es
//
// precision highp int;
//
// void foo(int x) { }
//
// void main() {
// int a;
// a = 2;
// foo(a);
// }
const std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %10 "foo(i1;"
OpName %9 "x"
OpName %12 "a"
OpName %14 "param"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypePointer Function %6
%8 = OpTypeFunction %2 %7
%13 = OpConstant %6 2
%4 = OpFunction %2 None %3
%5 = OpLabel
%12 = OpVariable %7 Function
%14 = OpVariable %7 Function
OpStore %12 %13
%15 = OpLoad %6 %12
OpStore %14 %15
%100 = OpCopyObject %7 %14
%16 = OpFunctionCall %2 %10 %14
OpReturn
OpFunctionEnd
%10 = OpFunction %2 None %8
%9 = OpFunctionParameter %7
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, context.get()));
FactManager fact_manager;
fact_manager.AddFact(MakeFact(14, 100), context.get());
// Replace %14 with %100 in:
// %16 = OpFunctionCall %2 %10 %14
auto replacement = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(14, SpvOpFunctionCall, 1, 16, 0),
MakeDataDescriptor(100, {}), 0);
ASSERT_FALSE(replacement.IsApplicable(context.get(), fact_manager));
}
TEST(TransformationReplaceIdWithSynonymTest, SynonymsOfAccessChainIndices) {
// The following SPIR-V comes from this GLSL, with object copies added:
//
// #version 310 es
//
// precision highp float;
// precision highp int;
//
// struct S {
// int[3] a;
// vec4 b;
// bool c;
// } d;
//
// float[20] e;
//
// struct T {
// float f;
// S g;
// } h;
//
// T[4] i;
//
// void main() {
// d.a[2] = 10;
// d.b[3] = 11.0;
// d.c = false;
// e[17] = 12.0;
// h.f = 13.0;
// h.g.a[1] = 14;
// h.g.b[0] = 15.0;
// h.g.c = true;
// i[0].f = 16.0;
// i[1].g.a[0] = 17;
// i[2].g.b[1] = 18.0;
// i[3].g.c = true;
// }
const std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %13 "S"
OpMemberName %13 0 "a"
OpMemberName %13 1 "b"
OpMemberName %13 2 "c"
OpName %15 "d"
OpName %31 "e"
OpName %35 "T"
OpMemberName %35 0 "f"
OpMemberName %35 1 "g"
OpName %37 "h"
OpName %50 "i"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypeInt 32 0
%8 = OpConstant %7 3
%9 = OpTypeArray %6 %8
%10 = OpTypeFloat 32
%11 = OpTypeVector %10 4
%12 = OpTypeBool
%13 = OpTypeStruct %9 %11 %12
%14 = OpTypePointer Private %13
%15 = OpVariable %14 Private
%16 = OpConstant %6 0
%17 = OpConstant %6 2
%18 = OpConstant %6 10
%19 = OpTypePointer Private %6
%21 = OpConstant %6 1
%22 = OpConstant %10 11
%23 = OpTypePointer Private %10
%25 = OpConstantFalse %12
%26 = OpTypePointer Private %12
%28 = OpConstant %7 20
%29 = OpTypeArray %10 %28
%30 = OpTypePointer Private %29
%31 = OpVariable %30 Private
%32 = OpConstant %6 17
%33 = OpConstant %10 12
%35 = OpTypeStruct %10 %13
%36 = OpTypePointer Private %35
%37 = OpVariable %36 Private
%38 = OpConstant %10 13
%40 = OpConstant %6 14
%42 = OpConstant %10 15
%43 = OpConstant %7 0
%45 = OpConstantTrue %12
%47 = OpConstant %7 4
%48 = OpTypeArray %35 %47
%49 = OpTypePointer Private %48
%50 = OpVariable %49 Private
%51 = OpConstant %10 16
%54 = OpConstant %10 18
%55 = OpConstant %7 1
%57 = OpConstant %6 3
%4 = OpFunction %2 None %3
%5 = OpLabel
%100 = OpCopyObject %6 %16 ; 0
%101 = OpCopyObject %6 %21 ; 1
%102 = OpCopyObject %6 %17 ; 2
%103 = OpCopyObject %6 %57 ; 3
%104 = OpCopyObject %6 %18 ; 10
%105 = OpCopyObject %6 %40 ; 14
%106 = OpCopyObject %6 %32 ; 17
%107 = OpCopyObject %7 %43 ; 0
%108 = OpCopyObject %7 %55 ; 1
%109 = OpCopyObject %7 %8 ; 3
%110 = OpCopyObject %7 %47 ; 4
%111 = OpCopyObject %7 %28 ; 20
%112 = OpCopyObject %12 %45 ; true
%20 = OpAccessChain %19 %15 %16 %17
OpStore %20 %18
%24 = OpAccessChain %23 %15 %21 %8
OpStore %24 %22
%27 = OpAccessChain %26 %15 %17
OpStore %27 %25
%34 = OpAccessChain %23 %31 %32
OpStore %34 %33
%39 = OpAccessChain %23 %37 %16
OpStore %39 %38
%41 = OpAccessChain %19 %37 %21 %16 %21
OpStore %41 %40
%44 = OpAccessChain %23 %37 %21 %21 %43
OpStore %44 %42
%46 = OpAccessChain %26 %37 %21 %17
OpStore %46 %45
%52 = OpAccessChain %23 %50 %16 %16
OpStore %52 %51
%53 = OpAccessChain %19 %50 %21 %21 %16 %16
OpStore %53 %32
%56 = OpAccessChain %23 %50 %17 %21 %21 %55
OpStore %56 %54
%58 = OpAccessChain %26 %50 %57 %21 %17
OpStore %58 %45
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, context.get()));
FactManager fact_manager;
// Add synonym facts corresponding to the OpCopyObject operations that have
// been applied to all constants in the module.
fact_manager.AddFact(MakeFact(16, 100), context.get());
fact_manager.AddFact(MakeFact(21, 101), context.get());
fact_manager.AddFact(MakeFact(17, 102), context.get());
fact_manager.AddFact(MakeFact(57, 103), context.get());
fact_manager.AddFact(MakeFact(18, 104), context.get());
fact_manager.AddFact(MakeFact(40, 105), context.get());
fact_manager.AddFact(MakeFact(32, 106), context.get());
fact_manager.AddFact(MakeFact(43, 107), context.get());
fact_manager.AddFact(MakeFact(55, 108), context.get());
fact_manager.AddFact(MakeFact(8, 109), context.get());
fact_manager.AddFact(MakeFact(47, 110), context.get());
fact_manager.AddFact(MakeFact(28, 111), context.get());
fact_manager.AddFact(MakeFact(45, 112), context.get());
// Replacements of the form %16 -> %100
// %20 = OpAccessChain %19 %15 *%16* %17
// Corresponds to d.*a*[2]
// The index %16 used for a cannot be replaced
auto replacement1 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(16, SpvOpAccessChain, 1, 20, 0),
MakeDataDescriptor(100, {}), 0);
ASSERT_FALSE(replacement1.IsApplicable(context.get(), fact_manager));
// %39 = OpAccessChain %23 %37 *%16*
// Corresponds to h.*f*
// The index %16 used for f cannot be replaced
auto replacement2 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(16, SpvOpAccessChain, 1, 39, 0),
MakeDataDescriptor(100, {}), 0);
ASSERT_FALSE(replacement2.IsApplicable(context.get(), fact_manager));
// %41 = OpAccessChain %19 %37 %21 *%16* %21
// Corresponds to h.g.*a*[1]
// The index %16 used for a cannot be replaced
auto replacement3 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(16, SpvOpAccessChain, 2, 41, 0),
MakeDataDescriptor(100, {}), 0);
ASSERT_FALSE(replacement3.IsApplicable(context.get(), fact_manager));
// %52 = OpAccessChain %23 %50 *%16* %16
// Corresponds to i[*0*].f
// The index %16 used for 0 *can* be replaced
auto replacement4 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(16, SpvOpAccessChain, 1, 52, 0),
MakeDataDescriptor(100, {}), 0);
ASSERT_TRUE(replacement4.IsApplicable(context.get(), fact_manager));
replacement4.Apply(context.get(), &fact_manager);
ASSERT_TRUE(IsValid(env, context.get()));
// %52 = OpAccessChain %23 %50 %16 *%16*
// Corresponds to i[0].*f*
// The index %16 used for f cannot be replaced
auto replacement5 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(16, SpvOpAccessChain, 2, 52, 0),
MakeDataDescriptor(100, {}), 0);
ASSERT_FALSE(replacement5.IsApplicable(context.get(), fact_manager));
// %53 = OpAccessChain %19 %50 %21 %21 *%16* %16
// Corresponds to i[1].g.*a*[0]
// The index %16 used for a cannot be replaced
auto replacement6 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(16, SpvOpAccessChain, 3, 53, 0),
MakeDataDescriptor(100, {}), 0);
ASSERT_FALSE(replacement6.IsApplicable(context.get(), fact_manager));
// %53 = OpAccessChain %19 %50 %21 %21 %16 *%16*
// Corresponds to i[1].g.a[*0*]
// The index %16 used for 0 *can* be replaced
auto replacement7 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(16, SpvOpAccessChain, 4, 53, 0),
MakeDataDescriptor(100, {}), 0);
ASSERT_TRUE(replacement7.IsApplicable(context.get(), fact_manager));
replacement7.Apply(context.get(), &fact_manager);
ASSERT_TRUE(IsValid(env, context.get()));
// Replacements of the form %21 -> %101
// %24 = OpAccessChain %23 %15 *%21* %8
// Corresponds to d.*b*[3]
// The index %24 used for b cannot be replaced
auto replacement8 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(21, SpvOpAccessChain, 1, 24, 0),
MakeDataDescriptor(101, {}), 0);
ASSERT_FALSE(replacement8.IsApplicable(context.get(), fact_manager));
// %41 = OpAccessChain %19 %37 *%21* %16 %21
// Corresponds to h.*g*.a[1]
// The index %24 used for g cannot be replaced
auto replacement9 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(21, SpvOpAccessChain, 1, 41, 0),
MakeDataDescriptor(101, {}), 0);
ASSERT_FALSE(replacement9.IsApplicable(context.get(), fact_manager));
// %41 = OpAccessChain %19 %37 %21 %16 *%21*
// Corresponds to h.g.a[*1*]
// The index %24 used for 1 *can* be replaced
auto replacement10 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(21, SpvOpAccessChain, 3, 41, 0),
MakeDataDescriptor(101, {}), 0);
ASSERT_TRUE(replacement10.IsApplicable(context.get(), fact_manager));
replacement10.Apply(context.get(), &fact_manager);
ASSERT_TRUE(IsValid(env, context.get()));
// %44 = OpAccessChain %23 %37 *%21* %21 %43
// Corresponds to h.*g*.b[0]
// The index %24 used for g cannot be replaced
auto replacement11 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(21, SpvOpAccessChain, 1, 44, 0),
MakeDataDescriptor(101, {}), 0);
ASSERT_FALSE(replacement11.IsApplicable(context.get(), fact_manager));
// %44 = OpAccessChain %23 %37 %21 *%21* %43
// Corresponds to h.g.*b*[0]
// The index %24 used for b cannot be replaced
auto replacement12 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(21, SpvOpAccessChain, 2, 44, 0),
MakeDataDescriptor(101, {}), 0);
ASSERT_FALSE(replacement12.IsApplicable(context.get(), fact_manager));
// %46 = OpAccessChain %26 %37 *%21* %17
// Corresponds to h.*g*.c
// The index %24 used for g cannot be replaced
auto replacement13 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(21, SpvOpAccessChain, 1, 46, 0),
MakeDataDescriptor(101, {}), 0);
ASSERT_FALSE(replacement13.IsApplicable(context.get(), fact_manager));
// %53 = OpAccessChain %19 %50 *%21* %21 %16 %16
// Corresponds to i[*1*].g.a[0]
// The index %24 used for 1 *can* be replaced
auto replacement14 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(21, SpvOpAccessChain, 1, 53, 0),
MakeDataDescriptor(101, {}), 0);
ASSERT_TRUE(replacement14.IsApplicable(context.get(), fact_manager));
replacement14.Apply(context.get(), &fact_manager);
ASSERT_TRUE(IsValid(env, context.get()));
// %53 = OpAccessChain %19 %50 %21 *%21* %16 %16
// Corresponds to i[1].*g*.a[0]
// The index %24 used for g cannot be replaced
auto replacement15 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(21, SpvOpAccessChain, 2, 53, 0),
MakeDataDescriptor(101, {}), 0);
ASSERT_FALSE(replacement15.IsApplicable(context.get(), fact_manager));
// %56 = OpAccessChain %23 %50 %17 *%21* %21 %55
// Corresponds to i[2].*g*.b[1]
// The index %24 used for g cannot be replaced
auto replacement16 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(21, SpvOpAccessChain, 2, 56, 0),
MakeDataDescriptor(101, {}), 0);
ASSERT_FALSE(replacement16.IsApplicable(context.get(), fact_manager));
// %56 = OpAccessChain %23 %50 %17 %21 *%21* %55
// Corresponds to i[2].g.*b*[1]
// The index %24 used for b cannot be replaced
auto replacement17 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(21, SpvOpAccessChain, 3, 56, 0),
MakeDataDescriptor(101, {}), 0);
ASSERT_FALSE(replacement17.IsApplicable(context.get(), fact_manager));
// %58 = OpAccessChain %26 %50 %57 *%21* %17
// Corresponds to i[3].*g*.c
// The index %24 used for g cannot be replaced
auto replacement18 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(21, SpvOpAccessChain, 2, 58, 0),
MakeDataDescriptor(101, {}), 0);
ASSERT_FALSE(replacement18.IsApplicable(context.get(), fact_manager));
// Replacements of the form %17 -> %102
// %20 = OpAccessChain %19 %15 %16 %17
// Corresponds to d.a[*2*]
// The index %17 used for 2 *can* be replaced
auto replacement19 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(17, SpvOpAccessChain, 2, 20, 0),
MakeDataDescriptor(102, {}), 0);
ASSERT_TRUE(replacement19.IsApplicable(context.get(), fact_manager));
replacement19.Apply(context.get(), &fact_manager);
ASSERT_TRUE(IsValid(env, context.get()));
// %27 = OpAccessChain %26 %15 %17
// Corresponds to d.c
// The index %17 used for c cannot be replaced
auto replacement20 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(17, SpvOpAccessChain, 1, 27, 0),
MakeDataDescriptor(102, {}), 0);
ASSERT_FALSE(replacement20.IsApplicable(context.get(), fact_manager));
// %46 = OpAccessChain %26 %37 %21 %17
// Corresponds to h.g.*c*
// The index %17 used for c cannot be replaced
auto replacement21 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(17, SpvOpAccessChain, 2, 46, 0),
MakeDataDescriptor(102, {}), 0);
ASSERT_FALSE(replacement21.IsApplicable(context.get(), fact_manager));
// %56 = OpAccessChain %23 %50 %17 %21 %21 %55
// Corresponds to i[*2*].g.b[1]
// The index %17 used for 2 *can* be replaced
auto replacement22 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(17, SpvOpAccessChain, 1, 56, 0),
MakeDataDescriptor(102, {}), 0);
ASSERT_TRUE(replacement22.IsApplicable(context.get(), fact_manager));
replacement22.Apply(context.get(), &fact_manager);
ASSERT_TRUE(IsValid(env, context.get()));
// %58 = OpAccessChain %26 %50 %57 %21 %17
// Corresponds to i[3].g.*c*
// The index %17 used for c cannot be replaced
auto replacement23 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(17, SpvOpAccessChain, 3, 58, 0),
MakeDataDescriptor(102, {}), 0);
ASSERT_FALSE(replacement23.IsApplicable(context.get(), fact_manager));
// Replacements of the form %57 -> %103
// %58 = OpAccessChain %26 %50 *%57* %21 %17
// Corresponds to i[*3*].g.c
// The index %57 used for 3 *can* be replaced
auto replacement24 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(57, SpvOpAccessChain, 1, 58, 0),
MakeDataDescriptor(103, {}), 0);
ASSERT_TRUE(replacement24.IsApplicable(context.get(), fact_manager));
replacement24.Apply(context.get(), &fact_manager);
ASSERT_TRUE(IsValid(env, context.get()));
// Replacements of the form %32 -> %106
// %34 = OpAccessChain %23 %31 *%32*
// Corresponds to e[*17*]
// The index %32 used for 17 *can* be replaced
auto replacement25 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(32, SpvOpAccessChain, 1, 34, 0),
MakeDataDescriptor(106, {}), 0);
ASSERT_TRUE(replacement25.IsApplicable(context.get(), fact_manager));
replacement25.Apply(context.get(), &fact_manager);
ASSERT_TRUE(IsValid(env, context.get()));
// Replacements of the form %43 -> %107
// %44 = OpAccessChain %23 %37 %21 %21 *%43*
// Corresponds to h.g.b[*0*]
// The index %43 used for 0 *can* be replaced
auto replacement26 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(43, SpvOpAccessChain, 3, 44, 0),
MakeDataDescriptor(107, {}), 0);
ASSERT_TRUE(replacement26.IsApplicable(context.get(), fact_manager));
replacement26.Apply(context.get(), &fact_manager);
ASSERT_TRUE(IsValid(env, context.get()));
// Replacements of the form %55 -> %108
// %56 = OpAccessChain %23 %50 %17 %21 %21 *%55*
// Corresponds to i[2].g.b[*1*]
// The index %55 used for 1 *can* be replaced
auto replacement27 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(55, SpvOpAccessChain, 4, 56, 0),
MakeDataDescriptor(108, {}), 0);
ASSERT_TRUE(replacement27.IsApplicable(context.get(), fact_manager));
replacement27.Apply(context.get(), &fact_manager);
ASSERT_TRUE(IsValid(env, context.get()));
// Replacements of the form %8 -> %109
// %24 = OpAccessChain %23 %15 %21 *%8*
// Corresponds to d.b[*3*]
// The index %8 used for 3 *can* be replaced
auto replacement28 = TransformationReplaceIdWithSynonym(
transformation::MakeIdUseDescriptor(8, SpvOpAccessChain, 2, 24, 0),
MakeDataDescriptor(109, {}), 0);
ASSERT_TRUE(replacement28.IsApplicable(context.get(), fact_manager));
replacement28.Apply(context.get(), &fact_manager);
ASSERT_TRUE(IsValid(env, context.get()));
const std::string after_transformation = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %13 "S"
OpMemberName %13 0 "a"
OpMemberName %13 1 "b"
OpMemberName %13 2 "c"
OpName %15 "d"
OpName %31 "e"
OpName %35 "T"
OpMemberName %35 0 "f"
OpMemberName %35 1 "g"
OpName %37 "h"
OpName %50 "i"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypeInt 32 0
%8 = OpConstant %7 3
%9 = OpTypeArray %6 %8
%10 = OpTypeFloat 32
%11 = OpTypeVector %10 4
%12 = OpTypeBool
%13 = OpTypeStruct %9 %11 %12
%14 = OpTypePointer Private %13
%15 = OpVariable %14 Private
%16 = OpConstant %6 0
%17 = OpConstant %6 2
%18 = OpConstant %6 10
%19 = OpTypePointer Private %6
%21 = OpConstant %6 1
%22 = OpConstant %10 11
%23 = OpTypePointer Private %10
%25 = OpConstantFalse %12
%26 = OpTypePointer Private %12
%28 = OpConstant %7 20
%29 = OpTypeArray %10 %28
%30 = OpTypePointer Private %29
%31 = OpVariable %30 Private
%32 = OpConstant %6 17
%33 = OpConstant %10 12
%35 = OpTypeStruct %10 %13
%36 = OpTypePointer Private %35
%37 = OpVariable %36 Private
%38 = OpConstant %10 13
%40 = OpConstant %6 14
%42 = OpConstant %10 15
%43 = OpConstant %7 0
%45 = OpConstantTrue %12
%47 = OpConstant %7 4
%48 = OpTypeArray %35 %47
%49 = OpTypePointer Private %48
%50 = OpVariable %49 Private
%51 = OpConstant %10 16
%54 = OpConstant %10 18
%55 = OpConstant %7 1
%57 = OpConstant %6 3
%4 = OpFunction %2 None %3
%5 = OpLabel
%100 = OpCopyObject %6 %16 ; 0
%101 = OpCopyObject %6 %21 ; 1
%102 = OpCopyObject %6 %17 ; 2
%103 = OpCopyObject %6 %57 ; 3
%104 = OpCopyObject %6 %18 ; 10
%105 = OpCopyObject %6 %40 ; 14
%106 = OpCopyObject %6 %32 ; 17
%107 = OpCopyObject %7 %43 ; 0
%108 = OpCopyObject %7 %55 ; 1
%109 = OpCopyObject %7 %8 ; 3
%110 = OpCopyObject %7 %47 ; 4
%111 = OpCopyObject %7 %28 ; 20
%112 = OpCopyObject %12 %45 ; true
%20 = OpAccessChain %19 %15 %16 %102
OpStore %20 %18
%24 = OpAccessChain %23 %15 %21 %109
OpStore %24 %22
%27 = OpAccessChain %26 %15 %17
OpStore %27 %25
%34 = OpAccessChain %23 %31 %106
OpStore %34 %33
%39 = OpAccessChain %23 %37 %16
OpStore %39 %38
%41 = OpAccessChain %19 %37 %21 %16 %101
OpStore %41 %40
%44 = OpAccessChain %23 %37 %21 %21 %107
OpStore %44 %42
%46 = OpAccessChain %26 %37 %21 %17
OpStore %46 %45
%52 = OpAccessChain %23 %50 %100 %16
OpStore %52 %51
%53 = OpAccessChain %19 %50 %101 %21 %16 %100
OpStore %53 %32
%56 = OpAccessChain %23 %50 %102 %21 %21 %108
OpStore %56 %54
%58 = OpAccessChain %26 %50 %103 %21 %17
OpStore %58 %45
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}
} // namespace
} // namespace fuzz
} // namespace spvtools

View File

@@ -687,12 +687,12 @@ TEST(TransformationSplitBlockTest, SplitOpPhiWithSinglePredecessor) {
%10 = OpVariable %7 Function
OpStore %8 %9
%11 = OpLoad %6 %8
OpBranch %20
%20 = OpLabel
%21 = OpPhi %6 %11 %5
OpStore %10 %21
OpReturn
OpFunctionEnd
OpBranch %20
%20 = OpLabel
%21 = OpPhi %6 %11 %5
OpStore %10 %21
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
@@ -732,14 +732,14 @@ TEST(TransformationSplitBlockTest, SplitOpPhiWithSinglePredecessor) {
%10 = OpVariable %7 Function
OpStore %8 %9
%11 = OpLoad %6 %8
OpBranch %20
%20 = OpLabel
OpBranch %100
%100 = OpLabel
%21 = OpPhi %6 %11 %20
OpStore %10 %21
OpReturn
OpFunctionEnd
OpBranch %20
%20 = OpLabel
OpBranch %100
%100 = OpLabel
%21 = OpPhi %6 %11 %20
OpStore %10 %21
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_split, context.get()));
}

View File

@@ -3333,7 +3333,16 @@ INSTANTIATE_TEST_SUITE_P(CompositeExtractFoldingTest, GeneralInstructionFoldingT
"%3 = OpCompositeExtract %float %2 4\n" +
"OpReturn\n" +
"OpFunctionEnd",
3, 0)
3, 0),
// Test case 14: Fold OpCompositeExtract with no indexes.
InstructionFoldingCase<uint32_t>(
Header() + "%main = OpFunction %void None %void_func\n" +
"%main_lab = OpLabel\n" +
"%2 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1\n" +
"%3 = OpCompositeExtract %v4float %2\n" +
"OpReturn\n" +
"OpFunctionEnd",
3, 2)
));
INSTANTIATE_TEST_SUITE_P(CompositeConstructFoldingTest, GeneralInstructionFoldingTest,

View File

@@ -820,6 +820,113 @@ OpFunctionEnd
SinglePassRunAndCheck<LocalAccessChainConvertPass>(test, test, false, true);
}
TEST_F(LocalAccessChainConvertTest, IdOverflowReplacingLoad) {
const std::string text =
R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "PSMain"
OpExecutionMode %4 OriginUpperLeft
OpDecorate %10 Location 47360
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_struct_8 = OpTypeStruct %v4float
%_ptr_Function__struct_8 = OpTypePointer Function %_struct_8
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%_ptr_Function_v4float = OpTypePointer Function %v4float
%4 = OpFunction %void None %3
%5 = OpLabel
%10 = OpVariable %_ptr_Function__struct_8 Function
%4194301 = OpAccessChain %_ptr_Function_v4float %10 %int_0
%4194302 = OpLoad %v4float %4194301
OpReturn
OpFunctionEnd
)";
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
std::vector<Message> messages = {
{SPV_MSG_ERROR, "", 0, 0, "ID overflow. Try running compact-ids."}};
SetMessageConsumer(GetTestMessageConsumer(messages));
auto result = SinglePassRunToBinary<LocalAccessChainConvertPass>(text, true);
EXPECT_EQ(Pass::Status::Failure, std::get<1>(result));
}
TEST_F(LocalAccessChainConvertTest, IdOverflowReplacingStore1) {
const std::string text =
R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "PSMain"
OpExecutionMode %4 OriginUpperLeft
OpDecorate %10 Location 47360
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_struct_7 = OpTypeStruct %v4float
%_ptr_Function__struct_7 = OpTypePointer Function %_struct_7
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%_ptr_Function_v4float = OpTypePointer Function %v4float
%13 = OpConstantNull %v4float
%4 = OpFunction %void None %3
%5 = OpLabel
%10 = OpVariable %_ptr_Function__struct_7 Function
%4194302 = OpAccessChain %_ptr_Function_v4float %10 %int_0
OpStore %4194302 %13
OpReturn
OpFunctionEnd
)";
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
std::vector<Message> messages = {
{SPV_MSG_ERROR, "", 0, 0, "ID overflow. Try running compact-ids."}};
SetMessageConsumer(GetTestMessageConsumer(messages));
auto result = SinglePassRunToBinary<LocalAccessChainConvertPass>(text, true);
EXPECT_EQ(Pass::Status::Failure, std::get<1>(result));
}
TEST_F(LocalAccessChainConvertTest, IdOverflowReplacingStore2) {
const std::string text =
R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "PSMain"
OpExecutionMode %4 OriginUpperLeft
OpDecorate %10 Location 47360
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_struct_7 = OpTypeStruct %v4float
%_ptr_Function__struct_7 = OpTypePointer Function %_struct_7
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%_ptr_Function_v4float = OpTypePointer Function %v4float
%13 = OpConstantNull %v4float
%4 = OpFunction %void None %3
%5 = OpLabel
%10 = OpVariable %_ptr_Function__struct_7 Function
%4194301 = OpAccessChain %_ptr_Function_v4float %10 %int_0
OpStore %4194301 %13
OpReturn
OpFunctionEnd
)";
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
std::vector<Message> messages = {
{SPV_MSG_ERROR, "", 0, 0, "ID overflow. Try running compact-ids."}};
SetMessageConsumer(GetTestMessageConsumer(messages));
auto result = SinglePassRunToBinary<LocalAccessChainConvertPass>(text, true);
EXPECT_EQ(Pass::Status::Failure, std::get<1>(result));
}
// TODO(greg-lunarg): Add tests to verify handling of these cases:
//
// Assorted vector and matrix types

View File

@@ -1899,6 +1899,37 @@ TEST_F(ScalarReplacementTest, RelaxedPrecisionMemberDecoration) {
SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
}
TEST_F(ScalarReplacementTest, ExtractWithNoIndex) {
const std::string text = R"(
; CHECK: [[var1:%\w+]] = OpVariable %_ptr_Function_float Function
; CHECK: [[var2:%\w+]] = OpVariable %_ptr_Function_float Function
; CHECK: [[ld1:%\w+]] = OpLoad %float [[var1]]
; CHECK: [[ld2:%\w+]] = OpLoad %float [[var2]]
; CHECK: [[constr:%\w+]] = OpCompositeConstruct {{%\w+}} [[ld2]] [[ld1]]
; CHECK: OpCompositeExtract {{%\w+}} [[constr]]
OpCapability Shader
OpExtension ""
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %1 "main"
OpExecutionMode %1 OriginUpperLeft
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
%_struct_5 = OpTypeStruct %float %float
%_ptr_Private__struct_5 = OpTypePointer Private %_struct_5
%_ptr_Function__struct_5 = OpTypePointer Function %_struct_5
%1 = OpFunction %void Inline %3
%8 = OpLabel
%9 = OpVariable %_ptr_Function__struct_5 Function
%10 = OpLoad %_struct_5 %9
%11 = OpCompositeExtract %_struct_5 %10
OpReturn
OpFunctionEnd
)";
SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
}
} // namespace
} // namespace opt
} // namespace spvtools

View File

@@ -338,6 +338,46 @@ OpFunctionEnd
EXPECT_EQ(Pass::Status::Failure, std::get<1>(result));
}
TEST_F(WrapOpKillTest, IdBoundOverflow5) {
const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 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
%_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
OpKill
OpFunctionEnd
%32257 = OpFunction %_struct_7 None %18
%28 = OpLabel
OpUnreachable
OpFunctionEnd
%64821 = OpFunction %_struct_7 Inline %18
%4194295 = OpLabel
OpKill
OpFunctionEnd
)";
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
std::vector<Message> messages = {
{SPV_MSG_ERROR, "", 0, 0, "ID overflow. Try running compact-ids."}};
SetMessageConsumer(GetTestMessageConsumer(messages));
auto result = SinglePassRunToBinary<WrapOpKill>(text, true);
EXPECT_EQ(Pass::Status::Failure, std::get<1>(result));
}
} // namespace
} // namespace opt
} // namespace spvtools

View File

@@ -629,15 +629,26 @@ TEST_F(ValidateData, specialize_boolean) {
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateData, specialize_boolean_to_int) {
TEST_F(ValidateData, specialize_boolean_true_to_int) {
std::string str = header + R"(
%2 = OpTypeInt 32 1
%3 = OpSpecConstantTrue %2)";
CompileSuccessfully(str.c_str());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("OpSpecConstantTrue Result Type <id> '1[%int]' is not "
"a boolean type"));
}
TEST_F(ValidateData, specialize_boolean_false_to_int) {
std::string str = header + R"(
%2 = OpTypeInt 32 1
%3 = OpSpecConstantTrue %2
%4 = OpSpecConstantFalse %2)";
CompileSuccessfully(str.c_str());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Specialization constant must be a boolean"));
HasSubstr("OpSpecConstantFalse Result Type <id> '1[%int]' is not "
"a boolean type"));
}
TEST_F(ValidateData, missing_forward_pointer_decl) {
@@ -648,7 +659,7 @@ TEST_F(ValidateData, missing_forward_pointer_decl) {
CompileSuccessfully(str.c_str());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("must first be declared using OpTypeForwardPointer"));
HasSubstr("Operand 3[%3] requires a previous definition"));
}
TEST_F(ValidateData, missing_forward_pointer_decl_self_reference) {
@@ -658,8 +669,9 @@ TEST_F(ValidateData, missing_forward_pointer_decl_self_reference) {
)";
CompileSuccessfully(str.c_str());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("must first be declared using OpTypeForwardPointer"));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Operand 2[%_struct_2] requires a previous definition"));
}
TEST_F(ValidateData, forward_pointer_missing_definition) {
@@ -698,9 +710,7 @@ OpTypeForwardPointer %_ptr_Generic_struct_A Generic
CompileSuccessfully(str.c_str());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("A forward reference operand in an OpTypeStruct must "
"be an OpTypePointer that points to an OpTypeStruct. "
"Found OpTypePointer that points to OpTypeInt."));
HasSubstr("Forward pointers must point to a structure"));
}
TEST_F(ValidateData, struct_forward_pointer_good) {
@@ -934,23 +944,6 @@ TEST_F(ValidateData, webgpu_RTA_not_at_end_of_struct) {
"OpTypeStruct %_runtimearr_uint %uint\n"));
}
TEST_F(ValidateData, invalid_forward_reference_in_array) {
std::string str = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%uint = OpTypeInt 32 0
%uint_1 = OpConstant %uint 1
%_arr_3_uint_1 = OpTypeArray %_arr_3_uint_1 %uint_1
)";
CompileSuccessfully(str.c_str(), SPV_ENV_UNIVERSAL_1_3);
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Forward reference operands in an OpTypeArray must "
"first be declared using OpTypeForwardPointer."));
}
} // namespace
} // namespace val
} // namespace spvtools

View File

@@ -1476,7 +1476,8 @@ TEST_F(ValidateIdWithMessage, OpSpecConstantTrueBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Specialization constant must be a boolean type."));
HasSubstr("OpSpecConstantTrue Result Type <id> '1[%void]' is not "
"a boolean type"));
}
TEST_F(ValidateIdWithMessage, OpSpecConstantFalseGood) {
@@ -1492,8 +1493,10 @@ TEST_F(ValidateIdWithMessage, OpSpecConstantFalseBad) {
%2 = OpSpecConstantFalse %1)";
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Specialization constant must be a boolean type."));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("OpSpecConstantFalse Result Type <id> '1[%void]' is not "
"a boolean type"));
}
TEST_F(ValidateIdWithMessage, OpSpecConstantGood) {
@@ -5176,8 +5179,7 @@ TEST_F(ValidateIdWithMessage, UndefinedTypeId) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Forward reference operands in an OpTypeStruct must "
"first be declared using OpTypeForwardPointer."));
HasSubstr("Operand 2[%2] requires a previous definition"));
}
TEST_F(ValidateIdWithMessage, UndefinedIdScope) {
@@ -6462,6 +6464,26 @@ TEST_F(ValidateIdWithMessage, OpTypeForwardPointerWrongStorageClass) {
"pointer definition.\n OpTypeForwardPointer "
"%_ptr_Function_int CrossWorkgroup"));
}
TEST_F(ValidateIdWithMessage, MissingForwardPointer) {
const std::string spirv = R"(
OpCapability Linkage
OpCapability Shader
OpMemoryModel Logical Simple
%float = OpTypeFloat 32
%_struct_9 = OpTypeStruct %float %_ptr_Uniform__struct_9
%_ptr_Uniform__struct_9 = OpTypePointer Uniform %_struct_9
%1278 = OpVariable %_ptr_Uniform__struct_9 Uniform
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Operand 3[%_ptr_Uniform__struct_2] requires a previous definition"));
}
} // namespace
} // namespace val
} // namespace spvtools

View File

@@ -1415,7 +1415,8 @@ TEST_F(ValidateSSA, TypeForwardPointerForwardReference) {
OpName %intptrt "intptrt"
OpTypeForwardPointer %intptrt UniformConstant
%uint = OpTypeInt 32 0
%intptrt = OpTypePointer UniformConstant %uint
%struct = OpTypeStruct %uint
%intptrt = OpTypePointer UniformConstant %struct
)";
CompileSuccessfully(str);

View File

@@ -210,9 +210,11 @@ OpMemoryModel Physical32 OpenCL
OpTypeForwardPointer %ptr Generic
OpTypeForwardPointer %ptr2 Generic
%intt = OpTypeInt 32 0
%int_struct = OpTypeStruct %intt
%floatt = OpTypeFloat 32
%ptr = OpTypePointer Generic %intt
%ptr2 = OpTypePointer Generic %floatt
%ptr = OpTypePointer Generic %int_struct
%float_struct = OpTypeStruct %floatt
%ptr2 = OpTypePointer Generic %float_struct
)";
CompileSuccessfully(str.c_str());
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());

View File

@@ -1,75 +0,0 @@
# 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.
if (NOT ${SPIRV_SKIP_EXECUTABLES})
add_subdirectory(lesspipe)
endif()
add_subdirectory(emacs)
# Add a SPIR-V Tools command line tool. Signature:
# add_spvtools_tool(
# TARGET target_name
# SRCS src_file1.cpp src_file2.cpp
# LIBS lib_target1 lib_target2
# )
function(add_spvtools_tool)
set(one_value_args TARGET)
set(multi_value_args SRCS LIBS)
cmake_parse_arguments(
ARG "" "${one_value_args}" "${multi_value_args}" ${ARGN})
add_executable(${ARG_TARGET} ${ARG_SRCS})
spvtools_default_compile_options(${ARG_TARGET})
target_link_libraries(${ARG_TARGET} PRIVATE ${ARG_LIBS})
target_include_directories(${ARG_TARGET} PRIVATE
${spirv-tools_SOURCE_DIR}
${spirv-tools_BINARY_DIR}
)
set_property(TARGET ${ARG_TARGET} PROPERTY FOLDER "SPIRV-Tools executables")
endfunction()
if (NOT ${SPIRV_SKIP_EXECUTABLES})
add_spvtools_tool(TARGET spirv-as SRCS as/as.cpp LIBS ${SPIRV_TOOLS})
add_spvtools_tool(TARGET spirv-dis SRCS dis/dis.cpp LIBS ${SPIRV_TOOLS})
add_spvtools_tool(TARGET spirv-val SRCS val/val.cpp util/cli_consumer.cpp LIBS ${SPIRV_TOOLS})
add_spvtools_tool(TARGET spirv-opt SRCS opt/opt.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS})
if (NOT DEFINED IOS_PLATFORM) # iOS does not allow std::system calls which spirv-reduce requires
add_spvtools_tool(TARGET spirv-reduce SRCS reduce/reduce.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-reduce ${SPIRV_TOOLS})
endif()
add_spvtools_tool(TARGET spirv-link SRCS link/linker.cpp LIBS SPIRV-Tools-link ${SPIRV_TOOLS})
add_spvtools_tool(TARGET spirv-cfg
SRCS cfg/cfg.cpp
cfg/bin_to_dot.h
cfg/bin_to_dot.cpp
LIBS ${SPIRV_TOOLS})
target_include_directories(spirv-cfg PRIVATE ${spirv-tools_SOURCE_DIR}
${SPIRV_HEADER_INCLUDE_DIR})
set(SPIRV_INSTALL_TARGETS spirv-as spirv-dis spirv-val spirv-opt
spirv-cfg spirv-link)
if(NOT DEFINED IOS_PLATFORM)
set(SPIRV_INSTALL_TARGETS ${SPIRV_INSTALL_TARGETS} spirv-reduce)
endif()
if(SPIRV_BUILD_FUZZER)
add_spvtools_tool(TARGET spirv-fuzz SRCS fuzz/fuzz.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-fuzz ${SPIRV_TOOLS})
set(SPIRV_INSTALL_TARGETS ${SPIRV_INSTALL_TARGETS} spirv-fuzz)
endif(SPIRV_BUILD_FUZZER)
if(ENABLE_SPIRV_TOOLS_INSTALL)
install(TARGETS ${SPIRV_INSTALL_TARGETS}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif(ENABLE_SPIRV_TOOLS_INSTALL)
endif()

View File

@@ -1,154 +0,0 @@
// 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.
#include <cstdio>
#include <cstring>
#include <vector>
#include "source/spirv_target_env.h"
#include "spirv-tools/libspirv.h"
#include "tools/io.h"
void print_usage(char* argv0) {
std::string target_env_list = spvTargetEnvList(19, 80);
printf(
R"(%s - Create a SPIR-V binary module from SPIR-V assembly text
Usage: %s [options] [<filename>]
The SPIR-V assembly text is read from <filename>. If no file is specified,
or if the filename is "-", then the assembly text is read from standard input.
The SPIR-V binary module is written to file "out.spv", unless the -o option
is used.
Options:
-h, --help Print this help.
-o <filename> Set the output filename. Use '-' to mean stdout.
--version Display assembler version information.
--preserve-numeric-ids
Numeric IDs in the binary will have the same values as in the
source. Non-numeric IDs are allocated by filling in the gaps,
starting with 1 and going up.
--target-env {%s}
Use specified environment.
)",
argv0, argv0, target_env_list.c_str());
}
static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_5;
int main(int argc, char** argv) {
const char* inFile = nullptr;
const char* outFile = nullptr;
uint32_t options = 0;
spv_target_env target_env = kDefaultEnvironment;
for (int argi = 1; argi < argc; ++argi) {
if ('-' == argv[argi][0]) {
switch (argv[argi][1]) {
case 'h': {
print_usage(argv[0]);
return 0;
}
case 'o': {
if (!outFile && argi + 1 < argc) {
outFile = argv[++argi];
} else {
print_usage(argv[0]);
return 1;
}
} break;
case 0: {
// Setting a filename of "-" to indicate stdin.
if (!inFile) {
inFile = argv[argi];
} else {
fprintf(stderr, "error: More than one input file specified\n");
return 1;
}
} break;
case '-': {
// Long options
if (0 == strcmp(argv[argi], "--version")) {
printf("%s\n", spvSoftwareVersionDetailsString());
printf("Target: %s\n",
spvTargetEnvDescription(kDefaultEnvironment));
return 0;
} else if (0 == strcmp(argv[argi], "--help")) {
print_usage(argv[0]);
return 0;
} else if (0 == strcmp(argv[argi], "--preserve-numeric-ids")) {
options |= SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS;
} else if (0 == strcmp(argv[argi], "--target-env")) {
if (argi + 1 < argc) {
const auto env_str = argv[++argi];
if (!spvParseTargetEnv(env_str, &target_env)) {
fprintf(stderr, "error: Unrecognized target env: %s\n",
env_str);
return 1;
}
} else {
fprintf(stderr, "error: Missing argument to --target-env\n");
return 1;
}
} else {
fprintf(stderr, "error: Unrecognized option: %s\n\n", argv[argi]);
print_usage(argv[0]);
return 1;
}
} break;
default:
fprintf(stderr, "error: Unrecognized option: %s\n\n", argv[argi]);
print_usage(argv[0]);
return 1;
}
} else {
if (!inFile) {
inFile = argv[argi];
} else {
fprintf(stderr, "error: More than one input file specified\n");
return 1;
}
}
}
if (!outFile) {
outFile = "out.spv";
}
std::vector<char> contents;
if (!ReadFile<char>(inFile, "r", &contents)) return 1;
spv_binary binary;
spv_diagnostic diagnostic = nullptr;
spv_context context = spvContextCreate(target_env);
spv_result_t error = spvTextToBinaryWithOptions(
context, contents.data(), contents.size(), options, &binary, &diagnostic);
spvContextDestroy(context);
if (error) {
spvDiagnosticPrint(diagnostic);
spvDiagnosticDestroy(diagnostic);
return error;
}
if (!WriteFile<uint32_t>(outFile, "wb", binary->code, binary->wordCount)) {
spvBinaryDestroy(binary);
return 1;
}
spvBinaryDestroy(binary);
return 0;
}

View File

@@ -1,187 +0,0 @@
// Copyright (c) 2016 Google 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.
#include "tools/cfg/bin_to_dot.h"
#include <cassert>
#include <iostream>
#include <utility>
#include <vector>
#include "source/assembly_grammar.h"
#include "source/name_mapper.h"
namespace {
const char* kMergeStyle = "style=dashed";
const char* kContinueStyle = "style=dotted";
// A DotConverter can be used to dump the GraphViz "dot" graph for
// a SPIR-V module.
class DotConverter {
public:
DotConverter(spvtools::NameMapper name_mapper, std::iostream* out)
: name_mapper_(std::move(name_mapper)), out_(*out) {}
// Emits the graph preamble.
void Begin() const {
out_ << "digraph {\n";
// Emit a simple legend
out_ << "legend_merge_src [shape=plaintext, label=\"\"];\n"
<< "legend_merge_dest [shape=plaintext, label=\"\"];\n"
<< "legend_merge_src -> legend_merge_dest [label=\" merge\","
<< kMergeStyle << "];\n"
<< "legend_continue_src [shape=plaintext, label=\"\"];\n"
<< "legend_continue_dest [shape=plaintext, label=\"\"];\n"
<< "legend_continue_src -> legend_continue_dest [label=\" continue\","
<< kContinueStyle << "];\n";
}
// Emits the graph postamble.
void End() const { out_ << "}\n"; }
// Emits the Dot commands for the given instruction.
spv_result_t HandleInstruction(const spv_parsed_instruction_t& inst);
private:
// Ends processing for the current block, emitting its dot code.
void FlushBlock(const std::vector<uint32_t>& successors);
// The ID of the current functio, or 0 if outside of a function.
uint32_t current_function_id_ = 0;
// The ID of the current basic block, or 0 if outside of a block.
uint32_t current_block_id_ = 0;
// Have we completed processing for the entry block to this fuction?
bool seen_function_entry_block_ = false;
// The Id of the merge block for this block if it exists, or 0 otherwise.
uint32_t merge_ = 0;
// The Id of the continue target block for this block if it exists, or 0
// otherwise.
uint32_t continue_target_ = 0;
// An object for mapping Ids to names.
spvtools::NameMapper name_mapper_;
// The output stream.
std::ostream& out_;
};
spv_result_t DotConverter::HandleInstruction(
const spv_parsed_instruction_t& inst) {
switch (inst.opcode) {
case SpvOpFunction:
current_function_id_ = inst.result_id;
seen_function_entry_block_ = false;
break;
case SpvOpFunctionEnd:
current_function_id_ = 0;
break;
case SpvOpLabel:
current_block_id_ = inst.result_id;
break;
case SpvOpBranch:
FlushBlock({inst.words[1]});
break;
case SpvOpBranchConditional:
FlushBlock({inst.words[2], inst.words[3]});
break;
case SpvOpSwitch: {
std::vector<uint32_t> successors{inst.words[2]};
for (size_t i = 3; i < inst.num_operands; i += 2) {
successors.push_back(inst.words[inst.operands[i].offset]);
}
FlushBlock(successors);
} break;
case SpvOpKill:
case SpvOpReturn:
case SpvOpUnreachable:
case SpvOpReturnValue:
FlushBlock({});
break;
case SpvOpLoopMerge:
merge_ = inst.words[1];
continue_target_ = inst.words[2];
break;
case SpvOpSelectionMerge:
merge_ = inst.words[1];
break;
default:
break;
}
return SPV_SUCCESS;
}
void DotConverter::FlushBlock(const std::vector<uint32_t>& successors) {
out_ << current_block_id_;
if (!seen_function_entry_block_) {
out_ << " [label=\"" << name_mapper_(current_block_id_) << "\nFn "
<< name_mapper_(current_function_id_) << " entry\", shape=box];\n";
} else {
out_ << " [label=\"" << name_mapper_(current_block_id_) << "\"];\n";
}
for (auto successor : successors) {
out_ << current_block_id_ << " -> " << successor << ";\n";
}
if (merge_) {
out_ << current_block_id_ << " -> " << merge_ << " [" << kMergeStyle
<< "];\n";
}
if (continue_target_) {
out_ << current_block_id_ << " -> " << continue_target_ << " ["
<< kContinueStyle << "];\n";
}
// Reset the book-keeping for a block.
seen_function_entry_block_ = true;
merge_ = 0;
continue_target_ = 0;
}
spv_result_t HandleInstruction(
void* user_data, const spv_parsed_instruction_t* parsed_instruction) {
assert(user_data);
auto converter = static_cast<DotConverter*>(user_data);
return converter->HandleInstruction(*parsed_instruction);
}
} // anonymous namespace
spv_result_t BinaryToDot(const spv_const_context context, const uint32_t* words,
size_t num_words, std::iostream* out,
spv_diagnostic* diagnostic) {
// Invalid arguments return error codes, but don't necessarily generate
// diagnostics. These are programmer errors, not user errors.
if (!diagnostic) return SPV_ERROR_INVALID_DIAGNOSTIC;
const spvtools::AssemblyGrammar grammar(context);
if (!grammar.isValid()) return SPV_ERROR_INVALID_TABLE;
spvtools::FriendlyNameMapper friendly_mapper(context, words, num_words);
DotConverter converter(friendly_mapper.GetNameMapper(), out);
converter.Begin();
if (auto error = spvBinaryParse(context, &converter, words, num_words,
nullptr, HandleInstruction, diagnostic)) {
return error;
}
converter.End();
return SPV_SUCCESS;
}

View File

@@ -1,28 +0,0 @@
// Copyright (c) 2016 Google 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.
#ifndef TOOLS_CFG_BIN_TO_DOT_H_
#define TOOLS_CFG_BIN_TO_DOT_H_
#include <iostream>
#include "spirv-tools/libspirv.h"
// Dumps the control flow graph for the given module to the output stream.
// Returns SPV_SUCCESS on succes.
spv_result_t BinaryToDot(const spv_const_context context, const uint32_t* words,
size_t num_words, std::iostream* out,
spv_diagnostic* diagnostic);
#endif // TOOLS_CFG_BIN_TO_DOT_H_

View File

@@ -1,127 +0,0 @@
// 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.
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <vector>
#include "spirv-tools/libspirv.h"
#include "tools/cfg/bin_to_dot.h"
#include "tools/io.h"
// Prints a program usage message to stdout.
static void print_usage(const char* argv0) {
printf(
R"(%s - Show the control flow graph in GraphiViz "dot" form. EXPERIMENTAL
Usage: %s [options] [<filename>]
The SPIR-V binary is read from <filename>. If no file is specified,
or if the filename is "-", then the binary is read from standard input.
Options:
-h, --help Print this help.
--version Display version information.
-o <filename> Set the output filename.
Output goes to standard output if this option is
not specified, or if the filename is "-".
)",
argv0, argv0);
}
static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_5;
int main(int argc, char** argv) {
const char* inFile = nullptr;
const char* outFile = nullptr; // Stays nullptr if printing to stdout.
for (int argi = 1; argi < argc; ++argi) {
if ('-' == argv[argi][0]) {
switch (argv[argi][1]) {
case 'h':
print_usage(argv[0]);
return 0;
case 'o': {
if (!outFile && argi + 1 < argc) {
outFile = argv[++argi];
} else {
print_usage(argv[0]);
return 1;
}
} break;
case '-': {
// Long options
if (0 == strcmp(argv[argi], "--help")) {
print_usage(argv[0]);
return 0;
} else if (0 == strcmp(argv[argi], "--version")) {
printf("%s EXPERIMENTAL\n", spvSoftwareVersionDetailsString());
printf("Target: %s\n",
spvTargetEnvDescription(kDefaultEnvironment));
return 0;
} else {
print_usage(argv[0]);
return 1;
}
} break;
case 0: {
// Setting a filename of "-" to indicate stdin.
if (!inFile) {
inFile = argv[argi];
} else {
fprintf(stderr, "error: More than one input file specified\n");
return 1;
}
} break;
default:
print_usage(argv[0]);
return 1;
}
} else {
if (!inFile) {
inFile = argv[argi];
} else {
fprintf(stderr, "error: More than one input file specified\n");
return 1;
}
}
}
// Read the input binary.
std::vector<uint32_t> contents;
if (!ReadFile<uint32_t>(inFile, "rb", &contents)) return 1;
spv_context context = spvContextCreate(kDefaultEnvironment);
spv_diagnostic diagnostic = nullptr;
std::stringstream ss;
auto error =
BinaryToDot(context, contents.data(), contents.size(), &ss, &diagnostic);
if (error) {
spvDiagnosticPrint(diagnostic);
spvDiagnosticDestroy(diagnostic);
spvContextDestroy(context);
return error;
}
std::string str = ss.str();
WriteFile(outFile, "w", str.data(), str.size());
spvDiagnosticDestroy(diagnostic);
spvContextDestroy(context);
return 0;
}

View File

@@ -1,209 +0,0 @@
// 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.
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
#include <stdio.h> // Need fileno
#include <unistd.h>
#endif
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include "spirv-tools/libspirv.h"
#include "tools/io.h"
static void print_usage(char* argv0) {
printf(
R"(%s - Disassemble a SPIR-V binary module
Usage: %s [options] [<filename>]
The SPIR-V binary is read from <filename>. If no file is specified,
or if the filename is "-", then the binary is read from standard input.
Options:
-h, --help Print this help.
--version Display disassembler version information.
-o <filename> Set the output filename.
Output goes to standard output if this option is
not specified, or if the filename is "-".
--color Force color output. The default when printing to a terminal.
Overrides a previous --no-color option.
--no-color Don't print in color. Overrides a previous --color option.
The default when output goes to something other than a
terminal (e.g. a file, a pipe, or a shell redirection).
--no-indent Don't indent instructions.
--no-header Don't output the header as leading comments.
--raw-id Show raw Id values instead of friendly names.
--offsets Show byte offsets for each instruction.
)",
argv0, argv0);
}
static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_5;
int main(int argc, char** argv) {
const char* inFile = nullptr;
const char* outFile = nullptr;
bool color_is_possible =
#if SPIRV_COLOR_TERMINAL
true;
#else
false;
#endif
bool force_color = false;
bool force_no_color = false;
bool allow_indent = true;
bool show_byte_offsets = false;
bool no_header = false;
bool friendly_names = true;
for (int argi = 1; argi < argc; ++argi) {
if ('-' == argv[argi][0]) {
switch (argv[argi][1]) {
case 'h':
print_usage(argv[0]);
return 0;
case 'o': {
if (!outFile && argi + 1 < argc) {
outFile = argv[++argi];
} else {
print_usage(argv[0]);
return 1;
}
} break;
case '-': {
// Long options
if (0 == strcmp(argv[argi], "--no-color")) {
force_no_color = true;
force_color = false;
} else if (0 == strcmp(argv[argi], "--color")) {
force_no_color = false;
force_color = true;
} else if (0 == strcmp(argv[argi], "--no-indent")) {
allow_indent = false;
} else if (0 == strcmp(argv[argi], "--offsets")) {
show_byte_offsets = true;
} else if (0 == strcmp(argv[argi], "--no-header")) {
no_header = true;
} else if (0 == strcmp(argv[argi], "--raw-id")) {
friendly_names = false;
} else if (0 == strcmp(argv[argi], "--help")) {
print_usage(argv[0]);
return 0;
} else if (0 == strcmp(argv[argi], "--version")) {
printf("%s\n", spvSoftwareVersionDetailsString());
printf("Target: %s\n",
spvTargetEnvDescription(kDefaultEnvironment));
return 0;
} else {
print_usage(argv[0]);
return 1;
}
} break;
case 0: {
// Setting a filename of "-" to indicate stdin.
if (!inFile) {
inFile = argv[argi];
} else {
fprintf(stderr, "error: More than one input file specified\n");
return 1;
}
} break;
default:
print_usage(argv[0]);
return 1;
}
} else {
if (!inFile) {
inFile = argv[argi];
} else {
fprintf(stderr, "error: More than one input file specified\n");
return 1;
}
}
}
uint32_t options = SPV_BINARY_TO_TEXT_OPTION_NONE;
if (allow_indent) options |= SPV_BINARY_TO_TEXT_OPTION_INDENT;
if (show_byte_offsets) options |= SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET;
if (no_header) options |= SPV_BINARY_TO_TEXT_OPTION_NO_HEADER;
if (friendly_names) options |= SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES;
if (!outFile || (0 == strcmp("-", outFile))) {
// Print to standard output.
options |= SPV_BINARY_TO_TEXT_OPTION_PRINT;
if (color_is_possible && !force_no_color) {
bool output_is_tty = true;
#if defined(_POSIX_VERSION)
output_is_tty = isatty(fileno(stdout));
#endif
if (output_is_tty || force_color) {
options |= SPV_BINARY_TO_TEXT_OPTION_COLOR;
}
}
}
// Read the input binary.
std::vector<uint32_t> contents;
if (!ReadFile<uint32_t>(inFile, "rb", &contents)) return 1;
// If printing to standard output, then spvBinaryToText should
// do the printing. In particular, colour printing on Windows is
// controlled by modifying console objects synchronously while
// outputting to the stream rather than by injecting escape codes
// into the output stream.
// If the printing option is off, then save the text in memory, so
// it can be emitted later in this function.
const bool print_to_stdout = SPV_BINARY_TO_TEXT_OPTION_PRINT & options;
spv_text text = nullptr;
spv_text* textOrNull = print_to_stdout ? nullptr : &text;
spv_diagnostic diagnostic = nullptr;
spv_context context = spvContextCreate(kDefaultEnvironment);
spv_result_t error =
spvBinaryToText(context, contents.data(), contents.size(), options,
textOrNull, &diagnostic);
spvContextDestroy(context);
if (error) {
spvDiagnosticPrint(diagnostic);
spvDiagnosticDestroy(diagnostic);
return error;
}
if (!print_to_stdout) {
if (!WriteFile<char>(outFile, "w", text->str, text->length)) {
spvTextDestroy(text);
return 1;
}
}
spvTextDestroy(text);
return 0;
}

View File

@@ -1,40 +0,0 @@
;; Copyright (c) 2016 LunarG 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.
;; Upon loading a file with the .spv extension into emacs, the file
;; will be disassembled using spirv-dis, and the result colorized with
;; asm-mode in emacs. The file may be edited within the constraints
;; of validity, and when re-saved will be re-assembled using spirv-as.
;; Note that symbol IDs are not preserved through a load/edit/save operation.
;; This may change if the ability is added to spirv-as.
;; It is required that those tools be in your PATH. If that is not the case
;; when starting emacs, the path can be modified as in this example:
;; (setenv "PATH" (concat (getenv "PATH") ":/path/to/spirv/tools"))
;;
;; See https://github.com/KhronosGroup/SPIRV-Tools/issues/359
(require 'jka-compr)
(require 'asm-mode)
(add-to-list 'jka-compr-compression-info-list
'["\\.spv\\'"
"Assembling SPIRV" "spirv-as" ("-o" "-")
"Disassembling SPIRV" "spirv-dis" ("--no-color" "--raw-id")
t nil "\003\002\043\007"])
(add-to-list 'auto-mode-alist '("\\.spv\\'" . asm-mode))
(jka-compr-update)

View File

@@ -1,48 +0,0 @@
# Copyright (c) 2016 LunarG 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.
# Install a script for use with the auto-compression feature of emacs(1).
# Upon loading a file with the .spv extension, the file will be disassembled
# using spirv-dis, and the result colorized with asm-mode in emacs. The file
# may be edited within the constraints of validity, and when re-saved will be
# re-assembled using spirv-as.
# It is required that those tools be in your PATH. If that is not the case
# when starting emacs, the path can be modified as in this example:
# (setenv "PATH" (concat (getenv "PATH") ":/path/to/spirv/tools"))
#
# See https://github.com/KhronosGroup/SPIRV-Tools/issues/359
# This is an absolute directory, and ignores CMAKE_INSTALL_PREFIX, or
# it will not be found by emacs upon startup. It is only installed if
# both of the following are true:
# 1. SPIRV_TOOLS_INSTALL_EMACS_HELPERS is defined
# 2. The directory /etc/emacs/site-start.d already exists at the time of
# cmake invocation (not at the time of make install). This is
# typically true if emacs is installed on the system.
# Note that symbol IDs are not preserved through a load/edit/save operation.
# This may change if the ability is added to spirv-as.
option(SPIRV_TOOLS_INSTALL_EMACS_HELPERS
"Install Emacs helper to disassemble/assemble SPIR-V binaries on file load/save."
${SPIRV_TOOLS_INSTALL_EMACS_HELPERS})
if (${SPIRV_TOOLS_INSTALL_EMACS_HELPERS})
if(EXISTS /etc/emacs/site-start.d)
if(ENABLE_SPIRV_TOOLS_INSTALL)
install(FILES 50spirv-tools.el DESTINATION /etc/emacs/site-start.d)
endif(ENABLE_SPIRV_TOOLS_INSTALL)
endif()
endif()

View File

@@ -1,519 +0,0 @@
// 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 <cassert>
#include <cerrno>
#include <cstring>
#include <fstream>
#include <functional>
#include <sstream>
#include <string>
#include "source/fuzz/fuzzer.h"
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/fuzz/replayer.h"
#include "source/fuzz/shrinker.h"
#include "source/opt/build_module.h"
#include "source/opt/ir_context.h"
#include "source/opt/log.h"
#include "source/spirv_fuzzer_options.h"
#include "source/util/string_utils.h"
#include "tools/io.h"
#include "tools/util/cli_consumer.h"
namespace {
// Check that the std::system function can actually be used.
bool CheckExecuteCommand() {
int res = std::system(nullptr);
return res != 0;
}
// Execute a command using the shell.
// Returns true if and only if the command's exit status was 0.
bool ExecuteCommand(const std::string& command) {
errno = 0;
int status = std::system(command.c_str());
assert(errno == 0 && "failed to execute command");
// The result returned by 'system' is implementation-defined, but is
// usually the case that the returned value is 0 when the command's exit
// code was 0. We are assuming that here, and that's all we depend on.
return status == 0;
}
// Status and actions to perform after parsing command-line arguments.
enum class FuzzActions {
FUZZ, // Run the fuzzer to apply transformations in a randomized fashion.
REPLAY, // Replay an existing sequence of transformations.
SHRINK, // Shrink an existing sequence of transformations with respect to an
// interestingness function.
STOP // Do nothing.
};
struct FuzzStatus {
FuzzActions action;
int code;
};
void PrintUsage(const char* program) {
// NOTE: Please maintain flags in lexicographical order.
printf(
R"(%s - Fuzzes an equivalent SPIR-V binary based on a given binary.
USAGE: %s [options] <input.spv> -o <output.spv>
The SPIR-V binary is read from <input.spv>, which must have extension .spv. If
<input.facts> is also present, facts about the SPIR-V binary are read from this
file.
The transformed SPIR-V binary is written to <output.spv>. Human-readable and
binary representations of the transformations that were applied are written to
<output.transformations_json> and <output.transformations>, respectively.
NOTE: The fuzzer is a work in progress.
Options (in lexicographical order):
-h, --help
Print this help.
--replay
File from which to read a sequence of transformations to replay
(instead of fuzzing)
--seed
Unsigned 32-bit integer seed to control random number
generation.
--shrink
File from which to read a sequence of transformations to shrink
(instead of fuzzing)
--shrinker-step-limit
Unsigned 32-bit integer specifying maximum number of steps the
shrinker will take before giving up. Ignored unless --shrink
is used.
--interestingness
Path to an interestingness function to guide shrinking: a script
that returns 0 if and only if a given binary is interesting.
Required if --shrink is provided; disallowed otherwise.
--replay-validation
Run the validator after applying each transformation during
replay (including the replay that occurs during shrinking).
Aborts if an invalid binary is created. Useful for debugging
spirv-fuzz.
--version
Display fuzzer version information.
)",
program, program);
}
// Message consumer for this tool. Used to emit diagnostics during
// initialization and setup. Note that |source| and |position| are irrelevant
// here because we are still not processing a SPIR-V input file.
void FuzzDiagnostic(spv_message_level_t level, const char* /*source*/,
const spv_position_t& /*position*/, const char* message) {
if (level == SPV_MSG_ERROR) {
fprintf(stderr, "error: ");
}
fprintf(stderr, "%s\n", message);
}
bool EndsWithSpv(const std::string& filename) {
std::string dot_spv = ".spv";
return filename.length() >= dot_spv.length() &&
0 == filename.compare(filename.length() - dot_spv.length(),
filename.length(), dot_spv);
}
FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
std::string* out_binary_file,
std::string* replay_transformations_file,
std::string* interestingness_function_file,
std::string* shrink_transformations_file,
spvtools::FuzzerOptions* fuzzer_options) {
uint32_t positional_arg_index = 0;
for (int argi = 1; argi < argc; ++argi) {
const char* cur_arg = argv[argi];
if ('-' == cur_arg[0]) {
if (0 == strcmp(cur_arg, "--version")) {
spvtools::Logf(FuzzDiagnostic, SPV_MSG_INFO, nullptr, {}, "%s\n",
spvSoftwareVersionDetailsString());
return {FuzzActions::STOP, 0};
} else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
PrintUsage(argv[0]);
return {FuzzActions::STOP, 0};
} else if (0 == strcmp(cur_arg, "-o")) {
if (out_binary_file->empty() && argi + 1 < argc) {
*out_binary_file = std::string(argv[++argi]);
} else {
PrintUsage(argv[0]);
return {FuzzActions::STOP, 1};
}
} else if (0 == strncmp(cur_arg, "--replay=", sizeof("--replay=") - 1)) {
const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
*replay_transformations_file = std::string(split_flag.second);
} else if (0 == strncmp(cur_arg, "--interestingness=",
sizeof("--interestingness=") - 1)) {
const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
*interestingness_function_file = std::string(split_flag.second);
} else if (0 == strncmp(cur_arg, "--replay-validation",
sizeof("--replay-validation") - 1)) {
fuzzer_options->enable_replay_validation();
} else if (0 == strncmp(cur_arg, "--shrink=", sizeof("--shrink=") - 1)) {
const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
*shrink_transformations_file = std::string(split_flag.second);
} else if (0 == strncmp(cur_arg, "--seed=", sizeof("--seed=") - 1)) {
const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
char* end = nullptr;
errno = 0;
const auto seed =
static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
assert(end != split_flag.second.c_str() && errno == 0);
fuzzer_options->set_random_seed(seed);
} else if (0 == strncmp(cur_arg, "--shrinker-step-limit=",
sizeof("--shrinker-step-limit=") - 1)) {
const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
char* end = nullptr;
errno = 0;
const auto step_limit =
static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
assert(end != split_flag.second.c_str() && errno == 0);
fuzzer_options->set_shrinker_step_limit(step_limit);
} else if ('\0' == cur_arg[1]) {
// We do not support fuzzing from standard input. We could support
// this if there was a compelling use case.
PrintUsage(argv[0]);
return {FuzzActions::STOP, 0};
}
} else if (positional_arg_index == 0) {
// Binary input file name
assert(in_binary_file->empty());
*in_binary_file = std::string(cur_arg);
positional_arg_index++;
} else {
spvtools::Error(FuzzDiagnostic, nullptr, {},
"Too many positional arguments specified");
return {FuzzActions::STOP, 1};
}
}
if (in_binary_file->empty()) {
spvtools::Error(FuzzDiagnostic, nullptr, {}, "No input file specified");
return {FuzzActions::STOP, 1};
}
if (!EndsWithSpv(*in_binary_file)) {
spvtools::Error(FuzzDiagnostic, nullptr, {},
"Input filename must have extension .spv");
return {FuzzActions::STOP, 1};
}
if (out_binary_file->empty()) {
spvtools::Error(FuzzDiagnostic, nullptr, {}, "-o required");
return {FuzzActions::STOP, 1};
}
if (!EndsWithSpv(*out_binary_file)) {
spvtools::Error(FuzzDiagnostic, nullptr, {},
"Output filename must have extension .spv");
return {FuzzActions::STOP, 1};
}
if (replay_transformations_file->empty() &&
shrink_transformations_file->empty() &&
static_cast<spv_const_fuzzer_options>(*fuzzer_options)
->replay_validation_enabled) {
spvtools::Error(FuzzDiagnostic, nullptr, {},
"The --replay-validation argument can only be used with "
"one of the --replay or --shrink arguments.");
return {FuzzActions::STOP, 1};
}
if (!replay_transformations_file->empty()) {
// A replay transformations file was given, thus the tool is being invoked
// in replay mode.
if (!shrink_transformations_file->empty()) {
spvtools::Error(
FuzzDiagnostic, nullptr, {},
"The --replay and --shrink arguments are mutually exclusive.");
return {FuzzActions::STOP, 1};
}
if (!interestingness_function_file->empty()) {
spvtools::Error(FuzzDiagnostic, nullptr, {},
"The --replay and --interestingness arguments are "
"mutually exclusive.");
return {FuzzActions::STOP, 1};
}
return {FuzzActions::REPLAY, 0};
}
if (!shrink_transformations_file->empty() ^
!interestingness_function_file->empty()) {
spvtools::Error(FuzzDiagnostic, nullptr, {},
"Both or neither of the --shrink and --interestingness "
"arguments must be provided.");
return {FuzzActions::STOP, 1};
}
if (!shrink_transformations_file->empty()) {
// The tool is being invoked in shrink mode.
assert(!interestingness_function_file->empty() &&
"An error should have been raised if --shrink but not --interesting "
"was provided.");
return {FuzzActions::SHRINK, 0};
}
return {FuzzActions::FUZZ, 0};
}
bool ParseTransformations(
const std::string& transformations_file,
spvtools::fuzz::protobufs::TransformationSequence* transformations) {
std::ifstream transformations_stream;
transformations_stream.open(transformations_file,
std::ios::in | std::ios::binary);
auto parse_success =
transformations->ParseFromIstream(&transformations_stream);
transformations_stream.close();
if (!parse_success) {
spvtools::Error(FuzzDiagnostic, nullptr, {},
("Error reading transformations from file '" +
transformations_file + "'")
.c_str());
return false;
}
return true;
}
bool Replay(const spv_target_env& target_env,
spv_const_fuzzer_options fuzzer_options,
const std::vector<uint32_t>& binary_in,
const spvtools::fuzz::protobufs::FactSequence& initial_facts,
const std::string& replay_transformations_file,
std::vector<uint32_t>* binary_out,
spvtools::fuzz::protobufs::TransformationSequence*
transformations_applied) {
spvtools::fuzz::protobufs::TransformationSequence transformation_sequence;
if (!ParseTransformations(replay_transformations_file,
&transformation_sequence)) {
return false;
}
spvtools::fuzz::Replayer replayer(target_env,
fuzzer_options->replay_validation_enabled);
replayer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
auto replay_result_status =
replayer.Run(binary_in, initial_facts, transformation_sequence,
binary_out, transformations_applied);
return !(replay_result_status !=
spvtools::fuzz::Replayer::ReplayerResultStatus::kComplete);
}
bool Shrink(const spv_target_env& target_env,
spv_const_fuzzer_options fuzzer_options,
const std::vector<uint32_t>& binary_in,
const spvtools::fuzz::protobufs::FactSequence& initial_facts,
const std::string& shrink_transformations_file,
const std::string& interestingness_function_file,
std::vector<uint32_t>* binary_out,
spvtools::fuzz::protobufs::TransformationSequence*
transformations_applied) {
spvtools::fuzz::protobufs::TransformationSequence transformation_sequence;
if (!ParseTransformations(shrink_transformations_file,
&transformation_sequence)) {
return false;
}
spvtools::fuzz::Shrinker shrinker(target_env,
fuzzer_options->shrinker_step_limit,
fuzzer_options->replay_validation_enabled);
shrinker.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
spvtools::fuzz::Shrinker::InterestingnessFunction interestingness_function =
[interestingness_function_file](std::vector<uint32_t> binary,
uint32_t reductions_applied) -> bool {
std::stringstream ss;
ss << "temp_" << std::setw(4) << std::setfill('0') << reductions_applied
<< ".spv";
const auto spv_file = ss.str();
const std::string command =
std::string(interestingness_function_file) + " " + spv_file;
auto write_file_succeeded =
WriteFile(spv_file.c_str(), "wb", &binary[0], binary.size());
(void)(write_file_succeeded);
assert(write_file_succeeded);
return ExecuteCommand(command);
};
auto shrink_result_status = shrinker.Run(
binary_in, initial_facts, transformation_sequence,
interestingness_function, binary_out, transformations_applied);
return spvtools::fuzz::Shrinker::ShrinkerResultStatus::kComplete ==
shrink_result_status ||
spvtools::fuzz::Shrinker::ShrinkerResultStatus::kStepLimitReached ==
shrink_result_status;
}
bool Fuzz(const spv_target_env& target_env,
const spvtools::FuzzerOptions& fuzzer_options,
const std::vector<uint32_t>& binary_in,
const spvtools::fuzz::protobufs::FactSequence& initial_facts,
std::vector<uint32_t>* binary_out,
spvtools::fuzz::protobufs::TransformationSequence*
transformations_applied) {
spvtools::fuzz::Fuzzer fuzzer(target_env);
fuzzer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
auto fuzz_result_status = fuzzer.Run(binary_in, initial_facts, fuzzer_options,
binary_out, transformations_applied);
if (fuzz_result_status !=
spvtools::fuzz::Fuzzer::FuzzerResultStatus::kComplete) {
spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error running fuzzer");
return false;
}
return true;
}
} // namespace
// Dumps |binary| to file |filename|. Useful for interactive debugging.
void DumpShader(const std::vector<uint32_t>& binary, const char* filename) {
auto write_file_succeeded =
WriteFile(filename, "wb", &binary[0], binary.size());
if (!write_file_succeeded) {
std::cerr << "Failed to dump shader" << std::endl;
}
}
// Dumps the SPIRV-V module in |context| to file |filename|. Useful for
// interactive debugging.
void DumpShader(spvtools::opt::IRContext* context, const char* filename) {
std::vector<uint32_t> binary;
context->module()->ToBinary(&binary, false);
DumpShader(binary, filename);
}
const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3;
int main(int argc, const char** argv) {
std::string in_binary_file;
std::string out_binary_file;
std::string replay_transformations_file;
std::string interestingness_function_file;
std::string shrink_transformations_file;
spvtools::FuzzerOptions fuzzer_options;
FuzzStatus status =
ParseFlags(argc, argv, &in_binary_file, &out_binary_file,
&replay_transformations_file, &interestingness_function_file,
&shrink_transformations_file, &fuzzer_options);
if (status.action == FuzzActions::STOP) {
return status.code;
}
std::vector<uint32_t> binary_in;
if (!ReadFile<uint32_t>(in_binary_file.c_str(), "rb", &binary_in)) {
return 1;
}
spvtools::fuzz::protobufs::FactSequence initial_facts;
const std::string dot_spv(".spv");
std::string in_facts_file =
in_binary_file.substr(0, in_binary_file.length() - dot_spv.length()) +
".facts";
std::ifstream facts_input(in_facts_file);
if (facts_input) {
std::string facts_json_string((std::istreambuf_iterator<char>(facts_input)),
std::istreambuf_iterator<char>());
facts_input.close();
if (google::protobuf::util::Status::OK !=
google::protobuf::util::JsonStringToMessage(facts_json_string,
&initial_facts)) {
spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error reading facts data");
return 1;
}
}
std::vector<uint32_t> binary_out;
spvtools::fuzz::protobufs::TransformationSequence transformations_applied;
spv_target_env target_env = kDefaultEnvironment;
switch (status.action) {
case FuzzActions::FUZZ:
if (!Fuzz(target_env, fuzzer_options, binary_in, initial_facts,
&binary_out, &transformations_applied)) {
return 1;
}
break;
case FuzzActions::REPLAY:
if (!Replay(target_env, fuzzer_options, binary_in, initial_facts,
replay_transformations_file, &binary_out,
&transformations_applied)) {
return 1;
}
break;
case FuzzActions::SHRINK: {
if (!CheckExecuteCommand()) {
std::cerr << "could not find shell interpreter for executing a command"
<< std::endl;
return 1;
}
if (!Shrink(target_env, fuzzer_options, binary_in, initial_facts,
shrink_transformations_file, interestingness_function_file,
&binary_out, &transformations_applied)) {
return 1;
}
} break;
default:
assert(false && "Unknown fuzzer action.");
break;
}
if (!WriteFile<uint32_t>(out_binary_file.c_str(), "wb", binary_out.data(),
binary_out.size())) {
spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error writing out binary");
return 1;
}
std::string output_file_prefix =
out_binary_file.substr(0, out_binary_file.length() - dot_spv.length());
std::ofstream transformations_file;
transformations_file.open(output_file_prefix + ".transformations",
std::ios::out | std::ios::binary);
bool success =
transformations_applied.SerializeToOstream(&transformations_file);
transformations_file.close();
if (!success) {
spvtools::Error(FuzzDiagnostic, nullptr, {},
"Error writing out transformations binary");
return 1;
}
std::string json_string;
auto json_options = google::protobuf::util::JsonOptions();
json_options.add_whitespace = true;
auto json_generation_status = google::protobuf::util::MessageToJsonString(
transformations_applied, &json_string, json_options);
if (json_generation_status != google::protobuf::util::Status::OK) {
spvtools::Error(FuzzDiagnostic, nullptr, {},
"Error writing out transformations in JSON format");
return 1;
}
std::ofstream transformations_json_file(output_file_prefix +
".transformations_json");
transformations_json_file << json_string;
transformations_json_file.close();
return 0;
}

View File

@@ -1,85 +0,0 @@
// Copyright (c) 2016 Google 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.
#ifndef TOOLS_IO_H_
#define TOOLS_IO_H_
#include <cstdint>
#include <cstdio>
#include <vector>
// Appends the content from the file named as |filename| to |data|, assuming
// each element in the file is of type |T|. The file is opened with the given
// |mode|. If |filename| is nullptr or "-", reads from the standard input, but
// reopened with the given mode. If any error occurs, writes error messages to
// standard error and returns false.
template <typename T>
bool ReadFile(const char* filename, const char* mode, std::vector<T>* data) {
const int buf_size = 1024;
const bool use_file = filename && strcmp("-", filename);
if (FILE* fp =
(use_file ? fopen(filename, mode) : freopen(nullptr, mode, stdin))) {
T buf[buf_size];
while (size_t len = fread(buf, sizeof(T), buf_size, fp)) {
data->insert(data->end(), buf, buf + len);
}
if (ftell(fp) == -1L) {
if (ferror(fp)) {
fprintf(stderr, "error: error reading file '%s'\n", filename);
if (use_file) fclose(fp);
return false;
}
} else {
if (sizeof(T) != 1 && (ftell(fp) % sizeof(T))) {
fprintf(
stderr,
"error: file size should be a multiple of %zd; file '%s' corrupt\n",
sizeof(T), filename);
if (use_file) fclose(fp);
return false;
}
}
if (use_file) fclose(fp);
} else {
fprintf(stderr, "error: file does not exist '%s'\n", filename);
return false;
}
return true;
}
// Writes the given |data| into the file named as |filename| using the given
// |mode|, assuming |data| is an array of |count| elements of type |T|. If
// |filename| is nullptr or "-", writes to standard output. If any error occurs,
// returns false and outputs error message to standard error.
template <typename T>
bool WriteFile(const char* filename, const char* mode, const T* data,
size_t count) {
const bool use_stdout =
!filename || (filename[0] == '-' && filename[1] == '\0');
if (FILE* fp = (use_stdout ? stdout : fopen(filename, mode))) {
size_t written = fwrite(data, sizeof(T), count, fp);
if (count != written) {
fprintf(stderr, "error: could not write to file '%s'\n", filename);
if (!use_stdout) fclose(fp);
return false;
}
if (!use_stdout) fclose(fp);
} else {
fprintf(stderr, "error: could not open file '%s'\n", filename);
return false;
}
return true;
}
#endif // TOOLS_IO_H_

View File

@@ -1,28 +0,0 @@
# Copyright (c) 2016 Google 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.
# Install a script for use with the LESSOPEN of less(1).
# For example, after installation into /usr/local do:
# export LESSOPEN='|/usr/local/bin "%s"'
# less -R foo.spv
#
# See https://github.com/KhronosGroup/SPIRV-Tools/issues/359
# The script will be installed with everyone having read and execute
# permissions.
# We have a .sh extension because Windows users often configure
# executable settings via filename extension.
if(ENABLE_SPIRV_TOOLS_INSTALL)
install(PROGRAMS spirv-lesspipe.sh DESTINATION ${CMAKE_INSTALL_BINDIR})
endif(ENABLE_SPIRV_TOOLS_INSTALL)

View File

@@ -1,27 +0,0 @@
#!/usr/bin/env sh
# Copyright (c) 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.
# A script for automatically disassembling a .spv file
# for less(1). This assumes spirv-dis is on our PATH.
#
# See https://github.com/KhronosGroup/SPIRV-Tools/issues/359
case "$1" in
*.spv) spirv-dis "$@" 2>/dev/null;;
*) exit 1;;
esac
exit $?

View File

@@ -1,160 +0,0 @@
// Copyright (c) 2017 Pierre Moreau
//
// 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 <cstring>
#include <iostream>
#include <vector>
#include "source/spirv_target_env.h"
#include "source/table.h"
#include "spirv-tools/libspirv.hpp"
#include "spirv-tools/linker.hpp"
#include "tools/io.h"
void print_usage(char* argv0) {
std::string target_env_list = spvTargetEnvList(27, 95);
printf(
R"(%s - Link SPIR-V binary files together.
USAGE: %s [options] <filename> [<filename> ...]
The SPIR-V binaries are read from the different <filename>.
NOTE: The linker is a work in progress.
Options:
-h, --help Print this help.
-o Name of the resulting linked SPIR-V binary.
--create-library Link the binaries into a library, keeping all exported symbols.
--allow-partial-linkage Allow partial linkage by accepting imported symbols to be unresolved.
--verify-ids Verify that IDs in the resulting modules are truly unique.
--version Display linker version information
--target-env {%s}
Use validation rules from the specified environment.
)",
argv0, argv0, target_env_list.c_str());
}
int main(int argc, char** argv) {
std::vector<const char*> inFiles;
const char* outFile = nullptr;
spv_target_env target_env = SPV_ENV_UNIVERSAL_1_0;
spvtools::LinkerOptions options;
bool continue_processing = true;
int return_code = 0;
for (int argi = 1; continue_processing && argi < argc; ++argi) {
const char* cur_arg = argv[argi];
if ('-' == cur_arg[0]) {
if (0 == strcmp(cur_arg, "-o")) {
if (argi + 1 < argc) {
if (!outFile) {
outFile = argv[++argi];
} else {
fprintf(stderr, "error: More than one output file specified\n");
continue_processing = false;
return_code = 1;
}
} else {
fprintf(stderr, "error: Missing argument to %s\n", cur_arg);
continue_processing = false;
return_code = 1;
}
} else if (0 == strcmp(cur_arg, "--create-library")) {
options.SetCreateLibrary(true);
} else if (0 == strcmp(cur_arg, "--verify-ids")) {
options.SetVerifyIds(true);
} else if (0 == strcmp(cur_arg, "--allow-partial-linkage")) {
options.SetAllowPartialLinkage(true);
} else if (0 == strcmp(cur_arg, "--version")) {
printf("%s\n", spvSoftwareVersionDetailsString());
// TODO(dneto): Add OpenCL 2.2 at least.
printf("Targets:\n %s\n %s\n %s\n",
spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_1),
spvTargetEnvDescription(SPV_ENV_VULKAN_1_0),
spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_2));
continue_processing = false;
return_code = 0;
} else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
print_usage(argv[0]);
continue_processing = false;
return_code = 0;
} else if (0 == strcmp(cur_arg, "--target-env")) {
if (argi + 1 < argc) {
const auto env_str = argv[++argi];
if (!spvParseTargetEnv(env_str, &target_env)) {
fprintf(stderr, "error: Unrecognized target env: %s\n", env_str);
continue_processing = false;
return_code = 1;
}
} else {
fprintf(stderr, "error: Missing argument to --target-env\n");
continue_processing = false;
return_code = 1;
}
}
} else {
inFiles.push_back(cur_arg);
}
}
// Exit if command line parsing was not successful.
if (!continue_processing) {
return return_code;
}
if (inFiles.empty()) {
fprintf(stderr, "error: No input file specified\n");
return 1;
}
std::vector<std::vector<uint32_t>> contents(inFiles.size());
for (size_t i = 0u; i < inFiles.size(); ++i) {
if (!ReadFile<uint32_t>(inFiles[i], "rb", &contents[i])) return 1;
}
const spvtools::MessageConsumer consumer = [](spv_message_level_t level,
const char*,
const spv_position_t& position,
const char* message) {
switch (level) {
case SPV_MSG_FATAL:
case SPV_MSG_INTERNAL_ERROR:
case SPV_MSG_ERROR:
std::cerr << "error: " << position.index << ": " << message
<< std::endl;
break;
case SPV_MSG_WARNING:
std::cout << "warning: " << position.index << ": " << message
<< std::endl;
break;
case SPV_MSG_INFO:
std::cout << "info: " << position.index << ": " << message << std::endl;
break;
default:
break;
}
};
spvtools::Context context(target_env);
context.SetMessageConsumer(consumer);
std::vector<uint32_t> linkingResult;
spv_result_t status = Link(context, contents, &linkingResult, options);
if (!WriteFile<uint32_t>(outFile, "wb", linkingResult.data(),
linkingResult.size()))
return 1;
return status == SPV_SUCCESS ? 0 : 1;
}

View File

@@ -1,887 +0,0 @@
// Copyright (c) 2016 Google 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.
#include <algorithm>
#include <cassert>
#include <cstring>
#include <fstream>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include "source/opt/log.h"
#include "source/spirv_target_env.h"
#include "source/util/string_utils.h"
#include "spirv-tools/libspirv.hpp"
#include "spirv-tools/optimizer.hpp"
#include "tools/io.h"
#include "tools/util/cli_consumer.h"
namespace {
// Status and actions to perform after parsing command-line arguments.
enum OptActions { OPT_CONTINUE, OPT_STOP };
struct OptStatus {
OptActions action;
int code;
};
// Message consumer for this tool. Used to emit diagnostics during
// initialization and setup. Note that |source| and |position| are irrelevant
// here because we are still not processing a SPIR-V input file.
void opt_diagnostic(spv_message_level_t level, const char* /*source*/,
const spv_position_t& /*positon*/, const char* message) {
if (level == SPV_MSG_ERROR) {
fprintf(stderr, "error: ");
}
fprintf(stderr, "%s\n", message);
}
std::string GetListOfPassesAsString(const spvtools::Optimizer& optimizer) {
std::stringstream ss;
for (const auto& name : optimizer.GetPassNames()) {
ss << "\n\t\t" << name;
}
return ss.str();
}
const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_5;
std::string GetLegalizationPasses() {
spvtools::Optimizer optimizer(kDefaultEnvironment);
optimizer.RegisterLegalizationPasses();
return GetListOfPassesAsString(optimizer);
}
std::string GetOptimizationPasses() {
spvtools::Optimizer optimizer(kDefaultEnvironment);
optimizer.RegisterPerformancePasses();
return GetListOfPassesAsString(optimizer);
}
std::string GetSizePasses() {
spvtools::Optimizer optimizer(kDefaultEnvironment);
optimizer.RegisterSizePasses();
return GetListOfPassesAsString(optimizer);
}
std::string GetVulkanToWebGPUPasses() {
spvtools::Optimizer optimizer(SPV_ENV_VULKAN_1_1);
optimizer.RegisterVulkanToWebGPUPasses();
return GetListOfPassesAsString(optimizer);
}
std::string GetWebGPUToVulkanPasses() {
spvtools::Optimizer optimizer(SPV_ENV_WEBGPU_0);
optimizer.RegisterWebGPUToVulkanPasses();
return GetListOfPassesAsString(optimizer);
}
void PrintUsage(const char* program) {
std::string target_env_list = spvTargetEnvList(16, 80);
// NOTE: Please maintain flags in lexicographical order.
printf(
R"(%s - Optimize a SPIR-V binary file.
USAGE: %s [options] [<input>] -o <output>
The SPIR-V binary is read from <input>. If no file is specified,
or if <input> is "-", then the binary is read from standard input.
if <output> is "-", then the optimized output is written to
standard output.
NOTE: The optimizer is a work in progress.
Options (in lexicographical order):)",
program, program);
printf(R"(
--amd-ext-to-khr
Replaces the extensions VK_AMD_shader_ballot, VK_AMD_gcn_shader,
and VK_AMD_shader_trinary_minmax with equivalant code using core
instructions and capabilities.)");
printf(R"(
--ccp
Apply the conditional constant propagation transform. This will
propagate constant values throughout the program, and simplify
expressions and conditional jumps with known predicate
values. Performed on entry point call tree functions and
exported functions.)");
printf(R"(
--cfg-cleanup
Cleanup the control flow graph. This will remove any unnecessary
code from the CFG like unreachable code. Performed on entry
point call tree functions and exported functions.)");
printf(R"(
--combine-access-chains
Combines chained access chains to produce a single instruction
where possible.)");
printf(R"(
--compact-ids
Remap result ids to a compact range starting from %%1 and without
any gaps.)");
printf(R"(
--convert-local-access-chains
Convert constant index access chain loads/stores into
equivalent load/stores with inserts and extracts. Performed
on function scope variables referenced only with load, store,
and constant index access chains in entry point call tree
functions.)");
printf(R"(
--convert-relaxed-to-half
Convert all RelaxedPrecision arithmetic operations to half
precision, inserting conversion operations where needed.
Run after function scope variable load and store elimination
for better results. Simplify-instructions, redundancy-elimination
and DCE should be run after this pass to eliminate excess
conversions. This conversion is useful when the target platform
does not support RelaxedPrecision or ignores it. This pass also
removes all RelaxedPrecision decorations.)");
printf(R"(
--copy-propagate-arrays
Does propagation of memory references when an array is a copy of
another. It will only propagate an array if the source is never
written to, and the only store to the target is the copy.)");
printf(R"(
--decompose-initialized-variables
Decomposes initialized variable declarations into a declaration
followed by a store of the initial value. This is done to work
around known issues with some Vulkan drivers for initialize
variables.)");
printf(R"(
--descriptor-scalar-replacement
Replaces every array variable |desc| that has a DescriptorSet
and Binding decorations with a new variable for each element of
the array. Suppose |desc| was bound at binding |b|. Then the
variable corresponding to |desc[i]| will have binding |b+i|.
The descriptor set will be the same. All accesses to |desc|
must be in OpAccessChain instructions with a literal index for
the first index.)");
printf(R"(
--eliminate-dead-branches
Convert conditional branches with constant condition to the
indicated unconditional brranch. Delete all resulting dead
code. Performed only on entry point call tree functions.)");
printf(R"(
--eliminate-dead-code-aggressive
Delete instructions which do not contribute to a function's
output. Performed only on entry point call tree functions.)");
printf(R"(
--eliminate-dead-const
Eliminate dead constants.)");
printf(R"(
--eliminate-dead-functions
Deletes functions that cannot be reached from entry points or
exported functions.)");
printf(R"(
--eliminate-dead-inserts
Deletes unreferenced inserts into composites, most notably
unused stores to vector components, that are not removed by
aggressive dead code elimination.)");
printf(R"(
--eliminate-dead-variables
Deletes module scope variables that are not referenced.)");
printf(R"(
--eliminate-insert-extract
DEPRECATED. This pass has been replaced by the simplification
pass, and that pass will be run instead.
See --simplify-instructions.)");
printf(R"(
--eliminate-local-multi-store
Replace stores and loads of function scope variables that are
stored multiple times. Performed on variables referenceed only
with loads and stores. Performed only on entry point call tree
functions.)");
printf(R"(
--eliminate-local-single-block
Perform single-block store/load and load/load elimination.
Performed only on function scope variables in entry point
call tree functions.)");
printf(R"(
--eliminate-local-single-store
Replace stores and loads of function scope variables that are
only stored once. Performed on variables referenceed only with
loads and stores. Performed only on entry point call tree
functions.)");
printf(R"(
--flatten-decorations
Replace decoration groups with repeated OpDecorate and
OpMemberDecorate instructions.)");
printf(R"(
--fold-spec-const-op-composite
Fold the spec constants defined by OpSpecConstantOp or
OpSpecConstantComposite instructions to front-end constants
when possible.)");
printf(R"(
--freeze-spec-const
Freeze the values of specialization constants to their default
values.)");
printf(R"(
--graphics-robust-access
Clamp indices used to access buffers and internal composite
values, providing guarantees that satisfy Vulkan's
robustBufferAccess rules.)");
printf(R"(
--generate-webgpu-initializers
Adds initial values to OpVariable instructions that are missing
them, due to their storage type requiring them for WebGPU.)");
printf(R"(
--if-conversion
Convert if-then-else like assignments into OpSelect.)");
printf(R"(
--inline-entry-points-exhaustive
Exhaustively inline all function calls in entry point call tree
functions. Currently does not inline calls to functions with
early return in a loop.)");
printf(R"(
--legalize-hlsl
Runs a series of optimizations that attempts to take SPIR-V
generated by an HLSL front-end and generates legal Vulkan SPIR-V.
The optimizations are:
%s
Note this does not guarantee legal code. This option passes the
option --relax-logical-pointer to the validator.)",
GetLegalizationPasses().c_str());
printf(R"(
--legalize-vector-shuffle
Converts any usages of 0xFFFFFFFF for the literals in
OpVectorShuffle to a literal 0. This is done since 0xFFFFFFFF is
forbidden in WebGPU.)");
printf(R"(
--local-redundancy-elimination
Looks for instructions in the same basic block that compute the
same value, and deletes the redundant ones.)");
printf(R"(
--loop-fission
Splits any top level loops in which the register pressure has
exceeded a given threshold. The threshold must follow the use of
this flag and must be a positive integer value.)");
printf(R"(
--loop-fusion
Identifies adjacent loops with the same lower and upper bound.
If this is legal, then merge the loops into a single loop.
Includes heuristics to ensure it does not increase number of
registers too much, while reducing the number of loads from
memory. Takes an additional positive integer argument to set
the maximum number of registers.)");
printf(R"(
--loop-invariant-code-motion
Identifies code in loops that has the same value for every
iteration of the loop, and move it to the loop pre-header.)");
printf(R"(
--loop-unroll
Fully unrolls loops marked with the Unroll flag)");
printf(R"(
--loop-unroll-partial
Partially unrolls loops marked with the Unroll flag. Takes an
additional non-0 integer argument to set the unroll factor, or
how many times a loop body should be duplicated)");
printf(R"(
--loop-peeling
Execute few first (respectively last) iterations before
(respectively after) the loop if it can elide some branches.)");
printf(R"(
--loop-peeling-threshold
Takes a non-0 integer argument to set the loop peeling code size
growth threshold. The threshold prevents the loop peeling
from happening if the code size increase created by
the optimization is above the threshold.)");
printf(R"(
--max-id-bound=<n>
Sets the maximum value for the id bound for the moudle. The
default is the minimum value for this limit, 0x3FFFFF. See
section 2.17 of the Spir-V specification.)");
printf(R"(
--merge-blocks
Join two blocks into a single block if the second has the
first as its only predecessor. Performed only on entry point
call tree functions.)");
printf(R"(
--merge-return
Changes functions that have multiple return statements so they
have a single return statement.
For structured control flow it is assumed that the only
unreachable blocks in the function are trivial merge and continue
blocks.
A trivial merge block contains the label and an OpUnreachable
instructions, nothing else. A trivial continue block contain a
label and an OpBranch to the header, nothing else.
These conditions are guaranteed to be met after running
dead-branch elimination.)");
printf(R"(
--loop-unswitch
Hoists loop-invariant conditionals out of loops by duplicating
the loop on each branch of the conditional and adjusting each
copy of the loop.)");
printf(R"(
-O
Optimize for performance. Apply a sequence of transformations
in an attempt to improve the performance of the generated
code. For this version of the optimizer, this flag is equivalent
to specifying the following optimization code names:
%s)",
GetOptimizationPasses().c_str());
printf(R"(
-Os
Optimize for size. Apply a sequence of transformations in an
attempt to minimize the size of the generated code. For this
version of the optimizer, this flag is equivalent to specifying
the following optimization code names:
%s
NOTE: The specific transformations done by -O and -Os change
from release to release.)",
GetSizePasses().c_str());
printf(R"(
-Oconfig=<file>
Apply the sequence of transformations indicated in <file>.
This file contains a sequence of strings separated by whitespace
(tabs, newlines or blanks). Each string is one of the flags
accepted by spirv-opt. Optimizations will be applied in the
sequence they appear in the file. This is equivalent to
specifying all the flags on the command line. For example,
given the file opts.cfg with the content:
--inline-entry-points-exhaustive
--eliminate-dead-code-aggressive
The following two invocations to spirv-opt are equivalent:
$ spirv-opt -Oconfig=opts.cfg program.spv
$ spirv-opt --inline-entry-points-exhaustive \
--eliminate-dead-code-aggressive program.spv
Lines starting with the character '#' in the configuration
file indicate a comment and will be ignored.
The -O, -Os, and -Oconfig flags act as macros. Using one of them
is equivalent to explicitly inserting the underlying flags at
that position in the command line. For example, the invocation
'spirv-opt --merge-blocks -O ...' applies the transformation
--merge-blocks followed by all the transformations implied by
-O.)");
printf(R"(
--preserve-bindings
Ensure that the optimizer preserves all bindings declared within
the module, even when those bindings are unused.)");
printf(R"(
--preserve-spec-constants
Ensure that the optimizer preserves all specialization constants declared
within the module, even when those constants are unused.)");
printf(R"(
--print-all
Print SPIR-V assembly to standard error output before each pass
and after the last pass.)");
printf(R"(
--private-to-local
Change the scope of private variables that are used in a single
function to that function.)");
printf(R"(
--reduce-load-size
Replaces loads of composite objects where not every component is
used by loads of just the elements that are used.)");
printf(R"(
--redundancy-elimination
Looks for instructions in the same function that compute the
same value, and deletes the redundant ones.)");
printf(R"(
--relax-float-ops
Decorate all float operations with RelaxedPrecision if not already
so decorated. This does not decorate types or variables.)");
printf(R"(
--relax-struct-store
Allow store from one struct type to a different type with
compatible layout and members. This option is forwarded to the
validator.)");
printf(R"(
--remove-duplicates
Removes duplicate types, decorations, capabilities and extension
instructions.)");
printf(R"(
--replace-invalid-opcode
Replaces instructions whose opcode is valid for shader modules,
but not for the current shader stage. To have an effect, all
entry points must have the same execution model.)");
printf(R"(
--ssa-rewrite
Replace loads and stores to function local variables with
operations on SSA IDs.)");
printf(R"(
--scalar-replacement[=<n>]
Replace aggregate function scope variables that are only accessed
via their elements with new function variables representing each
element. <n> is a limit on the size of the aggragates that will
be replaced. 0 means there is no limit. The default value is
100.)");
printf(R"(
--set-spec-const-default-value "<spec id>:<default value> ..."
Set the default values of the specialization constants with
<spec id>:<default value> pairs specified in a double-quoted
string. <spec id>:<default value> pairs must be separated by
blank spaces, and in each pair, spec id and default value must
be separated with colon ':' without any blank spaces in between.
e.g.: --set-spec-const-default-value "1:100 2:400")");
printf(R"(
--simplify-instructions
Will simplify all instructions in the function as much as
possible.)");
printf(R"(
--split-invalid-unreachable
Attempts to legalize for WebGPU cases where an unreachable
merge-block is also a continue-target by splitting it into two
seperate blocks. There exist legal, for Vulkan, instances of this
pattern that cannot be converted into legal WebGPU, so this
conversion may not succeed.)");
printf(R"(
--skip-validation
Will not validate the SPIR-V before optimizing. If the SPIR-V
is invalid, the optimizer may fail or generate incorrect code.
This options should be used rarely, and with caution.)");
printf(R"(
--strength-reduction
Replaces instructions with equivalent and less expensive ones.)");
printf(R"(
--strip-atomic-counter-memory
Removes AtomicCountMemory bit from memory semantics values.)");
printf(R"(
--strip-debug
Remove all debug instructions.)");
printf(R"(
--strip-reflect
Remove all reflection information. For now, this covers
reflection information defined by SPV_GOOGLE_hlsl_functionality1.)");
printf(R"(
--target-env=<env>
Set the target environment. Without this flag the target
enviroment defaults to spv1.3. <env> must be one of
{%s})",
target_env_list.c_str());
printf(R"(
--time-report
Print the resource utilization of each pass (e.g., CPU time,
RSS) to standard error output. Currently it supports only Unix
systems. This option is the same as -ftime-report in GCC. It
prints CPU/WALL/USR/SYS time (and RSS if possible), but note that
USR/SYS time are returned by getrusage() and can have a small
error.)");
printf(R"(
--upgrade-memory-model
Upgrades the Logical GLSL450 memory model to Logical VulkanKHR.
Transforms memory, image, atomic and barrier operations to conform
to that model's requirements.)");
printf(R"(
--vector-dce
This pass looks for components of vectors that are unused, and
removes them from the vector. Note this would still leave around
lots of dead code that a pass of ADCE will be able to remove.)");
printf(R"(
--vulkan-to-webgpu
Turns on the prescribed passes for converting from Vulkan to
WebGPU and sets the target environment to webgpu0. Other passes
may be turned on via additional flags, but such combinations are
not tested.
Using --target-env with this flag is not allowed.
This flag is the equivalent of passing in --target-env=webgpu0
and specifying the following optimization code names:
%s
NOTE: This flag is a WIP and its behaviour is subject to change.)",
GetVulkanToWebGPUPasses().c_str());
printf(R"(
--webgpu-to-vulkan
Turns on the prescribed passes for converting from WebGPU to
Vulkan and sets the target environment to vulkan1.1. Other passes
may be turned on via additional flags, but such combinations are
not tested.
Using --target-env with this flag is not allowed.
This flag is the equivalent of passing in --target-env=vulkan1.1
and specifying the following optimization code names:
%s
NOTE: This flag is a WIP and its behaviour is subject to change.)",
GetWebGPUToVulkanPasses().c_str());
printf(R"(
--workaround-1209
Rewrites instructions for which there are known driver bugs to
avoid triggering those bugs.
Current workarounds: Avoid OpUnreachable in loops.)");
printf(R"(
--unify-const
Remove the duplicated constants.)");
printf(R"(
--validate-after-all
Validate the module after each pass is performed.)");
printf(R"(
-h, --help
Print this help.)");
printf(R"(
--version
Display optimizer version information.
)");
}
// Reads command-line flags the file specified in |oconfig_flag|. This string
// is assumed to have the form "-Oconfig=FILENAME". This function parses the
// string and extracts the file name after the '=' sign.
//
// Flags found in |FILENAME| are pushed at the end of the vector |file_flags|.
//
// This function returns true on success, false on failure.
bool ReadFlagsFromFile(const char* oconfig_flag,
std::vector<std::string>* file_flags) {
const char* fname = strchr(oconfig_flag, '=');
if (fname == nullptr || fname[0] != '=') {
spvtools::Errorf(opt_diagnostic, nullptr, {}, "Invalid -Oconfig flag %s",
oconfig_flag);
return false;
}
fname++;
std::ifstream input_file;
input_file.open(fname);
if (input_file.fail()) {
spvtools::Errorf(opt_diagnostic, nullptr, {}, "Could not open file '%s'",
fname);
return false;
}
std::string line;
while (std::getline(input_file, line)) {
// Ignore empty lines and lines starting with the comment marker '#'.
if (line.length() == 0 || line[0] == '#') {
continue;
}
// Tokenize the line. Add all found tokens to the list of found flags. This
// mimics the way the shell will parse whitespace on the command line. NOTE:
// This does not support quoting and it is not intended to.
std::istringstream iss(line);
while (!iss.eof()) {
std::string flag;
iss >> flag;
file_flags->push_back(flag);
}
}
return true;
}
OptStatus ParseFlags(int argc, const char** argv,
spvtools::Optimizer* optimizer, const char** in_file,
const char** out_file,
spvtools::ValidatorOptions* validator_options,
spvtools::OptimizerOptions* optimizer_options);
// Parses and handles the -Oconfig flag. |prog_name| contains the name of
// the spirv-opt binary (used to build a new argv vector for the recursive
// invocation to ParseFlags). |opt_flag| contains the -Oconfig=FILENAME flag.
// |optimizer|, |in_file|, |out_file|, |validator_options|, and
// |optimizer_options| are as in ParseFlags.
//
// This returns the same OptStatus instance returned by ParseFlags.
OptStatus ParseOconfigFlag(const char* prog_name, const char* opt_flag,
spvtools::Optimizer* optimizer, const char** in_file,
const char** out_file,
spvtools::ValidatorOptions* validator_options,
spvtools::OptimizerOptions* optimizer_options) {
std::vector<std::string> flags;
flags.push_back(prog_name);
std::vector<std::string> file_flags;
if (!ReadFlagsFromFile(opt_flag, &file_flags)) {
spvtools::Error(opt_diagnostic, nullptr, {},
"Could not read optimizer flags from configuration file");
return {OPT_STOP, 1};
}
flags.insert(flags.end(), file_flags.begin(), file_flags.end());
const char** new_argv = new const char*[flags.size()];
for (size_t i = 0; i < flags.size(); i++) {
if (flags[i].find("-Oconfig=") != std::string::npos) {
spvtools::Error(
opt_diagnostic, nullptr, {},
"Flag -Oconfig= may not be used inside the configuration file");
return {OPT_STOP, 1};
}
new_argv[i] = flags[i].c_str();
}
auto ret_val =
ParseFlags(static_cast<int>(flags.size()), new_argv, optimizer, in_file,
out_file, validator_options, optimizer_options);
delete[] new_argv;
return ret_val;
}
// Canonicalize the flag in |argv[argi]| of the form '--pass arg' into
// '--pass=arg'. The optimizer only accepts arguments to pass names that use the
// form '--pass_name=arg'. Since spirv-opt also accepts the other form, this
// function makes the necessary conversion.
//
// Pass flags that require additional arguments should be handled here. Note
// that additional arguments should be given as a single string. If the flag
// requires more than one argument, the pass creator in
// Optimizer::GetPassFromFlag() should parse it accordingly (e.g., see the
// handler for --set-spec-const-default-value).
//
// If the argument requests one of the passes that need an additional argument,
// |argi| is modified to point past the current argument, and the string
// "argv[argi]=argv[argi + 1]" is returned. Otherwise, |argi| is unmodified and
// the string "|argv[argi]|" is returned.
std::string CanonicalizeFlag(const char** argv, int argc, int* argi) {
const char* cur_arg = argv[*argi];
const char* next_arg = (*argi + 1 < argc) ? argv[*argi + 1] : nullptr;
std::ostringstream canonical_arg;
canonical_arg << cur_arg;
// NOTE: DO NOT ADD NEW FLAGS HERE.
//
// These flags are supported for backwards compatibility. When adding new
// passes that need extra arguments in its command-line flag, please make them
// use the syntax "--pass_name[=pass_arg].
if (0 == strcmp(cur_arg, "--set-spec-const-default-value") ||
0 == strcmp(cur_arg, "--loop-fission") ||
0 == strcmp(cur_arg, "--loop-fusion") ||
0 == strcmp(cur_arg, "--loop-unroll-partial") ||
0 == strcmp(cur_arg, "--loop-peeling-threshold")) {
if (next_arg) {
canonical_arg << "=" << next_arg;
++(*argi);
}
}
return canonical_arg.str();
}
// Parses command-line flags. |argc| contains the number of command-line flags.
// |argv| points to an array of strings holding the flags. |optimizer| is the
// Optimizer instance used to optimize the program.
//
// On return, this function stores the name of the input program in |in_file|.
// The name of the output file in |out_file|. The return value indicates whether
// optimization should continue and a status code indicating an error or
// success.
OptStatus ParseFlags(int argc, const char** argv,
spvtools::Optimizer* optimizer, const char** in_file,
const char** out_file,
spvtools::ValidatorOptions* validator_options,
spvtools::OptimizerOptions* optimizer_options) {
std::vector<std::string> pass_flags;
bool target_env_set = false;
bool vulkan_to_webgpu_set = false;
bool webgpu_to_vulkan_set = false;
for (int argi = 1; argi < argc; ++argi) {
const char* cur_arg = argv[argi];
if ('-' == cur_arg[0]) {
if (0 == strcmp(cur_arg, "--version")) {
spvtools::Logf(opt_diagnostic, SPV_MSG_INFO, nullptr, {}, "%s\n",
spvSoftwareVersionDetailsString());
return {OPT_STOP, 0};
} else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
PrintUsage(argv[0]);
return {OPT_STOP, 0};
} else if (0 == strcmp(cur_arg, "-o")) {
if (!*out_file && argi + 1 < argc) {
*out_file = argv[++argi];
} else {
PrintUsage(argv[0]);
return {OPT_STOP, 1};
}
} else if ('\0' == cur_arg[1]) {
// Setting a filename of "-" to indicate stdin.
if (!*in_file) {
*in_file = cur_arg;
} else {
spvtools::Error(opt_diagnostic, nullptr, {},
"More than one input file specified");
return {OPT_STOP, 1};
}
} else if (0 == strncmp(cur_arg, "-Oconfig=", sizeof("-Oconfig=") - 1)) {
OptStatus status =
ParseOconfigFlag(argv[0], cur_arg, optimizer, in_file, out_file,
validator_options, optimizer_options);
if (status.action != OPT_CONTINUE) {
return status;
}
} else if (0 == strcmp(cur_arg, "--skip-validation")) {
optimizer_options->set_run_validator(false);
} else if (0 == strcmp(cur_arg, "--print-all")) {
optimizer->SetPrintAll(&std::cerr);
} else if (0 == strcmp(cur_arg, "--preserve-bindings")) {
optimizer_options->set_preserve_bindings(true);
} else if (0 == strcmp(cur_arg, "--preserve-spec-constants")) {
optimizer_options->set_preserve_spec_constants(true);
} else if (0 == strcmp(cur_arg, "--time-report")) {
optimizer->SetTimeReport(&std::cerr);
} else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
validator_options->SetRelaxStructStore(true);
} else if (0 == strncmp(cur_arg, "--max-id-bound=",
sizeof("--max-id-bound=") - 1)) {
auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
// Will not allow values in the range [2^31,2^32).
uint32_t max_id_bound =
static_cast<uint32_t>(atoi(split_flag.second.c_str()));
// That SPIR-V mandates the minimum value for max id bound but
// implementations may allow higher minimum bounds.
if (max_id_bound < kDefaultMaxIdBound) {
spvtools::Error(opt_diagnostic, nullptr, {},
"The max id bound must be at least 0x3FFFFF");
return {OPT_STOP, 1};
}
optimizer_options->set_max_id_bound(max_id_bound);
validator_options->SetUniversalLimit(spv_validator_limit_max_id_bound,
max_id_bound);
} else if (0 == strncmp(cur_arg,
"--target-env=", sizeof("--target-env=") - 1)) {
target_env_set = true;
if (vulkan_to_webgpu_set) {
spvtools::Error(opt_diagnostic, nullptr, {},
"--vulkan-to-webgpu defines the target environment, "
"so --target-env cannot be set at the same time");
return {OPT_STOP, 1};
}
if (webgpu_to_vulkan_set) {
spvtools::Error(opt_diagnostic, nullptr, {},
"--webgpu-to-vulkan defines the target environment, "
"so --target-env cannot be set at the same time");
return {OPT_STOP, 1};
}
const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
const auto target_env_str = split_flag.second.c_str();
spv_target_env target_env;
if (!spvParseTargetEnv(target_env_str, &target_env)) {
spvtools::Error(opt_diagnostic, nullptr, {},
"Invalid value passed to --target-env");
return {OPT_STOP, 1};
}
optimizer->SetTargetEnv(target_env);
} else if (0 == strcmp(cur_arg, "--vulkan-to-webgpu")) {
vulkan_to_webgpu_set = true;
if (target_env_set) {
spvtools::Error(opt_diagnostic, nullptr, {},
"--vulkan-to-webgpu defines the target environment, "
"so --target-env cannot be set at the same time");
return {OPT_STOP, 1};
}
if (webgpu_to_vulkan_set) {
spvtools::Error(opt_diagnostic, nullptr, {},
"Cannot use both --webgpu-to-vulkan and "
"--vulkan-to-webgpu at the same time, invoke twice "
"if you are wanting to go to and from");
return {OPT_STOP, 1};
}
optimizer->SetTargetEnv(SPV_ENV_VULKAN_1_1);
optimizer->RegisterVulkanToWebGPUPasses();
} else if (0 == strcmp(cur_arg, "--webgpu-to-vulkan")) {
webgpu_to_vulkan_set = true;
if (target_env_set) {
spvtools::Error(opt_diagnostic, nullptr, {},
"--webgpu-to-vulkan defines the target environment, "
"so --target-env cannot be set at the same time");
return {OPT_STOP, 1};
}
if (vulkan_to_webgpu_set) {
spvtools::Error(opt_diagnostic, nullptr, {},
"Cannot use both --webgpu-to-vulkan and "
"--vulkan-to-webgpu at the same time, invoke twice "
"if you are wanting to go to and from");
return {OPT_STOP, 1};
}
optimizer->SetTargetEnv(SPV_ENV_WEBGPU_0);
optimizer->RegisterWebGPUToVulkanPasses();
} else if (0 == strcmp(cur_arg, "--validate-after-all")) {
optimizer->SetValidateAfterAll(true);
} else {
// Some passes used to accept the form '--pass arg', canonicalize them
// to '--pass=arg'.
pass_flags.push_back(CanonicalizeFlag(argv, argc, &argi));
// If we were requested to legalize SPIR-V generated from the HLSL
// front-end, skip validation.
if (0 == strcmp(cur_arg, "--legalize-hlsl")) {
validator_options->SetBeforeHlslLegalization(true);
}
}
} else {
if (!*in_file) {
*in_file = cur_arg;
} else {
spvtools::Error(opt_diagnostic, nullptr, {},
"More than one input file specified");
return {OPT_STOP, 1};
}
}
}
if (!optimizer->RegisterPassesFromFlags(pass_flags)) {
return {OPT_STOP, 1};
}
return {OPT_CONTINUE, 0};
}
} // namespace
int main(int argc, const char** argv) {
const char* in_file = nullptr;
const char* out_file = nullptr;
spv_target_env target_env = kDefaultEnvironment;
spvtools::Optimizer optimizer(target_env);
optimizer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
spvtools::ValidatorOptions validator_options;
spvtools::OptimizerOptions optimizer_options;
OptStatus status = ParseFlags(argc, argv, &optimizer, &in_file, &out_file,
&validator_options, &optimizer_options);
optimizer_options.set_validator_options(validator_options);
if (status.action == OPT_STOP) {
return status.code;
}
if (out_file == nullptr) {
spvtools::Error(opt_diagnostic, nullptr, {}, "-o required");
return 1;
}
std::vector<uint32_t> binary;
if (!ReadFile<uint32_t>(in_file, "rb", &binary)) {
return 1;
}
// By using the same vector as input and output, we save time in the case
// that there was no change.
bool ok =
optimizer.Run(binary.data(), binary.size(), &binary, optimizer_options);
if (!WriteFile<uint32_t>(out_file, "wb", binary.data(), binary.size())) {
return 1;
}
return ok ? 0 : 1;
}

View File

@@ -1,282 +0,0 @@
// Copyright (c) 2018 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 <cassert>
#include <cerrno>
#include <cstring>
#include <functional>
#include "source/opt/build_module.h"
#include "source/opt/ir_context.h"
#include "source/opt/log.h"
#include "source/reduce/reducer.h"
#include "source/spirv_reducer_options.h"
#include "source/util/string_utils.h"
#include "tools/io.h"
#include "tools/util/cli_consumer.h"
namespace {
// Check that the std::system function can actually be used.
bool CheckExecuteCommand() {
int res = std::system(nullptr);
return res != 0;
}
// Execute a command using the shell.
// Returns true if and only if the command's exit status was 0.
bool ExecuteCommand(const std::string& command) {
errno = 0;
int status = std::system(command.c_str());
assert(errno == 0 && "failed to execute command");
// The result returned by 'system' is implementation-defined, but is
// usually the case that the returned value is 0 when the command's exit
// code was 0. We are assuming that here, and that's all we depend on.
return status == 0;
}
// Status and actions to perform after parsing command-line arguments.
enum ReduceActions { REDUCE_CONTINUE, REDUCE_STOP };
struct ReduceStatus {
ReduceActions action;
int code;
};
void PrintUsage(const char* program) {
// NOTE: Please maintain flags in lexicographical order.
printf(
R"(%s - Reduce a SPIR-V binary file with respect to a user-provided
interestingness test.
USAGE: %s [options] <input> <interestingness-test>
The SPIR-V binary is read from <input>.
Whether a binary is interesting is determined by <interestingness-test>, which
should be the path to a script.
* The script must be executable.
* The script should take the path to a SPIR-V binary file (.spv) as its single
argument, and exit with code 0 if and only if the binary file is
interesting.
* Example: an interestingness test for reducing a SPIR-V binary file that
causes tool "foo" to exit with error code 1 and print "Fatal error: bar" to
standard error should:
- invoke "foo" on the binary passed as the script argument;
- capture the return code and standard error from "bar";
- exit with code 0 if and only if the return code of "foo" was 1 and the
standard error from "bar" contained "Fatal error: bar".
* The reducer does not place a time limit on how long the interestingness test
takes to run, so it is advisable to use per-command timeouts inside the
script when invoking SPIR-V-processing tools (such as "foo" in the above
example).
NOTE: The reducer is a work in progress.
Options (in lexicographical order):
--fail-on-validation-error
Stop reduction with an error if any reduction step produces a
SPIR-V module that fails to validate.
-h, --help
Print this help.
--step-limit
32-bit unsigned integer specifying maximum number of steps the
reducer will take before giving up.
--version
Display reducer version information.
Supported validator options are as follows. See `spirv-val --help` for details.
--before-hlsl-legalization
--relax-block-layout
--relax-logical-pointer
--relax-struct-store
--scalar-block-layout
--skip-block-layout
)",
program, program);
}
// Message consumer for this tool. Used to emit diagnostics during
// initialization and setup. Note that |source| and |position| are irrelevant
// here because we are still not processing a SPIR-V input file.
void ReduceDiagnostic(spv_message_level_t level, const char* /*source*/,
const spv_position_t& /*position*/, const char* message) {
if (level == SPV_MSG_ERROR) {
fprintf(stderr, "error: ");
}
fprintf(stderr, "%s\n", message);
}
ReduceStatus ParseFlags(int argc, const char** argv, const char** in_file,
const char** interestingness_test,
spvtools::ReducerOptions* reducer_options,
spvtools::ValidatorOptions* validator_options) {
uint32_t positional_arg_index = 0;
for (int argi = 1; argi < argc; ++argi) {
const char* cur_arg = argv[argi];
if ('-' == cur_arg[0]) {
if (0 == strcmp(cur_arg, "--version")) {
spvtools::Logf(ReduceDiagnostic, SPV_MSG_INFO, nullptr, {}, "%s\n",
spvSoftwareVersionDetailsString());
return {REDUCE_STOP, 0};
} else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
PrintUsage(argv[0]);
return {REDUCE_STOP, 0};
} else if ('\0' == cur_arg[1]) {
// We do not support reduction from standard input. We could support
// this if there was a compelling use case.
PrintUsage(argv[0]);
return {REDUCE_STOP, 0};
} else if (0 == strncmp(cur_arg,
"--step-limit=", sizeof("--step-limit=") - 1)) {
const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
char* end = nullptr;
errno = 0;
const auto step_limit =
static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
assert(end != split_flag.second.c_str() && errno == 0);
reducer_options->set_step_limit(step_limit);
}
} else if (positional_arg_index == 0) {
// Input file name
assert(!*in_file);
*in_file = cur_arg;
positional_arg_index++;
} else if (positional_arg_index == 1) {
assert(!*interestingness_test);
*interestingness_test = cur_arg;
positional_arg_index++;
} else if (0 == strcmp(cur_arg, "--fail-on-validation-error")) {
reducer_options->set_fail_on_validation_error(true);
} else if (0 == strcmp(cur_arg, "--before-hlsl-legalization")) {
validator_options->SetBeforeHlslLegalization(true);
} else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) {
validator_options->SetRelaxLogicalPointer(true);
} else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
validator_options->SetRelaxBlockLayout(true);
} else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
validator_options->SetScalarBlockLayout(true);
} else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
validator_options->SetSkipBlockLayout(true);
} else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
validator_options->SetRelaxStructStore(true);
} else {
spvtools::Error(ReduceDiagnostic, nullptr, {},
"Too many positional arguments specified");
return {REDUCE_STOP, 1};
}
}
if (!*in_file) {
spvtools::Error(ReduceDiagnostic, nullptr, {}, "No input file specified");
return {REDUCE_STOP, 1};
}
if (!*interestingness_test) {
spvtools::Error(ReduceDiagnostic, nullptr, {},
"No interestingness test specified");
return {REDUCE_STOP, 1};
}
return {REDUCE_CONTINUE, 0};
}
} // namespace
// Dumps |binary| to file |filename|. Useful for interactive debugging.
void DumpShader(const std::vector<uint32_t>& binary, const char* filename) {
auto write_file_succeeded =
WriteFile(filename, "wb", &binary[0], binary.size());
if (!write_file_succeeded) {
std::cerr << "Failed to dump shader" << std::endl;
}
}
// Dumps the SPIRV-V module in |context| to file |filename|. Useful for
// interactive debugging.
void DumpShader(spvtools::opt::IRContext* context, const char* filename) {
std::vector<uint32_t> binary;
context->module()->ToBinary(&binary, false);
DumpShader(binary, filename);
}
const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_5;
int main(int argc, const char** argv) {
const char* in_file = nullptr;
const char* interestingness_test = nullptr;
spv_target_env target_env = kDefaultEnvironment;
spvtools::ReducerOptions reducer_options;
spvtools::ValidatorOptions validator_options;
ReduceStatus status = ParseFlags(argc, argv, &in_file, &interestingness_test,
&reducer_options, &validator_options);
if (status.action == REDUCE_STOP) {
return status.code;
}
if (!CheckExecuteCommand()) {
std::cerr << "could not find shell interpreter for executing a command"
<< std::endl;
return 2;
}
spvtools::reduce::Reducer reducer(target_env);
reducer.SetInterestingnessFunction(
[interestingness_test](std::vector<uint32_t> binary,
uint32_t reductions_applied) -> bool {
std::stringstream ss;
ss << "temp_" << std::setw(4) << std::setfill('0') << reductions_applied
<< ".spv";
const auto spv_file = ss.str();
const std::string command =
std::string(interestingness_test) + " " + spv_file;
auto write_file_succeeded =
WriteFile(spv_file.c_str(), "wb", &binary[0], binary.size());
(void)(write_file_succeeded);
assert(write_file_succeeded);
return ExecuteCommand(command);
});
reducer.AddDefaultReductionPasses();
reducer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
std::vector<uint32_t> binary_in;
if (!ReadFile<uint32_t>(in_file, "rb", &binary_in)) {
return 1;
}
std::vector<uint32_t> binary_out;
const auto reduction_status = reducer.Run(std::move(binary_in), &binary_out,
reducer_options, validator_options);
if (reduction_status == spvtools::reduce::Reducer::ReductionResultStatus::
kInitialStateNotInteresting ||
!WriteFile<uint32_t>("_reduced_final.spv", "wb", binary_out.data(),
binary_out.size())) {
return 1;
}
return 0;
}

View File

@@ -1,25 +0,0 @@
{
"env": {
"browser": true,
"es6": true,
"node": true,
"mocha": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"rules": {
"block-scoped-var": "error",
"consistent-return": "error",
"eqeqeq": ["error", "always"],
"indent": [ "error", 2 ],
"linebreak-style": [ "error", "unix" ],
"no-eval": "error",
"no-shadow": "error",
"no-shadow-restricted-names": "error",
"quotes": [ "error", "double" ],
"semi": [ "error", "always" ]
}
}

View File

@@ -1,6 +0,0 @@
.DS_Store
node_modules
third_party/spirv-headers
o.sva
build
yarn-error.log

View File

@@ -1,41 +0,0 @@
# SVA
SPIR-V Assember for WebGPU. The SPIR-V Assembler is a JavaScript library to
convert SPIR-V assembly (as produced by spirv-dis in SPIR-V Tools) into a
SPIR-V binary. The assembler assumes it is generating WebGPU SPIR-V and thus has
the following limitations.
* Only 32 bit integers and floats supported
* Only GLSL accepted as an extended instruction set
* Doesn't support ! syntax for integers
* Doesn't support hex encoding for float
```shell
yarn install
yarn test
```
You can also use `yarn watch` to watch all of the files and re-run tests as
needed.
## Webserver
Using `yarn serve` will start a webserver on localhost:5000. If you load the
`tests/index.html` file this will load the SVA files into browser.
## Command Line
There is a simple assembler binary with can be executed from the command line.
```shell
yarn sva tests/simple.spv_asm
```
The above will generate a `o.sva` file in the current directory.
## Update spirv.data.json
If there is a new spirv-headers release update the externals folder checkout
and then:
```shell
./tools/process_grammar.rb > src/spirv.data.json
```

View File

@@ -1,32 +0,0 @@
#!/usr/bin/env node
//
// Copyright 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.
"use strict";
const fs = require("fs");
import SVA from "../src/sva.js";
let input = fs.readFileSync(process.argv[2], "utf-8");
let u = SVA.assemble(input);
if (typeof u === "string") {
console.log(u);
} else {
fs.writeFileSync("o.sva", new Buffer(u.buffer), (err) => {
console.log(["ERROR", err]);
});
}

View File

@@ -1 +0,0 @@
--recursive

View File

@@ -1,25 +0,0 @@
{
"name": "sva",
"version": "0.1.0",
"description": "SPIR-V Assembler",
"main": "index.js",
"author": "dan sinclair <dsinclair@google.com>",
"license": "Apache-2.0",
"private": true,
"scripts": {
"sva": "node -r esm bin/sva.js",
"lint": "eslint --fix --ext .js .",
"test": "mocha --require esm src/**/*_test.js",
"watch": "mocha --require esm --watch --watch-extension js \"src/**/*_test.js\"",
"serve": "serve",
"bundle": "rollup -c"
},
"devDependencies": {
"chai": "^4.2.0",
"eslint": "^6.3.0",
"esm": "^3.2.25",
"mocha": "^6.2.0",
"rollup": "^1.21.4",
"serve": "^11.1.0"
}
}

View File

@@ -1,7 +0,0 @@
export default {
input: 'src/sva.js',
output: {
file: 'build/sva.js',
format: 'esm',
}
}

View File

@@ -1,98 +0,0 @@
// Copyright 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.
export default class Assembler {
static get GENERATOR_ID() { return 0; }
/**
* @param {AST} the AST to build the SPIR-V from
*/
constructor(ast) {
this.ast_ = ast;
}
/**
* Assembles the AST into binary SPIR-V.
* @return {Uint32Array} The SPIR-V binary data.
*/
assemble() {
let total_size = 5;
for (const inst of this.ast_.instructions()) {
total_size += 1;
for (const op of inst.operands()) {
total_size += op.length();
}
}
let u = new Uint32Array(total_size);
u[0] = 0x07230203; // Magic
u[1] = 0x00010500; // Version 1.5
u[2] = Assembler.GENERATOR_ID; // Generator magic number
u[3] = this.ast_.getIdBounds(); // ID bounds
u[4] = 0; // Reserved
let idx = 5;
for (const inst of this.ast_.instructions()) {
let op_size = 1;
for (const op of inst.operands()) {
op_size += op.length();
}
u[idx++] = op_size << 16 | inst.opcode();
for (const op of inst.operands()) {
idx = this.processOp(u, idx, op);
}
}
return u;
}
processOp(u, idx, op) {
if (op.type() === "string") {
let len = 0;
let v = 0;
for (const ch of op.value()) {
v = v | (ch.charCodeAt(0) << (len * 8));
len += 1;
if (len === 4) {
u[idx++] = v;
len = 0;
v = 0;
}
}
// Make sure either the terminating 0 byte is written or the last
// partial word is written.
u[idx++] = v;
} else if (op.type() === "float") {
// TODO(dsinclair): Handle 64 bit floats ...
let b = new ArrayBuffer(4);
let f = new Float32Array(b);
f[0] = op.value();
let u2 = new Uint32Array(b);
u[idx++] = u2[0];
} else {
u[idx++] = op.value();
}
for (const param of op.params()) {
idx = this.processOp(u, idx, param);
}
return idx;
}
}

View File

@@ -1,165 +0,0 @@
// Copyright 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.
import { assert } from "chai";
import Lexer from "./lexer";
import Parser from "./parser";
import grammar from "./spirv.data.js";
import Assembler from "./assembler";
describe("assembler", () => {
it("generates SPIR-V magic number", () => {
let input = `; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 7
; Bound: 6
; Schema: 0
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 440
OpName %main "main"
%void = OpTypeVoid
%3 = OpTypeFunction %void
%main = OpFunction %void None %3
%5 = OpLabel
OpReturn
OpFunctionEnd`;
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast, p.error);
let a = new Assembler(ast);
let res = a.assemble();
assert.equal(res[0], 0x07230203);
});
it("assembles enumerant params", () => {
let input = "OpExecutionMode %main LocalSize 2 3 4";
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast, p.error);
let a = new Assembler(ast);
let res = a.assemble();
assert.lengthOf(res, 11);
assert.equal(res[5], (6 /* word count */ << 16) | 16 /* opcode */);
assert.equal(res[6], 1 /* %main */);
assert.equal(res[7], 17 /* LocalSize */);
assert.equal(res[8], 2);
assert.equal(res[9], 3);
assert.equal(res[10], 4);
});
it("assembles float 32 values", () => {
let input = `%float = OpTypeFloat 32
%float1 = OpConstant %float 0.400000006`;
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast, p.error);
let a = new Assembler(ast);
let res = a.assemble();
assert.lengthOf(res, 12);
assert.equal(res[8], (4 /* word count */ << 16) | 43 /* opcode */);
assert.equal(res[9], 1 /* %float */);
assert.equal(res[10], 2 /* %float */);
assert.equal(res[11], 0x3ecccccd /* 0.400000006 */);
});
describe("strings", () => {
it("assembles 'abcd'", () => {
let input = `OpName %mains "abcd"`;
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast, p.error);
let a = new Assembler(ast);
let res = a.assemble();
assert.lengthOf(res, 9);
assert.equal(res[5], (4 /* word count */ << 16) | 5 /* opcode */);
assert.equal(res[6], 1 /* %mains */);
assert.equal(res[7], 0x64636261 /* food */);
assert.equal(res[8], 0x00000000 /* null byte */);
});
it("assembles 'abcde'", () => {
let input = `OpName %mains "abcde"`;
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast, p.error);
let a = new Assembler(ast);
let res = a.assemble();
assert.lengthOf(res, 9);
assert.equal(res[5], (4 /* word count */ << 16) | 5 /* opcode */);
assert.equal(res[6], 1 /* %mains */);
assert.equal(res[7], 0x64636261 /* abcd */);
assert.equal(res[8], 0x00000065 /* e */);
});
it("assembles 'abcdef'", () => {
let input = `OpName %mains "abcdef"`;
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast, p.error);
let a = new Assembler(ast);
let res = a.assemble();
assert.lengthOf(res, 9);
assert.equal(res[5], (4 /* word count */ << 16) | 5 /* opcode */);
assert.equal(res[6], 1 /* %mains */);
assert.equal(res[7], 0x64636261 /* abcd */);
assert.equal(res[8], 0x00006665 /* ef */);
});
it("assembles 'abcdefg'", () => {
let input = `OpName %mains "abcdefg"`;
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast, p.error);
let a = new Assembler(ast);
let res = a.assemble();
assert.lengthOf(res, 9);
assert.equal(res[5], (4 /* word count */ << 16) | 5 /* opcode */);
assert.equal(res[6], 1 /* %mains */);
assert.equal(res[7], 0x64636261 /* abcd */);
assert.equal(res[8], 0x00676665 /* efg */);
});
});
});

View File

@@ -1,141 +0,0 @@
// Copyright 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.
class Module {
constructor() {
this.instructions_ = [];
this.next_id_ = 1;
/**
* Maps {string, hash} where the string is the type name and the hash is:
* type- 'float' or 'int'
* width- number of bits needed to store number
* signed- the sign of the number
*/
this.types_ = {};
/**
* Maps {string, number} where the string is the type name and the number is
* the id value.
*/
this.assigned_ids_ = {};
}
instructions() { return this.instructions_; }
instruction(val) { return this.instructions_[val]; }
addInstruction(inst) {
this.instructions_.push(inst);
// Record type information
if (inst.name() === "OpTypeInt" || inst.name() === "OpTypeFloat") {
let is_int = inst.name() === "OpTypeInt";
this.types_[inst.operand(0).name()] = {
type: is_int ? "int" : "float",
width: inst.operand(1).value(),
signed: is_int ? inst.operand(2).value() : 1
};
}
// Record operand result id's
inst.operands().forEach((op) => {
if (op.rawValue() !== undefined && op.type() === "result_id") {
this.next_id_ = Math.max(this.next_id_, op.rawValue() + 1);
}
});
}
getType(name) { return this.types_[name]; }
getId(name) {
if (this.assigned_ids_[name] !== undefined) {
return this.assigned_ids_[name];
}
let next = this.next_id_;
this.assigned_ids_[name] = next;
this.next_id_ += 1;
return next;
}
getIdBounds() { return this.next_id_; }
}
class Instruction {
constructor(name, opcode, operands) {
this.name_ = name;
this.opcode_ = opcode;
this.operands_ = operands;
}
name() { return this.name_; }
opcode() { return this.opcode_; }
operands() { return this.operands_; }
operand(val) { return this.operands_[val]; }
}
class Operand {
constructor(mod, name, type, value, params) {
this.module_ = mod;
this.name_ = name;
this.type_ = type;
this.value_ = value;
this.params_ = params;
}
name() { return this.name_; }
length() {
// Get the value just to force it to be filled.
this.value();
if (this.type_ === "string") {
return Math.ceil((this.value_.length + 1) / 4);
}
let size = 1;
for (const param of this.params_) {
size += param.length();
}
return size;
}
type() { return this.type_; }
rawValue() { return this.value_; }
// This method should only be called on ResultId's after the full parse is
// complete. This is because the AST will only have the maximum seen numeric
// ResultId when the parse is done.
value() {
if (this.value_ === undefined) {
this.value_ = this.module_.getId(this.name_);
}
return this.value_;
}
params() { return this.params_; }
}
export {
Module,
Instruction,
Operand
};

View File

@@ -1,363 +0,0 @@
// Copyright 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.
import { Token, TokenType } from "./token.js";
export default class Lexer {
/**
* @param {String} input The input string to tokenize.
*/
constructor(input) {
this.input_ = input;
this.len_ = input.length;
this.cur_pos_ = 0;
this.cur_line_ = 1;
this.num_regex_ = /^[0-9]+$/;
this.alpha_regex_ = /^[a-zA-Z_]+$/;
this.op_regex_ = /^Op[A-Z][^\s]*$/;
this.hex_regex_ = /^[0-9a-fA-F]$/;
}
/**
* Parses the next token from the input stream.
* @return {Token} the next token.
*/
next() {
this.skipWhitespace();
this.skipComments();
if (this.cur_pos_ >= this.len_)
return new Token(TokenType.kEOF, this.cur_line_);
let n = this.tryHexInteger();
if (n !== undefined)
return n;
n = this.tryFloat();
if (n !== undefined)
return n;
n = this.tryInteger();
if (n !== undefined)
return n;
n = this.tryString();
if (n !== undefined)
return n;
n = this.tryOp();
if (n !== undefined)
return n;
n = this.tryPunctuation();
if (n !== undefined)
return n;
n = this.tryResultId();
if (n !== undefined)
return n;
n = this.tryIdent();
if (n !== undefined)
return n;
return new Token(TokenType.kError, this.cur_line_, "Failed to match token");
}
is(str) {
if (this.len_ <= this.cur_pos_ + (str.length - 1))
return false;
for (let i = 0; i < str.length; ++i) {
if (this.input_[this.cur_pos_ + i] !== str[i])
return false;
}
return true;
}
isNum(ch) {
return ch.match(this.num_regex_);
}
isAlpha(ch) {
return ch.match(this.alpha_regex_);
}
isAlphaNum(ch) {
return this.isNum(ch) || this.isAlpha(ch);
}
isHex(char) {
return char.match(this.hex_regex_);
}
isCurWhitespace() {
return this.is(" ") || this.is("\t") || this.is("\r") || this.is("\n");
}
skipWhitespace() {
for(;;) {
let cur_pos = this.cur_pos_;
while (this.cur_pos_ < this.len_ &&
this.isCurWhitespace()) {
if (this.is("\n"))
this.cur_line_ += 1;
this.cur_pos_ += 1;
}
this.skipComments();
// Cursor didn't move so no whitespace matched.
if (cur_pos === this.cur_pos_)
break;
}
}
skipComments() {
if (!this.is(";"))
return;
while (this.cur_pos_ < this.len_ && !this.is("\n"))
this.cur_pos_ += 1;
}
/**
* Attempt to parse the next part of the input as a float.
* @return {Token|undefined} returns a Token if a float is matched,
* undefined otherwise.
*/
tryFloat() {
let start = this.cur_pos_;
let end = start;
if (this.cur_pos_ >= this.len_)
return undefined;
if (this.input_[end] === "-")
end += 1;
while (end < this.len_ && this.isNum(this.input_[end]))
end += 1;
// Must have a "." in a float
if (end >= this.len_ || this.input_[end] !== ".")
return undefined;
end += 1;
while (end < this.len_ && this.isNum(this.input_[end]))
end += 1;
let substr = this.input_.substr(start, end - start);
if (substr === "." || substr === "-.")
return undefined;
this.cur_pos_ = end;
return new Token(TokenType.kFloatLiteral, this.cur_line_, parseFloat(substr));
}
/**
* Attempt to parse a hex encoded integer.
* @return {Token|undefined} returns a Token if a Hex number is matched,
* undefined otherwise.
*/
tryHexInteger() {
let start = this.cur_pos_;
let end = start;
if (this.cur_pos_ >= this.len_)
return undefined;
if (end + 2 >= this.len_ || this.input_[end] !== "0" ||
this.input_[end + 1] !== "x") {
return undefined;
}
end += 2;
while (end < this.len_ && this.isHex(this.input_[end]))
end += 1;
this.cur_pos_ = end;
let val = parseInt(this.input_.substr(start, end - start), 16);
return new Token(TokenType.kIntegerLiteral, this.cur_line_, val);
}
/**
* Attempt to parse an encoded integer.
* @return {Token|undefined} returns a Token if a number is matched,
* undefined otherwise.
*/
tryInteger() {
let start = this.cur_pos_;
let end = start;
if (this.cur_pos_ >= this.len_)
return undefined;
if (this.input_[end] === "-")
end += 1;
if (end >= this.len_ || !this.isNum(this.input_[end]))
return undefined;
while (end < this.len_ && this.isNum(this.input_[end]))
end += 1;
this.cur_pos_ = end;
let val = parseInt(this.input_.substr(start, end - start), 10);
return new Token(TokenType.kIntegerLiteral, this.cur_line_, val);
}
/**
* Attempt to parse a result id.
* @return {Token|undefined} returns a Token if a result id is matched,
* undefined otherwise.
*/
tryResultId() {
let start = this.cur_pos_;
if (start >= this.len_)
return undefined;
if (!this.is("%"))
return undefined;
start += 1;
this.cur_pos_ += 1;
while (this.cur_pos_ < this.len_ &&
(this.isAlphaNum(this.input_[this.cur_pos_]) || this.is("_"))) {
this.cur_pos_ += 1;
}
let ident = this.input_.substr(start, this.cur_pos_ - start);
let value = undefined;
if (ident.match(this.num_regex_))
value = parseInt(ident, 10);
return new Token(TokenType.kResultId, this.cur_line_, {
name: ident,
val: value
});
}
/**
* Attempt to parse an identifier.
* @return {Token|undefined} returns a Token if an identifier is matched,
* undefined otherwise.
*/
tryIdent() {
let start = this.cur_pos_;
if (start >= this.len_)
return undefined;
while (this.cur_pos_ < this.len_ &&
(this.isAlphaNum(this.input_[this.cur_pos_]) || this.is("_"))) {
this.cur_pos_ += 1;
}
let ident = this.input_.substr(start, this.cur_pos_ - start);
return new Token(TokenType.kIdentifier, this.cur_line_, ident);
}
/**
* Attempt to parse an Op command.
* @return {Token|undefined} returns a Token if an Op command is matched,
* undefined otherwise.
*/
tryOp() {
let start = this.cur_pos_;
if (this.cur_pos_ >= this.len_ || (this.cur_pos_ + 1 >= this.len_))
return undefined;
if (this.input_[this.cur_pos_] !== "O" ||
this.input_[this.cur_pos_ + 1] !== "p") {
return undefined;
}
while (this.cur_pos_ < this.len_ &&
!this.isCurWhitespace()) {
this.cur_pos_ += 1;
}
return new Token(TokenType.kOp, this.cur_line_, {
name: this.input_.substr(start, this.cur_pos_ - start)
});
}
/**
* Attempts to match punctuation strings against the input
* @return {Token|undefined} Returns the Token for the punctuation or
* undefined if no matches found.
*/
tryPunctuation() {
let type = undefined;
if (this.is("="))
type = TokenType.kEqual;
else if (this.is("|"))
type = TokenType.kPipe;
if (type === undefined)
return undefined;
this.cur_pos_ += type.length;
return new Token(type, this.cur_line_, type);
}
/**
* Attempts to match strings against the input
* @return {Token|undefined} Returns the Token for the string or undefined
* if no match found.
*/
tryString() {
let start = this.cur_pos_;
// Must have at least 2 chars for a string.
if (this.cur_pos_ >= this.len_ || (this.cur_pos_ + 1 >= this.len_))
return undefined;
if (!this.is("\""))
return undefined;
this.cur_pos_ += 1;
let str = "";
while (this.cur_pos_ <= this.len_) {
if (this.is("\""))
break;
if (this.is("\\")) {
this.cur_pos_ += 1;
if (this.cur_pos_ >= this.len_)
return undefined;
if (this.is("\\")) {
str += "\\";
} else if (this.is("\"")) {
str += '"';
} else {
str += this.input_[this.cur_pos_];
}
} else {
str += this.input_[this.cur_pos_];
}
this.cur_pos_ += 1;
}
if (this.cur_pos_ >= this.len_)
return undefined;
this.cur_pos_ += 1;
return new Token(TokenType.kStringLiteral, this.cur_line_, str);
}
}

View File

@@ -1,191 +0,0 @@
// Copyright 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.
import { assert } from "chai";
import Lexer from "./lexer";
import { TokenType } from "./token";
describe("lexer", () => {
describe("skipped content", () => {
it("skips whitespace", () => {
let input = " \t\r\n\t \tOpKill\t\n\t \r ";
let l = new Lexer(input);
let t = l.next();
assert.equal(t.type, TokenType.kOp);
assert.equal(t.line, 2);
assert.equal(t.data.name, "OpKill");
t = l.next();
assert.equal(t.type, TokenType.kEOF);
assert.equal(t.line, 3);
});
it("skips ; comments", () => {
let input = `; start with comment
OpKill ; end of line comment
; another comment
%1`;
let l = new Lexer(input);
let t = l.next();
assert.equal(t.type, TokenType.kOp);
assert.equal(t.data.name, "OpKill");
assert.equal(t.line, 2);
t = l.next();
assert.equal(t.type, TokenType.kResultId);
assert.equal(t.data.name, "1");
assert.equal(t.data.val, 1);
assert.equal(t.line, 4);
});
});
describe("numerics", () => {
it("parses floats", () => {
let input = ["0.0", "0.", ".0", "5.7", "5.", ".7", "-0.0", "-.0",
"-0.", "-5.7", "-5.", "-.7"];
let results = [0.0, 0.0, 0.0, 5.7, 5.0, 0.7, 0.0, 0.0, 0.0, -5.7, -5.0,
-0.7];
input.forEach((val, idx) => {
let l = new Lexer(val);
let t = l.next();
assert.equal(t.type, TokenType.kFloatLiteral,
`expected ${val} to be a float got ${t.type}`);
assert.equal(t.data, results[idx],
`expected ${results[idx]} === ${t.data}`);
t = l.next();
assert.equal(t.type, TokenType.kEOF);
assert.equal(t.data, undefined);
});
});
it("handles invalid floats", () => {
let input = [".", "-."];
input.forEach((val) => {
let l = new Lexer(val);
let t = l.next();
assert.notEqual(t.type, TokenType.kFloatLiteral,
`expect ${val} to not match type float`);
});
});
it("parses integers", () => {
let input = ["0", "-0", "123", "-123", "2147483647", "-2147483648",
"4294967295", "0x00", "0x24"];
let results = [0, 0, 123, -123,2147483647, -2147483648, 4294967295,
0x0, 0x24];
input.forEach((val, idx) => {
let l = new Lexer(val);
let t = l.next();
assert.equal(t.type, TokenType.kIntegerLiteral,
`expected ${val} to be an integer got ${t.type}`);
assert.equal(t.data, results[idx],
`expected ${results[idx]} === ${t.data}`);
t = l.next();
assert.equal(t.type, TokenType.kEOF);
assert.equal(t.data, undefined);
});
});
});
it("matches result_ids", () => {
let input = `%123
%001
%main
%_a_b_c`;
let result = [
{name: "123", val: 123},
{name: "001", val: 1},
{name: "main", val: undefined},
{name: "_a_b_c", val: undefined}
];
let l = new Lexer(input);
for (let i = 0; i < result.length; ++i) {
let t = l.next();
assert.equal(t.type, TokenType.kResultId);
assert.equal(t.data.name, result[i].name);
assert.equal(t.data.val, result[i].val);
}
});
it("matches punctuation", () => {
let input = "=";
let results = [TokenType.kEqual];
let l = new Lexer(input);
for (let i = 0; i < results.length; ++i) {
let t = l.next();
assert.equal(t.type, results[i]);
assert.equal(t.line, i + 1);
}
let t = l.next();
assert.equal(t.type, TokenType.kEOF);
});
describe("strings", () => {
it("matches strings", () => {
let input = "\"GLSL.std.450\"";
let l = new Lexer(input);
let t = l.next();
assert.equal(t.type, TokenType.kStringLiteral);
assert.equal(t.data, "GLSL.std.450");
});
it("handles unfinished strings", () => {
let input = "\"GLSL.std.450";
let l = new Lexer(input);
let t = l.next();
assert.equal(t.type, TokenType.kError);
});
it("handles escapes", () => {
let input = `"embedded\\"quote"
"embedded\\\\slash"
"embedded\\nchar"`;
let results = [`embedded\"quote`, `embedded\\slash`, `embeddednchar`];
let l = new Lexer(input);
for (let i = 0; i < results.length; ++i) {
let t = l.next();
assert.equal(t.type, TokenType.kStringLiteral, results[i]);
assert.equal(t.data, results[i]);
}
});
});
it("matches keywords", () => {
let input = "GLSL Function";
let results = ["GLSL", "Function"];
let l = new Lexer(input);
for (let i = 0; i < results.length; ++i) {
let t = l.next();
assert.equal(t.type, TokenType.kIdentifier, results[i]);
assert.equal(t.data, results[i]);
}
});
});

View File

@@ -1,277 +0,0 @@
// Copyright 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.
import { TokenType } from "./token.js";
import * as AST from "./ast.js";
export default class Parser {
/**
* @param {Hash} The SPIR-V grammar
* @param {Lexer} The lexer
* @return {AST} Attempts to build an AST from the tokens returned by the
* given lexer
*/
constructor(grammar, lexer) {
this.grammar_ = grammar;
this.lexer_ = lexer;
this.peek_ = [];
this.error_ = "";
}
get error() { return this.error_; }
next() {
return this.peek_.shift() || this.lexer_.next();
}
peek(idx) {
while (this.peek_.length <= idx) {
this.peek_.push(this.lexer_.next());
}
return this.peek_[idx];
}
/**
* Executes the parser.
*
* @return {AST|undefined} returns a parsed AST on success or undefined
* on error. The error message can be retrieved by
* calling error().
*/
parse() {
let ast = new AST.Module();
for(;;) {
let token = this.next();
if (token === TokenType.kError) {
this.error_ = token.line() + ": " + token.data();
return undefined;
}
if (token.type === TokenType.kEOF)
break;
let result_id = undefined;
if (token.type === TokenType.kResultId) {
result_id = token;
token = this.next();
if (token.type !== TokenType.kEqual) {
this.error_ = token.line + ": expected = after result id";
return undefined;
}
token = this.next();
}
if (token.type !== TokenType.kOp) {
this.error_ = token.line + ": expected Op got " + token.type;
return undefined;
}
let name = token.data.name;
let data = this.getInstructionData(name);
let operands = [];
let result_type = undefined;
for (let operand of data.operands) {
if (operand.kind === "IdResult") {
if (result_id === undefined) {
this.error_ = token.line + ": expected result id";
return undefined;
}
let o = new AST.Operand(ast, result_id.data.name, "result_id",
result_id.data.val, []);
if (o === undefined) {
return undefined;
}
operands.push(o);
} else {
if (operand.quantifier === "?") {
if (this.nextIsNewInstr()) {
break;
}
} else if (operand.quantifier === "*") {
while (!this.nextIsNewInstr()) {
let o = this.extractOperand(ast, result_type, operand);
if (o === undefined) {
return undefined;
}
operands.push(o);
}
break;
}
let o = this.extractOperand(ast, result_type, operand);
if (o === undefined) {
return undefined;
}
// Store the result type away so we can use it for context dependent
// numbers if needed.
if (operand.kind === "IdResultType") {
result_type = ast.getType(o.name());
}
operands.push(o);
}
}
// Verify only GLSL extended instructions are used
if (name === "OpExtInstImport" && operands[1].value() !== "GLSL.std.450") {
this.error_ = token.line + ": Only GLSL.std.450 external instructions supported";
return undefined;
}
let inst = new AST.Instruction(name, data.opcode, operands);
ast.addInstruction(inst);
}
return ast;
}
getInstructionData(name) {
return this.grammar_["instructions"][name];
}
nextIsNewInstr() {
let n0 = this.peek(0);
if (n0.type === TokenType.kOp || n0.type === TokenType.kEOF) {
return true;
}
let n1 = this.peek(1);
if (n1.type === TokenType.kEOF) {
return false;
}
if (n0.type === TokenType.kResultId && n1.type === TokenType.kEqual)
return true;
return false;
}
extractOperand(ast, result_type, data) {
let t = this.next();
let name = undefined;
let kind = undefined;
let value = undefined;
let params = [];
// TODO(dsinclair): There are a bunch of missing types here. See
// https://github.com/KhronosGroup/SPIRV-Tools/blob/master/source/text.cpp#L210
if (data.kind === "IdResult" || data.kind === "IdRef"
|| data.kind === "IdResultType") {
if (t.type !== TokenType.kResultId) {
this.error_ = t.line + ": expected result id";
return undefined;
}
name = t.data.name;
kind = "result_id";
value = t.data.val;
} else if (data.kind === "LiteralString") {
if (t.type !== TokenType.kStringLiteral) {
this.error_ = t.line + ": expected string not found";
return undefined;
}
name = t.data;
kind = "string";
value = t.data;
} else if (data.kind === "LiteralInteger") {
if (t.type !== TokenType.kIntegerLiteral) {
this.error_ = t.line + ": expected integer not found";
return undefined;
}
name = "" + t.data;
kind = t.type;
value = t.data;
} else if (data.kind === "LiteralContextDependentNumber") {
if (result_type === undefined) {
this.error_ = t.line +
": missing result type for context dependent number";
return undefined;
}
if (t.type !== TokenType.kIntegerLiteral
&& t.type !== TokenType.kFloatLiteral) {
this.error_ = t.line + ": expected number not found";
return undefined;
}
name = "" + t.data;
kind = result_type.type;
value = t.data;
} else if (data.kind === "LiteralExtInstInteger") {
if (t.type !== TokenType.kIdentifier) {
this.error_ = t.line + ": expected instruction identifier";
return undefined;
}
if (this.grammar_.ext[t.data] === undefined) {
this.error_ = t.line + `: unable to find extended instruction (${t.data})`;
return undefined;
}
name = t.data;
kind = "integer";
value = this.grammar_.ext[t.data];
} else {
let d = this.grammar_.operand_kinds[data.kind];
if (d === undefined) {
this.error_ = t.line + ": expected " + data.kind + " not found";
return undefined;
}
let val = d.values[t.data]["value"];
let names = [t.data];
if (d.type === "BitEnum") {
for(;;) {
let tmp = this.peek(0);
if (tmp.type !== TokenType.kPipe) {
break;
}
this.next(); // skip pipe
tmp = this.next();
if (tmp.type !== TokenType.kIdentifier) {
this.error_ = tmp.line() + ": expected identifier";
return undefined;
}
val |= d.values[tmp.data]["value"];
names.push(tmp.data);
}
}
name = names.join("|");
kind = d.type;
value = val;
for (const op_name of names) {
if (d.values[op_name]['params'] === undefined) {
continue;
}
for (const param of d.values[op_name]["params"]) {
params.push(this.extractOperand(ast, result_type, { kind: param }));
}
}
}
return new AST.Operand(ast, name, kind, value, params);
}
}

View File

@@ -1,464 +0,0 @@
// Copyright 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.
import { assert } from "chai";
import Lexer from "./lexer";
import Parser from "./parser";
import grammar from "./spirv.data.js";
describe("parser", () => {
it("parses an opcode", () => {
let input = "OpKill";
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast);
assert.lengthOf(ast.instructions(), 1);
let inst = ast.instruction(0);
assert.equal(inst.name(), "OpKill");
assert.equal(inst.opcode(), 252);
assert.lengthOf(inst.operands, 0);
});
it("parses an opcode with an identifier", () => {
let input = "OpCapability Shader";
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast, p.error);
assert.lengthOf(ast.instructions(), 1);
let inst = ast.instruction(0);
assert.equal(inst.name(), "OpCapability");
assert.equal(inst.opcode(), 17);
assert.lengthOf(inst.operands(), 1);
let op = inst.operand(0);
assert.equal(op.name(), "Shader");
assert.equal(op.type(), "ValueEnum");
assert.equal(op.value(), 1);
});
it("parses an opcode with a result", () => {
let input = "%void = OpTypeVoid";
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast);
assert.lengthOf(ast.instructions(), 1);
let inst = ast.instruction(0);
assert.equal(inst.name(), "OpTypeVoid");
assert.equal(inst.opcode(), 19);
assert.lengthOf(inst.operands(), 1);
let op = inst.operand(0);
assert.equal(op.name(), "void");
assert.equal(op.value(), 1);
});
it("sets module bounds based on numeric result", () => {
let input = "%3 = OpTypeVoid";
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast);
assert.equal(ast.getId("next"), 4);
});
it("returns the same value for a named result_id", () => {
let input = "%3 = OpTypeFunction %int %int";
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast);
assert.lengthOf(ast.instructions(), 1);
let inst = ast.instruction(0);
let op1 = inst.operand(1);
assert.equal(op1.name(), "int");
assert.equal(op1.value(), 4);
let op2 = inst.operand(2);
assert.equal(op2.name(), "int");
assert.equal(op2.value(), 4);
});
it("parses an opcode with a string", () => {
let input = "OpEntryPoint Fragment %main \"main\"";
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast);
assert.lengthOf(ast.instructions(), 1);
let inst = ast.instruction(0);
let op = inst.operand(2);
assert.equal(op.name(), "main");
assert.equal(op.value(), "main");
});
describe("numerics", () => {
describe("integers", () => {
it("parses an opcode with an integer", () => {
let input = "OpSource GLSL 440";
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast);
assert.lengthOf(ast.instructions(), 1);
let inst = ast.instruction(0);
let op0 = inst.operand(0);
assert.equal(op0.name(), "GLSL");
assert.equal(op0.type(), "ValueEnum");
assert.equal(op0.value(), 2);
let op1 = inst.operand(1);
assert.equal(op1.name(), "440");
assert.equal(op1.value(), 440);
});
it("parses an opcode with a hex integer", () => {
let input = "OpSource GLSL 0x440";
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast);
assert.lengthOf(ast.instructions(), 1);
let inst = ast.instruction(0);
let op0 = inst.operand(0);
assert.equal(op0.name(), "GLSL");
assert.equal(op0.type(), "ValueEnum");
assert.equal(op0.value(), 2);
let op1 = inst.operand(1);
assert.equal(op1.name(), "1088");
assert.equal(op1.value(), 0x440);
});
it.skip("parses immediate integers", () => {
// TODO(dsinclair): Support or skip?
});
});
describe("floats", () => {
it("parses floats", () => {
let input = `%float = OpTypeFloat 32
%float1 = OpConstant %float 0.400000006`;
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast, p.error);
assert.lengthOf(ast.instructions(), 2);
let inst = ast.instruction(1);
let op2 = inst.operand(2);
assert.equal(op2.value(), 0.400000006);
});
// TODO(dsinclair): Make hex encoded floats parse ...
it.skip("parses hex floats", () => {
let input = `%float = OpTypeFloat 32
%nfloat = OpConstant %float -0.4p+2
%pfloat = OpConstant %float 0.4p-2
%inf = OpConstant %float32 0x1p+128
%neginf = OpConstant %float32 -0x1p+128
%aNaN = OpConstant %float32 0x1.8p+128
%moreNaN = OpConstant %float32 -0x1.0002p+128`;
let results = [-40.0, .004, 0x00000, 0x00000, 0x7fc00000, 0xff800100];
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast, p.error);
assert.lengthOf(ast.instructions(), 7);
for (const idx in results) {
let inst = ast.instruction(idx);
let op2 = inst.operand(2);
assert.equal(op2.value(), results[idx]);
}
});
it("parses a float that looks like an int", () => {
let input = `%float = OpTypeFloat 32
%float1 = OpConstant %float 1`;
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast, p.error);
assert.lengthOf(ast.instructions(), 2);
let inst = ast.instruction(1);
let op2 = inst.operand(2);
assert.equal(op2.value(), 1);
assert.equal(op2.type(), "float");
});
});
});
describe("enums", () => {
it("parses enum values", () => {
let input = `%1 = OpTypeFloat 32
%30 = OpImageSampleExplicitLod %1 %20 %18 Grad|ConstOffset %22 %24 %29`;
let vals = [{val: 1, name: "1"},
{val: 30, name: "30"},
{val: 20, name: "20"},
{val: 18, name: "18"},
{val: 12, name: "Grad|ConstOffset"}];
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast, p.error);
assert.lengthOf(ast.instructions(), 2);
let inst = ast.instruction(1);
for (let idx in vals) {
let op = inst.operand(idx);
assert.equal(op.name(), vals[idx].name);
assert.equal(op.value(), vals[idx].val);
}
// BitEnum
let params = inst.operand(4).params();
assert.lengthOf(params, 3);
assert.equal(params[0].name(), "22");
assert.equal(params[0].value(), 22);
assert.equal(params[1].name(), "24");
assert.equal(params[1].value(), 24);
assert.equal(params[2].name(), "29");
assert.equal(params[2].value(), 29);
});
it("parses enumerants with parameters", () => {
let input ="OpExecutionMode %main LocalSize 2 3 4";
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast, p.error);
assert.lengthOf(ast.instructions(), 1);
let inst = ast.instruction(0);
assert.equal(inst.name(), "OpExecutionMode");
assert.lengthOf(inst.operands(), 2);
assert.equal(inst.operand(0).name(), "main");
assert.equal(inst.operand(1).name(), "LocalSize");
let params = inst.operand(1).params();
assert.lengthOf(params, 3);
assert.equal(params[0].name(), "2");
assert.equal(params[1].name(), "3");
assert.equal(params[2].name(), "4");
});
});
it("parses result into second operand if needed", () => {
let input = `%int = OpTypeInt 32 1
%int_3 = OpConstant %int 3`;
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast);
assert.lengthOf(ast.instructions(), 2);
let inst = ast.instruction(1);
assert.equal(inst.name(), "OpConstant");
assert.equal(inst.opcode(), 43);
assert.lengthOf(inst.operands(), 3);
let op0 = inst.operand(0);
assert.equal(op0.name(), "int");
assert.equal(op0.value(), 1);
let op1 = inst.operand(1);
assert.equal(op1.name(), "int_3");
assert.equal(op1.value(), 2);
let op2 = inst.operand(2);
assert.equal(op2.name(), "3");
assert.equal(op2.value(), 3);
});
describe("quantifiers", () => {
describe("?", () => {
it("skips if missing", () => {
let input = `OpImageWrite %1 %2 %3
OpKill`;
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast);
assert.lengthOf(ast.instructions(), 2);
let inst = ast.instruction(0);
assert.equal(inst.name(), "OpImageWrite");
assert.lengthOf(inst.operands(), 3);
});
it("skips if missing at EOF", () => {
let input = "OpImageWrite %1 %2 %3";
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast);
assert.lengthOf(ast.instructions(), 1);
let inst = ast.instruction(0);
assert.equal(inst.name(), "OpImageWrite");
assert.lengthOf(inst.operands(), 3);
});
it("extracts if available", () => {
let input = `OpImageWrite %1 %2 %3 ConstOffset %2
OpKill`;
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast);
assert.lengthOf(ast.instructions(), 2);
let inst = ast.instruction(0);
assert.equal(inst.name(), "OpImageWrite");
assert.lengthOf(inst.operands(), 4);
assert.equal(inst.operand(3).name(), "ConstOffset");
});
});
describe("*", () => {
it("skips if missing", () => {
let input = `OpEntryPoint Fragment %main "main"
OpKill`;
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast);
assert.lengthOf(ast.instructions(), 2);
let inst = ast.instruction(0);
assert.equal(inst.name(), "OpEntryPoint");
assert.lengthOf(inst.operands(), 3);
assert.equal(inst.operand(2).name(), "main");
});
it("extracts one if available", () => {
let input = `OpEntryPoint Fragment %main "main" %2
OpKill`;
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast);
assert.lengthOf(ast.instructions(), 2);
let inst = ast.instruction(0);
assert.equal(inst.name(), "OpEntryPoint");
assert.lengthOf(inst.operands(), 4);
assert.equal(inst.operand(3).name(), "2");
});
it("extracts multiple if available", () => {
let input = `OpEntryPoint Fragment %main "main" %2 %3 %4 %5
OpKill`;
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast);
assert.lengthOf(ast.instructions(), 2);
let inst = ast.instruction(0);
assert.equal(inst.name(), "OpEntryPoint");
assert.lengthOf(inst.operands(), 7);
assert.equal(inst.operand(3).name(), "2");
assert.equal(inst.operand(4).name(), "3");
assert.equal(inst.operand(5).name(), "4");
assert.equal(inst.operand(6).name(), "5");
});
});
});
describe("extended instructions", () => {
it("errors on non-glsl extensions", () => {
let input = "%1 = OpExtInstImport \"OpenCL.std.100\"";
let l = new Lexer(input);
let p = new Parser(grammar, l);
assert.isUndefined(p.parse());
});
it("handles extended instructions", () => {
let input = `%1 = OpExtInstImport "GLSL.std.450"
%44 = OpExtInst %7 %1 Sqrt %43`;
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
assert.exists(ast, p.error);
assert.lengthOf(ast.instructions(), 2);
let inst = ast.instruction(1);
assert.lengthOf(inst.operands(), 5);
assert.equal(inst.operand(3).value(), 31);
assert.equal(inst.operand(3).name(), "Sqrt");
assert.equal(inst.operand(4).value(), 43);
assert.equal(inst.operand(4).name(), "43");
});
});
it.skip("handles spec constant ops", () => {
// let input = "%sum = OpSpecConstantOp %i32 IAdd %a %b";
});
it.skip("handles OpCopyMemory", () => {
// let input = "OpCopyMemory %1 %2 " +
// "Volatile|Nontemporal|MakePointerVisible %3 " +
// "Aligned|MakePointerAvailable|NonPrivatePointer 16 %4";
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,40 +0,0 @@
// Copyright 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.
import Parser from "./parser.js";
import Lexer from "./lexer.js";
import Assembler from "./assembler.js";
import grammar from "./spirv.data.js";
export default class SVA {
/**
* Attempts to convert |input| SPIR-V assembly into SPIR-V binary.
*
* @param {String} the input string containing the assembly
* @return {Uint32Array|string} returns a Uint32Array containing the binary
* SPIR-V or a string on error.
*/
static assemble(input) {
let l = new Lexer(input);
let p = new Parser(grammar, l);
let ast = p.parse();
if (ast === undefined)
return p.error;
let a = new Assembler(ast);
return a.assemble();
}
}

View File

@@ -1,55 +0,0 @@
// Copyright 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.
const TokenType = {
kEOF: "end of file",
kError: "error",
kIdentifier: "identifier",
kIntegerLiteral: "integer_literal",
kFloatLiteral: "float_literal",
kStringLiteral: "string_literal",
kResultId: "result_id",
kOp: "Op",
kEqual: "=",
kPipe: "|",
};
class Token {
/**
* @param {TokenType} type The type of token
* @param {Integer} line The line number this token was on
* @param {Any} data Data attached to the token
* @param {Integer} bits If the type is a float or integer the bit width
*/
constructor(type, line, data) {
this.type_ = type;
this.line_ = line;
this.data_ = data;
this.bits_ = 0;
}
get type() { return this.type_; }
get line() { return this.line_; }
get data() { return this.data_; }
set data(val) { this.data_ = val; }
get bits() { return this.bits_; }
set bits(val) { this.bits_ = val; }
}
export {Token, TokenType};

View File

@@ -1,18 +0,0 @@
; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 7
; Bound: 6
; Schema: 0
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 440
OpName %main "main"
%void = OpTypeVoid
%3 = OpTypeFunction %void
%main = OpFunction %void None %3
%5 = OpLabel
OpReturn
OpFunctionEnd

View File

@@ -1,23 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset='utf-8'>
</head>
<body>
<pre id='code'><code></code></pre>
<script type="module">
let c = document.getElementById('code');
import SVA from "/build/sva.js";
let assembly = SVA.assemble("OpCapability Shader");
if (typeof assembly === "string") {
c.innerText = assembly;
} else {
c.innerText = Array.from(assembly)
.map(b => b.toString(16).padStart(8, "0")).join(" ");
}
</script>
</body>
</html>

View File

@@ -1,30 +0,0 @@
; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 7
; Bound: 14
; Schema: 0
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %gl_FragColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 330
OpName %main "main"
OpName %gl_FragColor "gl_FragColor"
OpDecorate %gl_FragColor Location 0
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Output_v4float = OpTypePointer Output %v4float
%gl_FragColor = OpVariable %_ptr_Output_v4float Output
%float_0_400000006 = OpConstant %float 0.400000006
%float_0_800000012 = OpConstant %float 0.800000012
%float_1 = OpConstant %float 1
%13 = OpConstantComposite %v4float %float_0_400000006 %float_0_400000006 %float_0_800000012 %float_1
%main = OpFunction %void None %3
%5 = OpLabel
OpStore %gl_FragColor %13
OpReturn
OpFunctionEnd

View File

@@ -1,119 +0,0 @@
#!/usr/bin/env ruby
# Copyright 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.
require 'json'
GRAMMAR = "../../external/spirv-headers/include/spirv/unified1/spirv.core.grammar.json"
GLSL = "../../external/spirv-headers/include/spirv/unified1/extinst.glsl.std.450.grammar.json"
CAPABILITIES = %w(
Matrix
Shader
Sampled1D
Image1D
DerivativeControl
ImageQuery
VulkanMemoryModel
)
g = JSON.parse(File.open(GRAMMAR).read)
magic = g['magic_number']
vers = [g['major_version'], g['minor_version']]
instructions = {}
g['instructions'].each do |inst|
if (inst.has_key?('capabilities'))
skip = true
inst['capabilities'].each do |cap|
if CAPABILITIES.include?(cap)
skip = false
break
end
end
next if skip
end
op = {
opcode: inst['opcode'],
operands: []
}
if !inst['operands'].nil?
inst['operands'].each do |operand|
operand.delete('name')
op[:operands] << operand
end
end
instructions[inst['opname']] = op
end
operand_kinds = {}
g['operand_kinds'].each do |op_kind|
next if op_kind['category'] !~ /Enum/
kind = {
type: op_kind['category'],
values: {}
}
op_kind['enumerants'].each do |enum|
if (enum.has_key?('capabilities'))
skip = true
enum['capabilities'].each do |cap|
if CAPABILITIES.include?(cap)
skip = false
break
end
end
next if skip
end
v = if op_kind['category'] == 'BitEnum'
enum['value'].to_i(16)
else
enum['value'].to_i
end
params = []
if enum.has_key?('parameters')
enum['parameters'].each do |param|
params << param['kind']
end
end
kind[:values][enum['enumerant']] = {value: v}
kind[:values][enum['enumerant']][:params] = params unless params.empty?
end
next if kind[:values].empty?
operand_kinds[op_kind['kind']] = kind
end
# We only support GLSL extensions at the moment.
ext = {}
glsl = JSON.parse(File.open(GLSL).read)
glsl['instructions'].each do |inst|
ext[inst['opname']] = inst['opcode']
end
puts "/*#{g['copyright'].join("\n")}*/"
puts "\n// THIS FILE IS GENERATED WITH tools/process_grammar.rb\n\n"
puts "export default " + JSON.pretty_generate({
magic: magic,
version: vers,
instructions: instructions,
operand_kinds: operand_kinds,
ext: ext
})

File diff suppressed because it is too large Load Diff

View File

@@ -1,45 +0,0 @@
// Copyright (c) 2018 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 "tools/util/cli_consumer.h"
#include <iostream>
namespace spvtools {
namespace utils {
void CLIMessageConsumer(spv_message_level_t level, const char*,
const spv_position_t& position, const char* message) {
switch (level) {
case SPV_MSG_FATAL:
case SPV_MSG_INTERNAL_ERROR:
case SPV_MSG_ERROR:
std::cerr << "error: line " << position.index << ": " << message
<< std::endl;
break;
case SPV_MSG_WARNING:
std::cout << "warning: line " << position.index << ": " << message
<< std::endl;
break;
case SPV_MSG_INFO:
std::cout << "info: line " << position.index << ": " << message
<< std::endl;
break;
default:
break;
}
}
} // namespace utils
} // namespace spvtools

View File

@@ -1,31 +0,0 @@
// Copyright (c) 2018 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_UTIL_CLI_CONSUMMER_H_
#define SOURCE_UTIL_CLI_CONSUMMER_H_
#include "include/spirv-tools/libspirv.h"
namespace spvtools {
namespace utils {
// A message consumer that can be used by command line tools like spirv-opt and
// spirv-val to display messages.
void CLIMessageConsumer(spv_message_level_t level, const char*,
const spv_position_t& position, const char* message);
} // namespace utils
} // namespace spvtools
#endif // SOURCE_UTIL_CLI_CONSUMMER_H_

View File

@@ -1,195 +0,0 @@
// 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.
#include <cassert>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include "source/spirv_target_env.h"
#include "source/spirv_validator_options.h"
#include "spirv-tools/libspirv.hpp"
#include "tools/io.h"
#include "tools/util/cli_consumer.h"
void print_usage(char* argv0) {
std::string target_env_list = spvTargetEnvList(36, 105);
printf(
R"(%s - Validate a SPIR-V binary file.
USAGE: %s [options] [<filename>]
The SPIR-V binary is read from <filename>. If no file is specified,
or if the filename is "-", then the binary is read from standard input.
NOTE: The validator is a work in progress.
Options:
-h, --help Print this help.
--max-struct-members <maximum number of structure members allowed>
--max-struct-depth <maximum allowed nesting depth of structures>
--max-local-variables <maximum number of local variables allowed>
--max-global-variables <maximum number of global variables allowed>
--max-switch-branches <maximum number of branches allowed in switch statements>
--max-function-args <maximum number arguments allowed per function>
--max-control-flow-nesting-depth <maximum Control Flow nesting depth allowed>
--max-access-chain-indexes <maximum number of indexes allowed to use for Access Chain instructions>
--max-id-bound <maximum value for the id bound>
--relax-logical-pointer Allow allocating an object of a pointer type and returning
a pointer value from a function in logical addressing mode
--relax-block-layout Enable VK_KHR_relaxed_block_layout when checking standard
uniform, storage buffer, and push constant layouts.
This is the default when targeting Vulkan 1.1 or later.
--uniform-buffer-standard-layout Enable VK_KHR_uniform_buffer_standard_layout when checking standard
uniform buffer layouts.
--scalar-block-layout Enable VK_EXT_scalar_block_layout when checking standard
uniform, storage buffer, and push constant layouts. Scalar layout
rules are more permissive than relaxed block layout so in effect
this will override the --relax-block-layout option.
--skip-block-layout Skip checking standard uniform/storage buffer layout.
Overrides any --relax-block-layout or --scalar-block-layout option.
--relax-struct-store Allow store from one struct type to a
different type with compatible layout and
members.
--before-hlsl-legalization Allows code patterns that are intended to be
fixed by spirv-opt's legalization passes.
--version Display validator version information.
--target-env {%s}
Use validation rules from the specified environment.
)",
argv0, argv0, target_env_list.c_str());
}
int main(int argc, char** argv) {
const char* inFile = nullptr;
spv_target_env target_env = SPV_ENV_UNIVERSAL_1_5;
spvtools::ValidatorOptions options;
bool continue_processing = true;
int return_code = 0;
for (int argi = 1; continue_processing && argi < argc; ++argi) {
const char* cur_arg = argv[argi];
if ('-' == cur_arg[0]) {
if (0 == strncmp(cur_arg, "--max-", 6)) {
if (argi + 1 < argc) {
spv_validator_limit limit_type;
if (spvParseUniversalLimitsOptions(cur_arg, &limit_type)) {
uint32_t limit = 0;
if (sscanf(argv[++argi], "%u", &limit)) {
options.SetUniversalLimit(limit_type, limit);
} else {
fprintf(stderr, "error: missing argument to %s\n", cur_arg);
continue_processing = false;
return_code = 1;
}
} else {
fprintf(stderr, "error: unrecognized option: %s\n", cur_arg);
continue_processing = false;
return_code = 1;
}
} else {
fprintf(stderr, "error: Missing argument to %s\n", cur_arg);
continue_processing = false;
return_code = 1;
}
} else if (0 == strcmp(cur_arg, "--version")) {
printf("%s\n", spvSoftwareVersionDetailsString());
printf(
"Targets:\n %s\n %s\n %s\n %s\n %s\n %s\n %s\n %s\n %s\n "
"%s\n %s\n",
spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_0),
spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_1),
spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_2),
spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_3),
spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_4),
spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_5),
spvTargetEnvDescription(SPV_ENV_OPENCL_2_2),
spvTargetEnvDescription(SPV_ENV_VULKAN_1_0),
spvTargetEnvDescription(SPV_ENV_VULKAN_1_1),
spvTargetEnvDescription(SPV_ENV_VULKAN_1_1_SPIRV_1_4),
spvTargetEnvDescription(SPV_ENV_WEBGPU_0));
continue_processing = false;
return_code = 0;
} else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
print_usage(argv[0]);
continue_processing = false;
return_code = 0;
} else if (0 == strcmp(cur_arg, "--target-env")) {
if (argi + 1 < argc) {
const auto env_str = argv[++argi];
if (!spvParseTargetEnv(env_str, &target_env)) {
fprintf(stderr, "error: Unrecognized target env: %s\n", env_str);
continue_processing = false;
return_code = 1;
}
} else {
fprintf(stderr, "error: Missing argument to --target-env\n");
continue_processing = false;
return_code = 1;
}
} else if (0 == strcmp(cur_arg, "--before-hlsl-legalization")) {
options.SetBeforeHlslLegalization(true);
} else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) {
options.SetRelaxLogicalPointer(true);
} else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
options.SetRelaxBlockLayout(true);
} else if (0 == strcmp(cur_arg, "--uniform-buffer-standard-layout")) {
options.SetUniformBufferStandardLayout(true);
} else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
options.SetScalarBlockLayout(true);
} else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
options.SetSkipBlockLayout(true);
} else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
options.SetRelaxStructStore(true);
} else if (0 == cur_arg[1]) {
// Setting a filename of "-" to indicate stdin.
if (!inFile) {
inFile = cur_arg;
} else {
fprintf(stderr, "error: More than one input file specified\n");
continue_processing = false;
return_code = 1;
}
} else {
print_usage(argv[0]);
continue_processing = false;
return_code = 1;
}
} else {
if (!inFile) {
inFile = cur_arg;
} else {
fprintf(stderr, "error: More than one input file specified\n");
continue_processing = false;
return_code = 1;
}
}
}
// Exit if command line parsing was not successful.
if (!continue_processing) {
return return_code;
}
std::vector<uint32_t> contents;
if (!ReadFile<uint32_t>(inFile, "rb", &contents)) return 1;
spvtools::SpirvTools tools(target_env);
tools.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
bool succeed = tools.Validate(contents.data(), contents.size(), options);
return !succeed;
}

View File

@@ -108,7 +108,6 @@ project "spirv-opt"
path.join(SPIRV_TOOLS, "source/val/validate_composites.cpp"),
path.join(SPIRV_TOOLS, "source/val/validate_constants.cpp"),
path.join(SPIRV_TOOLS, "source/val/validate_conversion.cpp"),
path.join(SPIRV_TOOLS, "source/val/validate_datarules.cpp"),
path.join(SPIRV_TOOLS, "source/val/validate_debug.cpp"),
path.join(SPIRV_TOOLS, "source/val/validate_decorations.cpp"),
path.join(SPIRV_TOOLS, "source/val/validate_derivatives.cpp"),