Merge remote-tracking branch 'tint/main' into HEAD Integrates Tint repo into Dawn KIs: - Building docs for Tint is turned off, because it fails due to lack of annotations in Dawn source files. - Dawn CQ needs to be updated to run Tint specific tests - Significant post-merge cleanup needed R=bclayton,cwallez BUG=dawn:1339 Change-Id: I6c9714a0030934edd6c51f3cac4684dcd59d1ea3
diff --git a/.clang-format b/.clang-format index 4d9e9c84..ff58eea 100644 --- a/.clang-format +++ b/.clang-format
@@ -1,3 +1,4 @@ +# http://clang.llvm.org/docs/ClangFormatStyleOptions.html BasedOnStyle: Chromium Standard: Cpp11
diff --git a/.gitignore b/.gitignore index a028df0..42d8414 100644 --- a/.gitignore +++ b/.gitignore
@@ -9,12 +9,15 @@ /testing /third_party/abseil-cpp/ /third_party/angle +/third_party/benchmark +/third_party/binutils /third_party/catapult /third_party/clang-format +/third_party/cpplint /third_party/glfw /third_party/googletest /third_party/gpuweb -/third_party/webgpu-cts +/third_party/gpuweb-cts /third_party/jinja2 /third_party/jsoncpp /third_party/llvm-build @@ -22,10 +25,11 @@ /third_party/node /third_party/node-addon-api /third_party/node-api-headers +/third_party/protobuf /third_party/swiftshader -/third_party/tint /third_party/vulkan-deps /third_party/vulkan_memory_allocator +/third_party/webgpu-cts /third_party/zlib /tools/clang /tools/cmake @@ -33,7 +37,7 @@ /tools/memory /out -# Modified from https://www.gitignore.io/api/vim,macos,linux,emacs,windows,sublimetext,visualstudio,visualstudiocode +# Modified from https://www.gitignore.io/api/vim,macos,linux,emacs,windows,sublimetext,visualstudio,visualstudiocode,intellij ### Emacs ### *~ @@ -101,8 +105,19 @@ Desktop.ini $RECYCLE.BIN/ +### Intellij ### +.idea + ### Dawn node tools binaries src/dawn/node/tools/bin/ ### Cached node transpiled tools /.node_transpile_work_dir + +# Misc inherited from Tint +/test.wgsl +coverage.summary +default.profraw +lcov.info +/cmake-build-*/ +/testing
diff --git a/AUTHORS b/AUTHORS index 32a6c3c..bded374 100644 --- a/AUTHORS +++ b/AUTHORS
@@ -1,6 +1,7 @@ -# This is the list of Dawn authors for copyright purposes. +# This is the list of Dawn & Tint authors for copyright purposes. # # This does not necessarily list everyone who has contributed code, since in # some cases, their employer may be the copyright holder. To see the full list # of contributors, see the revision history in source control. -Google Inc. +Google LLC +Vasyl Teliman
diff --git a/AUTHORS.dawn b/AUTHORS.dawn new file mode 100644 index 0000000..32a6c3c --- /dev/null +++ b/AUTHORS.dawn
@@ -0,0 +1,6 @@ +# This is the list of Dawn authors for copyright purposes. +# +# This does not necessarily list everyone who has contributed code, since in +# some cases, their employer may be the copyright holder. To see the full list +# of contributors, see the revision history in source control. +Google Inc.
diff --git a/AUTHORS.tint b/AUTHORS.tint new file mode 100644 index 0000000..a66d09e --- /dev/null +++ b/AUTHORS.tint
@@ -0,0 +1,8 @@ +# This is the list of the Tint authors for copyright purposes. +# +# This does not necessarily list everyone who has contributed code, since in +# some cases, their employer may be the copyright holder. To see the full list +# of contributors, see the revision history in source control. + +Google LLC +Vasyl Teliman
diff --git a/BUILD.gn b/BUILD.gn index 99df240..c33776d 100644 --- a/BUILD.gn +++ b/BUILD.gn
@@ -21,9 +21,15 @@ "src/dawn/native:webgpu_dawn", "src/dawn/tests", "src/fuzzers/dawn:dawn_fuzzers", + "src/tint/fuzzers", + "src/tint:libtint", + "test/tint:tint_unittests", ] if (dawn_standalone) { - deps += [ "samples/dawn:samples" ] + deps += [ + "samples/dawn:samples", + "src/tint/cmd:tint", + ] } }
diff --git a/CMakeLists.txt b/CMakeLists.txt index bdcfdf1..f125476 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -1,4 +1,4 @@ -# Copyright 2020 The Dawn Authors +# Copyright 2022 The Dawn & Tint Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.10.2) # When upgrading to CMake 3.11 we can remove DAWN_DUMMY_FILE because source-less add_library # becomes available. @@ -26,13 +26,18 @@ DESCRIPTION "Dawn, a WebGPU implementation" LANGUAGES C CXX ) +enable_testing() set_property(GLOBAL PROPERTY USE_FOLDERS ON) -if(NOT CMAKE_BUILD_TYPE) - message(WARNING "CMAKE_BUILD_TYPE not set, forcing it to Debug") - set(CMAKE_BUILD_TYPE "Debug" CACHE STRING - "Build type (Debug, Release, RelWithDebInfo, MinSizeRel)" FORCE) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_DEBUG_POSTFIX "") + +if ("${CMAKE_BUILD_TYPE}" STREQUAL "") + message(STATUS "No build type selected, default to Debug") + set(CMAKE_BUILD_TYPE "Debug") endif() set(DAWN_BUILD_GEN_DIR "${Dawn_BINARY_DIR}/gen") @@ -99,7 +104,7 @@ endif() # GLFW is not supported in UWP -if((WIN32 AND NOT WINDOWS_STORE) OR UNIX AND NOT ANDROID) +if ((WIN32 AND NOT WINDOWS_STORE) OR UNIX AND NOT ANDROID) set(DAWN_SUPPORTS_GLFW_FOR_WINDOWING ON) endif() @@ -132,7 +137,7 @@ set_if_not_defined(DAWN_JINJA2_DIR "${DAWN_THIRD_PARTY_DIR}/jinja2" "Directory in which to find Jinja2") set_if_not_defined(DAWN_SPIRV_HEADERS_DIR "${DAWN_THIRD_PARTY_DIR}/vulkan-deps/spirv-headers/src" "Directory in which to find SPIRV-Headers") set_if_not_defined(DAWN_SPIRV_TOOLS_DIR "${DAWN_THIRD_PARTY_DIR}/vulkan-deps/spirv-tools/src" "Directory in which to find SPIRV-Tools") -set_if_not_defined(DAWN_TINT_DIR "${DAWN_THIRD_PARTY_DIR}/tint" "Directory in which to find Tint") +set_if_not_defined(DAWN_TINT_DIR "${Dawn_SOURCE_DIR}" "Directory in which to find Tint") set_if_not_defined(DAWN_VULKAN_HEADERS_DIR "${DAWN_THIRD_PARTY_DIR}/vulkan-deps/vulkan-headers/src" "Directory in which to find Vulkan-Headers") # Dependencies for DAWN_BUILD_NODE_BINDINGS @@ -204,10 +209,354 @@ set(CMAKE_CXX_STANDARD "17") ################################################################################ +# Tint +################################################################################ + +# TINT_IS_SUBPROJECT is 1 if added via add_subdirectory() from another project. +get_directory_property(TINT_IS_SUBPROJECT PARENT_DIRECTORY) +if(TINT_IS_SUBPROJECT) + set(TINT_IS_SUBPROJECT 1) + + # If tint is used as a subproject, default to disabling the building of + # documentation and tests. These are unlikely to be desirable, but can be + # enabled. + set(TINT_BUILD_DOCS_DEFAULT OFF) + set(TINT_BUILD_TESTS_DEFAULT OFF) +else() + set(TINT_BUILD_DOCS_DEFAULT ON) + set(TINT_BUILD_TESTS_DEFAULT ON) +endif() + +# Forcing building docs off right now, since currently this will try to build docs for both Tint & Dawn, and Dawn isn't annotated yet. +set(TINT_BUILD_DOCS_DEFAULT OFF) + +option_if_not_defined(TINT_BUILD_SAMPLES "Build samples" ON) +option_if_not_defined(TINT_BUILD_DOCS "Build documentation" ${TINT_BUILD_DOCS_DEFAULT}) +option_if_not_defined(TINT_DOCS_WARN_AS_ERROR "When building documentation, treat warnings as errors" OFF) +option_if_not_defined(TINT_BUILD_SPV_READER "Build the SPIR-V input reader" ON) +option_if_not_defined(TINT_BUILD_WGSL_READER "Build the WGSL input reader" ON) +option_if_not_defined(TINT_BUILD_GLSL_WRITER "Build the GLSL output writer" ON) +option_if_not_defined(TINT_BUILD_HLSL_WRITER "Build the HLSL output writer" ON) +option_if_not_defined(TINT_BUILD_MSL_WRITER "Build the MSL output writer" ON) +option_if_not_defined(TINT_BUILD_SPV_WRITER "Build the SPIR-V output writer" ON) +option_if_not_defined(TINT_BUILD_WGSL_WRITER "Build the WGSL output writer" ON) +option_if_not_defined(TINT_BUILD_FUZZERS "Build fuzzers" OFF) +option_if_not_defined(TINT_BUILD_SPIRV_TOOLS_FUZZER "Build SPIRV-Tools fuzzer" OFF) +option_if_not_defined(TINT_BUILD_AST_FUZZER "Build AST fuzzer" OFF) +option_if_not_defined(TINT_BUILD_REGEX_FUZZER "Build regex fuzzer" OFF) +option_if_not_defined(TINT_BUILD_BENCHMARKS "Build benchmarks" OFF) +option_if_not_defined(TINT_BUILD_TESTS "Build tests" ${TINT_BUILD_TESTS_DEFAULT}) +option_if_not_defined(TINT_BUILD_AS_OTHER_OS "Override OS detection to force building of *_other.cc files" OFF) +option_if_not_defined(TINT_BUILD_REMOTE_COMPILE "Build the remote-compile tool for validating shaders on a remote machine" OFF) + +set(TINT_LIB_FUZZING_ENGINE_LINK_OPTIONS "" CACHE STRING "Used by OSS-Fuzz to control, via link options, which fuzzing engine should be used") + +option_if_not_defined(TINT_ENABLE_MSAN "Enable memory sanitizer" OFF) +option_if_not_defined(TINT_ENABLE_ASAN "Enable address sanitizer" OFF) +option_if_not_defined(TINT_ENABLE_UBSAN "Enable undefined behaviour sanitizer" OFF) + +option_if_not_defined(TINT_ENABLE_BREAK_IN_DEBUGGER "Enable tint::debugger::Break()" OFF) + +option_if_not_defined(TINT_EMIT_COVERAGE "Emit code coverage information" OFF) + +option_if_not_defined(TINT_CHECK_CHROMIUM_STYLE "Check for [chromium-style] issues during build" OFF) + +option_if_not_defined(TINT_SYMBOL_STORE_DEBUG_NAME "Enable storing of name in tint::ast::Symbol to help debugging the AST" OFF) + +message(STATUS "Tint build samples: ${TINT_BUILD_SAMPLES}") +message(STATUS "Tint build docs: ${TINT_BUILD_DOCS}") +message(STATUS "Tint build docs with warn as error: ${TINT_DOCS_WARN_AS_ERROR}") +message(STATUS "Tint build SPIR-V reader: ${TINT_BUILD_SPV_READER}") +message(STATUS "Tint build WGSL reader: ${TINT_BUILD_WGSL_READER}") +message(STATUS "Tint build GLSL writer: ${TINT_BUILD_GLSL_WRITER}") +message(STATUS "Tint build HLSL writer: ${TINT_BUILD_HLSL_WRITER}") +message(STATUS "Tint build MSL writer: ${TINT_BUILD_MSL_WRITER}") +message(STATUS "Tint build SPIR-V writer: ${TINT_BUILD_SPV_WRITER}") +message(STATUS "Tint build WGSL writer: ${TINT_BUILD_WGSL_WRITER}") +message(STATUS "Tint build fuzzers: ${TINT_BUILD_FUZZERS}") +message(STATUS "Tint build SPIRV-Tools fuzzer: ${TINT_BUILD_SPIRV_TOOLS_FUZZER}") +message(STATUS "Tint build AST fuzzer: ${TINT_BUILD_AST_FUZZER}") +message(STATUS "Tint build regex fuzzer: ${TINT_BUILD_REGEX_FUZZER}") +message(STATUS "Tint build benchmarks: ${TINT_BUILD_BENCHMARKS}") +message(STATUS "Tint build tests: ${TINT_BUILD_TESTS}") +message(STATUS "Tint build with ASAN: ${TINT_ENABLE_ASAN}") +message(STATUS "Tint build with MSAN: ${TINT_ENABLE_MSAN}") +message(STATUS "Tint build with UBSAN: ${TINT_ENABLE_UBSAN}") +message(STATUS "Tint build checking [chromium-style]: ${TINT_CHECK_CHROMIUM_STYLE}") +message(STATUS "Tint build remote-compile tool: ${TINT_BUILD_REMOTE_COMPILE}") + +if (NOT ${TINT_LIB_FUZZING_ENGINE_LINK_OPTIONS} STREQUAL "") + message(STATUS "Using provided LIB_FUZZING_ENGINE options: ${TINT_LIB_FUZZING_ENGINE_LINK_OPTIONS}") +endif() + +message(STATUS "Using python3") +find_package(PythonInterp 3 REQUIRED) + +if (${TINT_BUILD_SPIRV_TOOLS_FUZZER}) + message(STATUS "TINT_BUILD_SPIRV_TOOLS_FUZZER is ON - setting + TINT_BUILD_FUZZERS + TINT_BUILD_SPV_READER + TINT_BUILD_SPV_WRITER + TINT_BUILD_WGSL_READER + TINT_BUILD_WGSL_WRITER + TINT_BUILD_GLSL_WRITER + TINT_BUILD_HLSL_WRITER + TINT_BUILD_MSL_WRITER to ON") + set(TINT_BUILD_FUZZERS ON CACHE BOOL "Build tint fuzzers" FORCE) + set(TINT_BUILD_SPV_READER ON CACHE BOOL "Build SPIR-V reader" FORCE) + set(TINT_BUILD_SPV_WRITER ON CACHE BOOL "Build SPIR-V writer" FORCE) + set(TINT_BUILD_WGSL_READER ON CACHE BOOL "Build WGSL reader" FORCE) + set(TINT_BUILD_WGSL_WRITER ON CACHE BOOL "Build WGSL writer" FORCE) + set(TINT_BUILD_GLSL_WRITER ON CACHE BOOL "Build HLSL writer" FORCE) + set(TINT_BUILD_HLSL_WRITER ON CACHE BOOL "Build HLSL writer" FORCE) + set(TINT_BUILD_MSL_WRITER ON CACHE BOOL "Build MSL writer" FORCE) +endif() + +if (${TINT_BUILD_AST_FUZZER}) + message(STATUS "TINT_BUILD_AST_FUZZER is ON - setting + TINT_BUILD_FUZZERS + TINT_BUILD_WGSL_READER + TINT_BUILD_WGSL_WRITER + TINT_BUILD_SPV_WRITER + TINT_BUILD_MSL_WRITER + TINT_BUILD_GLSL_WRITER + TINT_BUILD_HLSL_WRITER to ON") + set(TINT_BUILD_FUZZERS ON CACHE BOOL "Build tint fuzzers" FORCE) + set(TINT_BUILD_WGSL_READER ON CACHE BOOL "Build WGSL reader" FORCE) + set(TINT_BUILD_WGSL_WRITER ON CACHE BOOL "Build WGSL writer" FORCE) + set(TINT_BUILD_SPV_WRITER ON CACHE BOOL "Build SPIR-V writer" FORCE) + set(TINT_BUILD_MSL_WRITER ON CACHE BOOL "Build MSL writer" FORCE) + set(TINT_BUILD_GLSL_WRITER ON CACHE BOOL "Build GLSL writer" FORCE) + set(TINT_BUILD_HLSL_WRITER ON CACHE BOOL "Build HLSL writer" FORCE) +endif() + +if (${TINT_BUILD_REGEX_FUZZER}) + message(STATUS "TINT_BUILD_REGEX_FUZZER is ON - setting + TINT_BUILD_FUZZERS + TINT_BUILD_WGSL_READER + TINT_BUILD_WGSL_WRITER + TINT_BUILD_SPV_WRITER + TINT_BUILD_MSL_WRITER + TINT_BUILD_GLSL_WRITER + TINT_BUILD_HLSL_WRITER to ON") + set(TINT_BUILD_FUZZERS ON CACHE BOOL "Build tint fuzzers" FORCE) + set(TINT_BUILD_WGSL_READER ON CACHE BOOL "Build WGSL reader" FORCE) + set(TINT_BUILD_WGSL_WRITER ON CACHE BOOL "Build WGSL writer" FORCE) + set(TINT_BUILD_SPV_WRITER ON CACHE BOOL "Build SPIR-V writer" FORCE) + set(TINT_BUILD_MSL_WRITER ON CACHE BOOL "Build MSL writer" FORCE) + set(TINT_BUILD_GLSL_WRITER ON CACHE BOOL "Build GLSL writer" FORCE) + set(TINT_BUILD_HLSL_WRITER ON CACHE BOOL "Build HLSL writer" FORCE) +endif() + +set(TINT_ROOT_SOURCE_DIR ${PROJECT_SOURCE_DIR}) + +# CMake < 3.15 sets /W3 in CMAKE_CXX_FLAGS. Remove it if it's there. +# See https://gitlab.kitware.com/cmake/cmake/-/issues/18317 +if (MSVC) + if (CMAKE_CXX_FLAGS MATCHES "/W3") + string(REPLACE "/W3" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + endif() +endif() + +if (${TINT_CHECK_CHROMIUM_STYLE}) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -add-plugin -Xclang find-bad-constructs") +endif() + +if (${TINT_BUILD_SPV_READER}) + include_directories("${DAWN_THIRD_PARTY_DIR}/vulkan-deps/spirv-tools/include") +endif() + +if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") AND (CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC")) + set(COMPILER_IS_CLANG_CL TRUE) +endif() + +if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR + (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") OR + ((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") AND + (NOT COMPILER_IS_CLANG_CL))) + set(COMPILER_IS_LIKE_GNU TRUE) +endif() + +# Enable msbuild multiprocessor builds +if (MSVC AND NOT COMPILER_IS_CLANG_CL) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") +endif() + +set(TINT_OS_CC_SUFFIX "other") +if (NOT TINT_BUILD_AS_OTHER_OS) + if(UNIX OR APPLE) + set(TINT_OS_CC_SUFFIX "posix") + set(TINT_OS_CC_SUFFIX "posix") + elseif(WIN32) + set(TINT_OS_CC_SUFFIX "windows") + set(TINT_OS_CC_SUFFIX "windows") + endif() +endif() + +if(${TINT_BUILD_DOCS}) + find_package(Doxygen) + if(DOXYGEN_FOUND) + set(DOXYGEN_WARN_AS_ERROR NO) + if(TINT_DOCS_WARN_AS_ERROR) + set(DOXYGEN_WARN_AS_ERROR YES) + endif() + + set(DOXYGEN_WARN_FORMAT "$file:$line: $text") + if (MSVC) + set(DOXYGEN_WARN_FORMAT "$file($line): $text") + endif() + + add_custom_target(tint-docs ALL + COMMAND ${CMAKE_COMMAND} + -E env + "DOXYGEN_OUTPUT_DIRECTORY=${CMAKE_BINARY_DIR}/docs" + "DOXYGEN_WARN_AS_ERROR=${DOXYGEN_WARN_AS_ERROR}" + "DOXYGEN_WARN_FORMAT=${DOXYGEN_WARN_FORMAT}" + ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Generating API documentation" + VERBATIM) + else() + message("Doxygen not found. Skipping documentation") + endif(DOXYGEN_FOUND) +endif() + +function(tint_core_compile_options TARGET) + target_include_directories(${TARGET} PUBLIC "${TINT_ROOT_SOURCE_DIR}") + target_include_directories(${TARGET} PUBLIC "${TINT_ROOT_SOURCE_DIR}/include") + + if (${TINT_BUILD_SPV_READER} OR ${TINT_BUILD_SPV_WRITER}) + target_include_directories(${TARGET} PUBLIC + "${DAWN_THIRD_PARTY_DIR}/spirv-headers/include") + endif() + + target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_SPV_READER=$<BOOL:${TINT_BUILD_SPV_READER}>) + target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_WGSL_READER=$<BOOL:${TINT_BUILD_WGSL_READER}>) + target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_GLSL_WRITER=$<BOOL:${TINT_BUILD_GLSL_WRITER}>) + target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_HLSL_WRITER=$<BOOL:${TINT_BUILD_HLSL_WRITER}>) + target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_MSL_WRITER=$<BOOL:${TINT_BUILD_MSL_WRITER}>) + target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_SPV_WRITER=$<BOOL:${TINT_BUILD_SPV_WRITER}>) + target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_WGSL_WRITER=$<BOOL:${TINT_BUILD_WGSL_WRITER}>) + + if (COMPILER_IS_LIKE_GNU) + target_compile_options(${TARGET} PRIVATE + -std=c++17 + -fno-exceptions + -fno-rtti + ) + + if (${TINT_ENABLE_MSAN}) + target_compile_options(${TARGET} PRIVATE -fsanitize=memory) + target_link_options(${TARGET} PRIVATE -fsanitize=memory) + elseif (${TINT_ENABLE_ASAN}) + target_compile_options(${TARGET} PRIVATE -fsanitize=address) + target_link_options(${TARGET} PRIVATE -fsanitize=address) + elseif (${TINT_ENABLE_UBSAN}) + target_compile_options(${TARGET} PRIVATE -fsanitize=undefined) + target_link_options(${TARGET} PRIVATE -fsanitize=undefined) + endif() + endif(COMPILER_IS_LIKE_GNU) + + if (TINT_EMIT_COVERAGE) + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + target_compile_options(${TARGET} PRIVATE "--coverage") + target_link_options(${TARGET} PRIVATE "gcov") + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_compile_options(${TARGET} PRIVATE "-fprofile-instr-generate" "-fcoverage-mapping") + target_link_options(${TARGET} PRIVATE "-fprofile-instr-generate" "-fcoverage-mapping") + else() + message(FATAL_ERROR "Coverage generation not supported for the ${CMAKE_CXX_COMPILER_ID} toolchain") + endif() + endif(TINT_EMIT_COVERAGE) +endfunction() + +function(tint_default_compile_options TARGET) + tint_core_compile_options(${TARGET}) + + set(COMMON_GNU_OPTIONS + -Wall + -Werror + -Wextra + -Wno-documentation-unknown-command + -Wno-padded + -Wno-switch-enum + -Wno-unknown-pragmas + ) + + set(COMMON_CLANG_OPTIONS + -Wno-c++98-compat + -Wno-c++98-compat-pedantic + -Wno-format-pedantic + -Wno-return-std-move-in-c++11 + -Wno-unknown-warning-option + -Wno-undefined-var-template + -Wno-used-but-marked-unused + -Weverything + ) + + if (COMPILER_IS_LIKE_GNU) + target_compile_options(${TARGET} PRIVATE + -pedantic-errors + ${COMMON_GNU_OPTIONS} + ) + + if (("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") OR + ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")) + target_compile_options(${TARGET} PRIVATE + ${COMMON_CLANG_OPTIONS} + ) + endif() + endif(COMPILER_IS_LIKE_GNU) + + if (MSVC) + # Specify /EHs for exception handling. + target_compile_options(${TARGET} PRIVATE + /bigobj + /EHsc + /W4 + /WX + /wd4068 + /wd4127 + /wd4244 + /wd4267 + /wd4324 + /wd4458 + /wd4514 + /wd4571 + /wd4625 + /wd4626 + /wd4710 + /wd4774 + /wd4820 + /wd5026 + /wd5027 + ) + + # When building with clang-cl on Windows, try to match our clang build + # options as much as possible. + if (COMPILER_IS_CLANG_CL) + target_compile_options(${TARGET} PRIVATE + ${COMMON_GNU_OPTIONS} + ${COMMON_CLANG_OPTIONS} + # Disable warnings that are usually disabled in downstream deps for + # gcc/clang, but aren't for clang-cl. + -Wno-global-constructors + -Wno-zero-as-null-pointer-constant + -Wno-shorten-64-to-32 + -Wno-shadow-field-in-constructor + -Wno-reserved-id-macro + -Wno-language-extension-token + ) + endif() + endif() +endfunction() + +################################################################################ # Run on all subdirectories ################################################################################ add_subdirectory(third_party) +add_subdirectory(src/tint) add_subdirectory(generator) add_subdirectory(src/dawn) @@ -220,3 +569,42 @@ #add_subdirectory(src/utils) add_subdirectory(samples/dawn) endif() + +if (TINT_BUILD_SAMPLES) + add_subdirectory(src/tint/cmd) +endif() + +if (TINT_BUILD_FUZZERS) + add_subdirectory(src/tint/fuzzers) +endif() + +add_custom_target(tint-lint + COMMAND ./tools/lint + WORKING_DIRECTORY ${TINT_ROOT_SOURCE_DIR} + COMMENT "Running linter" + VERBATIM) + +add_custom_target(tint-format + COMMAND ./tools/format + WORKING_DIRECTORY ${TINT_ROOT_SOURCE_DIR} + COMMENT "Running formatter" + VERBATIM) + + +if (TINT_EMIT_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # Generates a lcov.info file at the project root. + # This can be used by tools such as VSCode's Coverage Gutters extension to + # visualize code coverage in the editor. + get_filename_component(CLANG_BIN_DIR ${CMAKE_C_COMPILER} DIRECTORY) + set(PATH_WITH_CLANG "${CLANG_BIN_DIR}:$ENV{PATH}") + add_custom_target(tint-generate-coverage + COMMAND ${CMAKE_COMMAND} -E env PATH=${PATH_WITH_CLANG} ./tools/tint-generate-coverage $<TARGET_FILE:tint_unittests> + DEPENDS tint_unittests + WORKING_DIRECTORY ${TINT_ROOT_SOURCE_DIR} + COMMENT "Generating tint coverage data" + VERBATIM) +endif() + +if (TINT_BUILD_REMOTE_COMPILE) + add_subdirectory(tools/src/cmd/remote-compile) +endif()
diff --git a/CMakeSettings.json b/CMakeSettings.json new file mode 100644 index 0000000..ee3ee56 --- /dev/null +++ b/CMakeSettings.json
@@ -0,0 +1,100 @@ +{ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "variables": [] + }, + { + "name": "x64-Release", + "generator": "Ninja", + "configurationType": "RelWithDebInfo", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x64_x64" ], + "variables": [] + }, + { + "name": "x86-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x86" ], + "variables": [] + }, + { + "name": "x86-Release", + "generator": "Ninja", + "configurationType": "RelWithDebInfo", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x86" ], + "variables": [] + }, + { + "name": "x64-Clang-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "clang_cl_x64_x64" ], + "variables": [] + }, + { + "name": "x64-Clang-Release", + "generator": "Ninja", + "configurationType": "RelWithDebInfo", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "clang_cl_x64_x64" ], + "variables": [] + }, + { + "name": "x86-Clang-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "clang_cl_x86" ], + "variables": [] + }, + { + "name": "x86-Clang-Release", + "generator": "Ninja", + "configurationType": "RelWithDebInfo", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "clang_cl_x86" ], + "variables": [] + } + ] +} \ No newline at end of file
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..12921d9 --- /dev/null +++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,93 @@ +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of +experience, education, socio-economic status, nationality, personal appearance, +race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, or to ban temporarily or permanently any +contributor for other behaviors that they deem inappropriate, threatening, +offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +This Code of Conduct also applies outside the project spaces when the Project +Steward has a reasonable belief that an individual's behavior may have a +negative impact on the project or its community. + +## Conflict Resolution + +We do not believe that all conflict is bad; healthy debate and disagreement +often yield positive results. However, it is never okay to be disrespectful or +to engage in behavior that violates the project’s code of conduct. + +If you see someone violating the code of conduct, you are encouraged to address +the behavior directly with those involved. Many issues can be resolved quickly +and easily, and this gives people more control over the outcome of their +dispute. If you are unable to resolve the matter for any reason, or if the +behavior is threatening or harassing, report it. We are dedicated to providing +an environment where participants feel welcome and safe. + +Reports should be directed to David Neto <dneto@google.com>, the +Project Steward(s) for Tint. It is the Project Steward’s duty to +receive and address reported violations of the code of conduct. They will then +work with a committee consisting of representatives from the Open Source +Programs Office and the Google Open Source Strategy team. If for any reason you +are uncomfortable reaching out the Project Steward, please email +opensource@google.com. + +We will investigate every complaint, but you may not receive a direct response. +We will use our discretion in determining when and how to follow up on reported +incidents, which may range from not taking action to permanent expulsion from +the project and project-sponsored spaces. We will notify the accused of the +report and provide them an opportunity to discuss it before any action is taken. +The identity of the reporter will be omitted from the details of the report +supplied to the accused. In potentially harmful situations, such as ongoing +harassment or threats to anyone's safety, we may take action without notice. + +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 1.4, +available at +https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..329011e --- /dev/null +++ b/CONTRIBUTING.md
@@ -0,0 +1,45 @@ +# How to Contribute + +We'd love to accept your patches and contributions to this project. There are +just a few small guidelines you need to follow. + +## Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement. You (or your employer) retain the copyright to your contribution; +this simply gives us permission to use and redistribute your contributions as +part of the project. Head over to <https://cla.developers.google.com/> to see +your current agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Code reviews + +All submissions, including submissions by project members, require review. We +use [Dawn's Gerrit](https://dawn-review.googlesource.com/) for this purpose. + +Submissions should follow the [Tint style guide](docs/tint/style_guide.md). + +## Pushing to Gerrit + +Each change requires a `Change-Id` field in the commit message, which is generated by the [Gerrit commit-msg hook](](https://gerrit-review.googlesource.com/Documentation/cmd-hook-commit-msg.html)). \ +In a bash terminal, with the current path set to your tint source tree, this can be obtained by running the following: + +```bash +f=`git rev-parse --git-dir`/hooks/commit-msg ; mkdir -p $(dirname $f) ; curl -Lo $f https://gerrit-review.googlesource.com/tools/hooks/commit-msg ; chmod +x $f +``` + +If you've already locally committed a change without the `Change-Id`, running `git commit --amend` will add the missing `Change-Id`. + +To create a Gerrit change for review, type: + +```bash +git push origin HEAD:refs/for/main +``` + +## Community Guidelines + +This project follows +[Google's Open Source Community Guidelines](https://opensource.google.com/conduct/).
diff --git a/CPPLINT.cfg b/CPPLINT.cfg new file mode 100644 index 0000000..36d9cb2 --- /dev/null +++ b/CPPLINT.cfg
@@ -0,0 +1 @@ +set noparent
diff --git a/DEPS b/DEPS index 79df40c..0c13f63 100644 --- a/DEPS +++ b/DEPS
@@ -42,7 +42,6 @@ 'url': '{chromium_git}/external/github.com/llvm/llvm-project/clang/tools/clang-format.git@99803d74e35962f63a775f29477882afd4d57d94', 'condition': 'dawn_standalone', }, - 'buildtools/linux64': { 'packages': [{ 'package': 'gn/gn/linux-amd64', @@ -116,11 +115,6 @@ 'condition': 'dawn_standalone', }, - # WGSL support - 'third_party/tint': { - 'url': '{dawn_git}/tint@a730eb738e9f00fb52e9ac38cebe978373602a1e', - }, - # GLFW for tests and samples 'third_party/glfw': { 'url': '{chromium_git}/external/github.com/glfw/glfw@94773111300fee0453844a4c9407af7e880b4df8', @@ -176,6 +170,10 @@ 'url': '{github_git}/gpuweb/gpuweb.git@881403b5fda2d9ac9ffc5daa24e34738205bf155', 'condition': 'dawn_node', }, + 'third_party/gpuweb-cts': { + 'url': '{chromium_git}/external/github.com/gpuweb/cts@b0291fd966b55a5efc496772555b94842bde1085', + 'condition': 'dawn_standalone', + }, 'tools/golang': { 'condition': 'dawn_node', @@ -194,6 +192,16 @@ }], 'dep_type': 'cipd', }, + + # Misc dependencies inherited from Tint + 'third_party/benchmark': { + 'url': '{chromium_git}/external/github.com/google/benchmark.git@e991355c02b93fe17713efe04cbc2e278e00fdbd', + 'condition': 'dawn_standalone', + }, + 'third_party/protobuf': { + 'url': '{chromium_git}/external/github.com/protocolbuffers/protobuf.git@fde7cf7358ec7cd69e8db9be4f1fa6a5c431386a', + 'condition': 'dawn_standalone', + }, } hooks = [ @@ -253,33 +261,22 @@ 'condition': 'dawn_standalone and host_os == "win"', 'action': [ 'download_from_google_storage', '--no_resume', + '--platform=win32', '--no_auth', '--bucket', 'chromium-clang-format', '-s', 'buildtools/win/clang-format.exe.sha1', ], }, { - 'name': 'clang_format_mac_x64', + 'name': 'clang_format_mac', 'pattern': '.', - 'condition': 'dawn_standalone and host_os == "mac" and host_cpu == "x64"', + 'condition': 'dawn_standalone and host_os == "mac"', 'action': [ 'download_from_google_storage', '--no_resume', + '--platform=darwin', '--no_auth', '--bucket', 'chromium-clang-format', - '-s', 'buildtools/mac/clang-format.x64.sha1', - '-o', 'buildtools/mac/clang-format', - ], - }, - { - 'name': 'clang_format_mac_arm64', - 'pattern': '.', - 'condition': 'dawn_standalone and host_os == "mac" and host_cpu == "arm64"', - 'action': [ 'download_from_google_storage', - '--no_resume', - '--no_auth', - '--bucket', 'chromium-clang-format', - '-s', 'buildtools/mac/clang-format.arm64.sha1', - '-o', 'buildtools/mac/clang-format', + '-s', 'buildtools/mac/clang-format.sha1', ], }, { @@ -288,11 +285,59 @@ 'condition': 'dawn_standalone and host_os == "linux"', 'action': [ 'download_from_google_storage', '--no_resume', + '--platform=linux*', '--no_auth', '--bucket', 'chromium-clang-format', '-s', 'buildtools/linux64/clang-format.sha1', ], }, + # Pull the compilers and system libraries for hermetic builds + { + 'name': 'sysroot_x86', + 'pattern': '.', + 'condition': 'checkout_linux and ((checkout_x86 or checkout_x64))', + 'action': ['python3', 'build/linux/sysroot_scripts/install-sysroot.py', + '--arch=x86'], + }, + { + 'name': 'sysroot_x64', + 'pattern': '.', + 'condition': 'checkout_linux and (checkout_x64)', + 'action': ['python3', 'build/linux/sysroot_scripts/install-sysroot.py', + '--arch=x64'], + }, + { + # Update the Mac toolchain if necessary. + 'name': 'mac_toolchain', + 'pattern': '.', + 'condition': 'checkout_mac', + 'action': ['python3', 'build/mac_toolchain.py'], + }, + { + # Update the Windows toolchain if necessary. Must run before 'clang' below. + 'name': 'win_toolchain', + 'pattern': '.', + 'condition': 'checkout_win', + 'action': ['python3', 'build/vs_toolchain.py', 'update', '--force'], + }, + { + # Note: On Win, this should run after win_toolchain, as it may use it. + 'name': 'clang', + 'pattern': '.', + 'action': ['python3', 'tools/clang/scripts/update.py'], + }, + { + # Pull rc binaries using checked-in hashes. + 'name': 'rc_win', + 'pattern': '.', + 'condition': 'checkout_win and (host_os == "win")', + 'action': [ 'download_from_google_storage', + '--no_resume', + '--no_auth', + '--bucket', 'chromium-browser-clang/rc', + '-s', 'build/toolchain/win/rc/win/rc.exe.sha1', + ], + }, # Update build/util/LASTCHANGE. { 'name': 'lastchange',
diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..08eac60 --- /dev/null +++ b/Doxyfile
@@ -0,0 +1,2474 @@ +# Doxyfile 1.8.14 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "Tint" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = Tint + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = $(DOXYGEN_OUTPUT_DIRECTORY) + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 2 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 0. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 0 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = yes + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = yes + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = YES + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = YES + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = YES + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = YES + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if <section_label> ... \endif and \cond <section_label> +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = YES + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = $(DOXYGEN_WARN_AS_ERROR) + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = $(DOXYGEN_WARN_FORMAT) + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = CODE_OF_CONDUCT.md \ + src/tint/fuzzers/tint_spirv_tools_fuzzer \ + src \ + tools/src \ + src/tint/fuzzers/tint_spirv_tools_fuzzer \ + src/tint/fuzzers/tint_ast_fuzzer + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: https://www.gnu.org/software/libiconv/) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f \ + *.for \ + *.tcl \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = *_test.cc + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# <filter> <input-file> +# +# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = ./README.md + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via Javascript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have Javascript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +#HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: https://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://doc.qt.io/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://doc.qt.io/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://doc.qt.io/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/ + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use <access key> + S +# (what the <access key> is depends on the OS and browser, but it is typically +# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down +# key> to jump into the search results window, the results can be navigated +# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel +# the search. The filter options can be selected when the cursor is inside the +# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys> +# to select a filter and <Enter> or <escape> to activate or cancel the filter +# option. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a web server instead of a web client using Javascript. There +# are two flavors of web server based searching depending on the EXTERNAL_SEARCH +# setting. When disabled, doxygen will generate a PHP script for searching and +# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing +# and searching needs to be provided by external tools. See the section +# "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SERVER_BASED_SEARCH = NO + +# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain the +# search results. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: https://xapian.org/). +# +# See the section "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will return the search results when EXTERNAL_SEARCH is enabled. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: https://xapian.org/). See the section "External Indexing and +# Searching" for details. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. +# The default file is: searchdata.xml. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of +# to a relative location where the documentation can be found. The format is: +# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. +# The default value is: YES. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. +# +# Note that when enabling USE_PDFLATEX this option is only used for generating +# bitmaps for formulas in the HTML output, but not in the Makefile that is +# written to the output directory. +# The default file is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate +# index for LaTeX. +# The default file is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used by the +# printer. +# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x +# 14 inches) and executive (7.25 x 10.5 inches). +# The default value is: a4. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names +# that should be included in the LaTeX output. The package can be specified just +# by its name or with the correct syntax as to be used with the LaTeX +# \usepackage command. To get the times font for instance you can specify : +# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} +# To use the option intlimits with the amsmath package you can specify: +# EXTRA_PACKAGES=[intlimits]{amsmath} +# If left blank no extra packages will be included. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the +# generated LaTeX document. The header should contain everything until the first +# chapter. If it is left blank doxygen will generate a standard header. See +# section "Doxygen usage" for information on how to let doxygen write the +# default header to a separate file. +# +# Note: Only use a user-defined header if you know what you are doing! The +# following commands have a special meaning inside the header: $title, +# $datetime, $date, $doxygenversion, $projectname, $projectnumber, +# $projectbrief, $projectlogo. Doxygen will replace $title with the empty +# string, for the replacement values of the other commands the user is referred +# to HTML_HEADER. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the +# generated LaTeX document. The footer should contain everything after the last +# chapter. If it is left blank doxygen will generate a standard footer. See +# LATEX_HEADER for more information on how to generate a default footer and what +# special commands can be used inside the footer. +# +# Note: Only use a user-defined footer if you know what you are doing! +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_FOOTER = + +# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# LaTeX style sheets that are included after the standard style sheets created +# by doxygen. Using this option one can overrule certain style aspects. Doxygen +# will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_STYLESHEET = + +# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the LATEX_OUTPUT output +# directory. Note that the files will be copied as-is; there are no commands or +# markers available. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_FILES = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is +# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will +# contain links (just like the HTML output) instead of page references. This +# makes the output suitable for online browsing using a PDF viewer. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate +# the PDF file directly from the LaTeX files. Set this option to YES, to get a +# higher quality PDF documentation. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode +# command to the generated LaTeX files. This will instruct LaTeX to keep running +# if errors occur, instead of asking the user for help. This option is also used +# when generating formulas in HTML. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BATCHMODE = NO + +# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# index chapters (such as File Index, Compound Index, etc.) in the output. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HIDE_INDICES = NO + +# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source +# code with syntax highlighting in the LaTeX output. +# +# Note that which sources are shown also depends on other settings such as +# SOURCE_BROWSER. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. See +# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# The default value is: plain. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BIB_STYLE = plain + +# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_TIMESTAMP = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The +# RTF output is optimized for Word 97 and may not look too pretty with other RTF +# readers/editors. +# The default value is: NO. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: rtf. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will +# contain hyperlink fields. The RTF file will contain links (just like the HTML +# output) instead of page references. This makes the output suitable for online +# browsing using Word or some other Word compatible readers that support those +# fields. +# +# Note: WordPad (write) and others do not support links. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's config +# file, i.e. a series of assignments. You only have to provide replacements, +# missing definitions are set to their default value. +# +# See also section "Doxygen usage" for information on how to generate the +# default style sheet that doxygen normally uses. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an RTF document. Syntax is +# similar to doxygen's config file. A template extensions file can be generated +# using doxygen -e rtf extensionFile. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_EXTENSIONS_FILE = + +# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code +# with syntax highlighting in the RTF output. +# +# Note that which sources are shown also depends on other settings such as +# SOURCE_BROWSER. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for +# classes and files. +# The default value is: NO. + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. A directory man3 will be created inside the directory specified by +# MAN_OUTPUT. +# The default directory is: man. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to the generated +# man pages. In case the manual section does not start with a number, the number +# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is +# optional. +# The default value is: .3. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_EXTENSION = .3 + +# The MAN_SUBDIR tag determines the name of the directory created within +# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by +# MAN_EXTENSION with the initial . removed. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_SUBDIR = + +# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# will generate one additional man file for each entity documented in the real +# man page(s). These additional files only source the real man page, but without +# them the man command would be unable to find the correct page. +# The default value is: NO. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that +# captures the structure of the code including all documentation. +# The default value is: NO. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: xml. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_OUTPUT = xml + +# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program +# listings (including syntax highlighting and cross-referencing information) to +# the XML output. Note that enabling this will significantly increase the size +# of the XML output. +# The default value is: YES. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- + +# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files +# that can be used to generate PDF. +# The default value is: NO. + +GENERATE_DOCBOOK = NO + +# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in +# front of it. +# The default directory is: docbook. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_OUTPUT = docbook + +# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the +# program listings (including syntax highlighting and cross-referencing +# information) to the DOCBOOK output. Note that enabling this will significantly +# increase the size of the DOCBOOK output. +# The default value is: NO. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_PROGRAMLISTING = NO + +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an +# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# the structure of the code including all documentation. Note that this feature +# is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module +# file that captures the structure of the code including all documentation. +# +# Note that this feature is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary +# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI +# output from the Perl module output. +# The default value is: NO. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely +# formatted so it can be parsed by a human reader. This is useful if you want to +# understand what is going on. On the other hand, if this tag is set to NO, the +# size of the Perl module output will be much smaller and Perl will parse it +# just the same. +# The default value is: YES. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file are +# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful +# so different doxyrules.make files included by the same Makefile don't +# overwrite each other's variables. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all +# C-preprocessor directives found in the sources and include files. +# The default value is: YES. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names +# in the source code. If set to NO, only conditional compilation will be +# performed. Macro expansion can be done in a controlled way by setting +# EXPAND_ONLY_PREDEF to YES. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then +# the macro expansion is limited to the macros specified with the PREDEFINED and +# EXPAND_AS_DEFINED tags. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES, the include files in the +# INCLUDE_PATH will be searched if a #include is found. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by the +# preprocessor. +# This tag requires that the tag SEARCH_INCLUDES is set to YES. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will be +# used. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that are +# defined before the preprocessor is started (similar to the -D option of e.g. +# gcc). The argument of the tag is a list of macros of the form: name or +# name=definition (no spaces). If the definition and the "=" are omitted, "=1" +# is assumed. To prevent a macro definition from being undefined via #undef or +# recursively expanded use the := operator instead of the = operator. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +PREDEFINED = DOXYGEN + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this +# tag can be used to specify a list of macro names that should be expanded. The +# macro definition that is found in the sources will be used. Use the PREDEFINED +# tag if you want to use a different macro definition that overrules the +# definition found in the source code. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# remove all references to function-like macros that are alone on a line, have +# an all uppercase name, and do not end with a semicolon. Such function macros +# are typically used for boiler-plate code, and will confuse the parser if not +# removed. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tag files. For each tag +# file the location of the external documentation should be added. The format of +# a tag file without this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where loc1 and loc2 can be relative or absolute paths or URLs. See the +# section "Linking to external documentation" for more information about the use +# of tag files. +# Note: Each tag file must have a unique name (where the name does NOT include +# the path). If a tag file is not located in the directory in which doxygen is +# run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# tag file that is based on the input files it reads. See section "Linking to +# external documentation" for more information about the usage of tag files. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES, all external class will be listed in +# the class index. If set to NO, only the inherited external classes will be +# listed. +# The default value is: NO. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will be +# listed. +# The default value is: YES. + +EXTERNAL_GROUPS = YES + +# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in +# the related pages index. If set to NO, only the current project's pages will +# be listed. +# The default value is: YES. + +EXTERNAL_PAGES = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of 'which perl'). +# The default file (with absolute path) is: /usr/bin/perl. + +#PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram +# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to +# NO turns the diagrams off. Note that this option also works with HAVE_DOT +# disabled, but it is recommended to install and use dot, since it yields more +# powerful graphs. +# The default value is: YES. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see: +# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +#MSCGEN_PATH = + +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. + +DIA_PATH = + +# If set to YES the inheritance and collaboration graphs will hide inheritance +# and usage relations if the target is undocumented or is not a class. +# The default value is: YES. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz (see: +# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# Bell Labs. The other options in this section have no effect if this option is +# set to NO +# The default value is: NO. + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed +# to run in parallel. When set to 0 doxygen will base this on the number of +# processors available in the system. You can set it explicitly to a value +# larger than 0 to get control over the balance between CPU load and processing +# speed. +# Minimum value: 0, maximum value: 32, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NUM_THREADS = 0 + +# When you want a differently looking font in the dot files that doxygen +# generates you can specify the font name using DOT_FONTNAME. You need to make +# sure dot is able to find the font, which can be done by putting it in a +# standard location or by setting the DOTFONTPATH environment variable or by +# setting DOT_FONTPATH to the directory containing the font. +# The default value is: Helvetica. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of +# dot graphs. +# Minimum value: 4, maximum value: 24, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the default font as specified with +# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set +# the path where dot can find it using this tag. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTPATH = + +# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for +# each documented class showing the direct and indirect inheritance relations. +# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# graph for each documented class showing the direct and indirect implementation +# dependencies (inheritance, containment, and class references variables) of the +# class with other documented classes. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for +# groups, showing the direct groups dependencies. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside the +# class node. If there are many fields or methods and many nodes the graph may +# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the +# number of items for each type to make the size more manageable. Set this to 0 +# for no limit. Note that the threshold may be exceeded by 50% before the limit +# is enforced. So when you set the threshold to 10, up to 15 fields may appear, +# but if the number exceeds 15, the total amount of fields shown is limited to +# 10. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LIMIT_NUM_FIELDS = 10 + +# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and +# collaboration graphs will show the relations between templates and their +# instances. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +TEMPLATE_RELATIONS = NO + +# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to +# YES then doxygen will generate a graph for each documented file showing the +# direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDE_GRAPH = YES + +# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are +# set to YES then doxygen will generate a graph for each documented file showing +# the direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. Disabling a call graph can be +# accomplished by means of the command \hidecallgraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. Disabling a caller graph can be +# accomplished by means of the command \hidecallergraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# hierarchy of all classes instead of a textual one. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# dependencies a directory has on other directories in a graphical way. The +# dependency relations are determined by the #include relations between the +# files in the directories. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. For an explanation of the image formats see the section +# output formats in the documentation of the dot tool (Graphviz (see: +# http://www.graphviz.org/)). +# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order +# to make the SVG files visible in IE 9+ (other browsers do not have this +# requirement). +# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and +# png:gdiplus:gdiplus. +# The default value is: png. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# +# Note that this requires a modern browser other than Internet Explorer. Tested +# and working are Firefox, Chrome, Safari, and Opera. +# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make +# the SVG files visible. Older versions of IE do not have SVG support. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +INTERACTIVE_SVG = NO + +# The DOT_PATH tag can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the \dotfile +# command). +# This tag requires that the tag HAVE_DOT is set to YES. + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = + +# The DIAFILE_DIRS tag can be used to specify one or more directories that +# contain dia files that are included in the documentation (see the \diafile +# command). + +DIAFILE_DIRS = + +# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file. If left blank, it is assumed +# PlantUML is not used or called during a preprocessing step. Doxygen will +# generate a warning when it encounters a \startuml command in this case and +# will not generate output for the diagram. + +PLANTUML_JAR_PATH = + +# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for plantuml. + +PLANTUML_CFG_FILE = + +# When using plantuml, the specified paths are searched for files specified by +# the !include statement in a plantuml block. + +PLANTUML_INCLUDE_PATH = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes +# that will be shown in the graph. If the number of nodes in a graph becomes +# larger than this value, doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that doxygen if the number of direct +# children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that +# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# Minimum value: 0, maximum value: 10000, default value: 50. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs +# generated by dot. A depth value of 3 means that only nodes reachable from the +# root by following a path via at most 3 edges will be shown. Nodes that lay +# further from the root node will be omitted. Note that setting this option to 1 +# or 2 may greatly reduce the computation time needed for large code bases. Also +# note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. +# Minimum value: 0, maximum value: 1000, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not seem +# to support this out of the box. +# +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) support +# this, this feature is disabled by default. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# explaining the meaning of the various boxes and arrows in the dot generated +# graphs. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot +# files that are used to generate the various graphs. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_CLEANUP = YES
diff --git a/OWNERS b/OWNERS index 1856f30..a7cef5e 100644 --- a/OWNERS +++ b/OWNERS
@@ -9,3 +9,12 @@ per-file dawn.json=kainino@chromium.org per-file DEPS=* per-file README.md=file://docs/dawn/OWNERS + +# Tint specific OWNERS +amaiorano@google.com +bclayton@chromium.org +bclayton@google.com +cwallez@chromium.org +dneto@google.com +jrprice@google.com +rharrison@chromium.org
diff --git a/OWNERS.dawn b/OWNERS.dawn new file mode 100644 index 0000000..1856f30 --- /dev/null +++ b/OWNERS.dawn
@@ -0,0 +1,11 @@ +cwallez@chromium.org +enga@chromium.org +jiawei.shao@intel.com + +# Backup reviewers if needed. +bclayton@google.com +kainino@chromium.org + +per-file dawn.json=kainino@chromium.org +per-file DEPS=* +per-file README.md=file://docs/dawn/OWNERS
diff --git a/OWNERS.tint b/OWNERS.tint new file mode 100644 index 0000000..18239af --- /dev/null +++ b/OWNERS.tint
@@ -0,0 +1,7 @@ +amaiorano@google.com +bclayton@chromium.org +bclayton@google.com +cwallez@chromium.org +dneto@google.com +jrprice@google.com +rharrison@chromium.org
diff --git a/PRESUBMIT.py b/PRESUBMIT.py index 899e0e2..968a27c 100644 --- a/PRESUBMIT.py +++ b/PRESUBMIT.py
@@ -1,4 +1,4 @@ -# Copyright 2018 The Dawn Authors +# Copyright 2022 The Dawn & Tint Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,12 +12,97 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os -import platform -import subprocess +import re USE_PYTHON3 = True +NONINCLUSIVE_REGEXES = [ + r"(?i)black[-_]?list", + r"(?i)white[-_]?list", + r"(?i)gr[ea]y[-_]?list", + r"(?i)(first class citizen)", + r"(?i)black[-_]?hat", + r"(?i)white[-_]?hat", + r"(?i)gr[ea]y[-_]?hat", + r"(?i)master", + r"(?i)slave", + r"(?i)\bhim\b", + r"(?i)\bhis\b", + r"(?i)\bshe\b", + r"(?i)\bher\b", + r"(?i)\bguys\b", + r"(?i)\bhers\b", + r"(?i)\bman\b", + r"(?i)\bwoman\b", + r"(?i)\she\s", + r"(?i)\she$", + r"(?i)^he\s", + r"(?i)^he$", + r"(?i)\she['|\u2019]d\s", + r"(?i)\she['|\u2019]d$", + r"(?i)^he['|\u2019]d\s", + r"(?i)^he['|\u2019]d$", + r"(?i)\she['|\u2019]s\s", + r"(?i)\she['|\u2019]s$", + r"(?i)^he['|\u2019]s\s", + r"(?i)^he['|\u2019]s$", + r"(?i)\she['|\u2019]ll\s", + r"(?i)\she['|\u2019]ll$", + r"(?i)^he['|\u2019]ll\s", + r"(?i)^he['|\u2019]ll$", + r"(?i)grandfather", + r"(?i)\bmitm\b", + r"(?i)\bcrazy\b", + r"(?i)\binsane\b", + r"(?i)\bblind\sto\b", + r"(?i)\bflying\sblind\b", + r"(?i)\bblind\seye\b", + r"(?i)\bcripple\b", + r"(?i)\bcrippled\b", + r"(?i)\bdumb\b", + r"(?i)\bdummy\b", + r"(?i)\bparanoid\b", + r"(?i)\bsane\b", + r"(?i)\bsanity\b", + r"(?i)red[-_]?line", +] + +NONINCLUSIVE_REGEX_LIST = [] +for reg in NONINCLUSIVE_REGEXES: + NONINCLUSIVE_REGEX_LIST.append(re.compile(reg)) + + +def _CheckNonInclusiveLanguage(input_api, output_api, source_file_filter=None): + """Checks the files for non-inclusive language.""" + + matches = [] + for f in input_api.AffectedFiles(include_deletes=False, + file_filter=source_file_filter): + for line_num, line in f.ChangedContents(): + for reg in NONINCLUSIVE_REGEX_LIST: + match = reg.search(line) + if match: + matches.append( + "{} ({}): found non-inclusive language: {}".format( + f.LocalPath(), line_num, match.group(0))) + + if len(matches): + return [ + output_api.PresubmitPromptWarning('Non-inclusive language found:', + items=matches) + ] + + return [] + + +def _NonInclusiveFileFilter(file): + filter_list = [ + "PRESUBMIT.py", # Non-inclusive language check data + "docs/tint/spirv-input-output-variables.md", # External URL + "test/tint/samples/compute_boids.wgsl ", # External URL + ] + return file in filter_list + def _DoCommonChecks(input_api, output_api): results = [] @@ -27,6 +112,30 @@ input_api.canned_checks.CheckPatchFormatted(input_api, output_api, check_python=True)) + results.extend( + input_api.canned_checks.CheckChangeHasDescription( + input_api, output_api)) + results.extend( + input_api.canned_checks.CheckGNFormatted(input_api, output_api)) + results.extend( + input_api.canned_checks.CheckChangeHasNoCrAndHasOnlyOneEol( + input_api, output_api)) + results.extend( + input_api.canned_checks.CheckChangeHasNoTabs(input_api, output_api)) + results.extend( + input_api.canned_checks.CheckChangeTodoHasOwner(input_api, output_api)) + results.extend( + input_api.canned_checks.CheckChangeHasNoStrayWhitespace( + input_api, output_api)) + results.extend( + input_api.canned_checks.CheckDoNotSubmit(input_api, output_api)) + results.extend( + input_api.canned_checks.CheckChangeLintsClean(input_api, + output_api, + lint_filters="")) + results.extend( + _CheckNonInclusiveLanguage(input_api, output_api, + _NonInclusiveFileFilter)) return results
diff --git a/PRESUBMIT.py.dawn b/PRESUBMIT.py.dawn new file mode 100644 index 0000000..899e0e2 --- /dev/null +++ b/PRESUBMIT.py.dawn
@@ -0,0 +1,38 @@ +# Copyright 2018 The Dawn Authors +# +# 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 os +import platform +import subprocess + +USE_PYTHON3 = True + + +def _DoCommonChecks(input_api, output_api): + results = [] + results.extend( + input_api.canned_checks.CheckChangedLUCIConfigs(input_api, output_api)) + results.extend( + input_api.canned_checks.CheckPatchFormatted(input_api, + output_api, + check_python=True)) + return results + + +def CheckChangeOnUpload(input_api, output_api): + return _DoCommonChecks(input_api, output_api) + + +def CheckChangeOnCommit(input_api, output_api): + return _DoCommonChecks(input_api, output_api)
diff --git a/PRESUBMIT.py.tint b/PRESUBMIT.py.tint new file mode 100755 index 0000000..97623c1 --- /dev/null +++ b/PRESUBMIT.py.tint
@@ -0,0 +1,167 @@ +# Copyright 2020 The Tint Authors +# +# 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. +"""Presubmit script for Tint. +See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts +for more details about the presubmit API built into depot_tools. +""" + +import re + +USE_PYTHON3 = True + + +def _LicenseHeader(input_api): + """Returns the license header regexp.""" + # Accept any year number from 2019 to the current year + current_year = int(input_api.time.strftime('%Y')) + allowed_years = (str(s) for s in reversed(xrange(2019, current_year + 1))) + years_re = '(' + '|'.join(allowed_years) + ')' + license_header = ( + r'.*? Copyright( \(c\))? %(year)s The Tint [Aa]uthors\n ' + r'.*?\n' + r'.*? Licensed under the Apache License, Version 2.0 (the "License");\n' + r'.*? you may not use this file except in compliance with the License.\n' + r'.*? You may obtain a copy of the License at\n' + r'.*?\n' + r'.*? http://www.apache.org/licenses/LICENSE-2.0\n' + r'.*?\n' + r'.*? Unless required by applicable law or agreed to in writing, software\n' + r'.*? distributed under the License is distributed on an "AS IS" BASIS,\n' + r'.*? WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n' + r'.*? See the License for the specific language governing permissions and\n' + r'.*? limitations under the License.\n') % { + 'year': years_re, + } + return license_header + + +REGEXES = [ + r"(?i)black[-_]?list", + r"(?i)white[-_]?list", + r"(?i)gr[ea]y[-_]?list", + r"(?i)(first class citizen)", + r"(?i)black[-_]?hat", + r"(?i)white[-_]?hat", + r"(?i)gr[ea]y[-_]?hat", + r"(?i)master", + r"(?i)slave", + r"(?i)\bhim\b", + r"(?i)\bhis\b", + r"(?i)\bshe\b", + r"(?i)\bher\b", + r"(?i)\bguys\b", + r"(?i)\bhers\b", + r"(?i)\bman\b", + r"(?i)\bwoman\b", + r"(?i)\she\s", + r"(?i)\she$", + r"(?i)^he\s", + r"(?i)^he$", + r"(?i)\she['|\u2019]d\s", + r"(?i)\she['|\u2019]d$", + r"(?i)^he['|\u2019]d\s", + r"(?i)^he['|\u2019]d$", + r"(?i)\she['|\u2019]s\s", + r"(?i)\she['|\u2019]s$", + r"(?i)^he['|\u2019]s\s", + r"(?i)^he['|\u2019]s$", + r"(?i)\she['|\u2019]ll\s", + r"(?i)\she['|\u2019]ll$", + r"(?i)^he['|\u2019]ll\s", + r"(?i)^he['|\u2019]ll$", + r"(?i)grandfather", + r"(?i)\bmitm\b", + r"(?i)\bcrazy\b", + r"(?i)\binsane\b", + r"(?i)\bblind\sto\b", + r"(?i)\bflying\sblind\b", + r"(?i)\bblind\seye\b", + r"(?i)\bcripple\b", + r"(?i)\bcrippled\b", + r"(?i)\bdumb\b", + r"(?i)\bdummy\b", + r"(?i)\bparanoid\b", + r"(?i)\bsane\b", + r"(?i)\bsanity\b", + r"(?i)red[-_]?line", +] + +REGEX_LIST = [] +for reg in REGEXES: + REGEX_LIST.append(re.compile(reg)) + +def CheckNonInclusiveLanguage(input_api, output_api, source_file_filter=None): + """Checks the files for non-inclusive language.""" + + matches = [] + for f in input_api.AffectedFiles(include_deletes=False, + file_filter=source_file_filter): + for line_num, line in f.ChangedContents(): + for reg in REGEX_LIST: + match = reg.search(line) + if match: + matches.append( + "{} ({}): found non-inclusive language: {}".format( + f.LocalPath(), line_num, match.group(0))) + + if len(matches): + return [ + output_api.PresubmitPromptWarning('Non-inclusive language found:', + items=matches) + ] + + return [] + + +def CheckChange(input_api, output_api): + results = [] + + results += input_api.canned_checks.CheckChangeHasDescription( + input_api, output_api) + results += input_api.canned_checks.CheckPatchFormatted(input_api, + output_api, + check_python=True) + results += input_api.canned_checks.CheckGNFormatted(input_api, output_api) + results += input_api.canned_checks.CheckChangeHasNoCrAndHasOnlyOneEol( + input_api, output_api) + results += input_api.canned_checks.CheckChangeHasNoTabs( + input_api, output_api) + results += input_api.canned_checks.CheckChangeTodoHasOwner( + input_api, output_api) + results += input_api.canned_checks.CheckChangeHasNoStrayWhitespace( + input_api, output_api) + results += input_api.canned_checks.CheckDoNotSubmit(input_api, output_api) + results += input_api.canned_checks.CheckChangeLintsClean(input_api, + output_api, + lint_filters="") + + def NonInclusiveFileFilter(file): + filter_list = [ + "docs/tint/spirv-input-output-variables.md", # External URL + "test/tint/samples/compute_boids.wgsl ", # External URL + ] + return file in filter_list + + results += CheckNonInclusiveLanguage(input_api, output_api, + NonInclusiveFileFilter) + + return results + + +def CheckChangeOnUpload(input_api, output_api): + return CheckChange(input_api, output_api) + + +def CheckChangeOnCommit(input_api, output_api): + return CheckChange(input_api, output_api)
diff --git a/README.md b/README.md index 1871388..a430410 100644 --- a/README.md +++ b/README.md
@@ -50,3 +50,110 @@ ## Disclaimer This is not an officially supported Google product. + +# Tint + +Tint is a compiler for the WebGPU Shader Language (WGSL). + +This is not an officially supported Google product. + +## Requirements + * Git + * CMake (3.10.2 or later) + * Ninja (or other build tool) + * Python, for fetching dependencies + * [depot_tools] in your path + +## Build options + * `TINT_BUILD_SPV_READER` : enable the SPIR-V input reader (off by default) + * `TINT_BUILD_WGSL_READER` : enable the WGSL input reader (on by default) + * `TINT_BUILD_SPV_WRITER` : enable the SPIR-V output writer (on by default) + * `TINT_BUILD_WGSL_WRITER` : enable the WGSL output writer (on by default) + * `TINT_BUILD_FUZZERS` : enable building fuzzzers (off by default) + +## Building +Tint uses Chromium dependency management so you need to install [depot_tools] +and add it to your PATH. + +[depot_tools]: http://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up + +### Getting source & dependencies + +```sh +# Clone the repo as "tint" +git clone https://dawn.googlesource.com/tint tint +cd tint + +# Bootstrap the gclient configuration +cp standalone.gclient .gclient + +# Fetch external dependencies and toolchains with gclient +gclient sync +``` + +### Compiling using CMake + Ninja +```sh +mkdir -p out/Debug +cd out/Debug +cmake -GNinja ../.. +ninja # or autoninja +``` + +### Compiling using CMake + make +```sh +mkdir -p out/Debug +cd out/Debug +cmake ../.. +make # -j N for N-way parallel build +``` + +### Compiling using gn + ninja +```sh +mkdir -p out/Debug +gn gen out/Debug +autoninja -C out/Debug +``` + +### Fuzzers on MacOS +If you are attempting fuzz, using `TINT_BUILD_FUZZERS=ON`, the version of llvm +in the XCode SDK does not have the needed libfuzzer functionality included. + +The build error that you will see from using the XCode SDK will look something +like this: +``` +ld: file not found:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/11.0.0/lib/darwin/libclang_rt.fuzzer_osx.a +``` + +The solution to this problem is to use a full version llvm, like what you would +get via homebrew, `brew install llvm`, and use something like `CC=<path to full +clang> cmake ..` to setup a build using that toolchain. + +### Checking [chromium-style] issues in CMake builds +The gn based work flow uses the Chromium toolchain for building in anticipation +of integration of Tint into Chromium based projects. This toolchain has +additional plugins for checking for style issues, which are marked with +[chromium-style] in log messages. This means that this toolchain is more strict +then the default clang toolchain. + +In the future we will have a CQ that will build this work flow and flag issues +automatically. Until that is in place, to avoid causing breakages you can run +the [chromium-style] checks using the CMake based work flows. This requires +setting `CC` to the version of clang checked out by `gclient sync` and setting +the `TINT_CHECK_CHROMIUM_STYLE` to `ON`. + +```sh +mkdir -p out/style +cd out/style +cmake ../.. +CC=../../third_party/llvm-build/Release+Asserts/bin/clang cmake -DTINT_CHECK_CHROMIUM_STYLE=ON ../../ # add -GNinja for ninja builds +``` + +## Issues +Please file any issues or feature requests at +https://bugs.chromium.org/p/tint/issues/entry + +## Contributing +Please see the CONTRIBUTING and CODE_OF_CONDUCT files on how to contribute to +Tint. + +Tint has a process for supporting [experimental extensions](docs/tint/experimental_extensions.md).
diff --git a/README.md.dawn b/README.md.dawn new file mode 100644 index 0000000..1871388 --- /dev/null +++ b/README.md.dawn
@@ -0,0 +1,52 @@ + + +# Dawn, a WebGPU implementation + +Dawn is an open-source and cross-platform implementation of the work-in-progress [WebGPU](https://webgpu.dev) standard. +More precisely it implements [`webgpu.h`](https://github.com/webgpu-native/webgpu-headers/blob/master/webgpu.h) that is a one-to-one mapping with the WebGPU IDL. +Dawn is meant to be integrated as part of a larger system and is the underlying implementation of WebGPU in Chromium. + +Dawn provides several WebGPU building blocks: + - **WebGPU C/C++ headers** that applications and other building blocks use. + - The `webgpu.h` version that Dawn implements. + - A C++ wrapper for the `webgpu.h`. + - **A "native" implementation of WebGPU** using platforms' GPU APIs: + - **D3D12** on Windows 10 + - **Metal** on macOS and iOS + - **Vulkan** on Windows, Linux, ChromeOS, Android and Fuchsia + - OpenGL as best effort where available + - **A client-server implementation of WebGPU** for applications that are in a sandbox without access to native drivers + +Helpful links: + + - [Dawn's bug tracker](https://bugs.chromium.org/p/dawn/issues/entry) if you find issues with Dawn. + - [Dawn's mailing list](https://groups.google.com/forum/#!members/dawn-graphics) for other discussions related to Dawn. + - [Dawn's source code](https://dawn.googlesource.com/dawn) + - [Dawn's Matrix chatroom](https://matrix.to/#/#webgpu-dawn:matrix.org) for live discussion around contributing or using Dawn. + - [WebGPU's Matrix chatroom](https://matrix.to/#/#WebGPU:matrix.org) + +## Documentation table of content + +Developer documentation: + + - [Dawn overview](docs/dawn/overview.md) + - [Building Dawn](docs/dawn/building.md) + - [Contributing to Dawn](docs/dawn/contributing.md) + - [Testing Dawn](docs/dawn/testing.md) + - [Debugging Dawn](docs/dawn/debugging.md) + - [Dawn's infrastructure](docs/dawn/infra.md) + - [Dawn errors](docs/dawn/errors.md) + +User documentation: (TODO, figure out what overlaps with the webgpu.h docs) + +## Status + +(TODO) + +## License + +Apache 2.0 Public License, please see [LICENSE](/LICENSE). + +## Disclaimer + +This is not an officially supported Google product.
diff --git a/README.md.tint b/README.md.tint new file mode 100644 index 0000000..fbe6cfb --- /dev/null +++ b/README.md.tint
@@ -0,0 +1,106 @@ +# Tint + +Tint is a compiler for the WebGPU Shader Language (WGSL). + +This is not an officially supported Google product. + +## Requirements + * Git + * CMake (3.10.2 or later) + * Ninja (or other build tool) + * Python, for fetching dependencies + * [depot_tools] in your path + +## Build options + * `TINT_BUILD_SPV_READER` : enable the SPIR-V input reader (off by default) + * `TINT_BUILD_WGSL_READER` : enable the WGSL input reader (on by default) + * `TINT_BUILD_SPV_WRITER` : enable the SPIR-V output writer (on by default) + * `TINT_BUILD_WGSL_WRITER` : enable the WGSL output writer (on by default) + * `TINT_BUILD_FUZZERS` : enable building fuzzzers (off by default) + +## Building +Tint uses Chromium dependency management so you need to install [depot_tools] +and add it to your PATH. + +[depot_tools]: http://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up + +### Getting source & dependencies + +```sh +# Clone the repo as "tint" +git clone https://dawn.googlesource.com/tint tint +cd tint + +# Bootstrap the gclient configuration +cp standalone.gclient .gclient + +# Fetch external dependencies and toolchains with gclient +gclient sync +``` + +### Compiling using CMake + Ninja +```sh +mkdir -p out/Debug +cd out/Debug +cmake -GNinja ../.. +ninja # or autoninja +``` + +### Compiling using CMake + make +```sh +mkdir -p out/Debug +cd out/Debug +cmake ../.. +make # -j N for N-way parallel build +``` + +### Compiling using gn + ninja +```sh +mkdir -p out/Debug +gn gen out/Debug +autoninja -C out/Debug +``` + +### Fuzzers on MacOS +If you are attempting fuzz, using `TINT_BUILD_FUZZERS=ON`, the version of llvm +in the XCode SDK does not have the needed libfuzzer functionality included. + +The build error that you will see from using the XCode SDK will look something +like this: +``` +ld: file not found:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/11.0.0/lib/darwin/libclang_rt.fuzzer_osx.a +``` + +The solution to this problem is to use a full version llvm, like what you would +get via homebrew, `brew install llvm`, and use something like `CC=<path to full +clang> cmake ..` to setup a build using that toolchain. + +### Checking [chromium-style] issues in CMake builds +The gn based work flow uses the Chromium toolchain for building in anticipation +of integration of Tint into Chromium based projects. This toolchain has +additional plugins for checking for style issues, which are marked with +[chromium-style] in log messages. This means that this toolchain is more strict +then the default clang toolchain. + +In the future we will have a CQ that will build this work flow and flag issues +automatically. Until that is in place, to avoid causing breakages you can run +the [chromium-style] checks using the CMake based work flows. This requires +setting `CC` to the version of clang checked out by `gclient sync` and setting +the `TINT_CHECK_CHROMIUM_STYLE` to `ON`. + +```sh +mkdir -p out/style +cd out/style +cmake ../.. +CC=../../third_party/llvm-build/Release+Asserts/bin/clang cmake -DTINT_CHECK_CHROMIUM_STYLE=ON ../../ # add -GNinja for ninja builds +``` + +## Issues +Please file any issues or feature requests at +https://bugs.chromium.org/p/tint/issues/entry + +## Contributing +Please see the CONTRIBUTING and CODE_OF_CONDUCT files on how to contribute to +Tint. + +Tint has a process for supporting [experimental extensions](docs/tint/experimental_extensions.md).
diff --git a/build_overrides/build.gni b/build_overrides/build.gni index e883854..8717867 100644 --- a/build_overrides/build.gni +++ b/build_overrides/build.gni
@@ -1,4 +1,4 @@ -# Copyright 2018 The Dawn Authors +# Copyright 2022 The Dawn Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License.
diff --git a/build_overrides/dawn.gni b/build_overrides/dawn.gni index bbded06..87e1ded 100644 --- a/build_overrides/dawn.gni +++ b/build_overrides/dawn.gni
@@ -34,7 +34,6 @@ dawn_googletest_dir = "//third_party/googletest" dawn_spirv_tools_dir = "//third_party/vulkan-deps/spirv-tools/src" dawn_swiftshader_dir = "//third_party/swiftshader" -dawn_tint_dir = "//third_party/tint" dawn_vulkan_loader_dir = "//third_party/vulkan-deps/vulkan-loader/src" dawn_vulkan_validation_layers_dir = "//third_party/vulkan-deps/vulkan-validation-layers/src"
diff --git a/build_overrides/tint.gni b/build_overrides/tint.gni index c4d4d12..8349998 100644 --- a/build_overrides/tint.gni +++ b/build_overrides/tint.gni
@@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -tint_root_dir = "//third_party/tint" tint_spirv_tools_dir = "//third_party/vulkan-deps/spirv-tools/src" tint_spirv_headers_dir = "//third_party/vulkan-deps/spirv-headers/src"
diff --git a/docs/tint/arch.md b/docs/tint/arch.md new file mode 100644 index 0000000..204ff64 --- /dev/null +++ b/docs/tint/arch.md
@@ -0,0 +1,195 @@ +# Tint Architecture + +``` + ┏━━━━━━━━┓ ┏━━━━━━┓ + ┃ SPIR━V ┃ ┃ WGSL ┃ + ┗━━━━┃━━━┛ ┗━━━┃━━┛ + ▼ ▼ + ┏━━━━━━━━━┃━━━━━━━━━━━━━━━━━━━━━━━━━━━┃━━━━━━━━┓ + ┃ ┃ Reader ┃ ┃ + ┃ ┃ ┃ ┃ + ┃ ┏━━━━━━━┻━━━━━━┓ ┏━━━━━━┻━━━━━━┓ ┃ + ┃ ┃ SPIRV-Reader ┃ ┃ WGSL-Reader ┃ ┃ + ┃ ┗━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━┛ ┃ + ┗━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┛ + ▼ + ┏━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━┓ + ┃ ProgramBuilder ┃ + ┃ (mutable) ┃ + ┏━━━━━━━━━━━━►┫ ┏━━━━━┓ ┏━━━━━━━┓ ┏━━━━━━━━━┓ ┃ + ┃ ┃ ┃ AST ┃ ┃ Types ┃ ┃ Symbols ┃ ┃ + ┃ ┃ ┗━━━━━┛ ┗━━━━━━━┛ ┗━━━━━━━━━┛ ┃ + ┃ ┗━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┛ + ┃ ▼ + ┃ ┌┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┃┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┐ + ▲ ┆ Build ▼ ┆ + ┏━━━┻━━━┓ ┆ ┏━━━━━━━━┻━━━━━━━━┓ ┆ + ┃ Clone ┃ ┆ ┃ Resolver ┃ ┆ + ┗━━━┳━━━┛ ┆ ┗━━━━━━━━━━━━━━━━━┛ ┆ + ▲ └┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┃┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┘ + ┃ ▼ + ┃ ┏━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━┓ + ┃ ┃ Program ┃ + ┃ ┃ (immutable) ┃ + ┣━━━━━━◄┫ ┏━━━━━┓ ┏━━━━━━━┓ ┏━━━━━━━━━━┓ ┏━━━━━━━━━┓ ┃ + ┃ ┃ ┃ AST ┃ ┃ Types ┃ ┃ Semantic ┃ ┃ Symbols ┃ ┃ + ┃ ┃ ┗━━━━━┛ ┗━━━━━━━┛ ┗━━━━━━━━━━┛ ┗━━━━━━━━━┛ ┃ + ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┛ + ▲ ▼ +┏━━━━━┻━━━━━┓ ┃ ┏━━━━━━━━━━━┓ +┃ Transform ┃◄━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━►┃ Inspector ┃ +┗━━━━━━━━━━━┛ ┃ ┗━━━━━━━━━━━┛ + ▼ +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ Writers ┃ +┃ ┃ +┃ ┏━━━━━━━━━━━━━━┓┏━━━━━━━━━━━━━┓┏━━━━━━━━━━━━━┓┏━━━━━━━━━━━━━┓┏━━━━━━━━━━━━┓ ┃ +┃ ┃ SPIRV-Writer ┃┃ WGSL-Writer ┃┃ HLSL-Writer ┃┃ GLSL-Writer ┃┃ MSL-Writer ┃ ┃ +┃ ┗━━━━━━━┳━━━━━━┛┗━━━━━━┳━━━━━━┛┗━━━━━━┳━━━━━━┛┗━━━━━━┳━━━━━━┛┗━━━━━━┳━━━━━┛ ┃ +┗━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━━━━━━━━┃━━━━━━━┛ + ▼ ▼ ▼ ▼ ▼ + ┏━━━━┻━━━┓ ┏━━━┻━━┓ ┏━━━┻━━┓ ┏━━━┻━━┓ ┏━━┻━━┓ + ┃ SPIR-V ┃ ┃ WGSL ┃ ┃ HLSL ┃ ┃ GLSL ┃ ┃ MSL ┃ + ┗━━━━━━━━┛ ┗━━━━━━┛ ┗━━━━━━┛ ┗━━━━━━┛ ┗━━━━━┛ +``` + +## Reader + +Readers are responsible for parsing a shader program and populating a +`ProgramBuilder` with the parsed AST, type and symbol information. + +The WGSL reader is a recursive descent parser. It closely follows the WGSL +grammar in the naming of the parse methods. + +## ProgramBuilder + +A `ProgramBuilder` is the primary interface to construct an immutable `Program`. +There are a number of methods exposed which make creating of the `Program` +simpler. A `ProgramBuilder` can only be used once, and must be discarded after +the `Program` is constructed. + +A `Program` is built from the `ProgramBuilder` by `std::move()`ing the +`ProgramBuilder` to a new `Program` object. When built, resolution is performed +so the produced `Program` will contain all the needed semantic information. + +At any time before building the `Program`, `ProgramBuilder::IsValid()` may be +called to ensure the AST is **structurally** correct. This checks that things +like `if` statements have a condition and body attached. + +If further changes to the `Program` are needed (say via a `Transform`) then a +new `ProgramBuilder` can be produced by cloning the `Program` into a new +`ProgramBuilder`. + +Unlike `Program`s, `ProgramBuilder`s are not part of the public Tint API. + +## AST + +The Abstract Syntax Tree is a directed acyclic graph of `ast::Node`s which +encode the syntactic structure of the WGSL program. + +The root of the AST is the `ast::Module` class which holds each of the declared +functions, variables and user defined types (type aliases and structures). + +Each `ast::Node` represents a **single** part of the program's source, and so +`ast::Node`s are not shared. + +The AST does not perform any verification of its content. For example, the +`ast::StrideAttribute` node has numeric stride parameter, which is a count of +the number of bytes from the start of one array element to the start of the +next. The AST node itself does not constrain the set of stride values that you +can set, aside from storing it as an unsigned integer. + +## Types + +Types are constructed during the Reader and resolution phases, and are +held by the `Program` or `ProgramBuilder`. AST and semantic nodes can both +reference types. + +Each `type::Type` node **uniquely** represents a particular spelling of a WGSL +type within the program, so you can compare `type::Type*` pointers to check for +equivalence of type expressions. +For example, there is only one `type::Type` node for the `i32` type, no matter +how many times it is mentioned in the source program. +However, if `MyI32` is a type alias for `i32`, then they will have two different +type nodes. + +## Semantic information + +Semantic information is held by `sem::Node`s which describe the program at +a higher / more abstract level than the AST. This includes information such as +the resolved type of each expression, the resolved overload of a builtin +function call, and the module scoped variables used by each function. + +Semantic information is generated by the `Resolver` when the `Program` +is built from a `ProgramBuilder`. + +The `sem::Info` class holds a map of `ast::Node`s to `sem::Node`s. +This map is **many-to-one** - i.e. while a AST node might have a single +corresponding semantic node, the reverse may not be true. For example: +many `ast::IdentifierExpression` nodes may map to a single `sem::Variable`, +and so the `sem::Variable` does not have a single corresponding +`ast::Node`. + +Unlike `ast::Node`s, semantic nodes may not necessarily form a directed acyclic +graph, and the semantic graph may contain diamonds. + +## Symbols + +Symbols represent a unique string identifier in the source program. These string +identifiers are transformed into symbols within the `Reader`s. + +During the Writer phase, symbols may be emitted as strings using a `Namer`. +A `Namer` may output the symbol in any form that preserves the uniqueness of +that symbol. + +## Resolver + +The `Resolver` will automatically run when a `Program` is built. +A `Resolver` creates the `Program`s semantic information by analyzing the +`Program`s AST and type information. + +The `Resolver` will validate to make sure the generated `Program` is +semantically valid. + +## Program + +A `Program` holds an immutable version of the information from the +`ProgramBuilder` along with semantic information generated by the +`Resolver`. + +Like `ProgramBuilder`, `Program::IsValid()` may be called to ensure the AST is +structurally correct and semantically valid, and that the `Resolver` did not +report any errors. + +Unlike the `ProgramBuilder`, a `Program` is fully immutable, and is part of the +public Tint API. The immutable nature of `Program`s make these entirely safe +to share between multiple threads without the use of synchronization primitives. + +## Inspector + +The inspectors job is to go through the `Program` and pull out various pieces of +information. The information may be used to pass information into the downstream +compilers (things like specialization constants) or may be used to pass into +transforms to update the AST before generating the resulting code. + +The input `Program` to the inspector must be valid (pass validation). + +## Transforms + +There maybe various transforms we want to run over the `Program`. +This is for things like Vertex Pulling or Robust Buffer Access. + +A transform operates by cloning the input `Program` into a new `ProgramBuilder`, +applying the required changes, and then finally building and returning a new +output `Program`. As the resolver is always run when a `Program` is built, +Transforms will always emit a `Program` with semantic information. + +The input `Program` to a transform must be valid (pass validation). +If the input `Program` of a transform is valid then the transform must guarantee +that the output program is also valid. + +## Writers + +A writer is responsible for writing the `Program` in the target shader language. + +The input `Program` to a writer must be valid (pass validation).
diff --git a/docs/tint/compound_statements.md b/docs/tint/compound_statements.md new file mode 100644 index 0000000..a113cce --- /dev/null +++ b/docs/tint/compound_statements.md
@@ -0,0 +1,119 @@ +# Compound Statements + +Compound statements are statements that can hold other statements. + +This document maps the WGSL compound statements to their semantic tree representations. + +## if statement + +WGSL: +``` +if (condition_a) { + statement_a; +} else if (condition_b) { + statement_b; +} else { + statement_c; +} +``` + +Semantic tree: +``` +sem::IfStatement { + condition_a + sem::BlockStatement { + statement_a + } + sem::ElseStatement { + condition_b + sem::BlockStatement { + statement_b + } + } + sem::ElseStatement { + sem::BlockStatement { + statement_c + } + } +} +``` + +## for loop + +WGSL: +``` +for (initializer; condition; continuing) { + statement; +} +``` + +Semantic tree: +``` +sem::ForLoopStatement { + sem::Statement initializer + sem::Expression condition + sem::Statement continuing + + sem::LoopBlockStatement { + sem::Statement statement + } +} +``` + +## loop + +WGSL: +``` +loop (condition) { + statement_a; + continuing { + statement_b; + } +} +``` + +Semantic tree: +``` +sem::LoopStatement { + sem::Expression condition + + sem::LoopBlockStatement { + sem::Statement statement_a + sem::LoopContinuingBlockStatement { + sem::Statement statement_b + } + } +} +``` + + +## switch statement + +WGSL: +``` +switch (condition) { + case literal_a, literal_b: { + statement_a; + } + default { + statement_b; + } +} +``` + +Semantic tree: +``` +sem::SwitchStatement { + sem::Expression condition + sem::CaseStatement { + sem::BlockStatement { + sem::Statement statement_a + } + } + sem::CaseStatement { + sem::BlockStatement { + sem::Statement statement_b + } + } +} +```
diff --git a/docs/tint/coverage-info.md b/docs/tint/coverage-info.md new file mode 100644 index 0000000..fdb79df --- /dev/null +++ b/docs/tint/coverage-info.md
@@ -0,0 +1,24 @@ +# Generating and viewing Tint code-coverage + +Requirements: + +* Host running Linux or macOS +* Clang toolchain on the `PATH` environment variable + +## Building Tint with coverage generation enabled + +Follow the steps [to build Tint with CMake](../README.md), but include the additional `-DTINT_EMIT_COVERAGE=1` CMake flag. + +## Generate coverage information + +Use the `<tint>/tools/tint-generate-coverage` script to run the tint executable or unit tests and generate the coverage information. + +The script takes the executable to invoke as the first command line argument, followed by additional arguments to pass to the executable. + +For example, to see the code coverage for all unit tests, run: +`<tint>/tools/tint-generate-coverage <build>/tint_unittests --gtest_brief` + +The script will emit two files at the root of the tint directory: + +* `coverage.summary` - A text file giving a coverage summary for all Tint source files. +* `lcov.info` - A binary coverage file that can be consumed with the [VSCode Coverage Gutters](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters) extension.
diff --git a/docs/tint/diagnostics_guide.md b/docs/tint/diagnostics_guide.md new file mode 100644 index 0000000..fc8fa22 --- /dev/null +++ b/docs/tint/diagnostics_guide.md
@@ -0,0 +1,126 @@ +# Tint diagnostic style guide + +This guide provides a set of best practices when writing code that emits +diagnostic messages in Tint. These diagnostics are messages presented to the +user in case of error or warning. + +The goal of this document is to have our diagnostic messages be clear and +understandable to our users, so that problems are easy to fix, and to try and +keep a consistent style. + +## Message style + +* Start diagnostic messages with a lower-case letter +* Try to keep the message to a single sentence, if possible +* Do not end the message with punctuation (full stop, exclamation mark, etc) + +**Don't:** + +``` +shader.wgsl:7:1 error: Cannot take the address of expression. +``` + +**Do:** + +``` +shader.wgsl:7:1 error: cannot take the address of expression +``` + +**Justification:** + +Succinct messages are more important than grammatical correctness. \ +This style matches the style found in most other compilers. + +## Prefer to use a `Source` location instead of quoting the code in the message + +**Don't:** + +``` +shader.wgsl:5:7 error: cannot multiply 'expr_a * expr_b' with types i32 and f32 + +var res : f32 = expr_a * expr_b + ^^^^^^^^^^^^^^^ +``` + +**Do:** + +``` +shader.wgsl:5:7 error: cannot multiply types i32 and f32 + +var res : f32 = expr_a * expr_b + ^^^^^^^^^^^^^^^ +``` + +**Justification:** + +The highlighted line provides even more contextual information than the quoted +source, and duplicating this information doesn't provide any more help to the +developer. \ +Quoting single word identifiers or keywords from the source is not discouraged. + +## Use `note` diagnostics for providing additional links to relevant code + +**Don't:** + +``` +shader.wgsl:5:11 error: type cannot be used in storage class 'storage' as it is non-host-shareable + + cond : bool; + ^^^^ +``` + +**Do:** + +``` +shader.wgsl:5:11 error: type cannot be used in storage class 'storage' as it is non-host-shareable + + cond : bool; + ^^^^ + +shader.wgsl:8:4 note: while instantiating variable 'StorageBuffer' + +var<storage> sb : StorageBuffer; + ^^ +``` + +**Justification:** + +To properly understand some diagnostics requires looking at more than a single +line. \ +Multi-source links can greatly reduce the time it takes to properly +understand a diagnostic message. \ +This is especially important for diagnostics raised from complex whole-program +analysis, but can also greatly aid simple diagnostics like symbol collision errors. + +## Use simple terminology + +**Don't:** + +``` +shader.wgsl:7:1 error: the originating variable of the left-hand side of an assignment expression must not be declared with read access control. +``` + +**Do:** + +``` +shader.wgsl:7:1 error: cannot assign to variable with read access control + +x.y = 1; +^^^^^^^ + +shader.wgsl:2:8 note: read access control declared here + +var<storage, read> x : i32; + ^^^^ +``` + +**Justification:** + +Diagnostics will be read by Web developers who may not be native English +speakers and are unlikely to be familiar with WGSL specification terminology. +Too much technical jargon can be intimidating and confusing. \ +Diagnostics should give enough information to explain what's wrong, and most +importantly, give enough information so that a fix actionable. + +**Caution:** Be careful to not over simplify. Use the specification terminology +if there's potential ambiguity by not including it.
diff --git a/docs/tint/end-to-end-tests.md b/docs/tint/end-to-end-tests.md new file mode 100644 index 0000000..8a34f76 --- /dev/null +++ b/docs/tint/end-to-end-tests.md
@@ -0,0 +1,35 @@ +# Tint end-to-end tests + +This repo contains a large number of end-to-end tests at `<tint>/test`. + +## Test files + +Test input files have either the `.wgsl`, `.spv` or `.spvasm` file extension. + +Each test input file is tested against each of the Tint backends. There are `<number-of-input-files>` × `<number-of-tint-backends>` tests that are performed on an unfiltered end-to-end test run. + +Each backend test can have an **expectation file**. This expectation file sits next to the input file, with a `<input-file>.expected.<format>` extension. For example the test `test/foo.wgsl` would have the HLSL expectation file `test/foo.wgsl.expected.hlsl`. + +An expectation file contains the expected output of Tint, when passed the input file for the given backend. + +If the first line of the expectation file starts `SKIP`, then the test will be skipped instead of failing the end-to-end test run. It is good practice to include after the `SKIP` a reason for why the test is being skipped, along with any additional details, such as compiler error messages. + +## Running + +To run the end-to-end tests use the `<tint>/test/test-all.sh` script, passing the path to the tint executable as the first command line argument. + +You can pass `--help` to see the full list of command line flags.\ +The most commonly used flags are: + +| flag | description | +|----------------------|-------------| +|`--filter` | Filters the testing to subset of the tests. The filter argument is a glob pattern that can include `*` for any substring of a file or directory, and `**` for any number of directories.<br>Example: `--filter 'expressions/**/i32.wgsl'` will test all the `i32.wgsl` expression tests. +|`--format` | Filters the tests to the particular backend.<br>Example: `--format hlsl` will just test the HLSL backend. +|`--generate-expected` | Generate expectation files for the tests that previously had no expectation file, or were marked as `SKIP` but now pass. +|`--generate-skip` | Generate `SKIP` expectation files for tests that are not currently passing. + +## Authoring guidelines + +Each test should be as small as possible, and focused on the particular feature being tested. + +Use sub-directories whenever possible to group similar tests, and try to keep the pattern of directories as consistent as possible between different tests. This helps filter tests using the `--filter` glob patterns.
diff --git a/docs/tint/experimental_extensions.md b/docs/tint/experimental_extensions.md new file mode 100644 index 0000000..8fccd13 --- /dev/null +++ b/docs/tint/experimental_extensions.md
@@ -0,0 +1,44 @@ +# Experimental extensions + +Sometimes a language feature proposed for WGSL requires experiementation +to prove its worth. Tint needs to support these, in general to enable +that experimentation. + +The steps for doing so are: + +1. Choose a name for the feature, to be used in an `enable` directive. + An experimental extension should use prefix of `google_experimental_` + Example: + + enable google_experimental_f16; + +2. Write down what the feature is supposed to mean. + This informs the Tint implementation, and tells shader authors what + has changed. + Ideally, this will take the form of one of the following: + + - A PR against the WGSL spec. + + - A description of what the contents of that PR would be, committed + as a document in this Tint repository. + +3. File a tracking bug for adding the feature. + Note: Should the Tint repo have a label for experimental features? + +4. File a tracking bug for removing the feature or converting it to + non-experimental. + +5. Write a plan for removal of the experiment. + - Ideally, this plan is committed to this repository, especially the + description of public activities and commitments. However, we recognize + that some internal goals or metrics may be sensitive, and can be hidden. + - The plan is about process, not technical details. It should include: + - Who is the point of contact for this feature? The point of contact + is responsible when the feature causes an issue or gets in the way. + - What is your target date for declaring the experiment a success or + failure. In Chrome an experiment must be shipped or removed, in + finite time. + - What experience are you hoping to gain? Do you have target metrics? + - What approvals, if any, do you need from W3C? What is your plan to + present your case to W3C? + - The bug tracking removal of the experiment.
diff --git a/docs/tint/origin-trial-changes.md b/docs/tint/origin-trial-changes.md new file mode 100644 index 0000000..3f3c61d --- /dev/null +++ b/docs/tint/origin-trial-changes.md
@@ -0,0 +1,133 @@ +# Tint changes during Origin Trial + +## Changes for M102 + +### New Features + +* Parentheses are no longer required around expressions for if and switch statements [tint:1424](crbug.com/tint/1424) +* Compound assignment statements are now supported. [tint:1325](https://crbug.com/tint/1325) +* The colon in case statements is now optional. [tint:1485](crbug.com/tint/1485) + +### Breaking changes + +* Struct members are now separated by commas. [tint:1475](crbug.com/tint/1475) +* The `@block` attribute has been removed. [tint:1324](crbug.com/tint/1324) +* The `@stride` attribute has been removed. [tint:1381](crbug.com/tint/1381) +* Attributes using `[[attribute]]` syntax are no longer supported. [tint:1382](crbug.com/tint/1382) +* The `elseif` keyword is no longer supported. [tint:1289](crbug.com/tint/1289) + +### Deprecated Features + +* The `smoothStep()` builtin has been renamed to `smoothstep()`. [tint:1483](crbug.com/tint/1483) + +## Changes for M101 + +### New Features + +* Tint now supports unicode identifiers. [tint:1437](crbug.com/tint/1437) + +### Breaking changes + +* The `isNan()`, `isInf()`, `isFinite()`, and `isNormal()` builtins have been removed. [tint:1312](https://crbug.com/tint/1312) + +## Changes for M100 + +### Breaking changes + +* The `@interpolate(flat)` attribute must now be specified on integral user-defined IO. [tint:1224](crbug.com/tint/1224) +* The `ignore()` intrinsic has been removed. Use phoney-assignment instead: `ignore(expr);` -> `_ = expr;`. +* `break` statements in `continuing` blocks are now correctly validated. + +### New Features + +* Module-scope declarations can now be declared in any order. [tint:1266](crbug.com/tint/1266) +* The `override` keyword and `@id()` attribute for pipeline-overridable constants are now supported, replacing the `@override` attribute. [tint:1403](crbug.com/tint/1403) + +## Changes for M99 + +### Breaking changes + +Obviously infinite loops (no condition, no break) are now a validation error. + +### Deprecated Features + +The following features have been deprecated and will be removed in M102: + +* The `[[block]]` attribute has been deprecated. [tint:1324](https://crbug.com/tint/1324) +* Attributes now use the `@decoration` syntax instead of the `[[decoration]]` syntax. [tint:1382](https://crbug.com/tint/1382) +* `elseif` has been replaced with `else if`. [tint:1289](https://crbug.com/tint/1289) +* The `[[stride]]` attribute has been deprecated. [tint:1381](https://crbug.com/tint/1381) + +### New Features + +* Vector and matrix element type can now be inferred from constructor argument types. [tint:1334](https://crbug.com/tint/1334) +* Added builtins `degrees()` and `radians()` for converting between degrees and radians. [tint:1329](https://crbug.com/tint/1329) +* `let` arrays and matrices can now be dynamically indexed. [tint:1352](https://crbug.com/tint/1352) +* Storage and Uniform buffer types no longer have to be structures. [tint:1372](crbug.com/tint/1372) +* A struct declaration does not have to be followed by a semicolon. [tint:1380](crbug.com/tint/1380) + +### Fixes + +* Fixed an issue where for-loops that contain array or structure constructors in the loop initializer statements, condition expressions or continuing statements could fail to compile. [tint:1364](https://crbug.com/tint/1364) + +## Changes for M98 + +### Breaking Changes + +* Taking the address of a vector component is no longer allowed. +* Module-scope declarations can no longer alias a builtin name. [tint:1318](https://crbug.com/tint/1318) +* It is now an error to call a function either directly or transitively, from a loop continuing block, that uses `discard`. [tint:1302](https://crbug.com/tint/1302) + +### Deprecated Features + +* The `isNan()`, `isInf()`, `isFinite()` and `isNormal()` builtins has been deprecated and will be removed in M101. [tint:1312](https://crbug.com/tint/1312) + +### New Features + +* New texture gather builtins: `textureGather()` and `textureGatherCompare()`. [tint:1330](https://crbug.com/tint/1330) +* Shadowing is now fully supported. [tint:819](https://crbug.com/tint/819) +* The `dot()` builtin now supports integer vector types. +* Identifiers can now start with a single leading underscore. [tint:1292](https://crbug.com/tint/1292) +* Control flow analysis has been improved, and functions no longer need to `return` if the statement is unreachable. [tint:1302](https://crbug.com/tint/1302) +* Unreachable statements now produce a warning instead of an error, to allow WGSL code to be updated to the new analysis behavior. These warnings may become errors in the future [gpuweb#2378](https://github.com/gpuweb/gpuweb/issues/2378) + +### Fixes + +* Fixed an issue where using a module-scoped `let` in a `workgroup_size` may result in a compilation error. [tint:1320](https://crbug.com/tint/1320) + +## Changes for M97 + +### Breaking Changes + +* Deprecated `modf()` and `frexp()` builtin overloads that take a pointer second parameter have been removed. +* Deprecated texture builtin functions that accepted a `read` access controlled storage texture have been removed. +* Storage textures must now only use the `write` access control. + +### Deprecated Features + +* The `ignore()` builtin has been replaced with phony-assignment. [gpuweb#2127](https://github.com/gpuweb/gpuweb/pull/2127) + +### New Features + +* `any()` and `all()` now support a `bool` parameter. These simply return the passed argument. [tint:1253](https://crbug.com/tint/1253) +* Call statements may now include functions that return a value (`ignore()` is no longer needed). +* The `interpolate(flat)` attribute can now be specified on integral user-defined IO. It will eventually become an error to define integral user-defined IO without this attribute. +* Matrix construction from scalar element values is now supported. + +### Fixes + +* Swizzling of `vec3` types in `storage` and `uniform` buffers has been fixed for Metal 1.x. [tint:1249](https://crbug.com/tint/1249) +* Calling a function that returns an unused value no longer produces an FXC compilation error. [tint:1259](https://crbug.com/tint/1259) +* `abs()` fixed for unsigned integers on SPIR-V backend + +## Changes for M95 + +### New Features + +* The size of an array can now be defined using a non-overridable module-scope constant +* The `num_workgroups` builtin is now supported. + +### Fixes + +* Hex floats: now correctly errors when the magnitude is non-zero, and the exponent would cause overflow. [tint:1150](https://crbug.com/tint/1150), [tint:1166](https://crbug.com/tint/1166) +* Identifiers beginning with an underscore are now correctly rejected. [tint:1179](https://crbug.com/tint/1179)
diff --git a/docs/tint/spirv-input-output-variables.md b/docs/tint/spirv-input-output-variables.md new file mode 100644 index 0000000..0f149e0 --- /dev/null +++ b/docs/tint/spirv-input-output-variables.md
@@ -0,0 +1,267 @@ +# SPIR-V translation of shader input and output variables + +WGSL [MR 1315](https://github.com/gpuweb/gpuweb/issues/1315) changed WGSL so +that pipeline inputs and outputs are handled similar to HLSL: + +- Shader pipeline inputs are the WGSL entry point function arguments. +- Shader pipeline outputs are the WGSL entry point return value. + +Note: In both cases, a struct may be used to pack multiple values together. +In that case, I/O specific attributes appear on struct members at the struct declaration. + +Resource variables, e.g. buffers, samplers, and textures, are still declared +as variables at module scope. + +## Vulkan SPIR-V today + +SPIR-V for Vulkan models inputs and outputs as module-scope variables in +the Input and Output storage classes, respectively. + +The `OpEntryPoint` instruction has a list of module-scope variables that must +be a superset of all the input and output variables that are statically +accessed in the shader call tree. +From SPIR-V 1.4 onward, all interface variables that might be statically accessed +must appear on that list. +So that includes all resource variables that might be statically accessed +by the shader call tree. + +## Translation scheme for SPIR-V to WGSL + +A translation scheme from SPIR-V to WGSL is as follows: + +Each SPIR-V entry point maps to a set of Private variables proxying the +inputs and outputs, and two functions: + +- An inner function with no arguments or return values, and whose body + is the same as the original SPIR-V entry point. +- Original input variables are mapped to pseudo-in Private variables + with the same store types, but no other attributes or properties copied. + In Vulkan, Input variables don't have initalizers. +- Original output variables are mapped to pseudo-out Private variables + with the same store types and optional initializer, but no other attributes + or properties are copied. +- A wrapper entry point function whose arguments correspond in type, location + and builtin attributes the original input variables, and whose return type is + a structure containing members correspond in type, location, and builtin + attributes to the original output variables. + The body of the wrapper function the following phases: + - Copy formal parameter values into pseudo-in variables. + - Insert a bitcast if the WGSL builtin variable has different signedness + from the SPIR-V declared type. + - Execute the inner function. + - Copy pseudo-out variables into the return structure. + - Insert a bitcast if the WGSL builtin variable has different signedness + from the SPIR-V declared type. + - Return the return structure. + +- Replace uses of the the original input/output variables to the pseudo-in and + pseudo-out variables, respectively. +- Remap pointer-to-Input with pointer-to-Private +- Remap pointer-to-Output with pointer-to-Private + +We are not concerned with the cost of extra copying input/output values. +First, the pipeline inputs/outputs tend to be small. +Second, we expect the backend compiler in the driver will be able to see +through the copying and optimize the result. + +### Example + + +```glsl + #version 450 + + layout(location = 0) out vec4 frag_colour; + layout(location = 0) in vec4 the_colour; + + void bar() { + frag_colour = the_colour; + } + + void main() { + bar(); + } +``` + +Current translation, through SPIR-V, SPIR-V reader, WGSL writer: + +```groovy + @location(0) var<out> frag_colour : vec4<f32>; + @location(0) var<in> the_colour : vec4<f32>; + + fn bar_() -> void { + const x_14 : vec4<f32> = the_colour; + frag_colour = x_14; + return; + } + + @stage(fragment) + fn main() -> void { + bar_(); + return; + } +``` + +Proposed translation, through SPIR-V, SPIR-V reader, WGSL writer: + +```groovy + // 'in' variables are now 'private'. + var<private> frag_colour : vec4<f32>; + var<private> the_colour : vec4<f32>; + + fn bar_() -> void { + // Accesses to the module-scope variables do not change. + // This is a big simplifying advantage. + const x_14 : vec4<f32> = the_colour; + frag_colour = x_14; + return; + } + + fn main_inner() -> void { + bar_(); + return; + } + + // Declare a structure type to collect the return values. + struct main_result_type { + @location(0) frag_color : vec4<f32>; + }; + + @stage(fragment) + fn main( + + // 'in' variables are entry point parameters + @location(0) the_color_arg : vec4<f32> + + ) -> main_result_type { + + // Save 'in' arguments to 'private' variables. + the_color = the_color_arg; + + // Initialize 'out' variables. + // Use the zero value, since no initializer was specified. + frag_color = vec4<f32>(); + + // Invoke the original entry point. + main_inner(); + + // Collect outputs into a structure and return it. + var result : main_outer_result_type; + result.frag_color = frag_color; + return result; + } +``` + +Alternately, we could emit the body of the original entry point at +the point of invocation. +However that is more complex because the original entry point function +may return from multiple locations, and we would like to have only +a single exit path to construct and return the result value. + +### Handling fragment discard + +In SPIR-V `OpKill` causes immediate termination of the shader. +Is the shader obligated to write its outputs when `OpKill` is executed? + +The Vulkan fragment operations are as follows: +(see [6. Fragment operations](https://www.khronos.org/registry/vulkan/specs/1.2/html/vkspec.html#fragops)). + +* Scissor test +* Sample mask test +* Fragment shading +* Multisample coverage +* Depth bounds test +* Stencil test +* Depth test +* Sample counting +* Coverage reduction + +After that, the fragment results are used to update output attachments, including +colour, depth, and stencil attachments. + +Vulkan says: + +> If a fragment operation results in all bits of the coverage mask being 0, +> the fragment is discarded, and no further operations are performed. +> Fragments can also be programmatically discarded in a fragment shader by executing one of +> +> OpKill. + +I interpret this to mean that the outputs of a discarded fragment are ignored. + +Therefore, `OpKill` does not require us to modify the basic scheme from the previous +section. + +The `OpDemoteToHelperInvocationEXT` +instruction is an alternative way to throw away a fragment, but which +does not immediately terminate execution of the invocation. +It is introduced in the [`SPV_EXT_demote_to_helper_invocation](http://htmlpreview.github.io/?https://github.com/KhronosGroup/SPIRV-Registry/blob/master/extensions/EXT/SPV_EXT_demote_to_helper_invocation.html) +extension. WGSL does not have this feature, but we expect it will be introduced by a +future WGSL extension. The same analysis applies to demote-to-helper. When introduced, +it will not affect translation of pipeline outputs. + +### Handling depth-replacing mode + +A Vulkan fragment shader must write to the fragment depth builtin if and only if it +has a `DepthReplacing` execution mode. Otherwise behaviour is undefined. + +We will ignore the case where the SPIR-V shader writes to the `FragDepth` builtin +and then discards the fragment. +This is justified because "no further operations" are performed by the pipeline +after the fragment is discarded, and that includes writing to depth output attachments. + +Assuming the shader is valid, no special translation is required. + +### Handling output sample mask + +By the same reasoning as for depth-replacing, it is ok to incidentally not write +to the sample-mask builtin variable when the fragment is discarded. + +### Handling clip distance and cull distance + +Most builtin variables are scalars or vectors. +However, the `ClipDistance` and `CullDistance` builtin variables are arrays of 32-bit float values. +Each entry defines a clip half-plane (respectively cull half-plane) +A Vulkan implementation must support array sizes of up to 8 elements. + +How prevalent are shaders that use these features? +These variables are supported when Vulkan features `shaderClipDistance` and `shaderCullDistance` +are supported. +According to gpuinfo.org as of this writing, those +Vulkan features appear to be nearly universally supported on Windows devices (>99%), +but by only 70% on Android. +It appears that Qualcomm devices support them, but Mali devices do not (e.g. Mali-G77). + +The proposed translation scheme forces a copy of each array from private +variables into the return value of a vertex shader, or into a private +variable of a fragment shader. +In addition to the register pressure, there may be a performance degradation +due to the bulk copying of data. + +We think this is an acceptable tradeoff for the gain in usability and +consistency with other pipeline inputs and outputs. + +## Translation scheme for WGSL AST to SPIR-V + +To translate from the WGSL AST to SPIR-V, do the following: + +- Each entry point formal parameter is mapped to a SPIR-V `Input` variable. + - Struct and array inputs may have to be broken down into individual variables. +- The return of the entry point is broken down into fields, with one + `Output` variable per field. +- In the above, builtins must be separated from user attributes. + - Builtin attributes are moved to the corresponding variable. + - Location and interpolation attributes are moved to the corresponding + variables. +- This translation relies on the fact that pipeline inputs and pipeline + outputs are IO-shareable types. IO-shareable types are always storable, + and can be the store type of input/output variables. +- Input function parameters will be automatically initialized by the system + as part of setting up the pipeline inputs to the entry point. +- Replace each return statement in the entry point with a code sequence + which writes the return value components to the synthesized output variables, + and then executes an `OpReturn` (without value). + +This translation is sufficient even for fragment shaders with discard. +In that case, outputs will be ignored because downstream pipeline +operations will not be performed. +This is the same rationale as for translation from SPIR-V to WGSL AST.
diff --git a/docs/tint/spirv-ptr-ref.md b/docs/tint/spirv-ptr-ref.md new file mode 100644 index 0000000..615e12c --- /dev/null +++ b/docs/tint/spirv-ptr-ref.md
@@ -0,0 +1,115 @@ +# SPIR-V translation of WGSL pointers and references + +WGSL was updated to have two kinds of memory views: pointers and references. +See https://github.com/gpuweb/gpuweb/pull/1569 + +In summary: + +* Reference types are never explicitly mentioned in WGSL source. +* A use of a variable is a value of reference type corresponding + to the reference memory view of the storage allocated for the + variable. +* Let-declared constants can be of pointer type, but not reference + type. +* Function parameter can be of pointer type, but not reference type. +* A variable's store type is never a pointer type, and never a + reference type. +* The "Load Rule" allows a reference to decay to the underlying + store type, by issuing a load of the value in the underlying memory. +* For an assignment: + * The right-hand side evaluates to a non-reference type (atomic-free + plain type). + * The left-hand side evaluates to a reference type, whose store + type is the same as the result of evaluating the right hand side. +* The address-of (unary `&`) operator converts a reference to a + pointer. +* The dereference (unary `*`) operator converts a pointer to a + reference. + +TODO: Passing textures and samplers to helper functions might be +done by "handler value", or by pointer-to-handle. + +## Writing SPIR-V from WGSL + +The distinction in WGSL between reference and pointer disappears +at the SPIR-V level. Both types map into pointer types in SPIR-V. + +To translate a valid WGSL program to SPIR-V: + +* The dereference operator (unary `*`) is the identity operation. +* The address-of operator (unary `&`) is the identity operation. +* Assignment maps to OpStore. +* The Load Rule translates to OpLoad. + +## Reading SPIR-V to create WGSL + +The main changes to the SPIR-V reader are: + +* When translating a SPIR-V pointer expression, track whether the + corresponding WGSL expression is of corresponding WGSL pointer + type or correspoinding WGSL type. +* Insert dereference (unary-`*`) or address-of (unary-`&`) operators + as needed to generate valid WGSL expressions. + +The choices can be made deterministic, as described below. + +The SPIR-V reader only supports baseline functionality in Vulkan. +Therefore we assume no VariablePointers or VariablePointersStorageBuffer +capabilities. All pointers are +[SPIR-V logical pointers](https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#LogicalPointerType). +The [SPIR-V Universal Validation Rules](https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#_universal_validation_rules) +specify where logical pointers can appear as results of instructions +or operands of instructions. + +Each SPIR-V pointer result expression is a logical pointer, and +therefore is one of: + +* OpVariable: map to the reference type. +* OpFunctionParameter: map to the pointer type. +* OpCopyObject: + * When these only have one use, then these often fold away. + Otherwise, they map to a a let-declared constant. + * Map to the pointer type. +* OpAccessChain, OpInBoundsAccessChain: + * This could map to either pointer or reference, and adjustments + in other areas could make it work. However, we recommend mapping + this to the reference type. +* OpImageTexelPointer is not supported in WGSL. + It is used to get a pointer into a storage texture, for use with + atomic instructions. But image atomics is not supported in + WebGPU/WGSL. + +Each SPIR-V pointer operand is also a logical pointer, and is an +operand to one of: +* OpLoad Pointer operand: + * Map to reference, inserting a dereference operator if needed. +* OpStore Pointer operand: + * Map to reference, inserting a dereference operator if needed. +* OpStore Pointer operand: +* OpAccessChain, OpInBoundsAccessChain Base operand: + * WGSL array-access and subfield access only works on references. + * [Gpuweb issue 1530](https://github.com/gpuweb/gpuweb/issues/1530) + is filed to allow those operations to work on pointers. + * Map to reference, inserting a dereference operator if needed. +* OpFunctionCall function argument pointer operands + * Function operands can't be references. + * Map to pointer, inserting an address-of operator if needed. +* OpAtomic instruction Pointer operand + * These map to WGSL atomic builtins. + * Map to pointer, inserting an address-of operator if needed. + * Note: As of this writing, the atomic instructions are not supported + by the SPIR-V reader. +* OpCopyObject source operand + * This could have been mapped either way, but it's easiest to + map to pointer, to match the choice for OpCopyObject result type. + * Map to pointer, inserting an address-of operator if needed. +* OpCopyMemory, source and destination operands + * This acts as an assignment. + * Map both source and destination to reference, inserting dereference + operators if needed. + * Note: As of this writing, OpCopyMemory is not supported by the + SPIR-V reader. +* Extended instruction set instructions Modf and Frexp + * These map to builtins. + * Map the pointer operand to pointer, inserting an address-of + operator if needed.
diff --git a/docs/tint/style_guide.md b/docs/tint/style_guide.md new file mode 100644 index 0000000..52b08c5 --- /dev/null +++ b/docs/tint/style_guide.md
@@ -0,0 +1,47 @@ +# Tint style guide + +* Generally, follow the [Chromium style guide for C++](https://chromium.googlesource.com/chromium/src/+/HEAD/styleguide/c++/c++.md) + which itself is built on the [Google C++ style guide](https://google.github.io/styleguide/cppguide.html). + +* Overall try to use the same style and convention as code around your change. + +* Code must be formatted. Use `clang-format` with the provided [.clang-format](../.clang-format) + file. The `tools/format` script runs the formatter. + +* Code should not have linting errors. + The `tools/lint` script runs the linter. So does `git cl upload`. + +* Do not use C++ exceptions + +* Do not use C++ RTTI. + Instead, use `tint::Castable::As<T>()` from + [src/castable.h](../src/castable.h) + +* Generally, avoid `assert`. Instead, issue a [diagnostic](../src/diagnostic.h) + and fail gracefully, possibly by returning an error sentinel value. + Code that should not be reachable should call `TINT_UNREACHABLE` macro + and other internal error conditions should call the `TINT_ICE` macro. + See [src/debug.h](../src/debug.h) + +* Use `type` as part of a name only when the name refers to a type + in WGSL or another shader language processed by Tint. If the concept you are + trying to name is about distinguishing between alternatives, use `kind` instead. + +## Compiler support + +Tint requires C++17. + +Tint uses the Chromium build system and will stay synchronized with that system. +Compiler configurations beyond that baseline is on a best-effort basis. +We strive to support recent GCC and MSVC compilers. + +## Test code + +We might relax the above rules rules for test code, since test code +shouldn't ship to users. + +However, test code should still be readable and maintainable. + +For test code, the tradeoff between readability and maintainability +and other factors is weighted even more strongly toward readability +and maintainability.
diff --git a/docs/tint/translations.md b/docs/tint/translations.md new file mode 100644 index 0000000..5119434 --- /dev/null +++ b/docs/tint/translations.md
@@ -0,0 +1,187 @@ +# Translations + +This document attempts to document how WGSL translates into the various backends +for the cases where the translation is not a direct mapping. + +# Access Control + +## HLSL + * ReadOnly -> `ByteAddressBuffer` + * ReadWrite -> `RWByteAddressBuffer` + +## MSL + * ReadOnly -> `const` + +## SPIR-V +There are two ways this can be achieved in SPIR-V. Either the variable can be +decorated with `NonWritable` or each member of the struct can be decorated with +`NonWritable`. We chose to go the struct member route. + * The read-only becomes part of the type in this case. Otherwise, you are + treating the readonly type information as part of the variable which is + confusing. + * Treating the readonly as part of the variable means we should be + deduplicating the types behind the access control, which causes confusing + with the type_names and various tracking systems within Tint. + + +# Builtin Decorations +| Name | SPIR-V | MSL | HLSL | +|------|--------|-----|------| +| position | SpvBuiltInPosition |position | SV_Position | +| vertex_index | SpvBuiltInVertexIndex |vertex_id | SV_VertexID | +| instance_index | SpvBuiltInInstanceIndex | instance_id| SV_InstanceID | +| front_facing | SpvBuiltInFrontFacing | front_facing | SV_IsFrontFacing | +| frag_coord | SpvBuiltInFragCoord | position | SV_Position | +| frag_depth | SpvBuiltInFragDepth | depth(any) | SV_Depth | +| local_invocation_id | SpvBuiltInLocalInvocationId | thread_position_in_threadgroup | SV_GroupThreadID | +| local_invocation_index | SpvBuiltInLocalInvocationIndex | thread_index_in_threadgroup | SV_GroupIndex | +| global_invocation_id | SpvBuiltInGlobalInvocationId | thread_position_in_grid | SV_DispatchThreadID | + + +# Builtins Methods +| Name | SPIR-V | MSL | HLSL | +| ------|--------|-----|------ | +| abs | GLSLstd450FAbs or GLSLstd450SAbs| fabs or abs | abs | +| acos | GLSLstd450Acos | acos | acos | +| all | SpvOpAll | all | all | +| any | SpvOpAny | any | any | +| arrayLength | SpvOpArrayLength | | | +| asin | GLSLstd450Asin | asin | asin | +| atan | GLSLstd450Atan | atan | atan | +| atan2 | GLSLstd450Atan2| atan2 | atan2 | +| ceil | GLSLstd450Ceil| ceil | ceil | +| clamp | GLSLstd450NClamp or GLSLstd450UClamp or GLSLstd450SClamp| clamp | clamp | +| cos | GLSLstd450Cos | cos | cos | +| cosh | GLSLstd450Cosh | cosh | cosh | +| countOneBits | SpvOpBitCount | popcount | countbits | +| cross | GLSLstd450Cross | cross | cross | +| determinant | GLSLstd450Determinant | determinant | determinant | +| distance | GLSLstd450Distance | distance | distance | +| dot | SpOpDot | dot | dot | +| dpdx | SpvOpDPdx | dpdx | ddx | +| dpdxCoarse | SpvOpDPdxCoarse | dpdx | ddx_coarse | +| dpdxFine | SpvOpDPdxFine | dpdx | ddx_fine | +| dpdy | SpvOpDPdy | dpdy | ddy | +| dpdyCoarse | SpvOpDPdyCoarse | dpdy | ddy_coarse | +| dpdyFine | SpvOpDPdyFine | dpdy | ddy_fine | +| exp | GLSLstd450Exp | exp | exp | +| exp2 | GLSLstd450Exp2 | exp2 | exp2 | +| faceForward | GLSLstd450FaceForward | faceforward | faceforward | +| floor | GLSLstd450Floor | floor | floor | +| fma | GLSLstd450Fma | fma | fma | +| fract | GLSLstd450Fract | fract | frac | +| frexp | GLSLstd450Frexp | | | +| fwidth | SpvOpFwidth | fwidth | fwidth | +| fwidthCoarse | SpvOpFwidthCoarse | fwidth | fwidth | +| fwidthFine | SpvOpFwidthFine | fwidth | fwidth | +| inverseSqrt | GLSLstd450InverseSqrt | rsqrt | rsqrt | +| ldexp | GLSLstd450Ldexp | | | +| length | GLSLstd450Length | length | length | +| log | GLSLstd450Log | log | log | +| log2 | GLSLstd450Log2 | log2 | log2 | +| max | GLSLstd450NMax or GLSLstd450SMax or GLSLstd450UMax | fmax or max | max | +| min | GLSLstd450NMin or GLSLstd450SMin or GLSLstd450UMin | fmin or min | min | +| mix | GLSLstd450FMix | mix | mix | +| modf | GLSLstd450Modf | | | +| normalize | GLSLstd450Normalize | normalize | normalize | +| pow | GLSLstd450Pow | pow | pow | +| reflect | GLSLstd450Reflect | reflect | reflect | +| reverseBits | SpvOpBitReverse | reverse_bits | reversebits | +| round | GLSLstd450Round | round | round | +| select | SpvOpSelect | select | | +| sign | GLSLstd450FSign | sign | sign | +| sin | GLSLstd450Sin | sin | sin | +| sinh | GLSLstd450Sinh | sinh | sinh | +| smoothStep | GLSLstd450SmoothStep | smoothstep | smoothstep | +| sqrt | GLSLstd450Sqrt | sqrt | sqrt | +| step | GLSLstd450Step | step | step | +| tan | GLSLstd450Tan | tan | tan | +| tanh | GLSLstd450Tanh | tanh | tanh | +| trunc | GLSLstd450Trunc | trunc | trunc | + +# Types +## Sampler Types +| WGSL | SPIR-V | MSL | HLSL | +|------|--------|-----|------| +| sampler | OpTypeSampler | sampler | SamplerState | +| sampler_comparison | OpTypeSampler | sampler | SamplerComparisonState | + +## Texture Types +| WGSL | SPIR-V | MSL | HLSL | +|------|--------|-----|------| +| texture_1d<type> | OpTypeImage 1D Sampled=1 | texture1d<type, access::sample> | Texture1D | +| texture_2d<type> | OpTypeImage 2D Sampled=1 | texture2d<type, access::sample> | Texture2D | +| texture_2d_array<type> | OpTypeImage 2D Arrayed=1 Sampled=1 | texture2d_array<type, access::sample> | Texture2DArray | +| texture_3d<type> | OpTypeImage 3D Sampled=1 | texture3d<type, access::sample> | Texture3D | +| texture_cube<type> | OpTypeImage Cube Sampled=1 | texturecube<type, access::sample> | TextureCube | +| texture_cube_array<type> | OpTypeImage Cube Arrayed=1 Sampled=1 | texturecube_array<type, access::sample> | TextureCubeArray | +| | | | +| texture_multisampled_2d<type> | OpTypeImage 2D MS=1 Sampled=1 | texture2d_ms<type, access::sample> | Texture2D | +| | | | +| texture_depth_2d | OpTypeImage 2D Depth=1 Sampled=1 | depth2d<float, access::sample>| Texture2D | +| texture_depth_2d_array | OpTypeImage 2D Depth=1 Arrayed=1 Sampled=1 | depth2d_array<float, access::sample> | Texture2DArray | +| texture_depth_cube | OpTypeImage Cube Depth=1 Sampled=1 | depthcube<float, access::sample> | TextureCube | +| texture_depth_cube_array | OpTypeImage Cube Depth=1 Arrayed=1 Sampled=1 | depthcube_array<float, access::sample> | TextureCubeArray | +| texture_depth_multisampled_2d | OpTypeImage 2D Depth=1 MS=1 Sampled=1 | depth2d<float, access::sample>| Texture2DMSArray | +| | | | +| texture_storage_1d<image_storage_type> | OpTypeImage 1D Sampled=2| texture1d<type, access::read> | RWTexture1D | +| texture_storage_2d<image_storage_type> | OpTypeImage 2D Sampled=2 | texture2d<type, access::read> | RWTexture2D | +| texture_storage_2d_array<image_storage_type> | OpTypeImage 2D Arrayed=1 Sampled=2 | texture2d_array<type, access::read> | RWTexture2DArray | +| texture_storage_3d<image_storage_type> | OpTypeImage 3D Sampled=2 | texture3d<type, access::read> | RWTexture3D | +| | | | +| texture_storage_1d<image_storage_type> | OpTypeImage 1D Sampled=2 | texture1d<type, access::write> | RWTexture1D | +| texture_storage_2d<image_storage_type> | OpTypeImage 2D Sampled=1 | texture2d<type, access::write> | RWTexture2D | +| texture_storage_2d_array<image_storage_type> | OpTypeImage 2D Arrayed=1 Sampled=2 | texture2d_array<type, access::write> | RWTexture2DArray | +| texture_storage_3d<image_storage_type> | OpTypeImage 3D Sampled=2 | texture3d<type, access::write> | RWTexture3D| + +# Short-circuting +## HLSL +TODO(dsinclair): Nested if's + +## SPIR-V +TODO(dsinclair): Nested if's + +# Storage classes +TODO(dsinclair): do ... + +# Storage buffers +## HLSL +TODO(dsinclair): Rewriting of accessors to loads + +# Loop blocks +## HLSL +TODO(dsinclair): Rewrite with bools + +## MSL +TODO(dsinclair): Rewrite with bools + +# Input / Output storage class +## HLSL +TODO(dsinclair): Structs and params + +## MSL +TODO(dsinclair): Structs and params + +# Discard +## HLSL + * `discard` + +## MSL + * `discard_fragment()` + + +# Specialization constants +## HLSL +``` +#ifndef WGSL_SPEC_CONSTANT_<id> +-- if default provided +#define WGSL_SPEC_CONSTANT_<id> default value +-- else +#error spec constant required for constant id +-- +#endif +static const <type> <name> = WGSL_SPEC_CONSTANT_<id> +``` + +## MSL +`@function_constant(<id>)`
diff --git a/include/tint/tint.h b/include/tint/tint.h new file mode 100644 index 0000000..1a04196 --- /dev/null +++ b/include/tint/tint.h
@@ -0,0 +1,68 @@ +// Copyright 2020 The Tint Authors. +// +// 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 INCLUDE_TINT_TINT_H_ +#define INCLUDE_TINT_TINT_H_ + +// TODO(tint:88): When implementing support for an install target, all of these +// headers will need to be moved to include/tint/. + +#include "src/tint/ast/pipeline_stage.h" +#include "src/tint/demangler.h" +#include "src/tint/diagnostic/printer.h" +#include "src/tint/inspector/inspector.h" +#include "src/tint/reader/reader.h" +#include "src/tint/sem/type_manager.h" +#include "src/tint/transform/binding_remapper.h" +#include "src/tint/transform/first_index_offset.h" +#include "src/tint/transform/fold_trivial_single_use_lets.h" +#include "src/tint/transform/manager.h" +#include "src/tint/transform/multiplanar_external_texture.h" +#include "src/tint/transform/renamer.h" +#include "src/tint/transform/robustness.h" +#include "src/tint/transform/single_entry_point.h" +#include "src/tint/transform/vertex_pulling.h" +#include "src/tint/writer/writer.h" + +#if TINT_BUILD_SPV_READER +#include "src/tint/reader/spirv/parser.h" +#endif // TINT_BUILD_SPV_READER + +#if TINT_BUILD_WGSL_READER +#include "src/tint/reader/wgsl/parser.h" +#endif // TINT_BUILD_WGSL_READER + +#if TINT_BUILD_SPV_WRITER +#include "spirv-tools/libspirv.hpp" +#include "src/tint/writer/spirv/generator.h" +#endif // TINT_BUILD_SPV_WRITER + +#if TINT_BUILD_WGSL_WRITER +#include "src/tint/writer/wgsl/generator.h" +#endif // TINT_BUILD_WGSL_WRITER + +#if TINT_BUILD_MSL_WRITER +#include "src/tint/writer/msl/generator.h" +#endif // TINT_BUILD_MSL_WRITER + +#if TINT_BUILD_HLSL_WRITER +#include "src/tint/writer/hlsl/generator.h" +#endif // TINT_BUILD_HLSL_WRITER + +#if TINT_BUILD_GLSL_WRITER +#include "src/tint/transform/glsl.h" +#include "src/tint/writer/glsl/generator.h" +#endif // TINT_BUILD_GLSL_WRITER + +#endif // INCLUDE_TINT_TINT_H_
diff --git a/kokoro/linux/build.sh b/kokoro/linux/build.sh new file mode 100755 index 0000000..430f6c5 --- /dev/null +++ b/kokoro/linux/build.sh
@@ -0,0 +1,48 @@ +#!/bin/bash + +# Copyright 2021 The Tint Authors. +# +# 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. + +set -e # Fail on any error. + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )" +ROOT_DIR="$( cd "${SCRIPT_DIR}/../.." >/dev/null 2>&1 && pwd )" + +# Inside the docker VM, we clone the project to a new directory. +# We do this so that the docker script can be tested in a local development +# checkout, without having the build litter the local checkout with artifacts. +# This directory is mapped to the host temporary directory. +# Kokoro uses a '/tmpfs' root, where as most linux enviroments just have '/tmp' +if [ -d "/tmpfs" ]; then + TMP_DIR=/tmpfs +else + TMP_DIR=/tmp +fi + + +# --privileged is required for some sanitizer builds, as they seem to require PTRACE privileges +docker run --rm -i \ + --privileged \ + --volume "${ROOT_DIR}:${ROOT_DIR}" \ + --volume "${TMP_DIR}/kokoro/tint:/tint" \ + --volume "${KOKORO_ARTIFACTS_DIR}:/mnt/artifacts" \ + --workdir "${ROOT_DIR}" \ + --env SRC_DIR="/tint/src" \ + --env BUILD_DIR="/tint/build" \ + --env BUILD_TYPE=$BUILD_TYPE \ + --env BUILD_SYSTEM=$BUILD_SYSTEM \ + --env BUILD_SANITIZER=$BUILD_SANITIZER \ + --env BUILD_TOOLCHAIN=$BUILD_TOOLCHAIN \ + --entrypoint "${SCRIPT_DIR}/docker.sh" \ + "gcr.io/shaderc-build/radial-build:latest"
diff --git a/kokoro/linux/cmake-clang-debug-asan/build.sh b/kokoro/linux/cmake-clang-debug-asan/build.sh new file mode 100755 index 0000000..5548770 --- /dev/null +++ b/kokoro/linux/cmake-clang-debug-asan/build.sh
@@ -0,0 +1,26 @@ +#!/bin/bash + +# Copyright 2021 The Tint Authors. +# +# 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. + +set -e # Fail on any error. + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )" + +export BUILD_SYSTEM=cmake +export BUILD_TOOLCHAIN=clang +export BUILD_TYPE=Debug +export BUILD_SANITIZER=asan + +${SCRIPT_DIR}/../build.sh
diff --git a/kokoro/linux/cmake-clang-debug-asan/presubmit.cfg b/kokoro/linux/cmake-clang-debug-asan/presubmit.cfg new file mode 100644 index 0000000..bf23a27 --- /dev/null +++ b/kokoro/linux/cmake-clang-debug-asan/presubmit.cfg
@@ -0,0 +1,3 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +build_file: "tint/kokoro/linux/cmake-clang-debug-asan/build.sh"
diff --git a/kokoro/linux/cmake-clang-debug-ubsan/build.sh b/kokoro/linux/cmake-clang-debug-ubsan/build.sh new file mode 100755 index 0000000..55f4db1 --- /dev/null +++ b/kokoro/linux/cmake-clang-debug-ubsan/build.sh
@@ -0,0 +1,26 @@ +#!/bin/bash + +# Copyright 2021 The Tint Authors. +# +# 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. + +set -e # Fail on any error. + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )" + +export BUILD_SYSTEM=cmake +export BUILD_TOOLCHAIN=clang +export BUILD_TYPE=Debug +export BUILD_SANITIZER=ubsan + +${SCRIPT_DIR}/../build.sh
diff --git a/kokoro/linux/cmake-clang-debug-ubsan/presubmit.cfg b/kokoro/linux/cmake-clang-debug-ubsan/presubmit.cfg new file mode 100644 index 0000000..5fa246e --- /dev/null +++ b/kokoro/linux/cmake-clang-debug-ubsan/presubmit.cfg
@@ -0,0 +1,3 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +build_file: "tint/kokoro/linux/cmake-clang-debug-ubsan/build.sh"
diff --git a/kokoro/linux/cmake-clang-debug/build.sh b/kokoro/linux/cmake-clang-debug/build.sh new file mode 100755 index 0000000..fa4fb4c --- /dev/null +++ b/kokoro/linux/cmake-clang-debug/build.sh
@@ -0,0 +1,25 @@ +#!/bin/bash + +# Copyright 2021 The Tint Authors. +# +# 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. + +set -e # Fail on any error. + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )" + +export BUILD_SYSTEM=cmake +export BUILD_TOOLCHAIN=clang +export BUILD_TYPE=Debug + +${SCRIPT_DIR}/../build.sh
diff --git a/kokoro/linux/cmake-clang-debug/presubmit.cfg b/kokoro/linux/cmake-clang-debug/presubmit.cfg new file mode 100644 index 0000000..0cc8654 --- /dev/null +++ b/kokoro/linux/cmake-clang-debug/presubmit.cfg
@@ -0,0 +1,3 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +build_file: "tint/kokoro/linux/cmake-clang-debug/build.sh"
diff --git a/kokoro/linux/cmake-clang-release-asan/build.sh b/kokoro/linux/cmake-clang-release-asan/build.sh new file mode 100755 index 0000000..ed598ae --- /dev/null +++ b/kokoro/linux/cmake-clang-release-asan/build.sh
@@ -0,0 +1,26 @@ +#!/bin/bash + +# Copyright 2021 The Tint Authors. +# +# 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. + +set -e # Fail on any error. + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )" + +export BUILD_SYSTEM=cmake +export BUILD_TOOLCHAIN=clang +export BUILD_TYPE=Release +export BUILD_SANITIZER=asan + +${SCRIPT_DIR}/../build.sh
diff --git a/kokoro/linux/cmake-clang-release-asan/presubmit.cfg b/kokoro/linux/cmake-clang-release-asan/presubmit.cfg new file mode 100644 index 0000000..460c927 --- /dev/null +++ b/kokoro/linux/cmake-clang-release-asan/presubmit.cfg
@@ -0,0 +1,3 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +build_file: "tint/kokoro/linux/cmake-clang-release-asan/build.sh"
diff --git a/kokoro/linux/cmake-clang-release-ubsan/build.sh b/kokoro/linux/cmake-clang-release-ubsan/build.sh new file mode 100755 index 0000000..c476289 --- /dev/null +++ b/kokoro/linux/cmake-clang-release-ubsan/build.sh
@@ -0,0 +1,26 @@ +#!/bin/bash + +# Copyright 2021 The Tint Authors. +# +# 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. + +set -e # Fail on any error. + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )" + +export BUILD_SYSTEM=cmake +export BUILD_TOOLCHAIN=clang +export BUILD_TYPE=Release +export BUILD_SANITIZER=ubsan + +${SCRIPT_DIR}/../build.sh
diff --git a/kokoro/linux/cmake-clang-release-ubsan/presubmit.cfg b/kokoro/linux/cmake-clang-release-ubsan/presubmit.cfg new file mode 100644 index 0000000..f968295 --- /dev/null +++ b/kokoro/linux/cmake-clang-release-ubsan/presubmit.cfg
@@ -0,0 +1,3 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +build_file: "tint/kokoro/linux/cmake-clang-release-ubsan/build.sh"
diff --git a/kokoro/linux/cmake-clang-release/build.sh b/kokoro/linux/cmake-clang-release/build.sh new file mode 100755 index 0000000..394f5bb --- /dev/null +++ b/kokoro/linux/cmake-clang-release/build.sh
@@ -0,0 +1,25 @@ +#!/bin/bash + +# Copyright 2021 The Tint Authors. +# +# 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. + +set -e # Fail on any error. + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )" + +export BUILD_SYSTEM=cmake +export BUILD_TOOLCHAIN=clang +export BUILD_TYPE=Release + +${SCRIPT_DIR}/../build.sh
diff --git a/kokoro/linux/cmake-clang-release/presubmit.cfg b/kokoro/linux/cmake-clang-release/presubmit.cfg new file mode 100644 index 0000000..ccc9651 --- /dev/null +++ b/kokoro/linux/cmake-clang-release/presubmit.cfg
@@ -0,0 +1,3 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +build_file: "tint/kokoro/linux/cmake-clang-release/build.sh"
diff --git a/kokoro/linux/cmake-gcc-debug/build.sh b/kokoro/linux/cmake-gcc-debug/build.sh new file mode 100755 index 0000000..2bd68bd --- /dev/null +++ b/kokoro/linux/cmake-gcc-debug/build.sh
@@ -0,0 +1,25 @@ +#!/bin/bash + +# Copyright 2021 The Tint Authors. +# +# 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. + +set -e # Fail on any error. + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )" + +export BUILD_SYSTEM=cmake +export BUILD_TOOLCHAIN=gcc +export BUILD_TYPE=Debug + +${SCRIPT_DIR}/../build.sh
diff --git a/kokoro/linux/cmake-gcc-debug/presubmit.cfg b/kokoro/linux/cmake-gcc-debug/presubmit.cfg new file mode 100644 index 0000000..eb1d4d8 --- /dev/null +++ b/kokoro/linux/cmake-gcc-debug/presubmit.cfg
@@ -0,0 +1,3 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +build_file: "tint/kokoro/linux/cmake-gcc-debug/build.sh"
diff --git a/kokoro/linux/cmake-gcc-release/build.sh b/kokoro/linux/cmake-gcc-release/build.sh new file mode 100755 index 0000000..83af742 --- /dev/null +++ b/kokoro/linux/cmake-gcc-release/build.sh
@@ -0,0 +1,25 @@ +#!/bin/bash + +# Copyright 2021 The Tint Authors. +# +# 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. + +set -e # Fail on any error. + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )" + +export BUILD_SYSTEM=cmake +export BUILD_TOOLCHAIN=gcc +export BUILD_TYPE=Release + +${SCRIPT_DIR}/../build.sh
diff --git a/kokoro/linux/cmake-gcc-release/presubmit.cfg b/kokoro/linux/cmake-gcc-release/presubmit.cfg new file mode 100644 index 0000000..95a6cfc --- /dev/null +++ b/kokoro/linux/cmake-gcc-release/presubmit.cfg
@@ -0,0 +1,3 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +build_file: "tint/kokoro/linux/cmake-gcc-release/build.sh"
diff --git a/kokoro/linux/docker.sh b/kokoro/linux/docker.sh new file mode 100755 index 0000000..d0ec4c5 --- /dev/null +++ b/kokoro/linux/docker.sh
@@ -0,0 +1,183 @@ +#!/bin/bash + +# Copyright 2021 The Tint Authors. +# +# 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 +# +# https://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. + +# This is the bash script invoked inside a docker container. +# The script expects that the CWD points to a clean checkout of Tint. +# As `gclient sync` will litter the tint checkout with fetched tools and +# projects, this script will first clone the pristine tint checkout to +# ${SRC_DIR}. This allows developers to locally run this script without having +# to worry about their local tint copy being touched. +# +# This script expects the following environment variables to be set on entry: +# +# SRC_DIR - Path to where the local Tint copy will be made. See above. +# BUILD_DIR - Path to where Tint will be built. +# BUILD_TYPE - Either: 'Debug' or 'Release' +# BUILD_SYSTEM - Must be 'cmake' +# BUILD_SANITIZER - Either: '', 'asan', or 'ubstan' +# BUILD_TOOLCHAIN - Either: 'clang' or 'gcc' + +set -e # Fail on any error. + +function show_cmds { set -x; } +function hide_cmds { { set +x; } 2>/dev/null; } +function task_begin { + TASK_NAME="$@" + SECONDS=0 +} +function print_last_task_duration { + if [ ! -z "${TASK_NAME}" ]; then + echo "${TASK_NAME} completed in $(($SECONDS / 3600))h$((($SECONDS / 60) % 60))m$(($SECONDS % 60))s" + fi +} +function status { + echo "" + echo "" + print_last_task_duration + echo "" + echo "*****************************************************************" + echo "* $@" + echo "*****************************************************************" + echo "" + task_begin $@ +} +function with_retry { + local MAX_ATTEMPTS=5 + local RETRY_DELAY_SECS=5 + local ATTEMPT=1 + while true; do + "$@" && break + if [[ $ATTEMPT -ge $MAX_ATTEMPTS ]]; then + echo "The command has failed after $ATTEMPT attempts." + exit $? + fi + ((ATTEMPT++)) + echo "'$@' failed. Attempt ($ATTEMPT/$MAX_ATTEMPTS). Retrying..." + sleep $RETRY_DELAY_SECS; + done +} + +CLONE_SRC_DIR="$(pwd)" + +. /bin/using.sh # Declare the bash `using` function for configuring toolchains. + +using depot_tools +using go-1.14.4 # Speeds up ./tools/lint +using doxygen-1.8.18 + +status "Creating source directory '${SRC_DIR}' and build directory '${BUILD_DIR}'" +mkdir -p ${SRC_DIR} +mkdir -p ${BUILD_DIR} + +status "Cloning to source directory '${SRC_DIR}'" +cd ${SRC_DIR} +git clone ${CLONE_SRC_DIR} . + +status "Fetching dependencies" +cp standalone.gclient .gclient +with_retry gclient sync + +status "Linting" +./tools/lint + +status "Configuring build system" +if [ "$BUILD_SYSTEM" == "cmake" ]; then + using cmake-3.17.2 + + COMMON_CMAKE_FLAGS="" + COMMON_CMAKE_FLAGS+=" -DCMAKE_BUILD_TYPE=${BUILD_TYPE}" + COMMON_CMAKE_FLAGS+=" -DTINT_DOCS_WARN_AS_ERROR=1" + COMMON_CMAKE_FLAGS+=" -DTINT_BUILD_BENCHMARKS=1" + + if [ "$BUILD_TOOLCHAIN" == "clang" ]; then + using clang-10.0.0 + COMMON_CMAKE_FLAGS+=" -DTINT_BUILD_FUZZERS=1" + COMMON_CMAKE_FLAGS+=" -DTINT_BUILD_SPIRV_TOOLS_FUZZER=1" + COMMON_CMAKE_FLAGS+=" -DTINT_BUILD_AST_FUZZER=1" + COMMON_CMAKE_FLAGS+=" -DTINT_BUILD_REGEX_FUZZER=1" + elif [ "$BUILD_TOOLCHAIN" == "gcc" ]; then + using gcc-9 + fi + + if [ "$BUILD_SANITIZER" == "asan" ]; then + COMMON_CMAKE_FLAGS+=" -DTINT_ENABLE_ASAN=1" + elif [ "$BUILD_SANITIZER" == "ubsan" ]; then + COMMON_CMAKE_FLAGS+=" -DTINT_ENABLE_UBSAN=1" + export UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 + fi + + cd ${BUILD_DIR} + + status "Running Doxygen" + echo "NOTE: This will fail on first warning. Run with -DTINT_DOCS_WARN_AS_ERROR=OFF to see all warnings". + echo "" + show_cmds + # NOTE: If we upgrade Doxygen to a more recent version, we can set DOXYGEN_WARN_AS_ERROR to + # "FAIL_ON_WARNINGS" instead of "YES" in our CMakeLists.txt so see all warnings, and then + # fail. See https://www.doxygen.nl/manual/config.html#cfg_warn_as_error + cmake ${SRC_DIR} ${CMAKE_FLAGS} ${COMMON_CMAKE_FLAGS} + cmake --build . --target tint-docs + hide_cmds + + status "Building tint in '${BUILD_DIR}'" + show_cmds + cmake ${SRC_DIR} ${CMAKE_FLAGS} ${COMMON_CMAKE_FLAGS} + cmake --build . -- --jobs=$(nproc) + hide_cmds + + status "Running tint_unittests" + show_cmds + ./tint_unittests + hide_cmds + + if [ -f ./tint_ast_fuzzer_unittests ]; then + status "Running tint_ast_fuzzer_unittests" + show_cmds + ./tint_ast_fuzzer_unittests + hide_cmds + fi + + if [ -f ./tint_regex_fuzzer_unittests ]; then + status "Running tint_regex_fuzzer_unittests" + show_cmds + ./tint_regex_fuzzer_unittests + hide_cmds + fi + + status "Testing test/tint/test-all.sh" + show_cmds + ${SRC_DIR}/test/tint/test-all.sh "${BUILD_DIR}/tint" --verbose + hide_cmds + + status "Checking _other.cc files also build" + show_cmds + cmake ${SRC_DIR} ${CMAKE_FLAGS} ${COMMON_CMAKE_FLAGS} -DTINT_BUILD_AS_OTHER_OS=ON + cmake --build . -- --jobs=$(nproc) + cmake ${SRC_DIR} ${CMAKE_FLAGS} ${COMMON_CMAKE_FLAGS} -DTINT_BUILD_AS_OTHER_OS=OFF + hide_cmds + + status "Checking disabling all readers and writers also builds" + show_cmds + cmake ${SRC_DIR} ${CMAKE_FLAGS} ${COMMON_CMAKE_FLAGS} -DTINT_BUILD_SPV_READER=OFF -DTINT_BUILD_SPV_WRITER=OFF -DTINT_BUILD_WGSL_READER=OFF -DTINT_BUILD_WGSL_WRITER=OFF -DTINT_BUILD_MSL_WRITER=OFF -DTINT_BUILD_HLSL_WRITER=OFF -DTINT_BUILD_BENCHMARKS=OFF + cmake --build . -- --jobs=$(nproc) + cmake ${SRC_DIR} ${CMAKE_FLAGS} ${COMMON_CMAKE_FLAGS} -DTINT_BUILD_SPV_READER=ON -DTINT_BUILD_SPV_WRITER=ON -DTINT_BUILD_WGSL_READER=ON -DTINT_BUILD_WGSL_WRITER=ON -DTINT_BUILD_MSL_WRITER=ON -DTINT_BUILD_HLSL_WRITER=ON -DTINT_BUILD_BENCHMARKS=ON + hide_cmds +else + status "Unsupported build system: $BUILD_SYSTEM" + exit 1 +fi + +status "Done"
diff --git a/kokoro/windows/build.bat b/kokoro/windows/build.bat new file mode 100644 index 0000000..c6df367 --- /dev/null +++ b/kokoro/windows/build.bat
@@ -0,0 +1,167 @@ +@rem Copyright 2021 The Tint Authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. + +@echo off +SETLOCAL ENABLEDELAYEDEXPANSION + +goto :main + +:task_begin +set TASK_NAME=%~1 +echo %TASK_NAME% starting at %Time% +exit /b 0 + +:print_last_task_duration +if not "%TASK_NAME%" == "" ( + echo %TASK_NAME% completed at %Time% +) +exit /b 0 + +:status +echo. +echo. +call :print_last_task_duration +echo. +echo ***************************************************************** +echo %~1 +echo ***************************************************************** +echo. +call :task_begin "%~1" +exit /b 0 + +:main + +set ORIGINAL_SRC_DIR= %~dp0\..\.. +set TEMP_DIR=%TEMP%\tint-temp +set SRC_DIR="%TEMP_DIR%\tint-src" +set BUILD_DIR="%TEMP_DIR%\tint-build" + +cd /d %ORIGINAL_SRC_DIR% +if not exist ".git\" ( + echo "ORIGINAL_SRC_DIR should point to project root: %ORIGINAL_SRC_DIR%" + goto :error +) + +if exist %TEMP_DIR% ( + call :status "Deleting %TEMP_DIR%" + del /q/f/s %TEMP_DIR% > NUL || goto :error + rmdir /q/s %TEMP_DIR% > NUL || goto :error +) +mkdir %TEMP_DIR% || goto :error + +call :status "Fetching and installing DXC" +@echo on +set DXC_RELEASE="https://github.com/microsoft/DirectXShaderCompiler/releases/download/v1.6.2112/dxc_2021_12_08.zip" +curl -k -L %DXC_RELEASE% --output "%TEMP_DIR%\dxc_release.zip" || goto :error +powershell.exe -Command "Expand-Archive -LiteralPath '%TEMP_DIR%\dxc_release.zip' -DestinationPath '%TEMP_DIR%\dxc'" || goto :error +set DXC_PATH=%TEMP_DIR%\dxc\bin\x64 + +rem Patch with artifact build that contains fixes not present in the release build +set DXC_ARTIFACT="https://ci.appveyor.com/api/projects/dnovillo/directxshadercompiler/artifacts/build%%2FRelease%%2Fdxc-artifacts.zip?branch=master&pr=false&job=image%%3A%%20Visual%%20Studio%%202019" +curl -k -L %DXC_ARTIFACT% --output "%TEMP_DIR%\dxc_artifact.zip" || goto :error +powershell.exe -Command "Expand-Archive -Force -LiteralPath '%TEMP_DIR%\dxc_artifact.zip' -DestinationPath '%TEMP_DIR%\dxc_artifact'" || goto :error +move /Y %TEMP_DIR%\dxc_artifact\bin\* %DXC_PATH% +@echo off + +call :status "Fetching and installing Windows SDK for d3dcompiler DLL" +@echo on +set WINSDK_DLL_INSTALLER=https://go.microsoft.com/fwlink/?linkid=2164145 +set WINSDK_VERSION=10.0.20348.0 +curl -k -L %WINSDK_DLL_INSTALLER% --output "%TEMP_DIR%\winsdksetup.exe" || goto :error +start "download" /wait "%TEMP_DIR%\winsdksetup.exe" /quiet /norestart /ceip off /features OptionId.DesktopCPPx64 /layout "%TEMP_DIR%\winsdkinstall" || goto :error +start "install" /wait "%TEMP_DIR%\winsdkinstall\Installers\Windows SDK for Windows Store Apps Tools-x86_en-us.msi" || goto :error +set D3DCOMPILER_PATH=C:\Program Files (x86)\Windows Kits\10\bin\%WINSDK_VERSION%\x64 +@echo off + +call :status "Installing depot_tools" +@echo on +pushd %TEMP_DIR% +rem For Windows, we must download and extract a bundle. +rem See https://chromium.googlesource.com/chromium/src/+/HEAD/docs/windows_build_instructions.md#install +powershell -Command "(New-Object Net.WebClient).DownloadFile('https://storage.googleapis.com/chrome-infra/depot_tools.zip', 'depot_tools.zip')" || goto :error +powershell -Command "Expand-Archive -Force 'depot_tools.zip' 'depot_tools'" || goto :error +rem Run gclient once to install deps +set PATH=%TEMP_DIR%\depot_tools;%PATH% +set DEPOT_TOOLS_UPDATE=1 +set DEPOT_TOOLS_WIN_TOOLCHAIN=0 +call gclient || goto :error +@echo off +popd + +call :status "Cloning to clean source directory" +@echo on +mkdir %SRC_DIR% || goto :error +cd /d %SRC_DIR% || goto :error +call git clone %ORIGINAL_SRC_DIR% . || goto :error +@echo off + +call :status "Fetching dependencies" +@echo on +copy standalone.gclient .gclient || goto :error +call gclient sync || goto :error +@echo off + +call :status "Configuring build system" +@echo on +mkdir %BUILD_DIR% +cd /d %BUILD_DIR% +set COMMON_CMAKE_FLAGS=-DTINT_BUILD_DOCS=O -DTINT_BUILD_BENCHMARKS=1 -DCMAKE_BUILD_TYPE=%BUILD_TYPE% +@echo off + +call :status "Building tint" +@echo on +rem Disable msbuild "Intermediate or Output directory cannot reside in Temporary directory" +set IgnoreWarnIntDirInTempDetected=true +rem Add Python3 to path as this Kokoro image only has Python2 in it +set PATH=C:\Python37;%PATH% +rem To use ninja with CMake requires VC env vars +call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat" +@echo on +rem Note that we need to specify the C and C++ compiler only because Cygwin is in PATH and CMake finds GCC and picks that over MSVC +cmake %SRC_DIR% -G "Ninja" -DCMAKE_C_COMPILER="cl.exe" -DCMAKE_CXX_COMPILER="cl.exe" %COMMON_CMAKE_FLAGS% || goto :error +cmake --build . || goto :error +call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat" /clean_env +@echo off + +call :status "Running tint_unittests" +@echo on +tint_unittests.exe || goto :error +@echo off + +call :status "Testing test/tint/test-all.sh" +@echo on +cd /d %SRC_DIR% || goto :error +rem Run tests with DXC and Metal validation +set OLD_PATH=%PATH% +set PATH=C:\Program Files\Metal Developer Tools\macos\bin;%PATH% +where metal.exe +set PATH=%DXC_PATH%;%OLD_PATH% +where dxc.exe dxil.dll +call git bash -- ./test/tint/test-all.sh ../tint-build/tint.exe --verbose || goto :error +@echo on +set PATH=%OLD_PATH% +rem Run again to test with FXC validation +set PATH=%D3DCOMPILER_PATH%;%OLD_PATH% +where d3dcompiler_47.dll +call git bash -- ./test/tint/test-all.sh ../tint-build/tint.exe --verbose --format hlsl --fxc || goto :error +@echo on +set PATH=%OLD_PATH% +@echo off + +call :status "Done" +exit /b 0 + +:error +echo BUILD FAILED! errorlevel: %errorlevel% +exit /b %errorlevel%
diff --git a/kokoro/windows/cmake-msvc2019-debug/build.bat b/kokoro/windows/cmake-msvc2019-debug/build.bat new file mode 100644 index 0000000..25db347 --- /dev/null +++ b/kokoro/windows/cmake-msvc2019-debug/build.bat
@@ -0,0 +1,18 @@ +@rem Copyright 2021 The Tint Authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. + +@echo on +set BUILD_TYPE=Debug +call %~dp0\..\build.bat +exit /b %errorlevel%
diff --git a/kokoro/windows/cmake-msvc2019-debug/presubmit.cfg b/kokoro/windows/cmake-msvc2019-debug/presubmit.cfg new file mode 100644 index 0000000..c977aa8 --- /dev/null +++ b/kokoro/windows/cmake-msvc2019-debug/presubmit.cfg
@@ -0,0 +1,3 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +build_file: "tint/kokoro/windows/cmake-msvc2019-debug/build.bat"
diff --git a/kokoro/windows/cmake-msvc2019-release/build.bat b/kokoro/windows/cmake-msvc2019-release/build.bat new file mode 100644 index 0000000..a2f6072 --- /dev/null +++ b/kokoro/windows/cmake-msvc2019-release/build.bat
@@ -0,0 +1,18 @@ +@rem Copyright 2021 The Tint Authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. + +@echo on +set BUILD_TYPE=Release +call %~dp0\..\build.bat +exit /b %errorlevel%
diff --git a/kokoro/windows/cmake-msvc2019-release/presubmit.cfg b/kokoro/windows/cmake-msvc2019-release/presubmit.cfg new file mode 100644 index 0000000..00acdd6 --- /dev/null +++ b/kokoro/windows/cmake-msvc2019-release/presubmit.cfg
@@ -0,0 +1,3 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +build_file: "tint/kokoro/windows/cmake-msvc2019-release/build.bat"
diff --git a/scripts/dawn_overrides_with_defaults.gni b/scripts/dawn_overrides_with_defaults.gni index c667693..46f44ef 100644 --- a/scripts/dawn_overrides_with_defaults.gni +++ b/scripts/dawn_overrides_with_defaults.gni
@@ -75,11 +75,6 @@ dawn_vulkan_validation_layers_dir = "" } -if (!defined(dawn_tint_dir)) { - # Default to Tint being Dawn's DEPS - dawn_tint_dir = "${dawn_root}/third_party/tint" -} - if (!defined(dawn_abseil_dir)) { dawn_abseil_dir = "//third_party/abseil-cpp" }
diff --git a/src/dawn/native/BUILD.gn b/src/dawn/native/BUILD.gn index ce7d97f..5d97a8e 100644 --- a/src/dawn/native/BUILD.gn +++ b/src/dawn/native/BUILD.gn
@@ -163,7 +163,7 @@ "${dawn_root}/src/dawn/common", "${dawn_spirv_tools_dir}:spvtools_opt", "${dawn_spirv_tools_dir}:spvtools_val", - "${dawn_tint_dir}/src/tint:libtint", + "${dawn_root}/src/tint:libtint", ] defines = [] libs = []
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn new file mode 100644 index 0000000..58ec6a4 --- /dev/null +++ b/src/tint/BUILD.gn
@@ -0,0 +1,799 @@ +# Copyright 2021 The Tint Authors +# +# 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("//build_overrides/build.gni") +import("../../tint_overrides_with_defaults.gni") + +############################################################################### +# Common - Configs, etc. shared across targets +############################################################################### + +config("tint_common_config") { + include_dirs = [ + "${target_gen_dir}", + "${tint_root_dir}/", + "${tint_spirv_headers_dir}/include", + "${tint_spirv_tools_dir}/", + "${tint_spirv_tools_dir}/include", + ] +} + +config("tint_public_config") { + defines = [] + if (tint_build_spv_reader) { + defines += [ "TINT_BUILD_SPV_READER=1" ] + } else { + defines += [ "TINT_BUILD_SPV_READER=0" ] + } + + if (tint_build_spv_writer) { + defines += [ "TINT_BUILD_SPV_WRITER=1" ] + } else { + defines += [ "TINT_BUILD_SPV_WRITER=0" ] + } + + if (tint_build_wgsl_reader) { + defines += [ "TINT_BUILD_WGSL_READER=1" ] + } else { + defines += [ "TINT_BUILD_WGSL_READER=0" ] + } + + if (tint_build_wgsl_writer) { + defines += [ "TINT_BUILD_WGSL_WRITER=1" ] + } else { + defines += [ "TINT_BUILD_WGSL_WRITER=0" ] + } + + if (tint_build_msl_writer) { + defines += [ "TINT_BUILD_MSL_WRITER=1" ] + } else { + defines += [ "TINT_BUILD_MSL_WRITER=0" ] + } + + if (tint_build_hlsl_writer) { + defines += [ "TINT_BUILD_HLSL_WRITER=1" ] + } else { + defines += [ "TINT_BUILD_HLSL_WRITER=0" ] + } + + if (tint_build_glsl_writer) { + defines += [ "TINT_BUILD_GLSL_WRITER=1" ] + } else { + defines += [ "TINT_BUILD_GLSL_WRITER=0" ] + } + + include_dirs = [ + "${tint_root_dir}/", + "${tint_root_dir}/include/", + "${tint_spirv_headers_dir}/include", + ] +} + +config("tint_config") { + include_dirs = [] + if (tint_build_spv_reader || tint_build_spv_writer) { + include_dirs += [ "${tint_spirv_tools_dir}/include/" ] + } +} + +############################################################################### +# Helper library for IO operations +# Only to be used by tests and sample executable +############################################################################### +source_set("tint_utils_io") { + sources = [ + "utils/io/command.h", + "utils/io/tmpfile.h", + ] + + if (is_linux || is_mac) { + sources += [ "utils/io/command_posix.cc" ] + sources += [ "utils/io/tmpfile_posix.cc" ] + } else if (is_win) { + sources += [ "utils/io/command_windows.cc" ] + sources += [ "utils/io/tmpfile_windows.cc" ] + } else { + sources += [ "utils/io/command_other.cc" ] + sources += [ "utils/io/tmpfile_other.cc" ] + } + + public_deps = [ ":libtint_core_all_src" ] +} + +############################################################################### +# Helper library for validating generated shaders +# As this depends on tint_utils_io, this is only to be used by tests and sample +# executable +############################################################################### +source_set("tint_val") { + sources = [ + "val/hlsl.cc", + "val/msl.cc", + "val/val.h", + ] + public_deps = [ ":tint_utils_io" ] +} + +############################################################################### +# Library - Tint core and optional modules of libtint +############################################################################### +# libtint source sets are divided into a non-optional core in :libtint_core_src +# and optional :libtint_*_src subsets, because ninja does not like having +# multiple source files with the same name, like function.cc, in the same +# source set +# target. +# +# Targets that want to use tint as a library should depend on ":libtint" and +# use the build flags to control what is included, instead of trying to specify +# the subsets that they want. + +template("libtint_source_set") { + source_set(target_name) { + forward_variables_from(invoker, "*", [ "configs" ]) + + if (!defined(invoker.deps)) { + deps = [] + } + deps += [ + "${tint_spirv_headers_dir}:spv_headers", + "${tint_spirv_tools_dir}:spvtools_core_enums_unified1", + "${tint_spirv_tools_dir}:spvtools_core_tables_unified1", + "${tint_spirv_tools_dir}:spvtools_headers", + "${tint_spirv_tools_dir}:spvtools_language_header_cldebuginfo100", + "${tint_spirv_tools_dir}:spvtools_language_header_debuginfo", + "${tint_spirv_tools_dir}:spvtools_language_header_vkdebuginfo100", + ] + + if (defined(invoker.configs)) { + configs += invoker.configs + } + configs += [ ":tint_common_config" ] + if (build_with_chromium) { + configs -= [ "//build/config/compiler:chromium_code" ] + configs += [ "//build/config/compiler:no_chromium_code" ] + } + + if (!defined(invoker.public_configs)) { + public_configs = [] + } + public_configs += [ ":tint_public_config" ] + } +} + +libtint_source_set("libtint_core_all_src") { + sources = [ + "ast/access.cc", + "ast/access.h", + "ast/alias.cc", + "ast/alias.h", + "ast/array.cc", + "ast/array.h", + "ast/assignment_statement.cc", + "ast/assignment_statement.h", + "ast/ast_type.cc", # TODO(bclayton) - rename to type.cc + "ast/atomic.cc", + "ast/atomic.h", + "ast/attribute.cc", + "ast/attribute.h", + "ast/binary_expression.cc", + "ast/binary_expression.h", + "ast/binding_attribute.cc", + "ast/binding_attribute.h", + "ast/bitcast_expression.cc", + "ast/bitcast_expression.h", + "ast/block_statement.cc", + "ast/block_statement.h", + "ast/bool.cc", + "ast/bool.h", + "ast/bool_literal_expression.cc", + "ast/bool_literal_expression.h", + "ast/break_statement.cc", + "ast/break_statement.h", + "ast/builtin.cc", + "ast/builtin.h", + "ast/builtin_attribute.cc", + "ast/builtin_attribute.h", + "ast/call_expression.cc", + "ast/call_expression.h", + "ast/call_statement.cc", + "ast/call_statement.h", + "ast/case_statement.cc", + "ast/case_statement.h", + "ast/compound_assignment_statement.cc", + "ast/compound_assignment_statement.h", + "ast/continue_statement.cc", + "ast/continue_statement.h", + "ast/depth_multisampled_texture.cc", + "ast/depth_multisampled_texture.h", + "ast/depth_texture.cc", + "ast/depth_texture.h", + "ast/disable_validation_attribute.cc", + "ast/disable_validation_attribute.h", + "ast/discard_statement.cc", + "ast/discard_statement.h", + "ast/else_statement.cc", + "ast/else_statement.h", + "ast/expression.cc", + "ast/expression.h", + "ast/external_texture.cc", + "ast/external_texture.h", + "ast/f32.cc", + "ast/f32.h", + "ast/fallthrough_statement.cc", + "ast/fallthrough_statement.h", + "ast/float_literal_expression.cc", + "ast/float_literal_expression.h", + "ast/for_loop_statement.cc", + "ast/for_loop_statement.h", + "ast/function.cc", + "ast/function.h", + "ast/group_attribute.cc", + "ast/group_attribute.h", + "ast/i32.cc", + "ast/i32.h", + "ast/id_attribute.cc", + "ast/id_attribute.h", + "ast/identifier_expression.cc", + "ast/identifier_expression.h", + "ast/if_statement.cc", + "ast/if_statement.h", + "ast/index_accessor_expression.cc", + "ast/index_accessor_expression.h", + "ast/int_literal_expression.cc", + "ast/int_literal_expression.h", + "ast/internal_attribute.cc", + "ast/internal_attribute.h", + "ast/interpolate_attribute.cc", + "ast/interpolate_attribute.h", + "ast/invariant_attribute.cc", + "ast/invariant_attribute.h", + "ast/literal_expression.cc", + "ast/literal_expression.h", + "ast/location_attribute.cc", + "ast/location_attribute.h", + "ast/loop_statement.cc", + "ast/loop_statement.h", + "ast/matrix.cc", + "ast/matrix.h", + "ast/member_accessor_expression.cc", + "ast/member_accessor_expression.h", + "ast/module.cc", + "ast/module.h", + "ast/multisampled_texture.cc", + "ast/multisampled_texture.h", + "ast/node.cc", + "ast/node.h", + "ast/phony_expression.cc", + "ast/phony_expression.h", + "ast/pipeline_stage.cc", + "ast/pipeline_stage.h", + "ast/pointer.cc", + "ast/pointer.h", + "ast/return_statement.cc", + "ast/return_statement.h", + "ast/sampled_texture.cc", + "ast/sampled_texture.h", + "ast/sampler.cc", + "ast/sampler.h", + "ast/sint_literal_expression.cc", + "ast/sint_literal_expression.h", + "ast/stage_attribute.cc", + "ast/stage_attribute.h", + "ast/statement.cc", + "ast/statement.h", + "ast/storage_class.cc", + "ast/storage_class.h", + "ast/storage_texture.cc", + "ast/storage_texture.h", + "ast/stride_attribute.cc", + "ast/stride_attribute.h", + "ast/struct.cc", + "ast/struct.h", + "ast/struct_member.cc", + "ast/struct_member.h", + "ast/struct_member_align_attribute.cc", + "ast/struct_member_align_attribute.h", + "ast/struct_member_offset_attribute.cc", + "ast/struct_member_offset_attribute.h", + "ast/struct_member_size_attribute.cc", + "ast/struct_member_size_attribute.h", + "ast/switch_statement.cc", + "ast/switch_statement.h", + "ast/texture.cc", + "ast/texture.h", + "ast/traverse_expressions.h", + "ast/type.h", + "ast/type_decl.cc", + "ast/type_decl.h", + "ast/type_name.cc", + "ast/type_name.h", + "ast/u32.cc", + "ast/u32.h", + "ast/uint_literal_expression.cc", + "ast/uint_literal_expression.h", + "ast/unary_op.cc", + "ast/unary_op.h", + "ast/unary_op_expression.cc", + "ast/unary_op_expression.h", + "ast/variable.cc", + "ast/variable.h", + "ast/variable_decl_statement.cc", + "ast/variable_decl_statement.h", + "ast/vector.cc", + "ast/vector.h", + "ast/void.cc", + "ast/void.h", + "ast/workgroup_attribute.cc", + "ast/workgroup_attribute.h", + "builtin_table.cc", + "builtin_table.h", + "builtin_table.inl", + "castable.cc", + "castable.h", + "clone_context.cc", + "clone_context.h", + "debug.cc", + "debug.h", + "demangler.cc", + "demangler.h", + "diagnostic/diagnostic.cc", + "diagnostic/diagnostic.h", + "diagnostic/formatter.cc", + "diagnostic/formatter.h", + "diagnostic/printer.cc", + "diagnostic/printer.h", + "inspector/entry_point.cc", + "inspector/entry_point.h", + "inspector/inspector.cc", + "inspector/inspector.h", + "inspector/resource_binding.cc", + "inspector/resource_binding.h", + "inspector/scalar.cc", + "inspector/scalar.h", + "program.cc", + "program.h", + "program_builder.cc", + "program_builder.h", + "program_id.cc", + "program_id.h", + "reader/reader.cc", + "reader/reader.h", + "resolver/dependency_graph.cc", + "resolver/dependency_graph.h", + "resolver/resolver.cc", + "resolver/resolver.h", + "resolver/resolver_constants.cc", + "resolver/resolver_validation.cc", + "scope_stack.h", + "sem/array.h", + "sem/atomic_type.h", + "sem/behavior.h", + "sem/binding_point.h", + "sem/bool_type.h", + "sem/builtin.h", + "sem/builtin_type.h", + "sem/call.h", + "sem/call_target.h", + "sem/constant.h", + "sem/depth_multisampled_texture_type.h", + "sem/depth_texture_type.h", + "sem/expression.h", + "sem/external_texture_type.h", + "sem/f32_type.h", + "sem/for_loop_statement.h", + "sem/i32_type.h", + "sem/if_statement.h", + "sem/info.h", + "sem/loop_statement.h", + "sem/matrix_type.h", + "sem/module.h", + "sem/multisampled_texture_type.h", + "sem/node.h", + "sem/parameter_usage.h", + "sem/pipeline_stage_set.h", + "sem/pointer_type.h", + "sem/reference_type.h", + "sem/sampled_texture_type.h", + "sem/sampler_texture_pair.h", + "sem/sampler_type.h", + "sem/storage_texture_type.h", + "sem/switch_statement.h", + "sem/texture_type.h", + "sem/type.h", + "sem/type_constructor.h", + "sem/type_conversion.h", + "sem/type_manager.h", + "sem/type_mappings.h", + "sem/u32_type.h", + "sem/vector_type.h", + "sem/void_type.h", + "source.cc", + "source.h", + "symbol.cc", + "symbol.h", + "symbol_table.cc", + "symbol_table.h", + "text/unicode.cc", + "text/unicode.h", + "traits.h", + "transform/add_empty_entry_point.cc", + "transform/add_empty_entry_point.h", + "transform/add_spirv_block_attribute.cc", + "transform/add_spirv_block_attribute.h", + "transform/array_length_from_uniform.cc", + "transform/array_length_from_uniform.h", + "transform/binding_remapper.cc", + "transform/binding_remapper.h", + "transform/builtin_polyfill.cc", + "transform/builtin_polyfill.h", + "transform/calculate_array_length.cc", + "transform/calculate_array_length.h", + "transform/canonicalize_entry_point_io.cc", + "transform/canonicalize_entry_point_io.h", + "transform/combine_samplers.cc", + "transform/combine_samplers.h", + "transform/decompose_memory_access.cc", + "transform/decompose_memory_access.h", + "transform/decompose_strided_array.cc", + "transform/decompose_strided_array.h", + "transform/decompose_strided_matrix.cc", + "transform/decompose_strided_matrix.h", + "transform/first_index_offset.cc", + "transform/first_index_offset.h", + "transform/fold_constants.cc", + "transform/fold_constants.h", + "transform/fold_trivial_single_use_lets.cc", + "transform/fold_trivial_single_use_lets.h", + "transform/for_loop_to_loop.cc", + "transform/for_loop_to_loop.h", + "transform/expand_compound_assignment.cc", + "transform/expand_compound_assignment.h", + "transform/localize_struct_array_assignment.cc", + "transform/localize_struct_array_assignment.h", + "transform/loop_to_for_loop.cc", + "transform/loop_to_for_loop.h", + "transform/manager.cc", + "transform/manager.h", + "transform/module_scope_var_to_entry_point_param.cc", + "transform/module_scope_var_to_entry_point_param.h", + "transform/multiplanar_external_texture.cc", + "transform/multiplanar_external_texture.h", + "transform/num_workgroups_from_uniform.cc", + "transform/num_workgroups_from_uniform.h", + "transform/promote_initializers_to_const_var.cc", + "transform/promote_initializers_to_const_var.h", + "transform/promote_side_effects_to_decl.cc", + "transform/promote_side_effects_to_decl.h", + "transform/remove_continue_in_switch.cc", + "transform/remove_continue_in_switch.h", + "transform/remove_phonies.cc", + "transform/remove_phonies.h", + "transform/remove_unreachable_statements.cc", + "transform/remove_unreachable_statements.h", + "transform/renamer.cc", + "transform/renamer.h", + "transform/robustness.cc", + "transform/robustness.h", + "transform/simplify_pointers.cc", + "transform/simplify_pointers.h", + "transform/single_entry_point.cc", + "transform/single_entry_point.h", + "transform/transform.cc", + "transform/transform.h", + "transform/unshadow.cc", + "transform/unshadow.h", + "transform/unwind_discard_functions.cc", + "transform/unwind_discard_functions.h", + "transform/utils/get_insertion_point.cc", + "transform/utils/get_insertion_point.h", + "transform/utils/hoist_to_decl_before.cc", + "transform/utils/hoist_to_decl_before.h", + "transform/var_for_dynamic_index.cc", + "transform/var_for_dynamic_index.h", + "transform/vectorize_scalar_matrix_constructors.cc", + "transform/vectorize_scalar_matrix_constructors.h", + "transform/vertex_pulling.cc", + "transform/vertex_pulling.h", + "transform/wrap_arrays_in_structs.cc", + "transform/wrap_arrays_in_structs.h", + "transform/zero_init_workgroup_memory.cc", + "transform/zero_init_workgroup_memory.h", + "utils/block_allocator.h", + "utils/crc32.h", + "utils/debugger.cc", + "utils/debugger.h", + "utils/enum_set.h", + "utils/hash.h", + "utils/map.h", + "utils/math.h", + "utils/scoped_assignment.h", + "utils/string.h", + "utils/unique_allocator.h", + "utils/unique_vector.h", + "writer/append_vector.cc", + "writer/append_vector.h", + "writer/array_length_from_uniform_options.cc", + "writer/array_length_from_uniform_options.h", + "writer/float_to_string.cc", + "writer/float_to_string.h", + "writer/generate_external_texture_bindings.cc", + "writer/generate_external_texture_bindings.h", + "writer/text.cc", + "writer/text.h", + "writer/text_generator.cc", + "writer/text_generator.h", + "writer/writer.cc", + "writer/writer.h", + ] + + if (is_linux) { + sources += [ "diagnostic/printer_linux.cc" ] + } else if (is_win) { + sources += [ "diagnostic/printer_windows.cc" ] + } else { + sources += [ "diagnostic/printer_other.cc" ] + } +} + +libtint_source_set("libtint_sem_src") { + sources = [ + "sem/array.cc", + "sem/array.h", + "sem/atomic_type.cc", + "sem/atomic_type.h", + "sem/behavior.cc", + "sem/behavior.h", + "sem/binding_point.h", + "sem/block_statement.cc", + "sem/bool_type.cc", + "sem/bool_type.h", + "sem/builtin.cc", + "sem/builtin.h", + "sem/builtin_type.cc", + "sem/builtin_type.h", + "sem/call.cc", + "sem/call.h", + "sem/call_target.cc", + "sem/call_target.h", + "sem/constant.cc", + "sem/constant.h", + "sem/depth_multisampled_texture_type.cc", + "sem/depth_multisampled_texture_type.h", + "sem/depth_texture_type.cc", + "sem/depth_texture_type.h", + "sem/expression.cc", + "sem/expression.h", + "sem/external_texture_type.cc", + "sem/external_texture_type.h", + "sem/f32_type.cc", + "sem/f32_type.h", + "sem/for_loop_statement.cc", + "sem/for_loop_statement.h", + "sem/function.cc", + "sem/i32_type.cc", + "sem/i32_type.h", + "sem/if_statement.cc", + "sem/if_statement.h", + "sem/info.cc", + "sem/info.h", + "sem/loop_statement.cc", + "sem/loop_statement.h", + "sem/matrix_type.cc", + "sem/matrix_type.h", + "sem/member_accessor_expression.cc", + "sem/module.cc", + "sem/module.h", + "sem/multisampled_texture_type.cc", + "sem/multisampled_texture_type.h", + "sem/node.cc", + "sem/node.h", + "sem/parameter_usage.cc", + "sem/parameter_usage.h", + "sem/pipeline_stage_set.h", + "sem/pointer_type.cc", + "sem/pointer_type.h", + "sem/reference_type.cc", + "sem/reference_type.h", + "sem/sampled_texture_type.cc", + "sem/sampled_texture_type.h", + "sem/sampler_type.cc", + "sem/sampler_type.h", + "sem/statement.cc", + "sem/storage_texture_type.cc", + "sem/storage_texture_type.h", + "sem/struct.cc", + "sem/switch_statement.cc", + "sem/switch_statement.h", + "sem/texture_type.cc", + "sem/texture_type.h", + "sem/type.cc", + "sem/type.h", + "sem/type_constructor.cc", + "sem/type_constructor.h", + "sem/type_conversion.cc", + "sem/type_conversion.h", + "sem/type_manager.cc", + "sem/type_manager.h", + "sem/type_mappings.h", + "sem/u32_type.cc", + "sem/u32_type.h", + "sem/variable.cc", + "sem/vector_type.cc", + "sem/vector_type.h", + "sem/void_type.cc", + "sem/void_type.h", + ] + + public_deps = [ ":libtint_core_all_src" ] +} + +libtint_source_set("libtint_core_src") { + public_deps = [ + ":libtint_core_all_src", + ":libtint_sem_src", + ] +} + +libtint_source_set("libtint_spv_reader_src") { + sources = [ + "reader/spirv/construct.cc", + "reader/spirv/construct.h", + "reader/spirv/entry_point_info.cc", + "reader/spirv/entry_point_info.h", + "reader/spirv/enum_converter.cc", + "reader/spirv/enum_converter.h", + "reader/spirv/fail_stream.h", + "reader/spirv/function.cc", + "reader/spirv/function.h", + "reader/spirv/namer.cc", + "reader/spirv/namer.h", + "reader/spirv/parser.cc", + "reader/spirv/parser.h", + "reader/spirv/parser_impl.cc", + "reader/spirv/parser_impl.h", + "reader/spirv/parser_type.cc", + "reader/spirv/parser_type.h", + "reader/spirv/usage.cc", + "reader/spirv/usage.h", + ] + + public_deps = [ + ":libtint_core_src", + "${tint_spirv_tools_dir}/:spvtools_opt", + ] + + public_configs = [ "${tint_spirv_tools_dir}/:spvtools_internal_config" ] +} + +libtint_source_set("libtint_spv_writer_src") { + sources = [ + "writer/spirv/binary_writer.cc", + "writer/spirv/binary_writer.h", + "writer/spirv/builder.cc", + "writer/spirv/builder.h", + "writer/spirv/function.cc", + "writer/spirv/function.h", + "writer/spirv/generator.cc", + "writer/spirv/generator.h", + "writer/spirv/instruction.cc", + "writer/spirv/instruction.h", + "writer/spirv/operand.cc", + "writer/spirv/operand.h", + "writer/spirv/scalar_constant.h", + ] + + public_deps = [ ":libtint_core_src" ] +} + +libtint_source_set("libtint_wgsl_reader_src") { + sources = [ + "reader/wgsl/lexer.cc", + "reader/wgsl/lexer.h", + "reader/wgsl/parser.cc", + "reader/wgsl/parser.h", + "reader/wgsl/parser_impl.cc", + "reader/wgsl/parser_impl.h", + "reader/wgsl/parser_impl_detail.h", + "reader/wgsl/token.cc", + "reader/wgsl/token.h", + ] + + public_deps = [ ":libtint_core_src" ] +} + +libtint_source_set("libtint_wgsl_writer_src") { + sources = [ + "writer/wgsl/generator.cc", + "writer/wgsl/generator.h", + "writer/wgsl/generator_impl.cc", + "writer/wgsl/generator_impl.h", + ] + + public_deps = [ ":libtint_core_src" ] +} + +libtint_source_set("libtint_msl_writer_src") { + sources = [ + "writer/msl/generator.cc", + "writer/msl/generator.h", + "writer/msl/generator_impl.cc", + "writer/msl/generator_impl.h", + ] + + public_deps = [ ":libtint_core_src" ] +} + +libtint_source_set("libtint_hlsl_writer_src") { + sources = [ + "writer/hlsl/generator.cc", + "writer/hlsl/generator.h", + "writer/hlsl/generator_impl.cc", + "writer/hlsl/generator_impl.h", + ] + + public_deps = [ ":libtint_core_src" ] +} + +libtint_source_set("libtint_glsl_writer_src") { + sources = [ + "transform/glsl.cc", + "transform/glsl.h", + "writer/glsl/generator.cc", + "writer/glsl/generator.h", + "writer/glsl/generator_impl.cc", + "writer/glsl/generator_impl.h", + ] + + public_deps = [ ":libtint_core_src" ] +} + +source_set("libtint") { + public_deps = [ ":libtint_core_src" ] + + if (tint_build_spv_reader) { + public_deps += [ ":libtint_spv_reader_src" ] + } + + if (tint_build_spv_writer) { + public_deps += [ ":libtint_spv_writer_src" ] + } + + if (tint_build_wgsl_reader) { + public_deps += [ ":libtint_wgsl_reader_src" ] + } + + if (tint_build_wgsl_writer) { + public_deps += [ ":libtint_wgsl_writer_src" ] + } + + if (tint_build_msl_writer) { + public_deps += [ ":libtint_msl_writer_src" ] + } + + if (tint_build_hlsl_writer) { + public_deps += [ ":libtint_hlsl_writer_src" ] + } + + if (tint_build_glsl_writer) { + public_deps += [ ":libtint_glsl_writer_src" ] + } + + configs += [ ":tint_common_config" ] + public_configs = [ ":tint_public_config" ] + + if (build_with_chromium) { + configs -= [ "//build/config/compiler:chromium_code" ] + configs += [ "//build/config/compiler:no_chromium_code" ] + } +}
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt new file mode 100644 index 0000000..138dfde --- /dev/null +++ b/src/tint/CMakeLists.txt
@@ -0,0 +1,1239 @@ +# Copyright 2020 The Tint Authors. +# +# 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. + +function(tint_spvtools_compile_options TARGET) + # We'll use the optimizer for its nice SPIR-V in-memory representation + target_link_libraries(${TARGET} SPIRV-Tools-opt SPIRV-Tools) + + # We'll be cheating: using internal interfaces to the SPIRV-Tools + # optimizer. + target_include_directories(${TARGET} PRIVATE + ${spirv-tools_SOURCE_DIR} + ${spirv-tools_BINARY_DIR} + ) + + if (${CMAKE_CXX_COMPILER_ID} MATCHES Clang) + # The SPIRV-Tools code is conditioned against C++ and an older version of Clang. + # Suppress warnings triggered in our current compilation environment. + # TODO(dneto): Fix the issues upstream. + target_compile_options(${TARGET} PRIVATE + -Wno-newline-eof + -Wno-sign-conversion + -Wno-old-style-cast + -Wno-weak-vtables + ) + endif() +endfunction() + +## Tint diagnostic utilities. Used by libtint and tint_utils_io. +add_library(tint_diagnostic_utils + debug.cc + debug.h + source.h + source.cc + diagnostic/diagnostic.cc + diagnostic/diagnostic.h + diagnostic/formatter.cc + diagnostic/formatter.h + diagnostic/printer.cc + diagnostic/printer.h + utils/debugger.cc + utils/debugger.h +) +tint_default_compile_options(tint_diagnostic_utils) + +if (TINT_ENABLE_BREAK_IN_DEBUGGER) + set_source_files_properties(utils/debugger.cc + PROPERTIES COMPILE_DEFINITIONS "TINT_ENABLE_BREAK_IN_DEBUGGER=1" ) +endif() + +set(TINT_LIB_SRCS + ../../include/tint/tint.h + ast/access.cc + ast/access.h + ast/attribute.cc + ast/attribute.h + ast/alias.cc + ast/alias.h + ast/index_accessor_expression.cc + ast/index_accessor_expression.h + ast/array.cc + ast/array.h + ast/assignment_statement.cc + ast/assignment_statement.h + ast/atomic.cc + ast/atomic.h + ast/binary_expression.cc + ast/binary_expression.h + ast/binding_attribute.cc + ast/binding_attribute.h + ast/bitcast_expression.cc + ast/bitcast_expression.h + ast/block_statement.cc + ast/block_statement.h + ast/bool_literal_expression.cc + ast/bool_literal_expression.h + ast/bool.cc + ast/bool.h + ast/break_statement.cc + ast/break_statement.h + ast/builtin_attribute.cc + ast/builtin_attribute.h + ast/builtin.cc + ast/builtin.h + ast/call_expression.cc + ast/call_expression.h + ast/call_statement.cc + ast/call_statement.h + ast/case_statement.cc + ast/case_statement.h + ast/compound_assignment_statement.cc + ast/compound_assignment_statement.h + ast/continue_statement.cc + ast/continue_statement.h + ast/depth_multisampled_texture.cc + ast/depth_multisampled_texture.h + ast/disable_validation_attribute.cc + ast/disable_validation_attribute.h + ast/depth_texture.cc + ast/depth_texture.h + ast/discard_statement.cc + ast/discard_statement.h + ast/else_statement.cc + ast/else_statement.h + ast/expression.cc + ast/expression.h + ast/external_texture.cc + ast/external_texture.h + ast/f32.cc + ast/f32.h + ast/fallthrough_statement.cc + ast/fallthrough_statement.h + ast/float_literal_expression.cc + ast/float_literal_expression.h + ast/for_loop_statement.cc + ast/for_loop_statement.h + ast/function.cc + ast/function.h + ast/group_attribute.cc + ast/group_attribute.h + ast/i32.cc + ast/i32.h + ast/id_attribute.cc + ast/id_attribute.h + ast/identifier_expression.cc + ast/identifier_expression.h + ast/if_statement.cc + ast/if_statement.h + ast/int_literal_expression.cc + ast/int_literal_expression.h + ast/internal_attribute.cc + ast/internal_attribute.h + ast/interpolate_attribute.cc + ast/interpolate_attribute.h + ast/invariant_attribute.cc + ast/invariant_attribute.h + ast/literal_expression.cc + ast/literal_expression.h + ast/location_attribute.cc + ast/location_attribute.h + ast/loop_statement.cc + ast/loop_statement.h + ast/matrix.cc + ast/matrix.h + ast/member_accessor_expression.cc + ast/member_accessor_expression.h + ast/module.cc + ast/module.h + ast/multisampled_texture.cc + ast/multisampled_texture.h + ast/node.cc + ast/node.h + ast/phony_expression.cc + ast/phony_expression.h + ast/pipeline_stage.cc + ast/pipeline_stage.h + ast/pointer.cc + ast/pointer.h + ast/return_statement.cc + ast/return_statement.h + ast/sampled_texture.cc + ast/sampled_texture.h + ast/sampler.cc + ast/sampler.h + ast/sint_literal_expression.cc + ast/sint_literal_expression.h + ast/stage_attribute.cc + ast/stage_attribute.h + ast/statement.cc + ast/statement.h + ast/storage_class.cc + ast/storage_class.h + ast/storage_texture.cc + ast/storage_texture.h + ast/stride_attribute.cc + ast/stride_attribute.h + ast/struct_member_align_attribute.cc + ast/struct_member_align_attribute.h + ast/struct_member_offset_attribute.cc + ast/struct_member_offset_attribute.h + ast/struct_member_size_attribute.cc + ast/struct_member_size_attribute.h + ast/struct_member.cc + ast/struct_member.h + ast/struct.cc + ast/struct.h + ast/switch_statement.cc + ast/switch_statement.h + ast/texture.cc + ast/texture.h + ast/traverse_expressions.h + ast/type_name.cc + ast/type_name.h + ast/ast_type.cc # TODO(bclayton) - rename to type.cc + ast/type.h + ast/type_decl.cc + ast/type_decl.h + ast/type_name.cc + ast/type_name.h + ast/u32.cc + ast/u32.h + ast/uint_literal_expression.cc + ast/uint_literal_expression.h + ast/unary_op_expression.cc + ast/unary_op_expression.h + ast/unary_op.cc + ast/unary_op.h + ast/variable_decl_statement.cc + ast/variable_decl_statement.h + ast/variable.cc + ast/variable.h + ast/vector.cc + ast/vector.h + ast/void.cc + ast/void.h + ast/workgroup_attribute.cc + ast/workgroup_attribute.h + builtin_table.cc + builtin_table.h + builtin_table.inl + castable.cc + castable.h + clone_context.cc + clone_context.h + demangler.cc + demangler.h + inspector/entry_point.cc + inspector/entry_point.h + inspector/inspector.cc + inspector/inspector.h + inspector/resource_binding.cc + inspector/resource_binding.h + inspector/scalar.cc + inspector/scalar.h + program_builder.cc + program_builder.h + program_id.cc + program_id.h + program.cc + program.h + reader/reader.cc + reader/reader.h + resolver/dependency_graph.cc + resolver/dependency_graph.h + resolver/resolver.cc + resolver/resolver_constants.cc + resolver/resolver_validation.cc + resolver/resolver.h + scope_stack.h + sem/array.cc + sem/array.h + sem/atomic_type.cc + sem/atomic_type.h + sem/behavior.cc + sem/behavior.h + sem/binding_point.h + sem/block_statement.cc + sem/block_statement.h + sem/builtin_type.cc + sem/builtin_type.h + sem/builtin.cc + sem/builtin.h + sem/call_target.cc + sem/call_target.h + sem/call.cc + sem/call.h + sem/constant.cc + sem/constant.h + sem/depth_multisampled_texture_type.cc + sem/depth_multisampled_texture_type.h + sem/expression.cc + sem/expression.h + sem/function.cc + sem/info.cc + sem/info.h + sem/member_accessor_expression.cc + sem/parameter_usage.cc + sem/parameter_usage.h + sem/pipeline_stage_set.h + sem/node.cc + sem/node.h + sem/module.cc + sem/module.h + sem/sampler_texture_pair.h + sem/statement.cc + sem/struct.cc + sem/type_mappings.h + sem/variable.cc + symbol_table.cc + symbol_table.h + symbol.cc + symbol.h + text/unicode.cc + text/unicode.h + traits.h + transform/add_empty_entry_point.cc + transform/add_empty_entry_point.h + transform/add_spirv_block_attribute.cc + transform/add_spirv_block_attribute.h + transform/array_length_from_uniform.cc + transform/array_length_from_uniform.h + transform/binding_remapper.cc + transform/binding_remapper.h + transform/builtin_polyfill.cc + transform/builtin_polyfill.h + transform/calculate_array_length.cc + transform/calculate_array_length.h + transform/combine_samplers.cc + transform/combine_samplers.h + transform/canonicalize_entry_point_io.cc + transform/canonicalize_entry_point_io.h + transform/decompose_memory_access.cc + transform/decompose_memory_access.h + transform/decompose_strided_array.cc + transform/decompose_strided_array.h + transform/decompose_strided_matrix.cc + transform/decompose_strided_matrix.h + transform/first_index_offset.cc + transform/first_index_offset.h + transform/fold_constants.cc + transform/fold_constants.h + transform/fold_trivial_single_use_lets.cc + transform/fold_trivial_single_use_lets.h + transform/localize_struct_array_assignment.cc + transform/localize_struct_array_assignment.h + transform/for_loop_to_loop.cc + transform/for_loop_to_loop.h + transform/expand_compound_assignment.cc + transform/expand_compound_assignment.h + transform/glsl.cc + transform/glsl.h + transform/loop_to_for_loop.cc + transform/loop_to_for_loop.h + transform/manager.cc + transform/manager.h + transform/module_scope_var_to_entry_point_param.cc + transform/module_scope_var_to_entry_point_param.h + transform/multiplanar_external_texture.cc + transform/multiplanar_external_texture.h + transform/num_workgroups_from_uniform.cc + transform/num_workgroups_from_uniform.h + transform/promote_initializers_to_const_var.cc + transform/promote_initializers_to_const_var.h + transform/promote_side_effects_to_decl.cc + transform/promote_side_effects_to_decl.h + transform/remove_phonies.cc + transform/remove_phonies.h + transform/remove_continue_in_switch.cc + transform/remove_continue_in_switch.h + transform/remove_unreachable_statements.cc + transform/remove_unreachable_statements.h + transform/renamer.cc + transform/renamer.h + transform/robustness.cc + transform/robustness.h + transform/simplify_pointers.cc + transform/simplify_pointers.h + transform/single_entry_point.cc + transform/single_entry_point.h + transform/transform.cc + transform/transform.h + transform/unshadow.cc + transform/unshadow.h + transform/unwind_discard_functions.cc + transform/unwind_discard_functions.h + transform/vectorize_scalar_matrix_constructors.cc + transform/vectorize_scalar_matrix_constructors.h + transform/var_for_dynamic_index.cc + transform/var_for_dynamic_index.h + transform/vertex_pulling.cc + transform/vertex_pulling.h + transform/wrap_arrays_in_structs.cc + transform/wrap_arrays_in_structs.h + transform/zero_init_workgroup_memory.cc + transform/zero_init_workgroup_memory.h + transform/utils/get_insertion_point.cc + transform/utils/get_insertion_point.h + transform/utils/hoist_to_decl_before.cc + transform/utils/hoist_to_decl_before.h + sem/bool_type.cc + sem/bool_type.h + sem/depth_texture_type.cc + sem/depth_texture_type.h + sem/external_texture_type.cc + sem/external_texture_type.h + sem/f32_type.cc + sem/f32_type.h + sem/for_loop_statement.cc + sem/for_loop_statement.h + sem/i32_type.cc + sem/i32_type.h + sem/if_statement.cc + sem/if_statement.h + sem/loop_statement.cc + sem/loop_statement.h + sem/matrix_type.cc + sem/matrix_type.h + sem/multisampled_texture_type.cc + sem/multisampled_texture_type.h + sem/pointer_type.cc + sem/pointer_type.h + sem/reference_type.cc + sem/reference_type.h + sem/sampled_texture_type.cc + sem/sampled_texture_type.h + sem/sampler_type.cc + sem/sampler_type.h + sem/storage_texture_type.cc + sem/storage_texture_type.h + sem/switch_statement.cc + sem/switch_statement.h + sem/texture_type.cc + sem/texture_type.h + sem/type_constructor.cc + sem/type_constructor.h + sem/type_conversion.cc + sem/type_conversion.h + sem/type.cc + sem/type.h + sem/type_manager.cc + sem/type_manager.h + sem/u32_type.cc + sem/u32_type.h + sem/vector_type.cc + sem/vector_type.h + sem/void_type.cc + sem/void_type.h + utils/block_allocator.h + utils/crc32.h + utils/enum_set.h + utils/hash.h + utils/map.h + utils/math.h + utils/scoped_assignment.h + utils/string.h + utils/unique_allocator.h + utils/unique_vector.h + writer/append_vector.cc + writer/append_vector.h + writer/array_length_from_uniform_options.cc + writer/array_length_from_uniform_options.h + writer/float_to_string.cc + writer/float_to_string.h + writer/generate_external_texture_bindings.cc + writer/generate_external_texture_bindings.h + writer/text_generator.cc + writer/text_generator.h + writer/text.cc + writer/text.h + writer/writer.cc + writer/writer.h +) + +if(UNIX) + list(APPEND TINT_LIB_SRCS diagnostic/printer_linux.cc) +elseif(WIN32) + list(APPEND TINT_LIB_SRCS diagnostic/printer_windows.cc) +else() + list(APPEND TINT_LIB_SRCS diagnostic/printer_other.cc) +endif() + +if(${TINT_BUILD_SPV_READER}) + list(APPEND TINT_LIB_SRCS + reader/spirv/construct.h + reader/spirv/construct.cc + reader/spirv/entry_point_info.h + reader/spirv/entry_point_info.cc + reader/spirv/enum_converter.h + reader/spirv/enum_converter.cc + reader/spirv/fail_stream.h + reader/spirv/function.cc + reader/spirv/function.h + reader/spirv/namer.cc + reader/spirv/namer.h + reader/spirv/parser_type.cc + reader/spirv/parser_type.h + reader/spirv/parser.cc + reader/spirv/parser.h + reader/spirv/parser_impl.cc + reader/spirv/parser_impl.h + reader/spirv/usage.cc + reader/spirv/usage.h + ) +endif() + +if(${TINT_BUILD_WGSL_READER}) + list(APPEND TINT_LIB_SRCS + reader/wgsl/lexer.cc + reader/wgsl/lexer.h + reader/wgsl/parser.cc + reader/wgsl/parser.h + reader/wgsl/parser_impl.cc + reader/wgsl/parser_impl.h + reader/wgsl/parser_impl_detail.h + reader/wgsl/token.cc + reader/wgsl/token.h + ) +endif() + +if(${TINT_BUILD_SPV_WRITER}) + list(APPEND TINT_LIB_SRCS + writer/spirv/binary_writer.cc + writer/spirv/binary_writer.h + writer/spirv/builder.cc + writer/spirv/builder.h + writer/spirv/function.cc + writer/spirv/function.h + writer/spirv/generator.cc + writer/spirv/generator.h + writer/spirv/instruction.cc + writer/spirv/instruction.h + writer/spirv/operand.cc + writer/spirv/operand.h + writer/spirv/scalar_constant.h + ) +endif() + +if(${TINT_BUILD_WGSL_WRITER}) + list(APPEND TINT_LIB_SRCS + writer/wgsl/generator.cc + writer/wgsl/generator.h + writer/wgsl/generator_impl.cc + writer/wgsl/generator_impl.h + ) +endif() + +if(${TINT_BUILD_MSL_WRITER}) + list(APPEND TINT_LIB_SRCS + writer/msl/generator.cc + writer/msl/generator.h + writer/msl/generator_impl.cc + writer/msl/generator_impl.h + ) +endif() + +if(${TINT_BUILD_GLSL_WRITER}) + list(APPEND TINT_LIB_SRCS + writer/glsl/generator.cc + writer/glsl/generator.h + writer/glsl/generator_impl.cc + writer/glsl/generator_impl.h + writer/glsl/version.h + ) +endif() + +if(${TINT_BUILD_HLSL_WRITER}) + list(APPEND TINT_LIB_SRCS + writer/hlsl/generator.cc + writer/hlsl/generator.h + writer/hlsl/generator_impl.cc + writer/hlsl/generator_impl.h + ) +endif() + +if(MSVC) + list(APPEND TINT_LIB_SRCS + tint.natvis + ) +endif() + +## Tint IO utilities. Used by tint_val. +add_library(tint_utils_io + utils/io/command_${TINT_OS_CC_SUFFIX}.cc + utils/io/command.h + utils/io/tmpfile_${TINT_OS_CC_SUFFIX}.cc + utils/io/tmpfile.h +) +tint_default_compile_options(tint_utils_io) +target_link_libraries(tint_utils_io tint_diagnostic_utils) + +## Tint validation utilities. Used by tests and the tint executable. +add_library(tint_val + val/hlsl.cc + val/msl.cc + val/val.h +) + +# If we're building on mac / ios and we have CoreGraphics, then we can use the +# metal API to validate our shaders. This is roughly 4x faster than invoking +# the metal shader compiler executable. +if(APPLE) + find_library(LIB_CORE_GRAPHICS CoreGraphics) + if(LIB_CORE_GRAPHICS) + target_sources(tint_val PRIVATE "val/msl_metal.mm") + target_compile_definitions(tint_val PUBLIC "-DTINT_ENABLE_MSL_VALIDATION_USING_METAL_API=1") + target_compile_options(tint_val PRIVATE "-fmodules" "-fcxx-modules") + target_link_options(tint_val PUBLIC "-framework" "CoreGraphics") + endif() +endif() + +tint_default_compile_options(tint_val) +target_link_libraries(tint_val tint_utils_io) + +## Tint library +add_library(libtint ${TINT_LIB_SRCS}) +tint_default_compile_options(libtint) +target_link_libraries(libtint tint_diagnostic_utils) +if (${COMPILER_IS_LIKE_GNU}) + target_compile_options(libtint PRIVATE -fvisibility=hidden) +endif() +if (${TINT_SYMBOL_STORE_DEBUG_NAME}) + target_compile_definitions(libtint PUBLIC "TINT_SYMBOL_STORE_DEBUG_NAME=1") +endif() +set_target_properties(libtint PROPERTIES OUTPUT_NAME "tint") + +if (${TINT_BUILD_FUZZERS}) + # Tint library with fuzzer instrumentation + add_library(libtint-fuzz ${TINT_LIB_SRCS}) + tint_default_compile_options(libtint-fuzz) + target_link_libraries(libtint-fuzz tint_diagnostic_utils) + if (${COMPILER_IS_LIKE_GNU}) + target_compile_options(libtint-fuzz PRIVATE -fvisibility=hidden) + endif() + + if (NOT ${TINT_LIB_FUZZING_ENGINE_LINK_OPTIONS} STREQUAL "") + # This is set when the fuzzers are being built by OSS-Fuzz. In this case the + # variable provides the necessary linker flags, and OSS-Fuzz will take care + # of passing suitable compiler flags. + target_link_options(libtint-fuzz PUBLIC ${TINT_LIB_FUZZING_ENGINE_LINK_OPTIONS}) + else() + # When the fuzzers are being built outside of OSS-Fuzz, specific libFuzzer + # arguments to enable fuzzing are used. + target_compile_options(libtint-fuzz PUBLIC -fsanitize=fuzzer -fsanitize-coverage=trace-cmp) + target_link_options(libtint-fuzz PUBLIC -fsanitize=fuzzer -fsanitize-coverage=trace-cmp) + endif() +endif() + +if(${TINT_BUILD_SPV_READER} OR ${TINT_BUILD_SPV_WRITER}) + tint_spvtools_compile_options(libtint) + if (${TINT_BUILD_FUZZERS}) + tint_spvtools_compile_options(libtint-fuzz) + endif() +endif() + +################################################################################ +# Tests +################################################################################ +if(TINT_BUILD_TESTS) + set(TINT_TEST_SRCS + ast/alias_test.cc + ast/array_test.cc + ast/assignment_statement_test.cc + ast/atomic_test.cc + ast/binary_expression_test.cc + ast/binding_attribute_test.cc + ast/bitcast_expression_test.cc + ast/block_statement_test.cc + ast/bool_literal_expression_test.cc + ast/bool_test.cc + ast/break_statement_test.cc + ast/builtin_attribute_test.cc + ast/builtin_texture_helper_test.cc + ast/builtin_texture_helper_test.h + ast/call_expression_test.cc + ast/call_statement_test.cc + ast/case_statement_test.cc + ast/compound_assignment_statement_test.cc + ast/continue_statement_test.cc + ast/depth_multisampled_texture_test.cc + ast/depth_texture_test.cc + ast/discard_statement_test.cc + ast/else_statement_test.cc + ast/external_texture_test.cc + ast/f32_test.cc + ast/fallthrough_statement_test.cc + ast/float_literal_expression_test.cc + ast/for_loop_statement_test.cc + ast/function_test.cc + ast/group_attribute_test.cc + ast/i32_test.cc + ast/id_attribute_test.cc + ast/identifier_expression_test.cc + ast/if_statement_test.cc + ast/index_accessor_expression_test.cc + ast/int_literal_expression_test.cc + ast/interpolate_attribute_test.cc + ast/invariant_attribute_test.cc + ast/location_attribute_test.cc + ast/loop_statement_test.cc + ast/matrix_test.cc + ast/member_accessor_expression_test.cc + ast/module_clone_test.cc + ast/module_test.cc + ast/multisampled_texture_test.cc + ast/phony_expression_test.cc + ast/pointer_test.cc + ast/return_statement_test.cc + ast/sampled_texture_test.cc + ast/sampler_test.cc + ast/sint_literal_expression_test.cc + ast/stage_attribute_test.cc + ast/storage_texture_test.cc + ast/stride_attribute_test.cc + ast/struct_member_align_attribute_test.cc + ast/struct_member_offset_attribute_test.cc + ast/struct_member_size_attribute_test.cc + ast/struct_member_test.cc + ast/struct_test.cc + ast/switch_statement_test.cc + ast/test_helper.h + ast/texture_test.cc + ast/traverse_expressions_test.cc + ast/u32_test.cc + ast/uint_literal_expression_test.cc + ast/unary_op_expression_test.cc + ast/variable_decl_statement_test.cc + ast/variable_test.cc + ast/vector_test.cc + ast/workgroup_attribute_test.cc + builtin_table_test.cc + castable_test.cc + clone_context_test.cc + debug_test.cc + demangler_test.cc + diagnostic/diagnostic_test.cc + diagnostic/formatter_test.cc + diagnostic/printer_test.cc + program_test.cc + resolver/array_accessor_test.cc + resolver/assignment_validation_test.cc + resolver/atomics_test.cc + resolver/atomics_validation_test.cc + resolver/bitcast_validation_test.cc + resolver/builtins_validation_test.cc + resolver/builtin_test.cc + resolver/builtin_validation_test.cc + resolver/call_test.cc + resolver/call_validation_test.cc + resolver/compound_assignment_validation_test.cc + resolver/compound_statement_test.cc + resolver/control_block_validation_test.cc + resolver/attribute_validation_test.cc + resolver/dependency_graph_test.cc + resolver/entry_point_validation_test.cc + resolver/function_validation_test.cc + resolver/host_shareable_validation_test.cc + resolver/inferred_type_test.cc + resolver/is_host_shareable_test.cc + resolver/is_storeable_test.cc + resolver/pipeline_overridable_constant_test.cc + resolver/ptr_ref_test.cc + resolver/ptr_ref_validation_test.cc + resolver/resolver_behavior_test.cc + resolver/resolver_constants_test.cc + resolver/resolver_test_helper.cc + resolver/resolver_test_helper.h + resolver/resolver_test.cc + resolver/side_effects_test.cc + resolver/storage_class_layout_validation_test.cc + resolver/storage_class_validation_test.cc + resolver/struct_layout_test.cc + resolver/struct_pipeline_stage_use_test.cc + resolver/struct_storage_class_use_test.cc + resolver/type_constructor_validation_test.cc + resolver/type_validation_test.cc + resolver/validation_test.cc + resolver/var_let_test.cc + resolver/var_let_validation_test.cc + scope_stack_test.cc + sem/atomic_type_test.cc + sem/bool_type_test.cc + sem/builtin_test.cc + sem/depth_multisampled_texture_type_test.cc + sem/depth_texture_type_test.cc + sem/external_texture_type_test.cc + sem/f32_type_test.cc + sem/i32_type_test.cc + sem/matrix_type_test.cc + sem/multisampled_texture_type_test.cc + sem/pointer_type_test.cc + sem/reference_type_test.cc + sem/sampled_texture_type_test.cc + sem/sampler_type_test.cc + sem/sem_array_test.cc + sem/sem_struct_test.cc + sem/storage_texture_type_test.cc + sem/texture_type_test.cc + sem/type_manager_test.cc + sem/u32_type_test.cc + sem/vector_type_test.cc + source_test.cc + symbol_table_test.cc + symbol_test.cc + test_main.cc + text/unicode_test.cc + traits_test.cc + transform/transform_test.cc + utils/block_allocator_test.cc + utils/crc32_test.cc + utils/defer_test.cc + utils/enum_set_test.cc + utils/hash_test.cc + utils/io/command_test.cc + utils/io/tmpfile_test.cc + utils/map_test.cc + utils/math_test.cc + utils/reverse_test.cc + utils/scoped_assignment_test.cc + utils/string_test.cc + utils/transform_test.cc + utils/unique_allocator_test.cc + utils/unique_vector_test.cc + writer/append_vector_test.cc + writer/float_to_string_test.cc + writer/generate_external_texture_bindings_test.cc + writer/text_generator_test.cc + ) + + # Inspector tests depend on WGSL reader + if(${TINT_BUILD_WGSL_READER}) + list(APPEND TINT_TEST_SRCS + inspector/inspector_test.cc + inspector/test_inspector_builder.cc + inspector/test_inspector_builder.h + inspector/test_inspector_runner.cc + inspector/test_inspector_runner.h + ) + endif() + + if(${TINT_BUILD_SPV_READER} AND ${TINT_BUILD_WGSL_WRITER}) + list(APPEND TINT_TEST_SRCS + reader/spirv/enum_converter_test.cc + reader/spirv/fail_stream_test.cc + reader/spirv/function_arithmetic_test.cc + reader/spirv/function_bit_test.cc + reader/spirv/function_cfg_test.cc + reader/spirv/function_call_test.cc + reader/spirv/function_composite_test.cc + reader/spirv/function_conversion_test.cc + reader/spirv/function_decl_test.cc + reader/spirv/function_glsl_std_450_test.cc + reader/spirv/function_logical_test.cc + reader/spirv/function_memory_test.cc + reader/spirv/function_misc_test.cc + reader/spirv/function_var_test.cc + reader/spirv/namer_test.cc + reader/spirv/parser_impl_barrier_test.cc + reader/spirv/parser_impl_convert_member_decoration_test.cc + reader/spirv/parser_impl_convert_type_test.cc + reader/spirv/parser_impl_function_decl_test.cc + reader/spirv/parser_impl_get_decorations_test.cc + reader/spirv/parser_impl_handle_test.cc + reader/spirv/parser_impl_import_test.cc + reader/spirv/parser_impl_module_var_test.cc + reader/spirv/parser_impl_named_types_test.cc + reader/spirv/parser_impl_test_helper.cc + reader/spirv/parser_impl_test_helper.h + reader/spirv/parser_impl_test.cc + reader/spirv/parser_impl_user_name_test.cc + reader/spirv/parser_type_test.cc + reader/spirv/parser_test.cc + reader/spirv/spirv_tools_helpers_test.cc + reader/spirv/spirv_tools_helpers_test.h + reader/spirv/usage_test.cc + ) + endif() + + if(${TINT_BUILD_WGSL_READER}) + list(APPEND TINT_TEST_SRCS + reader/wgsl/lexer_test.cc + reader/wgsl/parser_test.cc + reader/wgsl/parser_impl_additive_expression_test.cc + reader/wgsl/parser_impl_and_expression_test.cc + reader/wgsl/parser_impl_argument_expression_list_test.cc + reader/wgsl/parser_impl_assignment_stmt_test.cc + reader/wgsl/parser_impl_body_stmt_test.cc + reader/wgsl/parser_impl_break_stmt_test.cc + reader/wgsl/parser_impl_bug_cases_test.cc + reader/wgsl/parser_impl_call_stmt_test.cc + reader/wgsl/parser_impl_case_body_test.cc + reader/wgsl/parser_impl_const_expr_test.cc + reader/wgsl/parser_impl_const_literal_test.cc + reader/wgsl/parser_impl_continue_stmt_test.cc + reader/wgsl/parser_impl_continuing_stmt_test.cc + reader/wgsl/parser_impl_depth_texture_type_test.cc + reader/wgsl/parser_impl_external_texture_type_test.cc + reader/wgsl/parser_impl_elseif_stmt_test.cc + reader/wgsl/parser_impl_equality_expression_test.cc + reader/wgsl/parser_impl_error_msg_test.cc + reader/wgsl/parser_impl_error_resync_test.cc + reader/wgsl/parser_impl_exclusive_or_expression_test.cc + reader/wgsl/parser_impl_for_stmt_test.cc + reader/wgsl/parser_impl_function_decl_test.cc + reader/wgsl/parser_impl_function_attribute_list_test.cc + reader/wgsl/parser_impl_function_attribute_test.cc + reader/wgsl/parser_impl_function_header_test.cc + reader/wgsl/parser_impl_global_constant_decl_test.cc + reader/wgsl/parser_impl_global_decl_test.cc + reader/wgsl/parser_impl_global_variable_decl_test.cc + reader/wgsl/parser_impl_if_stmt_test.cc + reader/wgsl/parser_impl_inclusive_or_expression_test.cc + reader/wgsl/parser_impl_logical_and_expression_test.cc + reader/wgsl/parser_impl_logical_or_expression_test.cc + reader/wgsl/parser_impl_loop_stmt_test.cc + reader/wgsl/parser_impl_multiplicative_expression_test.cc + reader/wgsl/parser_impl_param_list_test.cc + reader/wgsl/parser_impl_paren_rhs_stmt_test.cc + reader/wgsl/parser_impl_pipeline_stage_test.cc + reader/wgsl/parser_impl_primary_expression_test.cc + reader/wgsl/parser_impl_relational_expression_test.cc + reader/wgsl/parser_impl_reserved_keyword_test.cc + reader/wgsl/parser_impl_sampled_texture_type_test.cc + reader/wgsl/parser_impl_sampler_type_test.cc + reader/wgsl/parser_impl_shift_expression_test.cc + reader/wgsl/parser_impl_singular_expression_test.cc + reader/wgsl/parser_impl_statement_test.cc + reader/wgsl/parser_impl_statements_test.cc + reader/wgsl/parser_impl_storage_class_test.cc + reader/wgsl/parser_impl_storage_texture_type_test.cc + reader/wgsl/parser_impl_struct_body_decl_test.cc + reader/wgsl/parser_impl_struct_decl_test.cc + reader/wgsl/parser_impl_struct_attribute_decl_test.cc + reader/wgsl/parser_impl_struct_member_attribute_decl_test.cc + reader/wgsl/parser_impl_struct_member_attribute_test.cc + reader/wgsl/parser_impl_struct_member_test.cc + reader/wgsl/parser_impl_switch_body_test.cc + reader/wgsl/parser_impl_switch_stmt_test.cc + reader/wgsl/parser_impl_test.cc + reader/wgsl/parser_impl_test_helper.cc + reader/wgsl/parser_impl_test_helper.h + reader/wgsl/parser_impl_texel_format_test.cc + reader/wgsl/parser_impl_texture_sampler_types_test.cc + reader/wgsl/parser_impl_type_alias_test.cc + reader/wgsl/parser_impl_type_decl_test.cc + reader/wgsl/parser_impl_unary_expression_test.cc + reader/wgsl/parser_impl_variable_decl_test.cc + reader/wgsl/parser_impl_variable_attribute_list_test.cc + reader/wgsl/parser_impl_variable_attribute_test.cc + reader/wgsl/parser_impl_variable_ident_decl_test.cc + reader/wgsl/parser_impl_variable_stmt_test.cc + reader/wgsl/parser_impl_variable_qualifier_test.cc + reader/wgsl/token_test.cc + ) + endif() + + if(${TINT_BUILD_SPV_WRITER}) + list(APPEND TINT_TEST_SRCS + writer/spirv/binary_writer_test.cc + writer/spirv/builder_accessor_expression_test.cc + writer/spirv/builder_assign_test.cc + writer/spirv/builder_binary_expression_test.cc + writer/spirv/builder_bitcast_expression_test.cc + writer/spirv/builder_block_test.cc + writer/spirv/builder_builtin_test.cc + writer/spirv/builder_builtin_texture_test.cc + writer/spirv/builder_call_test.cc + writer/spirv/builder_constructor_expression_test.cc + writer/spirv/builder_discard_test.cc + writer/spirv/builder_entry_point_test.cc + writer/spirv/builder_format_conversion_test.cc + writer/spirv/builder_function_attribute_test.cc + writer/spirv/builder_function_test.cc + writer/spirv/builder_function_variable_test.cc + writer/spirv/builder_global_variable_test.cc + writer/spirv/builder_ident_expression_test.cc + writer/spirv/builder_if_test.cc + writer/spirv/builder_literal_test.cc + writer/spirv/builder_loop_test.cc + writer/spirv/builder_return_test.cc + writer/spirv/builder_switch_test.cc + writer/spirv/builder_test.cc + writer/spirv/builder_type_test.cc + writer/spirv/builder_unary_op_expression_test.cc + writer/spirv/instruction_test.cc + writer/spirv/operand_test.cc + writer/spirv/scalar_constant_test.cc + writer/spirv/spv_dump.cc + writer/spirv/spv_dump.h + writer/spirv/test_helper.h + ) + endif() + + if(${TINT_BUILD_WGSL_WRITER}) + list(APPEND TINT_TEST_SRCS + writer/wgsl/generator_impl_test.cc + writer/wgsl/generator_impl_alias_type_test.cc + writer/wgsl/generator_impl_array_accessor_test.cc + writer/wgsl/generator_impl_assign_test.cc + writer/wgsl/generator_impl_binary_test.cc + writer/wgsl/generator_impl_bitcast_test.cc + writer/wgsl/generator_impl_block_test.cc + writer/wgsl/generator_impl_break_test.cc + writer/wgsl/generator_impl_call_test.cc + writer/wgsl/generator_impl_case_test.cc + writer/wgsl/generator_impl_cast_test.cc + writer/wgsl/generator_impl_constructor_test.cc + writer/wgsl/generator_impl_continue_test.cc + writer/wgsl/generator_impl_discard_test.cc + writer/wgsl/generator_impl_fallthrough_test.cc + writer/wgsl/generator_impl_function_test.cc + writer/wgsl/generator_impl_global_decl_test.cc + writer/wgsl/generator_impl_identifier_test.cc + writer/wgsl/generator_impl_if_test.cc + writer/wgsl/generator_impl_loop_test.cc + writer/wgsl/generator_impl_literal_test.cc + writer/wgsl/generator_impl_member_accessor_test.cc + writer/wgsl/generator_impl_return_test.cc + writer/wgsl/generator_impl_switch_test.cc + writer/wgsl/generator_impl_type_test.cc + writer/wgsl/generator_impl_unary_op_test.cc + writer/wgsl/generator_impl_variable_decl_statement_test.cc + writer/wgsl/generator_impl_variable_test.cc + writer/wgsl/test_helper.h + ) + endif() + + if(${TINT_BUILD_WGSL_READER} AND ${TINT_BUILD_WGSL_WRITER}) + list(APPEND TINT_TEST_SRCS + transform/add_empty_entry_point_test.cc + transform/add_spirv_block_attribute_test.cc + transform/array_length_from_uniform_test.cc + transform/binding_remapper_test.cc + transform/builtin_polyfill_test.cc + transform/calculate_array_length_test.cc + transform/canonicalize_entry_point_io_test.cc + transform/combine_samplers_test.cc + transform/decompose_memory_access_test.cc + transform/decompose_strided_array_test.cc + transform/decompose_strided_matrix_test.cc + transform/first_index_offset_test.cc + transform/fold_constants_test.cc + transform/fold_trivial_single_use_lets_test.cc + transform/for_loop_to_loop_test.cc + transform/expand_compound_assignment.cc + transform/localize_struct_array_assignment_test.cc + transform/loop_to_for_loop_test.cc + transform/module_scope_var_to_entry_point_param_test.cc + transform/multiplanar_external_texture_test.cc + transform/num_workgroups_from_uniform_test.cc + transform/promote_initializers_to_const_var_test.cc + transform/promote_side_effects_to_decl_test.cc + transform/remove_continue_in_switch_test.cc + transform/remove_phonies_test.cc + transform/remove_unreachable_statements_test.cc + transform/renamer_test.cc + transform/robustness_test.cc + transform/simplify_pointers_test.cc + transform/single_entry_point_test.cc + transform/test_helper.h + transform/unshadow_test.cc + transform/unwind_discard_functions_test.cc + transform/var_for_dynamic_index_test.cc + transform/vectorize_scalar_matrix_constructors_test.cc + transform/vertex_pulling_test.cc + transform/wrap_arrays_in_structs_test.cc + transform/zero_init_workgroup_memory_test.cc + transform/utils/get_insertion_point_test.cc + transform/utils/hoist_to_decl_before_test.cc + ) + endif() + + if(${TINT_BUILD_MSL_WRITER}) + list(APPEND TINT_TEST_SRCS + writer/msl/generator_impl_array_accessor_test.cc + writer/msl/generator_impl_assign_test.cc + writer/msl/generator_impl_binary_test.cc + writer/msl/generator_impl_bitcast_test.cc + writer/msl/generator_impl_block_test.cc + writer/msl/generator_impl_break_test.cc + writer/msl/generator_impl_builtin_test.cc + writer/msl/generator_impl_builtin_texture_test.cc + writer/msl/generator_impl_call_test.cc + writer/msl/generator_impl_case_test.cc + writer/msl/generator_impl_cast_test.cc + writer/msl/generator_impl_constructor_test.cc + writer/msl/generator_impl_continue_test.cc + writer/msl/generator_impl_discard_test.cc + writer/msl/generator_impl_function_test.cc + writer/msl/generator_impl_identifier_test.cc + writer/msl/generator_impl_if_test.cc + writer/msl/generator_impl_import_test.cc + writer/msl/generator_impl_loop_test.cc + writer/msl/generator_impl_member_accessor_test.cc + writer/msl/generator_impl_module_constant_test.cc + writer/msl/generator_impl_return_test.cc + writer/msl/generator_impl_sanitizer_test.cc + writer/msl/generator_impl_switch_test.cc + writer/msl/generator_impl_test.cc + writer/msl/generator_impl_type_test.cc + writer/msl/generator_impl_unary_op_test.cc + writer/msl/generator_impl_variable_decl_statement_test.cc + writer/msl/test_helper.h + ) + endif() + + if (${TINT_BUILD_GLSL_WRITER}) + list(APPEND TINT_TEST_SRCS + writer/glsl/generator_impl_array_accessor_test.cc + writer/glsl/generator_impl_assign_test.cc + writer/glsl/generator_impl_binary_test.cc + writer/glsl/generator_impl_bitcast_test.cc + writer/glsl/generator_impl_block_test.cc + writer/glsl/generator_impl_break_test.cc + writer/glsl/generator_impl_builtin_test.cc + writer/glsl/generator_impl_builtin_texture_test.cc + writer/glsl/generator_impl_call_test.cc + writer/glsl/generator_impl_case_test.cc + writer/glsl/generator_impl_cast_test.cc + writer/glsl/generator_impl_constructor_test.cc + writer/glsl/generator_impl_continue_test.cc + writer/glsl/generator_impl_discard_test.cc + writer/glsl/generator_impl_function_test.cc + writer/glsl/generator_impl_identifier_test.cc + writer/glsl/generator_impl_if_test.cc + writer/glsl/generator_impl_import_test.cc + writer/glsl/generator_impl_loop_test.cc + writer/glsl/generator_impl_member_accessor_test.cc + writer/glsl/generator_impl_module_constant_test.cc + writer/glsl/generator_impl_return_test.cc + writer/glsl/generator_impl_sanitizer_test.cc + writer/glsl/generator_impl_storage_buffer_test.cc + writer/glsl/generator_impl_switch_test.cc + writer/glsl/generator_impl_test.cc + writer/glsl/generator_impl_type_test.cc + writer/glsl/generator_impl_unary_op_test.cc + writer/glsl/generator_impl_uniform_buffer_test.cc + writer/glsl/generator_impl_variable_decl_statement_test.cc + writer/glsl/generator_impl_workgroup_var_test.cc + writer/glsl/test_helper.h + ) + endif() + + if (${TINT_BUILD_HLSL_WRITER}) + list(APPEND TINT_TEST_SRCS + writer/hlsl/generator_impl_array_accessor_test.cc + writer/hlsl/generator_impl_assign_test.cc + writer/hlsl/generator_impl_binary_test.cc + writer/hlsl/generator_impl_bitcast_test.cc + writer/hlsl/generator_impl_block_test.cc + writer/hlsl/generator_impl_break_test.cc + writer/hlsl/generator_impl_builtin_test.cc + writer/hlsl/generator_impl_builtin_texture_test.cc + writer/hlsl/generator_impl_call_test.cc + writer/hlsl/generator_impl_case_test.cc + writer/hlsl/generator_impl_cast_test.cc + writer/hlsl/generator_impl_constructor_test.cc + writer/hlsl/generator_impl_continue_test.cc + writer/hlsl/generator_impl_discard_test.cc + writer/hlsl/generator_impl_function_test.cc + writer/hlsl/generator_impl_identifier_test.cc + writer/hlsl/generator_impl_if_test.cc + writer/hlsl/generator_impl_import_test.cc + writer/hlsl/generator_impl_loop_test.cc + writer/hlsl/generator_impl_member_accessor_test.cc + writer/hlsl/generator_impl_module_constant_test.cc + writer/hlsl/generator_impl_return_test.cc + writer/hlsl/generator_impl_sanitizer_test.cc + writer/hlsl/generator_impl_switch_test.cc + writer/hlsl/generator_impl_test.cc + writer/hlsl/generator_impl_type_test.cc + writer/hlsl/generator_impl_unary_op_test.cc + writer/hlsl/generator_impl_variable_decl_statement_test.cc + writer/hlsl/generator_impl_workgroup_var_test.cc + writer/hlsl/test_helper.h + ) + endif() + + if (${TINT_BUILD_FUZZERS}) + list(APPEND TINT_TEST_SRCS + fuzzers/mersenne_twister_engine.cc + fuzzers/mersenne_twister_engine.h + fuzzers/random_generator.cc + fuzzers/random_generator.h + fuzzers/random_generator_engine.cc + fuzzers/random_generator_engine.h + fuzzers/random_generator_test.cc + ) + endif() + + add_executable(tint_unittests ${TINT_TEST_SRCS}) + set_target_properties(${target} PROPERTIES FOLDER "Tests") + + if(NOT MSVC) + target_compile_options(tint_unittests PRIVATE + -Wno-global-constructors + -Wno-weak-vtables + ) + endif() + + ## Test executable + target_include_directories( + tint_unittests PRIVATE ${gmock_SOURCE_DIR}/include) + target_link_libraries(tint_unittests libtint gmock tint_utils_io) + tint_default_compile_options(tint_unittests) + + if(${TINT_BUILD_SPV_READER} OR ${TINT_BUILD_SPV_WRITER}) + tint_spvtools_compile_options(tint_unittests) + endif() + + add_test(NAME tint_unittests COMMAND tint_unittests) +endif(TINT_BUILD_TESTS) + +################################################################################ +# Benchmarks +################################################################################ +if(TINT_BUILD_BENCHMARKS) + if(NOT TINT_BUILD_WGSL_READER) + message(FATAL_ERROR "TINT_BUILD_BENCHMARKS requires TINT_BUILD_WGSL_READER") + endif() + + set(TINT_BENCHMARK_SRC + "castable_bench.cc" + "bench/benchmark.cc" + "reader/wgsl/parser_bench.cc" + ) + + if (${TINT_BUILD_GLSL_WRITER}) + list(APPEND TINT_BENCHMARK_SRC writer/glsl/generator_bench.cc) + endif() + if (${TINT_BUILD_HLSL_WRITER}) + list(APPEND TINT_BENCHMARK_SRC writer/hlsl/generator_bench.cc) + endif() + if (${TINT_BUILD_MSL_WRITER}) + list(APPEND TINT_BENCHMARK_SRC writer/msl/generator_bench.cc) + endif() + if (${TINT_BUILD_SPV_WRITER}) + list(APPEND TINT_BENCHMARK_SRC writer/spirv/generator_bench.cc) + endif() + if (${TINT_BUILD_WGSL_WRITER}) + list(APPEND TINT_BENCHMARK_SRC writer/wgsl/generator_bench.cc) + endif() + + add_executable(tint-benchmark ${TINT_BENCHMARK_SRC}) + set_target_properties(${target} PROPERTIES FOLDER "Benchmarks") + + tint_core_compile_options(tint-benchmark) + + target_link_libraries(tint-benchmark PRIVATE benchmark::benchmark libtint) +endif(TINT_BUILD_BENCHMARKS)
diff --git a/src/tint/ast/access.cc b/src/tint/ast/access.cc new file mode 100644 index 0000000..cb5f864 --- /dev/null +++ b/src/tint/ast/access.cc
@@ -0,0 +1,43 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/access.h" + +namespace tint { +namespace ast { + +std::ostream& operator<<(std::ostream& out, Access access) { + switch (access) { + case ast::Access::kUndefined: { + out << "undefined"; + break; + } + case ast::Access::kRead: { + out << "read"; + break; + } + case ast::Access::kReadWrite: { + out << "read_write"; + break; + } + case ast::Access::kWrite: { + out << "write"; + break; + } + } + return out; +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/access.h b/src/tint/ast/access.h new file mode 100644 index 0000000..67ad714 --- /dev/null +++ b/src/tint/ast/access.h
@@ -0,0 +1,46 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_ACCESS_H_ +#define SRC_TINT_AST_ACCESS_H_ + +#include <ostream> +#include <string> + +namespace tint { +namespace ast { + +/// The access control settings +enum Access { + /// Not declared in the source + kUndefined = 0, + /// Read only + kRead, + /// Write only + kWrite, + /// Read write + kReadWrite, + // Last valid access mode + kLastValid = kReadWrite, +}; + +/// @param out the std::ostream to write to +/// @param access the Access +/// @return the std::ostream so calls can be chained +std::ostream& operator<<(std::ostream& out, Access access); + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_ACCESS_H_
diff --git a/src/tint/ast/alias.cc b/src/tint/ast/alias.cc new file mode 100644 index 0000000..d852667 --- /dev/null +++ b/src/tint/ast/alias.cc
@@ -0,0 +1,45 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/alias.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::Alias); + +namespace tint { +namespace ast { + +Alias::Alias(ProgramID pid, + const Source& src, + const Symbol& n, + const Type* subtype) + : Base(pid, src, n), type(subtype) { + TINT_ASSERT(AST, type); +} + +Alias::Alias(Alias&&) = default; + +Alias::~Alias() = default; + +const Alias* Alias::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto sym = ctx->Clone(name); + auto* ty = ctx->Clone(type); + return ctx->dst->create<Alias>(src, sym, ty); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/alias.h b/src/tint/ast/alias.h new file mode 100644 index 0000000..c21b3e2 --- /dev/null +++ b/src/tint/ast/alias.h
@@ -0,0 +1,54 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_ALIAS_H_ +#define SRC_TINT_AST_ALIAS_H_ + +#include <string> + +#include "src/tint/ast/type_decl.h" + +namespace tint { +namespace ast { + +/// A type alias type. Holds a name and pointer to another type. +class Alias final : public Castable<Alias, TypeDecl> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param name the symbol for the alias + /// @param subtype the alias'd type + Alias(ProgramID pid, + const Source& src, + const Symbol& name, + const Type* subtype); + /// Move constructor + Alias(Alias&&); + /// Destructor + ~Alias() override; + + /// Clones this type and all transitive types using the `CloneContext` `ctx`. + /// @param ctx the clone context + /// @return the newly cloned type + const Alias* Clone(CloneContext* ctx) const override; + + /// the alias type + const Type* const type; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_ALIAS_H_
diff --git a/src/tint/ast/alias_test.cc b/src/tint/ast/alias_test.cc new file mode 100644 index 0000000..db82082 --- /dev/null +++ b/src/tint/ast/alias_test.cc
@@ -0,0 +1,45 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/alias.h" +#include "src/tint/ast/access.h" +#include "src/tint/ast/array.h" +#include "src/tint/ast/bool.h" +#include "src/tint/ast/f32.h" +#include "src/tint/ast/i32.h" +#include "src/tint/ast/matrix.h" +#include "src/tint/ast/pointer.h" +#include "src/tint/ast/sampler.h" +#include "src/tint/ast/struct.h" +#include "src/tint/ast/test_helper.h" +#include "src/tint/ast/texture.h" +#include "src/tint/ast/u32.h" +#include "src/tint/ast/vector.h" + +namespace tint { +namespace ast { +namespace { + +using AstAliasTest = TestHelper; + +TEST_F(AstAliasTest, Create) { + auto* u32 = create<U32>(); + auto* a = Alias("a_type", u32); + EXPECT_EQ(a->name, Symbol(1, ID())); + EXPECT_EQ(a->type, u32); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/array.cc b/src/tint/ast/array.cc new file mode 100644 index 0000000..99b8d56 --- /dev/null +++ b/src/tint/ast/array.cc
@@ -0,0 +1,78 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/array.h" + +#include <cmath> + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::Array); + +namespace tint { +namespace ast { + +namespace { +// Returns the string representation of an array size expression. +std::string SizeExprToString(const Expression* size, + const SymbolTable& symbols) { + if (auto* ident = size->As<IdentifierExpression>()) { + return symbols.NameFor(ident->symbol); + } + if (auto* literal = size->As<IntLiteralExpression>()) { + return std::to_string(literal->ValueAsU32()); + } + // This will never be exposed to the user as the Resolver will reject this + // expression for array size. + return "<invalid>"; +} +} // namespace + +Array::Array(ProgramID pid, + const Source& src, + const Type* subtype, + const Expression* cnt, + AttributeList attrs) + : Base(pid, src), type(subtype), count(cnt), attributes(attrs) {} + +Array::Array(Array&&) = default; + +Array::~Array() = default; + +std::string Array::FriendlyName(const SymbolTable& symbols) const { + std::ostringstream out; + for (auto* attr : attributes) { + if (auto* stride = attr->As<ast::StrideAttribute>()) { + out << "@stride(" << stride->stride << ") "; + } + } + out << "array<" << type->FriendlyName(symbols); + if (!IsRuntimeArray()) { + out << ", " << SizeExprToString(count, symbols); + } + out << ">"; + return out.str(); +} + +const Array* Array::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* ty = ctx->Clone(type); + auto* cnt = ctx->Clone(count); + auto attrs = ctx->Clone(attributes); + return ctx->dst->create<Array>(src, ty, cnt, attrs); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/array.h b/src/tint/ast/array.h new file mode 100644 index 0000000..413e4d3 --- /dev/null +++ b/src/tint/ast/array.h
@@ -0,0 +1,75 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_ARRAY_H_ +#define SRC_TINT_AST_ARRAY_H_ + +#include <string> + +#include "src/tint/ast/attribute.h" +#include "src/tint/ast/type.h" + +namespace tint { +namespace ast { + +// Forward declarations. +class Expression; + +/// An array type. If size is zero then it is a runtime array. +class Array final : public Castable<Array, Type> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param subtype the type of the array elements + /// @param count the number of elements in the array. nullptr represents a + /// runtime-sized array. + /// @param attributes the array attributes + Array(ProgramID pid, + const Source& src, + const Type* subtype, + const Expression* count, + AttributeList attributes); + /// Move constructor + Array(Array&&); + ~Array() override; + + /// @returns true if this is a runtime array. + /// i.e. the size is determined at runtime + bool IsRuntimeArray() const { return count == nullptr; } + + /// @param symbols the program's symbol table + /// @returns the name for this type that closely resembles how it would be + /// declared in WGSL. + std::string FriendlyName(const SymbolTable& symbols) const override; + + /// Clones this type and all transitive types using the `CloneContext` `ctx`. + /// @param ctx the clone context + /// @return the newly cloned type + const Array* Clone(CloneContext* ctx) const override; + + /// the array element type + const Type* const type; + + /// the array size in elements, or nullptr for a runtime array + const Expression* const count; + + /// the array attributes + const AttributeList attributes; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_ARRAY_H_
diff --git a/src/tint/ast/array_test.cc b/src/tint/ast/array_test.cc new file mode 100644 index 0000000..ff97734 --- /dev/null +++ b/src/tint/ast/array_test.cc
@@ -0,0 +1,71 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/array.h" + +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using AstArrayTest = TestHelper; + +TEST_F(AstArrayTest, CreateSizedArray) { + auto* u32 = create<U32>(); + auto* count = Expr(3); + auto* arr = create<Array>(u32, count, AttributeList{}); + EXPECT_EQ(arr->type, u32); + EXPECT_EQ(arr->count, count); + EXPECT_TRUE(arr->Is<Array>()); + EXPECT_FALSE(arr->IsRuntimeArray()); +} + +TEST_F(AstArrayTest, CreateRuntimeArray) { + auto* u32 = create<U32>(); + auto* arr = create<Array>(u32, nullptr, AttributeList{}); + EXPECT_EQ(arr->type, u32); + EXPECT_EQ(arr->count, nullptr); + EXPECT_TRUE(arr->Is<Array>()); + EXPECT_TRUE(arr->IsRuntimeArray()); +} + +TEST_F(AstArrayTest, FriendlyName_RuntimeSized) { + auto* i32 = create<I32>(); + auto* arr = create<Array>(i32, nullptr, AttributeList{}); + EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32>"); +} + +TEST_F(AstArrayTest, FriendlyName_LiteralSized) { + auto* i32 = create<I32>(); + auto* arr = create<Array>(i32, Expr(5), AttributeList{}); + EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32, 5>"); +} + +TEST_F(AstArrayTest, FriendlyName_ConstantSized) { + auto* i32 = create<I32>(); + auto* arr = create<Array>(i32, Expr("size"), AttributeList{}); + EXPECT_EQ(arr->FriendlyName(Symbols()), "array<i32, size>"); +} + +TEST_F(AstArrayTest, FriendlyName_WithStride) { + auto* i32 = create<I32>(); + auto* arr = + create<Array>(i32, Expr(5), AttributeList{create<StrideAttribute>(32)}); + EXPECT_EQ(arr->FriendlyName(Symbols()), "@stride(32) array<i32, 5>"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/assignment_statement.cc b/src/tint/ast/assignment_statement.cc new file mode 100644 index 0000000..a2340b7 --- /dev/null +++ b/src/tint/ast/assignment_statement.cc
@@ -0,0 +1,48 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/assignment_statement.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::AssignmentStatement); + +namespace tint { +namespace ast { + +AssignmentStatement::AssignmentStatement(ProgramID pid, + const Source& src, + const Expression* l, + const Expression* r) + : Base(pid, src), lhs(l), rhs(r) { + TINT_ASSERT(AST, lhs); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, lhs, program_id); + TINT_ASSERT(AST, rhs); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, rhs, program_id); +} + +AssignmentStatement::AssignmentStatement(AssignmentStatement&&) = default; + +AssignmentStatement::~AssignmentStatement() = default; + +const AssignmentStatement* AssignmentStatement::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* l = ctx->Clone(lhs); + auto* r = ctx->Clone(rhs); + return ctx->dst->create<AssignmentStatement>(src, l, r); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/assignment_statement.h b/src/tint/ast/assignment_statement.h new file mode 100644 index 0000000..55e07b8 --- /dev/null +++ b/src/tint/ast/assignment_statement.h
@@ -0,0 +1,57 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_ASSIGNMENT_STATEMENT_H_ +#define SRC_TINT_AST_ASSIGNMENT_STATEMENT_H_ + +#include "src/tint/ast/expression.h" +#include "src/tint/ast/statement.h" + +namespace tint { +namespace ast { + +/// An assignment statement +class AssignmentStatement final + : public Castable<AssignmentStatement, Statement> { + public: + /// Constructor + /// @param program_id the identifier of the program that owns this node + /// @param source the assignment statement source + /// @param lhs the left side of the expression + /// @param rhs the right side of the expression + AssignmentStatement(ProgramID program_id, + const Source& source, + const Expression* lhs, + const Expression* rhs); + /// Move constructor + AssignmentStatement(AssignmentStatement&&); + ~AssignmentStatement() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const AssignmentStatement* Clone(CloneContext* ctx) const override; + + /// left side expression + const Expression* const lhs; + + /// right side expression + const Expression* const rhs; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_ASSIGNMENT_STATEMENT_H_
diff --git a/src/tint/ast/assignment_statement_test.cc b/src/tint/ast/assignment_statement_test.cc new file mode 100644 index 0000000..4a41a7c --- /dev/null +++ b/src/tint/ast/assignment_statement_test.cc
@@ -0,0 +1,94 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/assignment_statement.h" + +#include "gtest/gtest-spi.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using AssignmentStatementTest = TestHelper; + +TEST_F(AssignmentStatementTest, Creation) { + auto* lhs = Expr("lhs"); + auto* rhs = Expr("rhs"); + + auto* stmt = create<AssignmentStatement>(lhs, rhs); + EXPECT_EQ(stmt->lhs, lhs); + EXPECT_EQ(stmt->rhs, rhs); +} + +TEST_F(AssignmentStatementTest, CreationWithSource) { + auto* lhs = Expr("lhs"); + auto* rhs = Expr("rhs"); + + auto* stmt = + create<AssignmentStatement>(Source{Source::Location{20, 2}}, lhs, rhs); + auto src = stmt->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(AssignmentStatementTest, IsAssign) { + auto* lhs = Expr("lhs"); + auto* rhs = Expr("rhs"); + + auto* stmt = create<AssignmentStatement>(lhs, rhs); + EXPECT_TRUE(stmt->Is<AssignmentStatement>()); +} + +TEST_F(AssignmentStatementTest, Assert_Null_LHS) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<AssignmentStatement>(nullptr, b.Expr(1)); + }, + "internal compiler error"); +} + +TEST_F(AssignmentStatementTest, Assert_Null_RHS) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<AssignmentStatement>(b.Expr(1), nullptr); + }, + "internal compiler error"); +} + +TEST_F(AssignmentStatementTest, Assert_DifferentProgramID_LHS) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<AssignmentStatement>(b2.Expr("lhs"), b1.Expr("rhs")); + }, + "internal compiler error"); +} + +TEST_F(AssignmentStatementTest, Assert_DifferentProgramID_RHS) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<AssignmentStatement>(b1.Expr("lhs"), b2.Expr("rhs")); + }, + "internal compiler error"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/ast_type.cc b/src/tint/ast/ast_type.cc new file mode 100644 index 0000000..cb01679 --- /dev/null +++ b/src/tint/ast/ast_type.cc
@@ -0,0 +1,41 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/type.h" + +#include "src/tint/ast/alias.h" +#include "src/tint/ast/bool.h" +#include "src/tint/ast/f32.h" +#include "src/tint/ast/i32.h" +#include "src/tint/ast/matrix.h" +#include "src/tint/ast/pointer.h" +#include "src/tint/ast/sampler.h" +#include "src/tint/ast/texture.h" +#include "src/tint/ast/u32.h" +#include "src/tint/ast/vector.h" +#include "src/tint/symbol_table.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::Type); + +namespace tint { +namespace ast { + +Type::Type(ProgramID pid, const Source& src) : Base(pid, src) {} + +Type::Type(Type&&) = default; + +Type::~Type() = default; + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/atomic.cc b/src/tint/ast/atomic.cc new file mode 100644 index 0000000..addefee --- /dev/null +++ b/src/tint/ast/atomic.cc
@@ -0,0 +1,45 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/atomic.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::Atomic); + +namespace tint { +namespace ast { + +Atomic::Atomic(ProgramID pid, const Source& src, const Type* const subtype) + : Base(pid, src), type(subtype) {} + +std::string Atomic::FriendlyName(const SymbolTable& symbols) const { + std::ostringstream out; + out << "atomic<" << type->FriendlyName(symbols) << ">"; + return out.str(); +} + +Atomic::Atomic(Atomic&&) = default; + +Atomic::~Atomic() = default; + +const Atomic* Atomic::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* ty = ctx->Clone(type); + return ctx->dst->create<Atomic>(src, ty); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/atomic.h b/src/tint/ast/atomic.h new file mode 100644 index 0000000..d5e1b9b --- /dev/null +++ b/src/tint/ast/atomic.h
@@ -0,0 +1,54 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_AST_ATOMIC_H_ +#define SRC_TINT_AST_ATOMIC_H_ + +#include <string> + +#include "src/tint/ast/type.h" + +namespace tint { +namespace ast { + +/// An atomic type. +class Atomic final : public Castable<Atomic, Type> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param subtype the pointee type + Atomic(ProgramID pid, const Source& src, const Type* const subtype); + /// Move constructor + Atomic(Atomic&&); + ~Atomic() override; + + /// @param symbols the program's symbol table + /// @returns the name for this type that closely resembles how it would be + /// declared in WGSL. + std::string FriendlyName(const SymbolTable& symbols) const override; + + /// Clones this type and all transitive types using the `CloneContext` `ctx`. + /// @param ctx the clone context + /// @return the newly cloned type + const Atomic* Clone(CloneContext* ctx) const override; + + /// the pointee type + const Type* const type; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_ATOMIC_H_
diff --git a/src/tint/ast/atomic_test.cc b/src/tint/ast/atomic_test.cc new file mode 100644 index 0000000..636654b --- /dev/null +++ b/src/tint/ast/atomic_test.cc
@@ -0,0 +1,40 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/atomic.h" + +#include "src/tint/ast/i32.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using AstAtomicTest = TestHelper; + +TEST_F(AstAtomicTest, Creation) { + auto* i32 = create<I32>(); + auto* p = create<Atomic>(i32); + EXPECT_EQ(p->type, i32); +} + +TEST_F(AstAtomicTest, FriendlyName) { + auto* i32 = create<I32>(); + auto* p = create<Atomic>(i32); + EXPECT_EQ(p->FriendlyName(Symbols()), "atomic<i32>"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/attribute.cc b/src/tint/ast/attribute.cc new file mode 100644 index 0000000..87ad3f1 --- /dev/null +++ b/src/tint/ast/attribute.cc
@@ -0,0 +1,25 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/attribute.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::Attribute); + +namespace tint { +namespace ast { + +Attribute::~Attribute() = default; + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/attribute.h b/src/tint/ast/attribute.h new file mode 100644 index 0000000..d336727 --- /dev/null +++ b/src/tint/ast/attribute.h
@@ -0,0 +1,71 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_ATTRIBUTE_H_ +#define SRC_TINT_AST_ATTRIBUTE_H_ + +#include <string> +#include <vector> + +#include "src/tint/ast/node.h" + +namespace tint { +namespace ast { + +/// The base class for all attributes +class Attribute : public Castable<Attribute, Node> { + public: + ~Attribute() override; + + /// @returns the WGSL name for the attribute + virtual std::string Name() const = 0; + + protected: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + Attribute(ProgramID pid, const Source& src) : Base(pid, src) {} +}; + +/// A list of attributes +using AttributeList = std::vector<const Attribute*>; + +/// @param attributes the list of attributes to search +/// @returns true if `attributes` includes a attribute of type `T` +template <typename T> +bool HasAttribute(const AttributeList& attributes) { + for (auto* attr : attributes) { + if (attr->Is<T>()) { + return true; + } + } + return false; +} + +/// @param attributes the list of attributes to search +/// @returns a pointer to `T` from `attributes` if found, otherwise nullptr. +template <typename T> +const T* GetAttribute(const AttributeList& attributes) { + for (auto* attr : attributes) { + if (attr->Is<T>()) { + return attr->As<T>(); + } + } + return nullptr; +} + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_ATTRIBUTE_H_
diff --git a/src/tint/ast/binary_expression.cc b/src/tint/ast/binary_expression.cc new file mode 100644 index 0000000..6b2be73 --- /dev/null +++ b/src/tint/ast/binary_expression.cc
@@ -0,0 +1,50 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/binary_expression.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::BinaryExpression); + +namespace tint { +namespace ast { + +BinaryExpression::BinaryExpression(ProgramID pid, + const Source& src, + BinaryOp o, + const Expression* l, + const Expression* r) + : Base(pid, src), op(o), lhs(l), rhs(r) { + TINT_ASSERT(AST, lhs); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, lhs, program_id); + TINT_ASSERT(AST, rhs); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, rhs, program_id); + TINT_ASSERT(AST, op != BinaryOp::kNone); +} + +BinaryExpression::BinaryExpression(BinaryExpression&&) = default; + +BinaryExpression::~BinaryExpression() = default; + +const BinaryExpression* BinaryExpression::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* l = ctx->Clone(lhs); + auto* r = ctx->Clone(rhs); + return ctx->dst->create<BinaryExpression>(src, op, l, r); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/binary_expression.h b/src/tint/ast/binary_expression.h new file mode 100644 index 0000000..bcacdd4 --- /dev/null +++ b/src/tint/ast/binary_expression.h
@@ -0,0 +1,264 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_BINARY_EXPRESSION_H_ +#define SRC_TINT_AST_BINARY_EXPRESSION_H_ + +#include "src/tint/ast/expression.h" + +namespace tint { +namespace ast { + +/// The operator type +enum class BinaryOp { + kNone = 0, + kAnd, // & + kOr, // | + kXor, + kLogicalAnd, // && + kLogicalOr, // || + kEqual, + kNotEqual, + kLessThan, + kGreaterThan, + kLessThanEqual, + kGreaterThanEqual, + kShiftLeft, + kShiftRight, + kAdd, + kSubtract, + kMultiply, + kDivide, + kModulo, +}; + +/// An binary expression +class BinaryExpression final : public Castable<BinaryExpression, Expression> { + public: + /// Constructor + /// @param program_id the identifier of the program that owns this node + /// @param source the binary expression source + /// @param op the operation type + /// @param lhs the left side of the expression + /// @param rhs the right side of the expression + BinaryExpression(ProgramID program_id, + const Source& source, + BinaryOp op, + const Expression* lhs, + const Expression* rhs); + /// Move constructor + BinaryExpression(BinaryExpression&&); + ~BinaryExpression() override; + + /// @returns true if the op is and + bool IsAnd() const { return op == BinaryOp::kAnd; } + /// @returns true if the op is or + bool IsOr() const { return op == BinaryOp::kOr; } + /// @returns true if the op is xor + bool IsXor() const { return op == BinaryOp::kXor; } + /// @returns true if the op is logical and + bool IsLogicalAnd() const { return op == BinaryOp::kLogicalAnd; } + /// @returns true if the op is logical or + bool IsLogicalOr() const { return op == BinaryOp::kLogicalOr; } + /// @returns true if the op is equal + bool IsEqual() const { return op == BinaryOp::kEqual; } + /// @returns true if the op is not equal + bool IsNotEqual() const { return op == BinaryOp::kNotEqual; } + /// @returns true if the op is less than + bool IsLessThan() const { return op == BinaryOp::kLessThan; } + /// @returns true if the op is greater than + bool IsGreaterThan() const { return op == BinaryOp::kGreaterThan; } + /// @returns true if the op is less than equal + bool IsLessThanEqual() const { return op == BinaryOp::kLessThanEqual; } + /// @returns true if the op is greater than equal + bool IsGreaterThanEqual() const { return op == BinaryOp::kGreaterThanEqual; } + /// @returns true if the op is shift left + bool IsShiftLeft() const { return op == BinaryOp::kShiftLeft; } + /// @returns true if the op is shift right + bool IsShiftRight() const { return op == BinaryOp::kShiftRight; } + /// @returns true if the op is add + bool IsAdd() const { return op == BinaryOp::kAdd; } + /// @returns true if the op is subtract + bool IsSubtract() const { return op == BinaryOp::kSubtract; } + /// @returns true if the op is multiply + bool IsMultiply() const { return op == BinaryOp::kMultiply; } + /// @returns true if the op is divide + bool IsDivide() const { return op == BinaryOp::kDivide; } + /// @returns true if the op is modulo + bool IsModulo() const { return op == BinaryOp::kModulo; } + /// @returns true if the op is an arithmetic operation + bool IsArithmetic() const; + /// @returns true if the op is a comparison operation + bool IsComparison() const; + /// @returns true if the op is a bitwise operation + bool IsBitwise() const; + /// @returns true if the op is a bit shift operation + bool IsBitshift() const; + /// @returns true if the op is a logical expression + bool IsLogical() const; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const BinaryExpression* Clone(CloneContext* ctx) const override; + + /// the binary op type + const BinaryOp op; + /// the left side expression + const Expression* const lhs; + /// the right side expression + const Expression* const rhs; +}; + +/// @param op the operator +/// @returns true if the op is an arithmetic operation +inline bool IsArithmetic(BinaryOp op) { + switch (op) { + case ast::BinaryOp::kAdd: + case ast::BinaryOp::kSubtract: + case ast::BinaryOp::kMultiply: + case ast::BinaryOp::kDivide: + case ast::BinaryOp::kModulo: + return true; + default: + return false; + } +} + +/// @param op the operator +/// @returns true if the op is a comparison operation +inline bool IsComparison(BinaryOp op) { + switch (op) { + case ast::BinaryOp::kEqual: + case ast::BinaryOp::kNotEqual: + case ast::BinaryOp::kLessThan: + case ast::BinaryOp::kLessThanEqual: + case ast::BinaryOp::kGreaterThan: + case ast::BinaryOp::kGreaterThanEqual: + return true; + default: + return false; + } +} + +/// @param op the operator +/// @returns true if the op is a bitwise operation +inline bool IsBitwise(BinaryOp op) { + switch (op) { + case ast::BinaryOp::kAnd: + case ast::BinaryOp::kOr: + case ast::BinaryOp::kXor: + return true; + default: + return false; + } +} + +/// @param op the operator +/// @returns true if the op is a bit shift operation +inline bool IsBitshift(BinaryOp op) { + switch (op) { + case ast::BinaryOp::kShiftLeft: + case ast::BinaryOp::kShiftRight: + return true; + default: + return false; + } +} + +inline bool BinaryExpression::IsLogical() const { + switch (op) { + case ast::BinaryOp::kLogicalAnd: + case ast::BinaryOp::kLogicalOr: + return true; + default: + return false; + } +} + +inline bool BinaryExpression::IsArithmetic() const { + return ast::IsArithmetic(op); +} + +inline bool BinaryExpression::IsComparison() const { + return ast::IsComparison(op); +} + +inline bool BinaryExpression::IsBitwise() const { + return ast::IsBitwise(op); +} + +inline bool BinaryExpression::IsBitshift() const { + return ast::IsBitshift(op); +} + +/// @returns the human readable name of the given BinaryOp +/// @param op the BinaryOp +constexpr const char* FriendlyName(BinaryOp op) { + switch (op) { + case BinaryOp::kNone: + return "none"; + case BinaryOp::kAnd: + return "and"; + case BinaryOp::kOr: + return "or"; + case BinaryOp::kXor: + return "xor"; + case BinaryOp::kLogicalAnd: + return "logical_and"; + case BinaryOp::kLogicalOr: + return "logical_or"; + case BinaryOp::kEqual: + return "equal"; + case BinaryOp::kNotEqual: + return "not_equal"; + case BinaryOp::kLessThan: + return "less_than"; + case BinaryOp::kGreaterThan: + return "greater_than"; + case BinaryOp::kLessThanEqual: + return "less_than_equal"; + case BinaryOp::kGreaterThanEqual: + return "greater_than_equal"; + case BinaryOp::kShiftLeft: + return "shift_left"; + case BinaryOp::kShiftRight: + return "shift_right"; + case BinaryOp::kAdd: + return "add"; + case BinaryOp::kSubtract: + return "subtract"; + case BinaryOp::kMultiply: + return "multiply"; + case BinaryOp::kDivide: + return "divide"; + case BinaryOp::kModulo: + return "modulo"; + } + return "INVALID"; +} + +/// @param out the std::ostream to write to +/// @param op the BinaryOp +/// @return the std::ostream so calls can be chained +inline std::ostream& operator<<(std::ostream& out, BinaryOp op) { + out << FriendlyName(op); + return out; +} + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_BINARY_EXPRESSION_H_
diff --git a/src/tint/ast/binary_expression_test.cc b/src/tint/ast/binary_expression_test.cc new file mode 100644 index 0000000..20b8f8f --- /dev/null +++ b/src/tint/ast/binary_expression_test.cc
@@ -0,0 +1,95 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "gtest/gtest-spi.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using BinaryExpressionTest = TestHelper; + +TEST_F(BinaryExpressionTest, Creation) { + auto* lhs = Expr("lhs"); + auto* rhs = Expr("rhs"); + + auto* r = create<BinaryExpression>(BinaryOp::kEqual, lhs, rhs); + EXPECT_EQ(r->lhs, lhs); + EXPECT_EQ(r->rhs, rhs); + EXPECT_EQ(r->op, BinaryOp::kEqual); +} + +TEST_F(BinaryExpressionTest, Creation_WithSource) { + auto* lhs = Expr("lhs"); + auto* rhs = Expr("rhs"); + + auto* r = create<BinaryExpression>(Source{Source::Location{20, 2}}, + BinaryOp::kEqual, lhs, rhs); + auto src = r->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(BinaryExpressionTest, IsBinary) { + auto* lhs = Expr("lhs"); + auto* rhs = Expr("rhs"); + + auto* r = create<BinaryExpression>(BinaryOp::kEqual, lhs, rhs); + EXPECT_TRUE(r->Is<BinaryExpression>()); +} + +TEST_F(BinaryExpressionTest, Assert_Null_LHS) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<BinaryExpression>(BinaryOp::kEqual, nullptr, b.Expr("rhs")); + }, + "internal compiler error"); +} + +TEST_F(BinaryExpressionTest, Assert_Null_RHS) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<BinaryExpression>(BinaryOp::kEqual, b.Expr("lhs"), nullptr); + }, + "internal compiler error"); +} + +TEST_F(BinaryExpressionTest, Assert_DifferentProgramID_LHS) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<BinaryExpression>(BinaryOp::kEqual, b2.Expr("lhs"), + b1.Expr("rhs")); + }, + "internal compiler error"); +} + +TEST_F(BinaryExpressionTest, Assert_DifferentProgramID_RHS) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<BinaryExpression>(BinaryOp::kEqual, b1.Expr("lhs"), + b2.Expr("rhs")); + }, + "internal compiler error"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/binding_attribute.cc b/src/tint/ast/binding_attribute.cc new file mode 100644 index 0000000..bc1a74a --- /dev/null +++ b/src/tint/ast/binding_attribute.cc
@@ -0,0 +1,44 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/binding_attribute.h" + +#include <string> + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::BindingAttribute); + +namespace tint { +namespace ast { + +BindingAttribute::BindingAttribute(ProgramID pid, + const Source& src, + uint32_t val) + : Base(pid, src), value(val) {} + +BindingAttribute::~BindingAttribute() = default; + +std::string BindingAttribute::Name() const { + return "binding"; +} + +const BindingAttribute* BindingAttribute::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + return ctx->dst->create<BindingAttribute>(src, value); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/binding_attribute.h b/src/tint/ast/binding_attribute.h new file mode 100644 index 0000000..8f3214d --- /dev/null +++ b/src/tint/ast/binding_attribute.h
@@ -0,0 +1,51 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_BINDING_ATTRIBUTE_H_ +#define SRC_TINT_AST_BINDING_ATTRIBUTE_H_ + +#include <string> + +#include "src/tint/ast/attribute.h" + +namespace tint { +namespace ast { + +/// A binding attribute +class BindingAttribute final : public Castable<BindingAttribute, Attribute> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param value the binding value + BindingAttribute(ProgramID pid, const Source& src, uint32_t value); + ~BindingAttribute() override; + + /// @returns the WGSL name for the attribute + std::string Name() const override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const BindingAttribute* Clone(CloneContext* ctx) const override; + + /// the binding value + const uint32_t value; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_BINDING_ATTRIBUTE_H_
diff --git a/src/tint/ast/binding_attribute_test.cc b/src/tint/ast/binding_attribute_test.cc new file mode 100644 index 0000000..c4c7e39 --- /dev/null +++ b/src/tint/ast/binding_attribute_test.cc
@@ -0,0 +1,30 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using BindingAttributeTest = TestHelper; + +TEST_F(BindingAttributeTest, Creation) { + auto* d = create<BindingAttribute>(2); + EXPECT_EQ(2u, d->value); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/bitcast_expression.cc b/src/tint/ast/bitcast_expression.cc new file mode 100644 index 0000000..8de3afc --- /dev/null +++ b/src/tint/ast/bitcast_expression.cc
@@ -0,0 +1,46 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/bitcast_expression.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::BitcastExpression); + +namespace tint { +namespace ast { + +BitcastExpression::BitcastExpression(ProgramID pid, + const Source& src, + const Type* t, + const Expression* e) + : Base(pid, src), type(t), expr(e) { + TINT_ASSERT(AST, type); + TINT_ASSERT(AST, expr); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, expr, program_id); +} + +BitcastExpression::BitcastExpression(BitcastExpression&&) = default; +BitcastExpression::~BitcastExpression() = default; + +const BitcastExpression* BitcastExpression::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* t = ctx->Clone(type); + auto* e = ctx->Clone(expr); + return ctx->dst->create<BitcastExpression>(src, t, e); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/bitcast_expression.h b/src/tint/ast/bitcast_expression.h new file mode 100644 index 0000000..e16ae22 --- /dev/null +++ b/src/tint/ast/bitcast_expression.h
@@ -0,0 +1,57 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_BITCAST_EXPRESSION_H_ +#define SRC_TINT_AST_BITCAST_EXPRESSION_H_ + +#include "src/tint/ast/expression.h" + +namespace tint { +namespace ast { + +// Forward declaration +class Type; + +/// A bitcast expression +class BitcastExpression final : public Castable<BitcastExpression, Expression> { + public: + /// Constructor + /// @param program_id the identifier of the program that owns this node + /// @param source the bitcast expression source + /// @param type the type + /// @param expr the expr + BitcastExpression(ProgramID program_id, + const Source& source, + const Type* type, + const Expression* expr); + /// Move constructor + BitcastExpression(BitcastExpression&&); + ~BitcastExpression() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const BitcastExpression* Clone(CloneContext* ctx) const override; + + /// the target cast type + const Type* const type; + /// the expression + const Expression* const expr; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_BITCAST_EXPRESSION_H_
diff --git a/src/tint/ast/bitcast_expression_test.cc b/src/tint/ast/bitcast_expression_test.cc new file mode 100644 index 0000000..5803003 --- /dev/null +++ b/src/tint/ast/bitcast_expression_test.cc
@@ -0,0 +1,81 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/bitcast_expression.h" + +#include "gtest/gtest-spi.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using BitcastExpressionTest = TestHelper; + +TEST_F(BitcastExpressionTest, Create) { + auto* expr = Expr("expr"); + + auto* exp = create<BitcastExpression>(ty.f32(), expr); + EXPECT_TRUE(exp->type->Is<ast::F32>()); + ASSERT_EQ(exp->expr, expr); +} + +TEST_F(BitcastExpressionTest, CreateWithSource) { + auto* expr = Expr("expr"); + + auto* exp = create<BitcastExpression>(Source{Source::Location{20, 2}}, + ty.f32(), expr); + auto src = exp->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(BitcastExpressionTest, IsBitcast) { + auto* expr = Expr("expr"); + + auto* exp = create<BitcastExpression>(ty.f32(), expr); + EXPECT_TRUE(exp->Is<BitcastExpression>()); +} + +TEST_F(BitcastExpressionTest, Assert_Null_Type) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<BitcastExpression>(nullptr, b.Expr("idx")); + }, + "internal compiler error"); +} + +TEST_F(BitcastExpressionTest, Assert_Null_Expr) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<BitcastExpression>(b.ty.f32(), nullptr); + }, + "internal compiler error"); +} + +TEST_F(BitcastExpressionTest, Assert_DifferentProgramID_Expr) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<BitcastExpression>(b1.ty.f32(), b2.Expr("idx")); + }, + "internal compiler error"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/block_statement.cc b/src/tint/ast/block_statement.cc new file mode 100644 index 0000000..5a7dcba --- /dev/null +++ b/src/tint/ast/block_statement.cc
@@ -0,0 +1,46 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/block_statement.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::BlockStatement); + +namespace tint { +namespace ast { + +BlockStatement::BlockStatement(ProgramID pid, + const Source& src, + const StatementList& stmts) + : Base(pid, src), statements(std::move(stmts)) { + for (auto* stmt : statements) { + TINT_ASSERT(AST, stmt); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, stmt, program_id); + } +} + +BlockStatement::BlockStatement(BlockStatement&&) = default; + +BlockStatement::~BlockStatement() = default; + +const BlockStatement* BlockStatement::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto stmts = ctx->Clone(statements); + return ctx->dst->create<BlockStatement>(src, stmts); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/block_statement.h b/src/tint/ast/block_statement.h new file mode 100644 index 0000000..31b2ef1 --- /dev/null +++ b/src/tint/ast/block_statement.h
@@ -0,0 +1,60 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_BLOCK_STATEMENT_H_ +#define SRC_TINT_AST_BLOCK_STATEMENT_H_ + +#include <utility> + +#include "src/tint/ast/statement.h" + +namespace tint { +namespace ast { + +/// A block statement +class BlockStatement final : public Castable<BlockStatement, Statement> { + public: + /// Constructor + /// @param program_id the identifier of the program that owns this node + /// @param source the block statement source + /// @param statements the statements + BlockStatement(ProgramID program_id, + const Source& source, + const StatementList& statements); + /// Move constructor + BlockStatement(BlockStatement&&); + ~BlockStatement() override; + + /// @returns true if the block has no statements + bool Empty() const { return statements.empty(); } + + /// @returns the last statement in the block or nullptr if block empty + const Statement* Last() const { + return statements.empty() ? nullptr : statements.back(); + } + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const BlockStatement* Clone(CloneContext* ctx) const override; + + /// the statement list + const StatementList statements; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_BLOCK_STATEMENT_H_
diff --git a/src/tint/ast/block_statement_test.cc b/src/tint/ast/block_statement_test.cc new file mode 100644 index 0000000..1cc8f38 --- /dev/null +++ b/src/tint/ast/block_statement_test.cc
@@ -0,0 +1,71 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "gtest/gtest-spi.h" +#include "src/tint/ast/discard_statement.h" +#include "src/tint/ast/if_statement.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using BlockStatementTest = TestHelper; + +TEST_F(BlockStatementTest, Creation) { + auto* d = create<DiscardStatement>(); + auto* ptr = d; + + auto* b = create<BlockStatement>(StatementList{d}); + + ASSERT_EQ(b->statements.size(), 1u); + EXPECT_EQ(b->statements[0], ptr); +} + +TEST_F(BlockStatementTest, Creation_WithSource) { + auto* b = create<BlockStatement>(Source{Source::Location{20, 2}}, + ast::StatementList{}); + auto src = b->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(BlockStatementTest, IsBlock) { + auto* b = create<BlockStatement>(ast::StatementList{}); + EXPECT_TRUE(b->Is<BlockStatement>()); +} + +TEST_F(BlockStatementTest, Assert_Null_Statement) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<BlockStatement>(ast::StatementList{nullptr}); + }, + "internal compiler error"); +} + +TEST_F(BlockStatementTest, Assert_DifferentProgramID_Statement) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<BlockStatement>( + ast::StatementList{b2.create<DiscardStatement>()}); + }, + "internal compiler error"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/bool.cc b/src/tint/ast/bool.cc new file mode 100644 index 0000000..79596a3 --- /dev/null +++ b/src/tint/ast/bool.cc
@@ -0,0 +1,40 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/bool.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::Bool); + +namespace tint { +namespace ast { + +Bool::Bool(ProgramID pid, const Source& src) : Base(pid, src) {} + +Bool::Bool(Bool&&) = default; + +Bool::~Bool() = default; + +std::string Bool::FriendlyName(const SymbolTable&) const { + return "bool"; +} + +const Bool* Bool::Clone(CloneContext* ctx) const { + auto src = ctx->Clone(source); + return ctx->dst->create<Bool>(src); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/bool.h b/src/tint/ast/bool.h new file mode 100644 index 0000000..fa326da --- /dev/null +++ b/src/tint/ast/bool.h
@@ -0,0 +1,56 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_BOOL_H_ +#define SRC_TINT_AST_BOOL_H_ + +#include <string> + +#include "src/tint/ast/type.h" + +// X11 likes to #define Bool leading to confusing error messages. +// If its defined, undefine it. +#ifdef Bool +#undef Bool +#endif + +namespace tint { +namespace ast { + +/// A boolean type +class Bool final : public Castable<Bool, Type> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + Bool(ProgramID pid, const Source& src); + /// Move constructor + Bool(Bool&&); + ~Bool() override; + + /// @param symbols the program's symbol table + /// @returns the name for this type that closely resembles how it would be + /// declared in WGSL. + std::string FriendlyName(const SymbolTable& symbols) const override; + + /// Clones this type and all transitive types using the `CloneContext` `ctx`. + /// @param ctx the clone context + /// @return the newly cloned type + const Bool* Clone(CloneContext* ctx) const override; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_BOOL_H_
diff --git a/src/tint/ast/bool_literal_expression.cc b/src/tint/ast/bool_literal_expression.cc new file mode 100644 index 0000000..5c961b4 --- /dev/null +++ b/src/tint/ast/bool_literal_expression.cc
@@ -0,0 +1,39 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/bool_literal_expression.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::BoolLiteralExpression); + +namespace tint { +namespace ast { + +BoolLiteralExpression::BoolLiteralExpression(ProgramID pid, + const Source& src, + bool val) + : Base(pid, src), value(val) {} + +BoolLiteralExpression::~BoolLiteralExpression() = default; + +const BoolLiteralExpression* BoolLiteralExpression::Clone( + CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + return ctx->dst->create<BoolLiteralExpression>(src, value); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/bool_literal_expression.h b/src/tint/ast/bool_literal_expression.h new file mode 100644 index 0000000..421453e --- /dev/null +++ b/src/tint/ast/bool_literal_expression.h
@@ -0,0 +1,49 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_BOOL_LITERAL_EXPRESSION_H_ +#define SRC_TINT_AST_BOOL_LITERAL_EXPRESSION_H_ + +#include <string> + +#include "src/tint/ast/literal_expression.h" + +namespace tint { +namespace ast { + +/// A boolean literal +class BoolLiteralExpression final + : public Castable<BoolLiteralExpression, LiteralExpression> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param value the bool literals value + BoolLiteralExpression(ProgramID pid, const Source& src, bool value); + ~BoolLiteralExpression() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const BoolLiteralExpression* Clone(CloneContext* ctx) const override; + + /// The boolean literal value + const bool value; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_BOOL_LITERAL_EXPRESSION_H_
diff --git a/src/tint/ast/bool_literal_expression_test.cc b/src/tint/ast/bool_literal_expression_test.cc new file mode 100644 index 0000000..78cd632 --- /dev/null +++ b/src/tint/ast/bool_literal_expression_test.cc
@@ -0,0 +1,37 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using BoolLiteralExpressionTest = TestHelper; + +TEST_F(BoolLiteralExpressionTest, True) { + auto* b = create<BoolLiteralExpression>(true); + ASSERT_TRUE(b->Is<BoolLiteralExpression>()); + ASSERT_TRUE(b->value); +} + +TEST_F(BoolLiteralExpressionTest, False) { + auto* b = create<BoolLiteralExpression>(false); + ASSERT_TRUE(b->Is<BoolLiteralExpression>()); + ASSERT_FALSE(b->value); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/bool_test.cc b/src/tint/ast/bool_test.cc new file mode 100644 index 0000000..3defbd4 --- /dev/null +++ b/src/tint/ast/bool_test.cc
@@ -0,0 +1,32 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/bool.h" + +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using AstBoolTest = TestHelper; + +TEST_F(AstBoolTest, FriendlyName) { + auto* b = create<Bool>(); + EXPECT_EQ(b->FriendlyName(Symbols()), "bool"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/break_statement.cc b/src/tint/ast/break_statement.cc new file mode 100644 index 0000000..0c78c73 --- /dev/null +++ b/src/tint/ast/break_statement.cc
@@ -0,0 +1,38 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/break_statement.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::BreakStatement); + +namespace tint { +namespace ast { + +BreakStatement::BreakStatement(ProgramID pid, const Source& src) + : Base(pid, src) {} + +BreakStatement::BreakStatement(BreakStatement&&) = default; + +BreakStatement::~BreakStatement() = default; + +const BreakStatement* BreakStatement::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + return ctx->dst->create<BreakStatement>(src); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/break_statement.h b/src/tint/ast/break_statement.h new file mode 100644 index 0000000..cf50c74 --- /dev/null +++ b/src/tint/ast/break_statement.h
@@ -0,0 +1,44 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_BREAK_STATEMENT_H_ +#define SRC_TINT_AST_BREAK_STATEMENT_H_ + +#include "src/tint/ast/statement.h" + +namespace tint { +namespace ast { + +/// An break statement +class BreakStatement final : public Castable<BreakStatement, Statement> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + BreakStatement(ProgramID pid, const Source& src); + /// Move constructor + BreakStatement(BreakStatement&&); + ~BreakStatement() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const BreakStatement* Clone(CloneContext* ctx) const override; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_BREAK_STATEMENT_H_
diff --git a/src/tint/ast/break_statement_test.cc b/src/tint/ast/break_statement_test.cc new file mode 100644 index 0000000..ce419ae --- /dev/null +++ b/src/tint/ast/break_statement_test.cc
@@ -0,0 +1,39 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/break_statement.h" + +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using BreakStatementTest = TestHelper; + +TEST_F(BreakStatementTest, Creation_WithSource) { + auto* stmt = create<BreakStatement>(Source{Source::Location{20, 2}}); + auto src = stmt->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(BreakStatementTest, IsBreak) { + auto* stmt = create<BreakStatement>(); + EXPECT_TRUE(stmt->Is<BreakStatement>()); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/builtin.cc b/src/tint/ast/builtin.cc new file mode 100644 index 0000000..48744e0 --- /dev/null +++ b/src/tint/ast/builtin.cc
@@ -0,0 +1,82 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/builtin.h" + +namespace tint { +namespace ast { + +std::ostream& operator<<(std::ostream& out, Builtin builtin) { + switch (builtin) { + case Builtin::kNone: { + out << "none"; + break; + } + case Builtin::kPosition: { + out << "position"; + break; + } + case Builtin::kVertexIndex: { + out << "vertex_index"; + break; + } + case Builtin::kInstanceIndex: { + out << "instance_index"; + break; + } + case Builtin::kFrontFacing: { + out << "front_facing"; + break; + } + case Builtin::kFragDepth: { + out << "frag_depth"; + break; + } + case Builtin::kLocalInvocationId: { + out << "local_invocation_id"; + break; + } + case Builtin::kLocalInvocationIndex: { + out << "local_invocation_index"; + break; + } + case Builtin::kGlobalInvocationId: { + out << "global_invocation_id"; + break; + } + case Builtin::kWorkgroupId: { + out << "workgroup_id"; + break; + } + case Builtin::kNumWorkgroups: { + out << "num_workgroups"; + break; + } + case Builtin::kSampleIndex: { + out << "sample_index"; + break; + } + case Builtin::kSampleMask: { + out << "sample_mask"; + break; + } + case Builtin::kPointSize: { + out << "pointsize"; + } + } + return out; +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/builtin.h b/src/tint/ast/builtin.h new file mode 100644 index 0000000..913eba2 --- /dev/null +++ b/src/tint/ast/builtin.h
@@ -0,0 +1,52 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_BUILTIN_H_ +#define SRC_TINT_AST_BUILTIN_H_ + +#include <ostream> + +namespace tint { +namespace ast { + +/// The builtin identifiers +enum class Builtin { + kNone = -1, + kPosition, + kVertexIndex, + kInstanceIndex, + kFrontFacing, + kFragDepth, + kLocalInvocationId, + kLocalInvocationIndex, + kGlobalInvocationId, + kWorkgroupId, + kNumWorkgroups, + kSampleIndex, + kSampleMask, + + // Below are not currently WGSL builtins, but are included in this enum as + // they are used by certain backends. + kPointSize, +}; + +/// @param out the std::ostream to write to +/// @param builtin the Builtin +/// @return the std::ostream so calls can be chained +std::ostream& operator<<(std::ostream& out, Builtin builtin); + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_BUILTIN_H_
diff --git a/src/tint/ast/builtin_attribute.cc b/src/tint/ast/builtin_attribute.cc new file mode 100644 index 0000000..0591b91 --- /dev/null +++ b/src/tint/ast/builtin_attribute.cc
@@ -0,0 +1,42 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/builtin_attribute.h" + +#include <string> + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::BuiltinAttribute); + +namespace tint { +namespace ast { + +BuiltinAttribute::BuiltinAttribute(ProgramID pid, const Source& src, Builtin b) + : Base(pid, src), builtin(b) {} + +BuiltinAttribute::~BuiltinAttribute() = default; + +std::string BuiltinAttribute::Name() const { + return "builtin"; +} + +const BuiltinAttribute* BuiltinAttribute::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + return ctx->dst->create<BuiltinAttribute>(src, builtin); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/builtin_attribute.h b/src/tint/ast/builtin_attribute.h new file mode 100644 index 0000000..4366739 --- /dev/null +++ b/src/tint/ast/builtin_attribute.h
@@ -0,0 +1,52 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_BUILTIN_ATTRIBUTE_H_ +#define SRC_TINT_AST_BUILTIN_ATTRIBUTE_H_ + +#include <string> + +#include "src/tint/ast/attribute.h" +#include "src/tint/ast/builtin.h" + +namespace tint { +namespace ast { + +/// A builtin attribute +class BuiltinAttribute final : public Castable<BuiltinAttribute, Attribute> { + public: + /// constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param builtin the builtin value + BuiltinAttribute(ProgramID pid, const Source& src, Builtin builtin); + ~BuiltinAttribute() override; + + /// @returns the WGSL name for the attribute + std::string Name() const override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const BuiltinAttribute* Clone(CloneContext* ctx) const override; + + /// The builtin value + const Builtin builtin; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_BUILTIN_ATTRIBUTE_H_
diff --git a/src/tint/ast/builtin_attribute_test.cc b/src/tint/ast/builtin_attribute_test.cc new file mode 100644 index 0000000..e5a91ea --- /dev/null +++ b/src/tint/ast/builtin_attribute_test.cc
@@ -0,0 +1,30 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using BuiltinAttributeTest = TestHelper; + +TEST_F(BuiltinAttributeTest, Creation) { + auto* d = create<BuiltinAttribute>(Builtin::kFragDepth); + EXPECT_EQ(Builtin::kFragDepth, d->builtin); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/builtin_texture_helper_test.cc b/src/tint/ast/builtin_texture_helper_test.cc new file mode 100644 index 0000000..2f80558 --- /dev/null +++ b/src/tint/ast/builtin_texture_helper_test.cc
@@ -0,0 +1,2286 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/builtin_texture_helper_test.h" + +#include "src/tint/sem/depth_texture_type.h" +#include "src/tint/sem/multisampled_texture_type.h" +#include "src/tint/sem/sampled_texture_type.h" + +namespace tint { +namespace ast { +namespace builtin { +namespace test { + +using u32 = ProgramBuilder::u32; +using i32 = ProgramBuilder::i32; +using f32 = ProgramBuilder::f32; + +TextureOverloadCase::TextureOverloadCase( + ValidTextureOverload o, + const char* desc, + TextureKind tk, + ast::SamplerKind sk, + ast::TextureDimension dims, + TextureDataType datatype, + const char* f, + std::function<ExpressionList(ProgramBuilder*)> a) + : overload(o), + description(desc), + texture_kind(tk), + sampler_kind(sk), + texture_dimension(dims), + texture_data_type(datatype), + function(f), + args(std::move(a)) {} +TextureOverloadCase::TextureOverloadCase( + ValidTextureOverload o, + const char* desc, + TextureKind tk, + ast::TextureDimension dims, + TextureDataType datatype, + const char* f, + std::function<ExpressionList(ProgramBuilder*)> a) + : overload(o), + description(desc), + texture_kind(tk), + texture_dimension(dims), + texture_data_type(datatype), + function(f), + args(std::move(a)) {} +TextureOverloadCase::TextureOverloadCase( + ValidTextureOverload o, + const char* d, + Access acc, + ast::TexelFormat fmt, + ast::TextureDimension dims, + TextureDataType datatype, + const char* f, + std::function<ExpressionList(ProgramBuilder*)> a) + : overload(o), + description(d), + texture_kind(TextureKind::kStorage), + access(acc), + texel_format(fmt), + texture_dimension(dims), + texture_data_type(datatype), + function(f), + args(std::move(a)) {} +TextureOverloadCase::TextureOverloadCase(const TextureOverloadCase&) = default; +TextureOverloadCase::~TextureOverloadCase() = default; + +std::ostream& operator<<(std::ostream& out, const TextureKind& kind) { + switch (kind) { + case TextureKind::kRegular: + out << "regular"; + break; + case TextureKind::kDepth: + out << "depth"; + break; + case TextureKind::kDepthMultisampled: + out << "depth-multisampled"; + break; + case TextureKind::kMultisampled: + out << "multisampled"; + break; + case TextureKind::kStorage: + out << "storage"; + break; + } + return out; +} + +std::ostream& operator<<(std::ostream& out, const TextureDataType& ty) { + switch (ty) { + case TextureDataType::kF32: + out << "f32"; + break; + case TextureDataType::kU32: + out << "u32"; + break; + case TextureDataType::kI32: + out << "i32"; + break; + } + return out; +} + +std::ostream& operator<<(std::ostream& out, const TextureOverloadCase& data) { + out << "TextureOverloadCase " << static_cast<int>(data.overload) << "\n"; + out << data.description << "\n"; + out << "texture_kind: " << data.texture_kind << "\n"; + out << "sampler_kind: "; + if (data.texture_kind != TextureKind::kStorage) { + out << data.sampler_kind; + } else { + out << "<unused>"; + } + out << "\n"; + out << "access: " << data.access << "\n"; + out << "texel_format: " << data.texel_format << "\n"; + out << "texture_dimension: " << data.texture_dimension << "\n"; + out << "texture_data_type: " << data.texture_data_type << "\n"; + return out; +} + +const ast::Type* TextureOverloadCase::BuildResultVectorComponentType( + ProgramBuilder* b) const { + switch (texture_data_type) { + case ast::builtin::test::TextureDataType::kF32: + return b->ty.f32(); + case ast::builtin::test::TextureDataType::kU32: + return b->ty.u32(); + case ast::builtin::test::TextureDataType::kI32: + return b->ty.i32(); + } + + TINT_UNREACHABLE(AST, b->Diagnostics()); + return {}; +} + +const ast::Variable* TextureOverloadCase::BuildTextureVariable( + ProgramBuilder* b) const { + AttributeList attrs = { + b->create<ast::GroupAttribute>(0), + b->create<ast::BindingAttribute>(0), + }; + switch (texture_kind) { + case ast::builtin::test::TextureKind::kRegular: + return b->Global("texture", + b->ty.sampled_texture(texture_dimension, + BuildResultVectorComponentType(b)), + attrs); + + case ast::builtin::test::TextureKind::kDepth: + return b->Global("texture", b->ty.depth_texture(texture_dimension), + attrs); + + case ast::builtin::test::TextureKind::kDepthMultisampled: + return b->Global("texture", + b->ty.depth_multisampled_texture(texture_dimension), + attrs); + + case ast::builtin::test::TextureKind::kMultisampled: + return b->Global( + "texture", + b->ty.multisampled_texture(texture_dimension, + BuildResultVectorComponentType(b)), + attrs); + + case ast::builtin::test::TextureKind::kStorage: { + auto* st = b->ty.storage_texture(texture_dimension, texel_format, access); + return b->Global("texture", st, attrs); + } + } + + TINT_UNREACHABLE(AST, b->Diagnostics()); + return nullptr; +} + +const ast::Variable* TextureOverloadCase::BuildSamplerVariable( + ProgramBuilder* b) const { + AttributeList attrs = { + b->create<ast::GroupAttribute>(0), + b->create<ast::BindingAttribute>(1), + }; + return b->Global("sampler", b->ty.sampler(sampler_kind), attrs); +} + +std::vector<TextureOverloadCase> TextureOverloadCase::ValidCases() { + return { + { + ValidTextureOverload::kDimensions1d, + "textureDimensions(t : texture_1d<f32>) -> i32", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k1d, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kDimensions2d, + "textureDimensions(t : texture_2d<f32>) -> vec2<i32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kDimensions2dLevel, + "textureDimensions(t : texture_2d<f32>,\n" + " level : i32) -> vec2<i32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture", 1); }, + }, + { + ValidTextureOverload::kDimensions2dArray, + "textureDimensions(t : texture_2d_array<f32>) -> vec2<i32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kDimensions2dArrayLevel, + "textureDimensions(t : texture_2d_array<f32>,\n" + " level : i32) -> vec2<i32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture", 1); }, + }, + { + ValidTextureOverload::kDimensions3d, + "textureDimensions(t : texture_3d<f32>) -> vec3<i32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k3d, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kDimensions3dLevel, + "textureDimensions(t : texture_3d<f32>,\n" + " level : i32) -> vec3<i32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k3d, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture", 1); }, + }, + { + ValidTextureOverload::kDimensionsCube, + "textureDimensions(t : texture_cube<f32>) -> vec2<i32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCube, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kDimensionsCubeLevel, + "textureDimensions(t : texture_cube<f32>,\n" + " level : i32) -> vec2<i32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCube, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture", 1); }, + }, + { + ValidTextureOverload::kDimensionsCubeArray, + "textureDimensions(t : texture_cube_array<f32>) -> vec2<i32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCubeArray, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kDimensionsCubeArrayLevel, + "textureDimensions(t : texture_cube_array<f32>,\n" + " level : i32) -> vec2<i32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCubeArray, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture", 1); }, + }, + { + ValidTextureOverload::kDimensionsMultisampled2d, + "textureDimensions(t : texture_multisampled_2d<f32>)-> vec2<i32>", + TextureKind::kMultisampled, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kDimensionsDepth2d, + "textureDimensions(t : texture_depth_2d) -> vec2<i32>", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kDimensionsDepth2dLevel, + "textureDimensions(t : texture_depth_2d,\n" + " level : i32) -> vec2<i32>", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture", 1); }, + }, + { + ValidTextureOverload::kDimensionsDepth2dArray, + "textureDimensions(t : texture_depth_2d_array) -> vec2<i32>", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kDimensionsDepth2dArrayLevel, + "textureDimensions(t : texture_depth_2d_array,\n" + " level : i32) -> vec2<i32>", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture", 1); }, + }, + { + ValidTextureOverload::kDimensionsDepthCube, + "textureDimensions(t : texture_depth_cube) -> vec2<i32>", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCube, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kDimensionsDepthCubeLevel, + "textureDimensions(t : texture_depth_cube,\n" + " level : i32) -> vec2<i32>", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCube, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture", 1); }, + }, + { + ValidTextureOverload::kDimensionsDepthCubeArray, + "textureDimensions(t : texture_depth_cube_array) -> vec2<i32>", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCubeArray, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kDimensionsDepthCubeArrayLevel, + "textureDimensions(t : texture_depth_cube_array,\n" + " level : i32) -> vec2<i32>", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCubeArray, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture", 1); }, + }, + { + ValidTextureOverload::kDimensionsDepthMultisampled2d, + "textureDimensions(t : texture_depth_multisampled_2d) -> vec2<i32>", + TextureKind::kDepthMultisampled, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kDimensionsStorageWO1d, + "textureDimensions(t : texture_storage_1d<rgba32float>) -> i32", + ast::Access::kWrite, + ast::TexelFormat::kRgba32Float, + ast::TextureDimension::k1d, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kDimensionsStorageWO2d, + "textureDimensions(t : texture_storage_2d<rgba32float>) -> " + "vec2<i32>", + ast::Access::kWrite, + ast::TexelFormat::kRgba32Float, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kDimensionsStorageWO2dArray, + "textureDimensions(t : texture_storage_2d_array<rgba32float>) -> " + "vec2<i32>", + ast::Access::kWrite, + ast::TexelFormat::kRgba32Float, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kDimensionsStorageWO3d, + "textureDimensions(t : texture_storage_3d<rgba32float>) -> " + "vec3<i32>", + ast::Access::kWrite, + ast::TexelFormat::kRgba32Float, + ast::TextureDimension::k3d, + TextureDataType::kF32, + "textureDimensions", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + + { + ValidTextureOverload::kGather2dF32, + "textureGather(component : i32,\n" + " t : texture_2d<T>,\n" + " s : sampler,\n" + " coords : vec2<f32>) -> vec4<T>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureGather", + [](ProgramBuilder* b) { + return b->ExprList(0, // component + "texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f)); // coords + }, + }, + { + ValidTextureOverload::kGather2dOffsetF32, + "textureGather(component : i32,\n" + " t : texture_2d<T>,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " offset : vec2<i32>) -> vec4<T>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureGather", + [](ProgramBuilder* b) { + return b->ExprList(0, // component + "texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + b->vec2<i32>(3, 4)); // offset + }, + }, + { + ValidTextureOverload::kGather2dArrayF32, + "textureGather(component : i32,\n" + " t : texture_2d_array<T>,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " array_index : i32) -> vec4<T>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureGather", + [](ProgramBuilder* b) { + return b->ExprList(0, // component + "texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3); // array index + }, + }, + { + ValidTextureOverload::kGather2dArrayOffsetF32, + "textureGather(component : i32,\n" + " t : texture_2d_array<T>,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " array_index : i32,\n" + " offset : vec2<i32>) -> vec4<T>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureGather", + [](ProgramBuilder* b) { + return b->ExprList(0, // component + "texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3, // array_index + b->vec2<i32>(4, 5)); // offset + }, + }, + { + ValidTextureOverload::kGatherCubeF32, + "textureGather(component : i32,\n" + " t : texture_cube<T>,\n" + " s : sampler,\n" + " coords : vec3<f32>) -> vec4<T>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCube, + TextureDataType::kF32, + "textureGather", + [](ProgramBuilder* b) { + return b->ExprList(0, // component + "texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f)); // coords + }, + }, + { + ValidTextureOverload::kGatherCubeArrayF32, + "textureGather(component : i32,\n" + " t : texture_cube_array<T>,\n" + " s : sampler,\n" + " coords : vec3<f32>,\n" + " array_index : i32) -> vec4<T>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCubeArray, + TextureDataType::kF32, + "textureGather", + [](ProgramBuilder* b) { + return b->ExprList(0, // component + "texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + 4); // array_index + }, + }, + { + ValidTextureOverload::kGatherDepth2dF32, + "textureGather(t : texture_depth_2d,\n" + " s : sampler,\n" + " coords : vec2<f32>) -> vec4<f32>", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureGather", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f)); // coords + }, + }, + { + ValidTextureOverload::kGatherDepth2dOffsetF32, + "textureGather(t : texture_depth_2d,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " offset : vec2<i32>) -> vec4<f32>", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureGather", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + b->vec2<i32>(3, 4)); // offset + }, + }, + { + ValidTextureOverload::kGatherDepth2dArrayF32, + "textureGather(t : texture_depth_2d_array,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " array_index : i32) -> vec4<f32>", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureGather", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3); // array_index + }, + }, + { + ValidTextureOverload::kGatherDepth2dArrayOffsetF32, + "textureGather(t : texture_depth_2d_array,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " array_index : i32,\n" + " offset : vec2<i32>) -> vec4<f32>", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureGather", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3, // array_index + b->vec2<i32>(4, 5)); // offset + }, + }, + { + ValidTextureOverload::kGatherDepthCubeF32, + "textureGather(t : texture_depth_cube,\n" + " s : sampler,\n" + " coords : vec3<f32>) -> vec4<f32>", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCube, + TextureDataType::kF32, + "textureGather", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f)); // coords + }, + }, + { + ValidTextureOverload::kGatherDepthCubeArrayF32, + "textureGather(t : texture_depth_cube_array,\n" + " s : sampler,\n" + " coords : vec3<f32>,\n" + " array_index : i32) -> vec4<f32>", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCubeArray, + TextureDataType::kF32, + "textureGather", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + 4); // array_index + }, + }, + { + ValidTextureOverload::kGatherCompareDepth2dF32, + "textureGatherCompare(t : texture_depth_2d,\n" + " s : sampler_comparison,\n" + " coords : vec2<f32>,\n" + " depth_ref : f32) -> vec4<f32>", + TextureKind::kDepth, + ast::SamplerKind::kComparisonSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureGatherCompare", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3.f); // depth_ref + }, + }, + { + ValidTextureOverload::kGatherCompareDepth2dOffsetF32, + "textureGatherCompare(t : texture_depth_2d,\n" + " s : sampler_comparison,\n" + " coords : vec2<f32>,\n" + " depth_ref : f32,\n" + " offset : vec2<i32>) -> vec4<f32>", + TextureKind::kDepth, + ast::SamplerKind::kComparisonSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureGatherCompare", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3.f, // depth_ref + b->vec2<i32>(4, 5)); // offset + }, + }, + { + ValidTextureOverload::kGatherCompareDepth2dArrayF32, + "textureGatherCompare(t : texture_depth_2d_array,\n" + " s : sampler_comparison,\n" + " coords : vec2<f32>,\n" + " array_index : i32,\n" + " depth_ref : f32) -> vec4<f32>", + TextureKind::kDepth, + ast::SamplerKind::kComparisonSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureGatherCompare", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3, // array_index + 4.f); // depth_ref + }, + }, + { + ValidTextureOverload::kGatherCompareDepth2dArrayOffsetF32, + "textureGatherCompare(t : texture_depth_2d_array,\n" + " s : sampler_comparison,\n" + " coords : vec2<f32>,\n" + " array_index : i32,\n" + " depth_ref : f32,\n" + " offset : vec2<i32>) -> vec4<f32>", + TextureKind::kDepth, + ast::SamplerKind::kComparisonSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureGatherCompare", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3, // array_index + 4.f, // depth_ref + b->vec2<i32>(5, 6)); // offset + }, + }, + { + ValidTextureOverload::kGatherCompareDepthCubeF32, + "textureGatherCompare(t : texture_depth_cube,\n" + " s : sampler_comparison,\n" + " coords : vec3<f32>,\n" + " depth_ref : f32) -> vec4<f32>", + TextureKind::kDepth, + ast::SamplerKind::kComparisonSampler, + ast::TextureDimension::kCube, + TextureDataType::kF32, + "textureGatherCompare", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + 4.f); // depth_ref + }, + }, + { + ValidTextureOverload::kGatherCompareDepthCubeArrayF32, + "textureGatherCompare(t : texture_depth_cube_array,\n" + " s : sampler_comparison,\n" + " coords : vec3<f32>,\n" + " array_index : i32,\n" + " depth_ref : f32) -> vec4<f32>", + TextureKind::kDepth, + ast::SamplerKind::kComparisonSampler, + ast::TextureDimension::kCubeArray, + TextureDataType::kF32, + "textureGatherCompare", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + 4, // array_index + 5.f); // depth_ref + }, + }, + { + ValidTextureOverload::kNumLayers2dArray, + "textureNumLayers(t : texture_2d_array<f32>) -> i32", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureNumLayers", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kNumLayersCubeArray, + "textureNumLayers(t : texture_cube_array<f32>) -> i32", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCubeArray, + TextureDataType::kF32, + "textureNumLayers", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kNumLayersDepth2dArray, + "textureNumLayers(t : texture_depth_2d_array) -> i32", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureNumLayers", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kNumLayersDepthCubeArray, + "textureNumLayers(t : texture_depth_cube_array) -> i32", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCubeArray, + TextureDataType::kF32, + "textureNumLayers", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kNumLayersStorageWO2dArray, + "textureNumLayers(t : texture_storage_2d_array<rgba32float>) -> i32", + ast::Access::kWrite, + ast::TexelFormat::kRgba32Float, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureNumLayers", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kNumLevels2d, + "textureNumLevels(t : texture_2d<f32>) -> i32", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureNumLevels", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kNumLevels2dArray, + "textureNumLevels(t : texture_2d_array<f32>) -> i32", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureNumLevels", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kNumLevels3d, + "textureNumLevels(t : texture_3d<f32>) -> i32", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k3d, + TextureDataType::kF32, + "textureNumLevels", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kNumLevelsCube, + "textureNumLevels(t : texture_cube<f32>) -> i32", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCube, + TextureDataType::kF32, + "textureNumLevels", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kNumLevelsCubeArray, + "textureNumLevels(t : texture_cube_array<f32>) -> i32", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCubeArray, + TextureDataType::kF32, + "textureNumLevels", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kNumLevelsDepth2d, + "textureNumLevels(t : texture_depth_2d) -> i32", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureNumLevels", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kNumLevelsDepth2dArray, + "textureNumLevels(t : texture_depth_2d_array) -> i32", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureNumLevels", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kNumLevelsDepthCube, + "textureNumLevels(t : texture_depth_cube) -> i32", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCube, + TextureDataType::kF32, + "textureNumLevels", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kNumLevelsDepthCubeArray, + "textureNumLevels(t : texture_depth_cube_array) -> i32", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCubeArray, + TextureDataType::kF32, + "textureNumLevels", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kNumSamplesMultisampled2d, + "textureNumSamples(t : texture_multisampled_2d<f32>) -> i32", + TextureKind::kMultisampled, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureNumSamples", + [](ProgramBuilder* b) { return b->ExprList("texture"); }, + }, + { + ValidTextureOverload::kSample1dF32, + "textureSample(t : texture_1d<f32>,\n" + " s : sampler,\n" + " coords : f32) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k1d, + TextureDataType::kF32, + "textureSample", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + 1.0f); // coords + }, + }, + { + ValidTextureOverload::kSample2dF32, + "textureSample(t : texture_2d<f32>,\n" + " s : sampler,\n" + " coords : vec2<f32>) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureSample", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f)); // coords + }, + }, + { + ValidTextureOverload::kSample2dOffsetF32, + "textureSample(t : texture_2d<f32>,\n" + " s : sampler,\n" + " coords : vec2<f32>\n" + " offset : vec2<i32>) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureSample", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + b->vec2<i32>(3, 4)); // offset + }, + }, + { + ValidTextureOverload::kSample2dArrayF32, + "textureSample(t : texture_2d_array<f32>,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " array_index : i32) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureSample", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3); // array_index + }, + }, + { + ValidTextureOverload::kSample2dArrayOffsetF32, + "textureSample(t : texture_2d_array<f32>,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " array_index : i32\n" + " offset : vec2<i32>) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureSample", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3, // array_index + b->vec2<i32>(4, 5)); // offset + }, + }, + { + ValidTextureOverload::kSample3dF32, + "textureSample(t : texture_3d<f32>,\n" + " s : sampler,\n" + " coords : vec3<f32>) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k3d, + TextureDataType::kF32, + "textureSample", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f)); // coords + }, + }, + { + ValidTextureOverload::kSample3dOffsetF32, + "textureSample(t : texture_3d<f32>,\n" + " s : sampler,\n" + " coords : vec3<f32>\n" + " offset : vec3<i32>) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k3d, + TextureDataType::kF32, + "textureSample", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + b->vec3<i32>(4, 5, 6)); // offset + }, + }, + { + ValidTextureOverload::kSampleCubeF32, + "textureSample(t : texture_cube<f32>,\n" + " s : sampler,\n" + " coords : vec3<f32>) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCube, + TextureDataType::kF32, + "textureSample", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f)); // coords + }, + }, + { + ValidTextureOverload::kSampleCubeArrayF32, + "textureSample(t : texture_cube_array<f32>,\n" + " s : sampler,\n" + " coords : vec3<f32>,\n" + " array_index : i32) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCubeArray, + TextureDataType::kF32, + "textureSample", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + 4); // array_index + }, + }, + { + ValidTextureOverload::kSampleDepth2dF32, + "textureSample(t : texture_depth_2d,\n" + " s : sampler,\n" + " coords : vec2<f32>) -> f32", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureSample", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f)); // coords + }, + }, + { + ValidTextureOverload::kSampleDepth2dOffsetF32, + "textureSample(t : texture_depth_2d,\n" + " s : sampler,\n" + " coords : vec2<f32>\n" + " offset : vec2<i32>) -> f32", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureSample", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + b->vec2<i32>(3, 4)); // offset + }, + }, + { + ValidTextureOverload::kSampleDepth2dArrayF32, + "textureSample(t : texture_depth_2d_array,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " array_index : i32) -> f32", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureSample", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3); // array_index + }, + }, + { + ValidTextureOverload::kSampleDepth2dArrayOffsetF32, + "textureSample(t : texture_depth_2d_array,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " array_index : i32\n" + " offset : vec2<i32>) -> f32", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureSample", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3, // array_index + b->vec2<i32>(4, 5)); // offset + }, + }, + { + ValidTextureOverload::kSampleDepthCubeF32, + "textureSample(t : texture_depth_cube,\n" + " s : sampler,\n" + " coords : vec3<f32>) -> f32", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCube, + TextureDataType::kF32, + "textureSample", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f)); // coords + }, + }, + { + ValidTextureOverload::kSampleDepthCubeArrayF32, + "textureSample(t : texture_depth_cube_array,\n" + " s : sampler,\n" + " coords : vec3<f32>,\n" + " array_index : i32) -> f32", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCubeArray, + TextureDataType::kF32, + "textureSample", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + 4); // array_index + }, + }, + { + ValidTextureOverload::kSampleBias2dF32, + "textureSampleBias(t : texture_2d<f32>,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " bias : f32) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureSampleBias", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3.f); // bias + }, + }, + { + ValidTextureOverload::kSampleBias2dOffsetF32, + "textureSampleBias(t : texture_2d<f32>,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " bias : f32,\n" + " offset : vec2<i32>) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureSampleBias", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3.f, // bias + b->vec2<i32>(4, 5)); // offset + }, + }, + { + ValidTextureOverload::kSampleBias2dArrayF32, + "textureSampleBias(t : texture_2d_array<f32>,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " array_index : i32,\n" + " bias : f32) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureSampleBias", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 4, // array_index + 3.f); // bias + }, + }, + { + ValidTextureOverload::kSampleBias2dArrayOffsetF32, + "textureSampleBias(t : texture_2d_array<f32>,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " array_index : i32,\n" + " bias : f32,\n" + " offset : vec2<i32>) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureSampleBias", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3, // array_index + 4.f, // bias + b->vec2<i32>(5, 6)); // offset + }, + }, + { + ValidTextureOverload::kSampleBias3dF32, + "textureSampleBias(t : texture_3d<f32>,\n" + " s : sampler,\n" + " coords : vec3<f32>,\n" + " bias : f32) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k3d, + TextureDataType::kF32, + "textureSampleBias", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + 4.f); // bias + }, + }, + { + ValidTextureOverload::kSampleBias3dOffsetF32, + "textureSampleBias(t : texture_3d<f32>,\n" + " s : sampler,\n" + " coords : vec3<f32>,\n" + " bias : f32,\n" + " offset : vec3<i32>) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k3d, + TextureDataType::kF32, + "textureSampleBias", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + 4.f, // bias + b->vec3<i32>(5, 6, 7)); // offset + }, + }, + { + ValidTextureOverload::kSampleBiasCubeF32, + "textureSampleBias(t : texture_cube<f32>,\n" + " s : sampler,\n" + " coords : vec3<f32>,\n" + " bias : f32) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCube, + TextureDataType::kF32, + "textureSampleBias", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + 4.f); // bias + }, + }, + { + ValidTextureOverload::kSampleBiasCubeArrayF32, + "textureSampleBias(t : texture_cube_array<f32>,\n" + " s : sampler,\n" + " coords : vec3<f32>,\n" + " array_index : i32,\n" + " bias : f32) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCubeArray, + TextureDataType::kF32, + "textureSampleBias", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + 3, // array_index + 4.f); // bias + }, + }, + { + ValidTextureOverload::kSampleLevel2dF32, + "textureSampleLevel(t : texture_2d<f32>,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " level : f32) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureSampleLevel", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3.f); // level + }, + }, + { + ValidTextureOverload::kSampleLevel2dOffsetF32, + "textureSampleLevel(t : texture_2d<f32>,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " level : f32,\n" + " offset : vec2<i32>) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureSampleLevel", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3.f, // level + b->vec2<i32>(4, 5)); // offset + }, + }, + { + ValidTextureOverload::kSampleLevel2dArrayF32, + "textureSampleLevel(t : texture_2d_array<f32>,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " array_index : i32,\n" + " level : f32) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureSampleLevel", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3, // array_index + 4.f); // level + }, + }, + { + ValidTextureOverload::kSampleLevel2dArrayOffsetF32, + "textureSampleLevel(t : texture_2d_array<f32>,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " array_index : i32,\n" + " level : f32,\n" + " offset : vec2<i32>) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureSampleLevel", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3, // array_index + 4.f, // level + b->vec2<i32>(5, 6)); // offset + }, + }, + { + ValidTextureOverload::kSampleLevel3dF32, + "textureSampleLevel(t : texture_3d<f32>,\n" + " s : sampler,\n" + " coords : vec3<f32>,\n" + " level : f32) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k3d, + TextureDataType::kF32, + "textureSampleLevel", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + 4.f); // level + }, + }, + { + ValidTextureOverload::kSampleLevel3dOffsetF32, + "textureSampleLevel(t : texture_3d<f32>,\n" + " s : sampler,\n" + " coords : vec3<f32>,\n" + " level : f32,\n" + " offset : vec3<i32>) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k3d, + TextureDataType::kF32, + "textureSampleLevel", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + 4.f, // level + b->vec3<i32>(5, 6, 7)); // offset + }, + }, + { + ValidTextureOverload::kSampleLevelCubeF32, + "textureSampleLevel(t : texture_cube<f32>,\n" + " s : sampler,\n" + " coords : vec3<f32>,\n" + " level : f32) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCube, + TextureDataType::kF32, + "textureSampleLevel", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + 4.f); // level + }, + }, + { + ValidTextureOverload::kSampleLevelCubeArrayF32, + "textureSampleLevel(t : texture_cube_array<f32>,\n" + " s : sampler,\n" + " coords : vec3<f32>,\n" + " array_index : i32,\n" + " level : f32) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCubeArray, + TextureDataType::kF32, + "textureSampleLevel", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + 4, // array_index + 5.f); // level + }, + }, + { + ValidTextureOverload::kSampleLevelDepth2dF32, + "textureSampleLevel(t : texture_depth_2d,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " level : i32) -> f32", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureSampleLevel", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3); // level + }, + }, + { + ValidTextureOverload::kSampleLevelDepth2dOffsetF32, + "textureSampleLevel(t : texture_depth_2d,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " level : i32,\n" + " offset : vec2<i32>) -> f32", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureSampleLevel", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3, // level + b->vec2<i32>(4, 5)); // offset + }, + }, + { + ValidTextureOverload::kSampleLevelDepth2dArrayF32, + "textureSampleLevel(t : texture_depth_2d_array,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " array_index : i32,\n" + " level : i32) -> f32", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureSampleLevel", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3, // array_index + 4); // level + }, + }, + { + ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32, + "textureSampleLevel(t : texture_depth_2d_array,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " array_index : i32,\n" + " level : i32,\n" + " offset : vec2<i32>) -> f32", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureSampleLevel", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3, // array_index + 4, // level + b->vec2<i32>(5, 6)); // offset + }, + }, + { + ValidTextureOverload::kSampleLevelDepthCubeF32, + "textureSampleLevel(t : texture_depth_cube,\n" + " s : sampler,\n" + " coords : vec3<f32>,\n" + " level : i32) -> f32", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCube, + TextureDataType::kF32, + "textureSampleLevel", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + 4); // level + }, + }, + { + ValidTextureOverload::kSampleLevelDepthCubeArrayF32, + "textureSampleLevel(t : texture_depth_cube_array,\n" + " s : sampler,\n" + " coords : vec3<f32>,\n" + " array_index : i32,\n" + " level : i32) -> f32", + TextureKind::kDepth, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCubeArray, + TextureDataType::kF32, + "textureSampleLevel", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + 4, // array_index + 5); // level + }, + }, + { + ValidTextureOverload::kSampleGrad2dF32, + "textureSampleGrad(t : texture_2d<f32>,\n" + " s : sampler,\n" + " coords : vec2<f32>\n" + " ddx : vec2<f32>,\n" + " ddy : vec2<f32>) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureSampleGrad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.0f, 2.0f), // coords + b->vec2<f32>(3.0f, 4.0f), // ddx + b->vec2<f32>(5.0f, 6.0f)); // ddy + }, + }, + { + ValidTextureOverload::kSampleGrad2dOffsetF32, + "textureSampleGrad(t : texture_2d<f32>,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " ddx : vec2<f32>,\n" + " ddy : vec2<f32>,\n" + " offset : vec2<i32>) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureSampleGrad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + b->vec2<f32>(3.f, 4.f), // ddx + b->vec2<f32>(5.f, 6.f), // ddy + b->vec2<i32>(7, 7)); // offset + }, + }, + { + ValidTextureOverload::kSampleGrad2dArrayF32, + "textureSampleGrad(t : texture_2d_array<f32>,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " array_index : i32,\n" + " ddx : vec2<f32>,\n" + " ddy : vec2<f32>) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureSampleGrad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3, // array_index + b->vec2<f32>(4.f, 5.f), // ddx + b->vec2<f32>(6.f, 7.f)); // ddy + }, + }, + { + ValidTextureOverload::kSampleGrad2dArrayOffsetF32, + "textureSampleGrad(t : texture_2d_array<f32>,\n" + " s : sampler,\n" + " coords : vec2<f32>,\n" + " array_index : i32,\n" + " ddx : vec2<f32>,\n" + " ddy : vec2<f32>,\n" + " offset : vec2<i32>) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureSampleGrad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3, // array_index + b->vec2<f32>(4.f, 5.f), // ddx + b->vec2<f32>(6.f, 7.f), // ddy + b->vec2<i32>(6, 7)); // offset + }, + }, + { + ValidTextureOverload::kSampleGrad3dF32, + "textureSampleGrad(t : texture_3d<f32>,\n" + " s : sampler,\n" + " coords : vec3<f32>,\n" + " ddx : vec3<f32>,\n" + " ddy : vec3<f32>) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k3d, + TextureDataType::kF32, + "textureSampleGrad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + b->vec3<f32>(4.f, 5.f, 6.f), // ddx + b->vec3<f32>(7.f, 8.f, 9.f)); // ddy + }, + }, + { + ValidTextureOverload::kSampleGrad3dOffsetF32, + "textureSampleGrad(t : texture_3d<f32>,\n" + " s : sampler,\n" + " coords : vec3<f32>,\n" + " ddx : vec3<f32>,\n" + " ddy : vec3<f32>,\n" + " offset : vec3<i32>) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::k3d, + TextureDataType::kF32, + "textureSampleGrad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + b->vec3<f32>(4.f, 5.f, 6.f), // ddx + b->vec3<f32>(7.f, 8.f, 9.f), // ddy + b->vec3<i32>(0, 1, 2)); // offset + }, + }, + { + ValidTextureOverload::kSampleGradCubeF32, + "textureSampleGrad(t : texture_cube<f32>,\n" + " s : sampler,\n" + " coords : vec3<f32>,\n" + " ddx : vec3<f32>,\n" + " ddy : vec3<f32>) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCube, + TextureDataType::kF32, + "textureSampleGrad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + b->vec3<f32>(4.f, 5.f, 6.f), // ddx + b->vec3<f32>(7.f, 8.f, 9.f)); // ddy + }, + }, + { + ValidTextureOverload::kSampleGradCubeArrayF32, + "textureSampleGrad(t : texture_cube_array<f32>,\n" + " s : sampler,\n" + " coords : vec3<f32>,\n" + " array_index : i32,\n" + " ddx : vec3<f32>,\n" + " ddy : vec3<f32>) -> vec4<f32>", + TextureKind::kRegular, + ast::SamplerKind::kSampler, + ast::TextureDimension::kCubeArray, + TextureDataType::kF32, + "textureSampleGrad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + 4, // array_index + b->vec3<f32>(5.f, 6.f, 7.f), // ddx + b->vec3<f32>(8.f, 9.f, 10.f)); // ddy + }, + }, + { + ValidTextureOverload::kSampleCompareDepth2dF32, + "textureSampleCompare(t : texture_depth_2d,\n" + " s : sampler_comparison,\n" + " coords : vec2<f32>,\n" + " depth_ref : f32) -> f32", + TextureKind::kDepth, + ast::SamplerKind::kComparisonSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureSampleCompare", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3.f); // depth_ref + }, + }, + { + ValidTextureOverload::kSampleCompareDepth2dOffsetF32, + "textureSampleCompare(t : texture_depth_2d,\n" + " s : sampler_comparison,\n" + " coords : vec2<f32>,\n" + " depth_ref : f32,\n" + " offset : vec2<i32>) -> f32", + TextureKind::kDepth, + ast::SamplerKind::kComparisonSampler, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureSampleCompare", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 3.f, // depth_ref + b->vec2<i32>(4, 5)); // offset + }, + }, + { + ValidTextureOverload::kSampleCompareDepth2dArrayF32, + "textureSampleCompare(t : texture_depth_2d_array,\n" + " s : sampler_comparison,\n" + " coords : vec2<f32>,\n" + " array_index : i32,\n" + " depth_ref : f32) -> f32", + TextureKind::kDepth, + ast::SamplerKind::kComparisonSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureSampleCompare", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 4, // array_index + 3.f); // depth_ref + }, + }, + { + ValidTextureOverload::kSampleCompareDepth2dArrayOffsetF32, + "textureSampleCompare(t : texture_depth_2d_array,\n" + " s : sampler_comparison,\n" + " coords : vec2<f32>,\n" + " array_index : i32,\n" + " depth_ref : f32,\n" + " offset : vec2<i32>) -> f32", + TextureKind::kDepth, + ast::SamplerKind::kComparisonSampler, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureSampleCompare", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec2<f32>(1.f, 2.f), // coords + 4, // array_index + 3.f, // depth_ref + b->vec2<i32>(5, 6)); // offset + }, + }, + { + ValidTextureOverload::kSampleCompareDepthCubeF32, + "textureSampleCompare(t : texture_depth_cube,\n" + " s : sampler_comparison,\n" + " coords : vec3<f32>,\n" + " depth_ref : f32) -> f32", + TextureKind::kDepth, + ast::SamplerKind::kComparisonSampler, + ast::TextureDimension::kCube, + TextureDataType::kF32, + "textureSampleCompare", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + 4.f); // depth_ref + }, + }, + { + ValidTextureOverload::kSampleCompareDepthCubeArrayF32, + "textureSampleCompare(t : texture_depth_cube_array,\n" + " s : sampler_comparison,\n" + " coords : vec3<f32>,\n" + " array_index : i32,\n" + " depth_ref : f32) -> f32", + TextureKind::kDepth, + ast::SamplerKind::kComparisonSampler, + ast::TextureDimension::kCubeArray, + TextureDataType::kF32, + "textureSampleCompare", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + "sampler", // s + b->vec3<f32>(1.f, 2.f, 3.f), // coords + 4, // array_index + 5.f); // depth_ref + }, + }, + { + ValidTextureOverload::kLoad1dLevelF32, + "textureLoad(t : texture_1d<f32>,\n" + " coords : i32,\n" + " level : i32) -> vec4<f32>", + TextureKind::kRegular, + ast::TextureDimension::k1d, + TextureDataType::kF32, + "textureLoad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + 1, // coords + 3); // level + }, + }, + { + ValidTextureOverload::kLoad1dLevelU32, + "textureLoad(t : texture_1d<u32>,\n" + " coords : i32,\n" + " level : i32) -> vec4<u32>", + TextureKind::kRegular, + ast::TextureDimension::k1d, + TextureDataType::kU32, + "textureLoad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + 1, // coords + 3); // level + }, + }, + { + ValidTextureOverload::kLoad1dLevelI32, + "textureLoad(t : texture_1d<i32>,\n" + " coords : i32,\n" + " level : i32) -> vec4<i32>", + TextureKind::kRegular, + ast::TextureDimension::k1d, + TextureDataType::kI32, + "textureLoad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + 1, // coords + 3); // level + }, + }, + { + ValidTextureOverload::kLoad2dLevelF32, + "textureLoad(t : texture_2d<f32>,\n" + " coords : vec2<i32>,\n" + " level : i32) -> vec4<f32>", + TextureKind::kRegular, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureLoad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + b->vec2<i32>(1, 2), // coords + 3); // level + }, + }, + { + ValidTextureOverload::kLoad2dLevelU32, + "textureLoad(t : texture_2d<u32>,\n" + " coords : vec2<i32>,\n" + " level : i32) -> vec4<u32>", + TextureKind::kRegular, + ast::TextureDimension::k2d, + TextureDataType::kU32, + "textureLoad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + b->vec2<i32>(1, 2), // coords + 3); // level + }, + }, + { + ValidTextureOverload::kLoad2dLevelI32, + "textureLoad(t : texture_2d<i32>,\n" + " coords : vec2<i32>,\n" + " level : i32) -> vec4<i32>", + TextureKind::kRegular, + ast::TextureDimension::k2d, + TextureDataType::kI32, + "textureLoad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + b->vec2<i32>(1, 2), // coords + 3); // level + }, + }, + { + ValidTextureOverload::kLoad2dArrayLevelF32, + "textureLoad(t : texture_2d_array<f32>,\n" + " coords : vec2<i32>,\n" + " array_index : i32,\n" + " level : i32) -> vec4<f32>", + TextureKind::kRegular, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureLoad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + b->vec2<i32>(1, 2), // coords + 3, // array_index + 4); // level + }, + }, + { + ValidTextureOverload::kLoad2dArrayLevelU32, + "textureLoad(t : texture_2d_array<u32>,\n" + " coords : vec2<i32>,\n" + " array_index : i32,\n" + " level : i32) -> vec4<u32>", + TextureKind::kRegular, + ast::TextureDimension::k2dArray, + TextureDataType::kU32, + "textureLoad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + b->vec2<i32>(1, 2), // coords + 3, // array_index + 4); // level + }, + }, + { + ValidTextureOverload::kLoad2dArrayLevelI32, + "textureLoad(t : texture_2d_array<i32>,\n" + " coords : vec2<i32>,\n" + " array_index : i32,\n" + " level : i32) -> vec4<i32>", + TextureKind::kRegular, + ast::TextureDimension::k2dArray, + TextureDataType::kI32, + "textureLoad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + b->vec2<i32>(1, 2), // coords + 3, // array_index + 4); // level + }, + }, + { + ValidTextureOverload::kLoad3dLevelF32, + "textureLoad(t : texture_3d<f32>,\n" + " coords : vec3<i32>,\n" + " level : i32) -> vec4<f32>", + TextureKind::kRegular, + ast::TextureDimension::k3d, + TextureDataType::kF32, + "textureLoad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + b->vec3<i32>(1, 2, 3), // coords + 4); // level + }, + }, + { + ValidTextureOverload::kLoad3dLevelU32, + "textureLoad(t : texture_3d<u32>,\n" + " coords : vec3<i32>,\n" + " level : i32) -> vec4<u32>", + TextureKind::kRegular, + ast::TextureDimension::k3d, + TextureDataType::kU32, + "textureLoad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + b->vec3<i32>(1, 2, 3), // coords + 4); // level + }, + }, + { + ValidTextureOverload::kLoad3dLevelI32, + "textureLoad(t : texture_3d<i32>,\n" + " coords : vec3<i32>,\n" + " level : i32) -> vec4<i32>", + TextureKind::kRegular, + ast::TextureDimension::k3d, + TextureDataType::kI32, + "textureLoad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + b->vec3<i32>(1, 2, 3), // coords + 4); // level + }, + }, + { + ValidTextureOverload::kLoadMultisampled2dF32, + "textureLoad(t : texture_multisampled_2d<f32>,\n" + " coords : vec2<i32>,\n" + " sample_index : i32) -> vec4<f32>", + TextureKind::kMultisampled, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureLoad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + b->vec2<i32>(1, 2), // coords + 3); // sample_index + }, + }, + { + ValidTextureOverload::kLoadMultisampled2dU32, + "textureLoad(t : texture_multisampled_2d<u32>,\n" + " coords : vec2<i32>,\n" + " sample_index : i32) -> vec4<u32>", + TextureKind::kMultisampled, + ast::TextureDimension::k2d, + TextureDataType::kU32, + "textureLoad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + b->vec2<i32>(1, 2), // coords + 3); // sample_index + }, + }, + { + ValidTextureOverload::kLoadMultisampled2dI32, + "textureLoad(t : texture_multisampled_2d<i32>,\n" + " coords : vec2<i32>,\n" + " sample_index : i32) -> vec4<i32>", + TextureKind::kMultisampled, + ast::TextureDimension::k2d, + TextureDataType::kI32, + "textureLoad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + b->vec2<i32>(1, 2), // coords + 3); // sample_index + }, + }, + { + ValidTextureOverload::kLoadDepth2dLevelF32, + "textureLoad(t : texture_depth_2d,\n" + " coords : vec2<i32>,\n" + " level : i32) -> f32", + TextureKind::kDepth, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureLoad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + b->vec2<i32>(1, 2), // coords + 3); // level + }, + }, + { + ValidTextureOverload::kLoadDepth2dArrayLevelF32, + "textureLoad(t : texture_depth_2d_array,\n" + " coords : vec2<i32>,\n" + " array_index : i32,\n" + " level : i32) -> f32", + TextureKind::kDepth, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureLoad", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + b->vec2<i32>(1, 2), // coords + 3, // array_index + 4); // level + }, + }, + { + ValidTextureOverload::kStoreWO1dRgba32float, + "textureStore(t : texture_storage_1d<rgba32float>,\n" + " coords : i32,\n" + " value : vec4<T>)", + ast::Access::kWrite, + ast::TexelFormat::kRgba32Float, + ast::TextureDimension::k1d, + TextureDataType::kF32, + "textureStore", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + 1, // coords + b->vec4<f32>(2.f, 3.f, 4.f, 5.f)); // value + }, + }, + { + ValidTextureOverload::kStoreWO2dRgba32float, + "textureStore(t : texture_storage_2d<rgba32float>,\n" + " coords : vec2<i32>,\n" + " value : vec4<T>)", + ast::Access::kWrite, + ast::TexelFormat::kRgba32Float, + ast::TextureDimension::k2d, + TextureDataType::kF32, + "textureStore", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + b->vec2<i32>(1, 2), // coords + b->vec4<f32>(3.f, 4.f, 5.f, 6.f)); // value + }, + }, + { + ValidTextureOverload::kStoreWO2dArrayRgba32float, + "textureStore(t : texture_storage_2d_array<rgba32float>,\n" + " coords : vec2<i32>,\n" + " array_index : i32,\n" + " value : vec4<T>)", + ast::Access::kWrite, + ast::TexelFormat::kRgba32Float, + ast::TextureDimension::k2dArray, + TextureDataType::kF32, + "textureStore", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + b->vec2<i32>(1, 2), // coords + 3, // array_index + b->vec4<f32>(4.f, 5.f, 6.f, 7.f)); // value + }, + }, + { + ValidTextureOverload::kStoreWO3dRgba32float, + "textureStore(t : texture_storage_3d<rgba32float>,\n" + " coords : vec3<i32>,\n" + " value : vec4<T>)", + ast::Access::kWrite, + ast::TexelFormat::kRgba32Float, + ast::TextureDimension::k3d, + TextureDataType::kF32, + "textureStore", + [](ProgramBuilder* b) { + return b->ExprList("texture", // t + b->vec3<i32>(1, 2, 3), // coords + b->vec4<f32>(4.f, 5.f, 6.f, 7.f)); // value + }, + }, + }; +} + +bool ReturnsVoid(ValidTextureOverload texture_overload) { + switch (texture_overload) { + case ValidTextureOverload::kStoreWO1dRgba32float: + case ValidTextureOverload::kStoreWO2dRgba32float: + case ValidTextureOverload::kStoreWO2dArrayRgba32float: + case ValidTextureOverload::kStoreWO3dRgba32float: + return true; + default: + return false; + } +} + +} // namespace test +} // namespace builtin +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/builtin_texture_helper_test.h b/src/tint/ast/builtin_texture_helper_test.h new file mode 100644 index 0000000..751f51b --- /dev/null +++ b/src/tint/ast/builtin_texture_helper_test.h
@@ -0,0 +1,269 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_BUILTIN_TEXTURE_HELPER_TEST_H_ +#define SRC_TINT_AST_BUILTIN_TEXTURE_HELPER_TEST_H_ + +#include <vector> + +#include "src/tint/ast/access.h" +#include "src/tint/program_builder.h" +#include "src/tint/sem/storage_texture_type.h" + +namespace tint { +namespace ast { +namespace builtin { +namespace test { + +enum class TextureKind { + kRegular, + kDepth, + kDepthMultisampled, + kMultisampled, + kStorage +}; +enum class TextureDataType { kF32, kU32, kI32 }; + +std::ostream& operator<<(std::ostream& out, const TextureKind& kind); +std::ostream& operator<<(std::ostream& out, const TextureDataType& ty); + +/// Non-exhaustive list of valid texture overloads +enum class ValidTextureOverload { + kDimensions1d, + kDimensions2d, + kDimensions2dLevel, + kDimensions2dArray, + kDimensions2dArrayLevel, + kDimensions3d, + kDimensions3dLevel, + kDimensionsCube, + kDimensionsCubeLevel, + kDimensionsCubeArray, + kDimensionsCubeArrayLevel, + kDimensionsMultisampled2d, + kDimensionsDepth2d, + kDimensionsDepth2dLevel, + kDimensionsDepth2dArray, + kDimensionsDepth2dArrayLevel, + kDimensionsDepthCube, + kDimensionsDepthCubeLevel, + kDimensionsDepthCubeArray, + kDimensionsDepthCubeArrayLevel, + kDimensionsDepthMultisampled2d, + kDimensionsStorageWO1d, + kDimensionsStorageWO2d, + kDimensionsStorageWO2dArray, + kDimensionsStorageWO3d, + kGather2dF32, + kGather2dOffsetF32, + kGather2dArrayF32, + kGather2dArrayOffsetF32, + kGatherCubeF32, + kGatherCubeArrayF32, + kGatherDepth2dF32, + kGatherDepth2dOffsetF32, + kGatherDepth2dArrayF32, + kGatherDepth2dArrayOffsetF32, + kGatherDepthCubeF32, + kGatherDepthCubeArrayF32, + kGatherCompareDepth2dF32, + kGatherCompareDepth2dOffsetF32, + kGatherCompareDepth2dArrayF32, + kGatherCompareDepth2dArrayOffsetF32, + kGatherCompareDepthCubeF32, + kGatherCompareDepthCubeArrayF32, + kNumLayers2dArray, + kNumLayersCubeArray, + kNumLayersDepth2dArray, + kNumLayersDepthCubeArray, + kNumLayersStorageWO2dArray, + kNumLevels2d, + kNumLevels2dArray, + kNumLevels3d, + kNumLevelsCube, + kNumLevelsCubeArray, + kNumLevelsDepth2d, + kNumLevelsDepth2dArray, + kNumLevelsDepthCube, + kNumLevelsDepthCubeArray, + kNumSamplesMultisampled2d, + kNumSamplesDepthMultisampled2d, + kSample1dF32, + kSample2dF32, + kSample2dOffsetF32, + kSample2dArrayF32, + kSample2dArrayOffsetF32, + kSample3dF32, + kSample3dOffsetF32, + kSampleCubeF32, + kSampleCubeArrayF32, + kSampleDepth2dF32, + kSampleDepth2dOffsetF32, + kSampleDepth2dArrayF32, + kSampleDepth2dArrayOffsetF32, + kSampleDepthCubeF32, + kSampleDepthCubeArrayF32, + kSampleBias2dF32, + kSampleBias2dOffsetF32, + kSampleBias2dArrayF32, + kSampleBias2dArrayOffsetF32, + kSampleBias3dF32, + kSampleBias3dOffsetF32, + kSampleBiasCubeF32, + kSampleBiasCubeArrayF32, + kSampleLevel2dF32, + kSampleLevel2dOffsetF32, + kSampleLevel2dArrayF32, + kSampleLevel2dArrayOffsetF32, + kSampleLevel3dF32, + kSampleLevel3dOffsetF32, + kSampleLevelCubeF32, + kSampleLevelCubeArrayF32, + kSampleLevelDepth2dF32, + kSampleLevelDepth2dOffsetF32, + kSampleLevelDepth2dArrayF32, + kSampleLevelDepth2dArrayOffsetF32, + kSampleLevelDepthCubeF32, + kSampleLevelDepthCubeArrayF32, + kSampleGrad2dF32, + kSampleGrad2dOffsetF32, + kSampleGrad2dArrayF32, + kSampleGrad2dArrayOffsetF32, + kSampleGrad3dF32, + kSampleGrad3dOffsetF32, + kSampleGradCubeF32, + kSampleGradCubeArrayF32, + kSampleCompareDepth2dF32, + kSampleCompareDepth2dOffsetF32, + kSampleCompareDepth2dArrayF32, + kSampleCompareDepth2dArrayOffsetF32, + kSampleCompareDepthCubeF32, + kSampleCompareDepthCubeArrayF32, + kSampleCompareLevelDepth2dF32, + kSampleCompareLevelDepth2dOffsetF32, + kSampleCompareLevelDepth2dArrayF32, + kSampleCompareLevelDepth2dArrayOffsetF32, + kSampleCompareLevelDepthCubeF32, + kSampleCompareLevelDepthCubeArrayF32, + kLoad1dLevelF32, + kLoad1dLevelU32, + kLoad1dLevelI32, + kLoad2dLevelF32, + kLoad2dLevelU32, + kLoad2dLevelI32, + kLoad2dArrayLevelF32, + kLoad2dArrayLevelU32, + kLoad2dArrayLevelI32, + kLoad3dLevelF32, + kLoad3dLevelU32, + kLoad3dLevelI32, + kLoadMultisampled2dF32, + kLoadMultisampled2dU32, + kLoadMultisampled2dI32, + kLoadDepth2dLevelF32, + kLoadDepth2dArrayLevelF32, + kLoadDepthMultisampled2dF32, + kStoreWO1dRgba32float, // Not permutated for all texel formats + kStoreWO2dRgba32float, // Not permutated for all texel formats + kStoreWO2dArrayRgba32float, // Not permutated for all texel formats + kStoreWO3dRgba32float, // Not permutated for all texel formats +}; + +/// @param texture_overload the ValidTextureOverload +/// @returns true if the ValidTextureOverload builtin returns no value. +bool ReturnsVoid(ValidTextureOverload texture_overload); + +/// Describes a texture builtin overload +struct TextureOverloadCase { + /// Constructor for textureSample...() functions + TextureOverloadCase(ValidTextureOverload, + const char*, + TextureKind, + ast::SamplerKind, + ast::TextureDimension, + TextureDataType, + const char*, + std::function<ExpressionList(ProgramBuilder*)>); + /// Constructor for textureLoad() functions with non-storage textures + TextureOverloadCase(ValidTextureOverload, + const char*, + TextureKind, + ast::TextureDimension, + TextureDataType, + const char*, + std::function<ExpressionList(ProgramBuilder*)>); + /// Constructor for textureLoad() with storage textures + TextureOverloadCase(ValidTextureOverload, + const char*, + Access, + ast::TexelFormat, + ast::TextureDimension, + TextureDataType, + const char*, + std::function<ExpressionList(ProgramBuilder*)>); + /// Copy constructor + TextureOverloadCase(const TextureOverloadCase&); + /// Destructor + ~TextureOverloadCase(); + + /// @return a vector containing a large number (non-exhaustive) of valid + /// texture overloads. + static std::vector<TextureOverloadCase> ValidCases(); + + /// @param builder the AST builder used for the test + /// @returns the vector component type of the texture function return value + const ast::Type* BuildResultVectorComponentType( + ProgramBuilder* builder) const; + /// @param builder the AST builder used for the test + /// @returns a variable holding the test texture, automatically registered as + /// a global variable. + const ast::Variable* BuildTextureVariable(ProgramBuilder* builder) const; + /// @param builder the AST builder used for the test + /// @returns a Variable holding the test sampler, automatically registered as + /// a global variable. + const ast::Variable* BuildSamplerVariable(ProgramBuilder* builder) const; + + /// The enumerator for this overload + const ValidTextureOverload overload; + /// A human readable description of the overload + const char* const description; + /// The texture kind for the texture parameter + const TextureKind texture_kind; + /// The sampler kind for the sampler parameter + /// Used only when texture_kind is not kStorage + ast::SamplerKind const sampler_kind = ast::SamplerKind::kSampler; + /// The access control for the storage texture + /// Used only when texture_kind is kStorage + Access const access = Access::kReadWrite; + /// The image format for the storage texture + /// Used only when texture_kind is kStorage + ast::TexelFormat const texel_format = ast::TexelFormat::kNone; + /// The dimensions of the texture parameter + ast::TextureDimension const texture_dimension; + /// The data type of the texture parameter + const TextureDataType texture_data_type; + /// Name of the function. e.g. `textureSample`, `textureSampleGrad`, etc + const char* const function; + /// A function that builds the AST arguments for the overload + std::function<ExpressionList(ProgramBuilder*)> const args; +}; + +std::ostream& operator<<(std::ostream& out, const TextureOverloadCase& data); + +} // namespace test +} // namespace builtin +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_BUILTIN_TEXTURE_HELPER_TEST_H_
diff --git a/src/tint/ast/call_expression.cc b/src/tint/ast/call_expression.cc new file mode 100644 index 0000000..7abf4d7 --- /dev/null +++ b/src/tint/ast/call_expression.cc
@@ -0,0 +1,78 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/call_expression.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::CallExpression); + +namespace tint { +namespace ast { + +namespace { +CallExpression::Target ToTarget(const IdentifierExpression* name) { + CallExpression::Target target; + target.name = name; + return target; +} +CallExpression::Target ToTarget(const Type* type) { + CallExpression::Target target; + target.type = type; + return target; +} +} // namespace + +CallExpression::CallExpression(ProgramID pid, + const Source& src, + const IdentifierExpression* name, + ExpressionList a) + : Base(pid, src), target(ToTarget(name)), args(a) { + TINT_ASSERT(AST, name); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, name, program_id); + for (auto* arg : args) { + TINT_ASSERT(AST, arg); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, arg, program_id); + } +} + +CallExpression::CallExpression(ProgramID pid, + const Source& src, + const Type* type, + ExpressionList a) + : Base(pid, src), target(ToTarget(type)), args(a) { + TINT_ASSERT(AST, type); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, type, program_id); + for (auto* arg : args) { + TINT_ASSERT(AST, arg); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, arg, program_id); + } +} + +CallExpression::CallExpression(CallExpression&&) = default; + +CallExpression::~CallExpression() = default; + +const CallExpression* CallExpression::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto p = ctx->Clone(args); + return target.name + ? ctx->dst->create<CallExpression>(src, ctx->Clone(target.name), p) + : ctx->dst->create<CallExpression>(src, ctx->Clone(target.type), + p); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/call_expression.h b/src/tint/ast/call_expression.h new file mode 100644 index 0000000..efb1841 --- /dev/null +++ b/src/tint/ast/call_expression.h
@@ -0,0 +1,84 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_CALL_EXPRESSION_H_ +#define SRC_TINT_AST_CALL_EXPRESSION_H_ + +#include "src/tint/ast/expression.h" + +namespace tint { +namespace ast { + +// Forward declarations. +class Type; +class IdentifierExpression; + +/// A call expression - represents either a: +/// * sem::Function +/// * sem::Builtin +/// * sem::TypeConstructor +/// * sem::TypeConversion +class CallExpression final : public Castable<CallExpression, Expression> { + public: + /// Constructor + /// @param program_id the identifier of the program that owns this node + /// @param source the call expression source + /// @param name the function or type name + /// @param args the arguments + CallExpression(ProgramID program_id, + const Source& source, + const IdentifierExpression* name, + ExpressionList args); + + /// Constructor + /// @param program_id the identifier of the program that owns this node + /// @param source the call expression source + /// @param type the type + /// @param args the arguments + CallExpression(ProgramID program_id, + const Source& source, + const Type* type, + ExpressionList args); + + /// Move constructor + CallExpression(CallExpression&&); + ~CallExpression() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const CallExpression* Clone(CloneContext* ctx) const override; + + /// Target is either an identifier, or a Type. + /// One of these must be nullptr and the other a non-nullptr. + struct Target { + /// name is a function or builtin to call, or type name to construct or + /// cast-to + const IdentifierExpression* name = nullptr; + /// type to construct or cast-to + const Type* type = nullptr; + }; + + /// The target function + const Target target; + + /// The arguments + const ExpressionList args; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_CALL_EXPRESSION_H_
diff --git a/src/tint/ast/call_expression_test.cc b/src/tint/ast/call_expression_test.cc new file mode 100644 index 0000000..a150af6 --- /dev/null +++ b/src/tint/ast/call_expression_test.cc
@@ -0,0 +1,149 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "gtest/gtest-spi.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using CallExpressionTest = TestHelper; + +TEST_F(CallExpressionTest, CreationIdentifier) { + auto* func = Expr("func"); + ExpressionList params; + params.push_back(Expr("param1")); + params.push_back(Expr("param2")); + + auto* stmt = create<CallExpression>(func, params); + EXPECT_EQ(stmt->target.name, func); + EXPECT_EQ(stmt->target.type, nullptr); + + const auto& vec = stmt->args; + ASSERT_EQ(vec.size(), 2u); + EXPECT_EQ(vec[0], params[0]); + EXPECT_EQ(vec[1], params[1]); +} + +TEST_F(CallExpressionTest, CreationIdentifier_WithSource) { + auto* func = Expr("func"); + auto* stmt = create<CallExpression>(Source{{20, 2}}, func, ExpressionList{}); + EXPECT_EQ(stmt->target.name, func); + EXPECT_EQ(stmt->target.type, nullptr); + + auto src = stmt->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(CallExpressionTest, CreationType) { + auto* type = ty.f32(); + ExpressionList params; + params.push_back(Expr("param1")); + params.push_back(Expr("param2")); + + auto* stmt = create<CallExpression>(type, params); + EXPECT_EQ(stmt->target.name, nullptr); + EXPECT_EQ(stmt->target.type, type); + + const auto& vec = stmt->args; + ASSERT_EQ(vec.size(), 2u); + EXPECT_EQ(vec[0], params[0]); + EXPECT_EQ(vec[1], params[1]); +} + +TEST_F(CallExpressionTest, CreationType_WithSource) { + auto* type = ty.f32(); + auto* stmt = create<CallExpression>(Source{{20, 2}}, type, ExpressionList{}); + EXPECT_EQ(stmt->target.name, nullptr); + EXPECT_EQ(stmt->target.type, type); + + auto src = stmt->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(CallExpressionTest, IsCall) { + auto* func = Expr("func"); + auto* stmt = create<CallExpression>(func, ExpressionList{}); + EXPECT_TRUE(stmt->Is<CallExpression>()); +} + +TEST_F(CallExpressionTest, Assert_Null_Identifier) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<CallExpression>(static_cast<IdentifierExpression*>(nullptr), + ExpressionList{}); + }, + "internal compiler error"); +} + +TEST_F(CallExpressionTest, Assert_Null_Type) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<CallExpression>(static_cast<Type*>(nullptr), ExpressionList{}); + }, + "internal compiler error"); +} + +TEST_F(CallExpressionTest, Assert_Null_Param) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + ExpressionList params; + params.push_back(b.Expr("param1")); + params.push_back(nullptr); + params.push_back(b.Expr("param2")); + b.create<CallExpression>(b.Expr("func"), params); + }, + "internal compiler error"); +} + +TEST_F(CallExpressionTest, Assert_DifferentProgramID_Identifier) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<CallExpression>(b2.Expr("func"), ExpressionList{}); + }, + "internal compiler error"); +} + +TEST_F(CallExpressionTest, Assert_DifferentProgramID_Type) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<CallExpression>(b2.ty.f32(), ExpressionList{}); + }, + "internal compiler error"); +} + +TEST_F(CallExpressionTest, Assert_DifferentProgramID_Param) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<CallExpression>(b1.Expr("func"), + ExpressionList{b2.Expr("param1")}); + }, + "internal compiler error"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/call_statement.cc b/src/tint/ast/call_statement.cc new file mode 100644 index 0000000..be2e97c --- /dev/null +++ b/src/tint/ast/call_statement.cc
@@ -0,0 +1,44 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/call_statement.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::CallStatement); + +namespace tint { +namespace ast { + +CallStatement::CallStatement(ProgramID pid, + const Source& src, + const CallExpression* call) + : Base(pid, src), expr(call) { + TINT_ASSERT(AST, expr); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, expr, program_id); +} + +CallStatement::CallStatement(CallStatement&&) = default; + +CallStatement::~CallStatement() = default; + +const CallStatement* CallStatement::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* call = ctx->Clone(expr); + return ctx->dst->create<CallStatement>(src, call); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/call_statement.h b/src/tint/ast/call_statement.h new file mode 100644 index 0000000..b9e0c4c --- /dev/null +++ b/src/tint/ast/call_statement.h
@@ -0,0 +1,49 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_CALL_STATEMENT_H_ +#define SRC_TINT_AST_CALL_STATEMENT_H_ + +#include "src/tint/ast/call_expression.h" +#include "src/tint/ast/statement.h" + +namespace tint { +namespace ast { + +/// A call expression +class CallStatement final : public Castable<CallStatement, Statement> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node for the statement + /// @param call the function + CallStatement(ProgramID pid, const Source& src, const CallExpression* call); + /// Move constructor + CallStatement(CallStatement&&); + ~CallStatement() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const CallStatement* Clone(CloneContext* ctx) const override; + + /// The call expression + const CallExpression* const expr; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_CALL_STATEMENT_H_
diff --git a/src/tint/ast/call_statement_test.cc b/src/tint/ast/call_statement_test.cc new file mode 100644 index 0000000..1267a6d --- /dev/null +++ b/src/tint/ast/call_statement_test.cc
@@ -0,0 +1,60 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/call_statement.h" + +#include "gtest/gtest-spi.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using CallStatementTest = TestHelper; + +TEST_F(CallStatementTest, Creation) { + auto* expr = create<CallExpression>(Expr("func"), ExpressionList{}); + + auto* c = create<CallStatement>(expr); + EXPECT_EQ(c->expr, expr); +} + +TEST_F(CallStatementTest, IsCall) { + auto* c = create<CallStatement>(Call("f")); + EXPECT_TRUE(c->Is<CallStatement>()); +} + +TEST_F(CallStatementTest, Assert_Null_Call) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<CallStatement>(nullptr); + }, + "internal compiler error"); +} + +TEST_F(CallStatementTest, Assert_DifferentProgramID_Call) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<CallStatement>( + b2.create<CallExpression>(b2.Expr("func"), ExpressionList{})); + }, + "internal compiler error"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/case_statement.cc b/src/tint/ast/case_statement.cc new file mode 100644 index 0000000..98a2277 --- /dev/null +++ b/src/tint/ast/case_statement.cc
@@ -0,0 +1,50 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/case_statement.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::CaseStatement); + +namespace tint { +namespace ast { + +CaseStatement::CaseStatement(ProgramID pid, + const Source& src, + CaseSelectorList s, + const BlockStatement* b) + : Base(pid, src), selectors(s), body(b) { + TINT_ASSERT(AST, body); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id); + for (auto* selector : selectors) { + TINT_ASSERT(AST, selector); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, selector, program_id); + } +} + +CaseStatement::CaseStatement(CaseStatement&&) = default; + +CaseStatement::~CaseStatement() = default; + +const CaseStatement* CaseStatement::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto sel = ctx->Clone(selectors); + auto* b = ctx->Clone(body); + return ctx->dst->create<CaseStatement>(src, sel, b); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/case_statement.h b/src/tint/ast/case_statement.h new file mode 100644 index 0000000..99fd81d5 --- /dev/null +++ b/src/tint/ast/case_statement.h
@@ -0,0 +1,67 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_CASE_STATEMENT_H_ +#define SRC_TINT_AST_CASE_STATEMENT_H_ + +#include <vector> + +#include "src/tint/ast/block_statement.h" +#include "src/tint/ast/int_literal_expression.h" + +namespace tint { +namespace ast { + +/// A list of case literals +using CaseSelectorList = std::vector<const IntLiteralExpression*>; + +/// A case statement +class CaseStatement final : public Castable<CaseStatement, Statement> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param selectors the case selectors + /// @param body the case body + CaseStatement(ProgramID pid, + const Source& src, + CaseSelectorList selectors, + const BlockStatement* body); + /// Move constructor + CaseStatement(CaseStatement&&); + ~CaseStatement() override; + + /// @returns true if this is a default statement + bool IsDefault() const { return selectors.empty(); } + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const CaseStatement* Clone(CloneContext* ctx) const override; + + /// The case selectors, empty if none set + const CaseSelectorList selectors; + + /// The case body + const BlockStatement* const body; +}; + +/// A list of case statements +using CaseStatementList = std::vector<const CaseStatement*>; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_CASE_STATEMENT_H_
diff --git a/src/tint/ast/case_statement_test.cc b/src/tint/ast/case_statement_test.cc new file mode 100644 index 0000000..b716f9f --- /dev/null +++ b/src/tint/ast/case_statement_test.cc
@@ -0,0 +1,137 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/case_statement.h" + +#include "gtest/gtest-spi.h" +#include "src/tint/ast/discard_statement.h" +#include "src/tint/ast/if_statement.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using CaseStatementTest = TestHelper; + +TEST_F(CaseStatementTest, Creation_i32) { + CaseSelectorList b; + auto* selector = create<SintLiteralExpression>(2); + b.push_back(selector); + + auto* discard = create<DiscardStatement>(); + auto* body = create<BlockStatement>(StatementList{discard}); + + auto* c = create<CaseStatement>(b, body); + ASSERT_EQ(c->selectors.size(), 1u); + EXPECT_EQ(c->selectors[0], selector); + ASSERT_EQ(c->body->statements.size(), 1u); + EXPECT_EQ(c->body->statements[0], discard); +} + +TEST_F(CaseStatementTest, Creation_u32) { + CaseSelectorList b; + auto* selector = create<UintLiteralExpression>(2u); + b.push_back(selector); + + auto* discard = create<DiscardStatement>(); + auto* body = create<BlockStatement>(StatementList{discard}); + + auto* c = create<CaseStatement>(b, body); + ASSERT_EQ(c->selectors.size(), 1u); + EXPECT_EQ(c->selectors[0], selector); + ASSERT_EQ(c->body->statements.size(), 1u); + EXPECT_EQ(c->body->statements[0], discard); +} + +TEST_F(CaseStatementTest, Creation_WithSource) { + CaseSelectorList b; + b.push_back(create<SintLiteralExpression>(2)); + + auto* body = create<BlockStatement>(StatementList{ + create<DiscardStatement>(), + }); + auto* c = create<CaseStatement>(Source{Source::Location{20, 2}}, b, body); + auto src = c->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(CaseStatementTest, IsDefault_WithoutSelectors) { + auto* body = create<BlockStatement>(StatementList{ + create<DiscardStatement>(), + }); + auto* c = create<CaseStatement>(CaseSelectorList{}, body); + EXPECT_TRUE(c->IsDefault()); +} + +TEST_F(CaseStatementTest, IsDefault_WithSelectors) { + CaseSelectorList b; + b.push_back(create<SintLiteralExpression>(2)); + + auto* c = create<CaseStatement>(b, create<BlockStatement>(StatementList{})); + EXPECT_FALSE(c->IsDefault()); +} + +TEST_F(CaseStatementTest, IsCase) { + auto* c = create<CaseStatement>(CaseSelectorList{}, + create<BlockStatement>(StatementList{})); + EXPECT_TRUE(c->Is<CaseStatement>()); +} + +TEST_F(CaseStatementTest, Assert_Null_Body) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<CaseStatement>(CaseSelectorList{}, nullptr); + }, + "internal compiler error"); +} + +TEST_F(CaseStatementTest, Assert_Null_Selector) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<CaseStatement>(CaseSelectorList{nullptr}, + b.create<BlockStatement>(StatementList{})); + }, + "internal compiler error"); +} + +TEST_F(CaseStatementTest, Assert_DifferentProgramID_Call) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<CaseStatement>(CaseSelectorList{}, + b2.create<BlockStatement>(StatementList{})); + }, + "internal compiler error"); +} + +TEST_F(CaseStatementTest, Assert_DifferentProgramID_Selector) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<CaseStatement>( + CaseSelectorList{b2.create<SintLiteralExpression>(2)}, + b1.create<BlockStatement>(StatementList{})); + }, + "internal compiler error"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/compound_assignment_statement.cc b/src/tint/ast/compound_assignment_statement.cc new file mode 100644 index 0000000..cc1f07c --- /dev/null +++ b/src/tint/ast/compound_assignment_statement.cc
@@ -0,0 +1,51 @@ +// Copyright 2022 The Tint Authors. +// +// 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 "src/tint/ast/compound_assignment_statement.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::CompoundAssignmentStatement); + +namespace tint { +namespace ast { + +CompoundAssignmentStatement::CompoundAssignmentStatement(ProgramID pid, + const Source& src, + const Expression* l, + const Expression* r, + BinaryOp o) + : Base(pid, src), lhs(l), rhs(r), op(o) { + TINT_ASSERT(AST, lhs); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, lhs, program_id); + TINT_ASSERT(AST, rhs); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, rhs, program_id); +} + +CompoundAssignmentStatement::CompoundAssignmentStatement( + CompoundAssignmentStatement&&) = default; + +CompoundAssignmentStatement::~CompoundAssignmentStatement() = default; + +const CompoundAssignmentStatement* CompoundAssignmentStatement::Clone( + CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* l = ctx->Clone(lhs); + auto* r = ctx->Clone(rhs); + return ctx->dst->create<CompoundAssignmentStatement>(src, l, r, op); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/compound_assignment_statement.h b/src/tint/ast/compound_assignment_statement.h new file mode 100644 index 0000000..49a2004 --- /dev/null +++ b/src/tint/ast/compound_assignment_statement.h
@@ -0,0 +1,63 @@ +// Copyright 2022 The Tint Authors. +// +// 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 SRC_TINT_AST_COMPOUND_ASSIGNMENT_STATEMENT_H_ +#define SRC_TINT_AST_COMPOUND_ASSIGNMENT_STATEMENT_H_ + +#include "src/tint/ast/binary_expression.h" +#include "src/tint/ast/expression.h" +#include "src/tint/ast/statement.h" + +namespace tint { +namespace ast { + +/// A compound assignment statement +class CompoundAssignmentStatement final + : public Castable<CompoundAssignmentStatement, Statement> { + public: + /// Constructor + /// @param program_id the identifier of the program that owns this node + /// @param source the compound assignment statement source + /// @param lhs the left side of the expression + /// @param rhs the right side of the expression + /// @param op the binary operator + CompoundAssignmentStatement(ProgramID program_id, + const Source& source, + const Expression* lhs, + const Expression* rhs, + BinaryOp op); + /// Move constructor + CompoundAssignmentStatement(CompoundAssignmentStatement&&); + ~CompoundAssignmentStatement() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const CompoundAssignmentStatement* Clone(CloneContext* ctx) const override; + + /// left side expression + const Expression* const lhs; + + /// right side expression + const Expression* const rhs; + + /// the binary operator + const BinaryOp op; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_COMPOUND_ASSIGNMENT_STATEMENT_H_
diff --git a/src/tint/ast/compound_assignment_statement_test.cc b/src/tint/ast/compound_assignment_statement_test.cc new file mode 100644 index 0000000..c84faf0 --- /dev/null +++ b/src/tint/ast/compound_assignment_statement_test.cc
@@ -0,0 +1,102 @@ +// Copyright 2022 The Tint Authors. +// +// 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 "src/tint/ast/compound_assignment_statement.h" + +#include "gtest/gtest-spi.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using CompoundAssignmentStatementTest = TestHelper; + +TEST_F(CompoundAssignmentStatementTest, Creation) { + auto* lhs = Expr("lhs"); + auto* rhs = Expr("rhs"); + auto op = BinaryOp::kAdd; + + auto* stmt = create<CompoundAssignmentStatement>(lhs, rhs, op); + EXPECT_EQ(stmt->lhs, lhs); + EXPECT_EQ(stmt->rhs, rhs); + EXPECT_EQ(stmt->op, op); +} + +TEST_F(CompoundAssignmentStatementTest, CreationWithSource) { + auto* lhs = Expr("lhs"); + auto* rhs = Expr("rhs"); + auto op = BinaryOp::kMultiply; + + auto* stmt = create<CompoundAssignmentStatement>( + Source{Source::Location{20, 2}}, lhs, rhs, op); + auto src = stmt->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(CompoundAssignmentStatementTest, IsCompoundAssign) { + auto* lhs = Expr("lhs"); + auto* rhs = Expr("rhs"); + auto op = BinaryOp::kSubtract; + + auto* stmt = create<CompoundAssignmentStatement>(lhs, rhs, op); + EXPECT_TRUE(stmt->Is<CompoundAssignmentStatement>()); +} + +TEST_F(CompoundAssignmentStatementTest, Assert_Null_LHS) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<CompoundAssignmentStatement>(nullptr, b.Expr(1), + BinaryOp::kAdd); + }, + "internal compiler error"); +} + +TEST_F(CompoundAssignmentStatementTest, Assert_Null_RHS) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<CompoundAssignmentStatement>(b.Expr(1), nullptr, + BinaryOp::kAdd); + }, + "internal compiler error"); +} + +TEST_F(CompoundAssignmentStatementTest, Assert_DifferentProgramID_LHS) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<CompoundAssignmentStatement>(b2.Expr("lhs"), b1.Expr("rhs"), + BinaryOp::kAdd); + }, + "internal compiler error"); +} + +TEST_F(CompoundAssignmentStatementTest, Assert_DifferentProgramID_RHS) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<CompoundAssignmentStatement>(b1.Expr("lhs"), b2.Expr("rhs"), + BinaryOp::kAdd); + }, + "internal compiler error"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/continue_statement.cc b/src/tint/ast/continue_statement.cc new file mode 100644 index 0000000..764c912 --- /dev/null +++ b/src/tint/ast/continue_statement.cc
@@ -0,0 +1,38 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/continue_statement.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::ContinueStatement); + +namespace tint { +namespace ast { + +ContinueStatement::ContinueStatement(ProgramID pid, const Source& src) + : Base(pid, src) {} + +ContinueStatement::ContinueStatement(ContinueStatement&&) = default; + +ContinueStatement::~ContinueStatement() = default; + +const ContinueStatement* ContinueStatement::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + return ctx->dst->create<ContinueStatement>(src); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/continue_statement.h b/src/tint/ast/continue_statement.h new file mode 100644 index 0000000..ab0b12e --- /dev/null +++ b/src/tint/ast/continue_statement.h
@@ -0,0 +1,44 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_CONTINUE_STATEMENT_H_ +#define SRC_TINT_AST_CONTINUE_STATEMENT_H_ + +#include "src/tint/ast/statement.h" + +namespace tint { +namespace ast { + +/// An continue statement +class ContinueStatement final : public Castable<ContinueStatement, Statement> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + ContinueStatement(ProgramID pid, const Source& src); + /// Move constructor + ContinueStatement(ContinueStatement&&); + ~ContinueStatement() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const ContinueStatement* Clone(CloneContext* ctx) const override; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_CONTINUE_STATEMENT_H_
diff --git a/src/tint/ast/continue_statement_test.cc b/src/tint/ast/continue_statement_test.cc new file mode 100644 index 0000000..8f78852 --- /dev/null +++ b/src/tint/ast/continue_statement_test.cc
@@ -0,0 +1,39 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/continue_statement.h" + +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using ContinueStatementTest = TestHelper; + +TEST_F(ContinueStatementTest, Creation_WithSource) { + auto* stmt = create<ContinueStatement>(Source{Source::Location{20, 2}}); + auto src = stmt->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(ContinueStatementTest, IsContinue) { + auto* stmt = create<ContinueStatement>(); + EXPECT_TRUE(stmt->Is<ContinueStatement>()); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/depth_multisampled_texture.cc b/src/tint/ast/depth_multisampled_texture.cc new file mode 100644 index 0000000..bda191d --- /dev/null +++ b/src/tint/ast/depth_multisampled_texture.cc
@@ -0,0 +1,56 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/depth_multisampled_texture.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::DepthMultisampledTexture); + +namespace tint { +namespace ast { +namespace { + +bool IsValidDepthDimension(TextureDimension dim) { + return dim == TextureDimension::k2d; +} + +} // namespace + +DepthMultisampledTexture::DepthMultisampledTexture(ProgramID pid, + const Source& src, + TextureDimension d) + : Base(pid, src, d) { + TINT_ASSERT(AST, IsValidDepthDimension(dim)); +} + +DepthMultisampledTexture::DepthMultisampledTexture(DepthMultisampledTexture&&) = + default; + +DepthMultisampledTexture::~DepthMultisampledTexture() = default; + +std::string DepthMultisampledTexture::FriendlyName(const SymbolTable&) const { + std::ostringstream out; + out << "texture_depth_multisampled_" << dim; + return out.str(); +} + +const DepthMultisampledTexture* DepthMultisampledTexture::Clone( + CloneContext* ctx) const { + auto src = ctx->Clone(source); + return ctx->dst->create<DepthMultisampledTexture>(src, dim); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/depth_multisampled_texture.h b/src/tint/ast/depth_multisampled_texture.h new file mode 100644 index 0000000..95051a4 --- /dev/null +++ b/src/tint/ast/depth_multisampled_texture.h
@@ -0,0 +1,54 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_AST_DEPTH_MULTISAMPLED_TEXTURE_H_ +#define SRC_TINT_AST_DEPTH_MULTISAMPLED_TEXTURE_H_ + +#include <string> + +#include "src/tint/ast/texture.h" + +namespace tint { +namespace ast { + +/// A multisampled depth texture type. +class DepthMultisampledTexture final + : public Castable<DepthMultisampledTexture, Texture> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param dim the dimensionality of the texture + DepthMultisampledTexture(ProgramID pid, + const Source& src, + TextureDimension dim); + /// Move constructor + DepthMultisampledTexture(DepthMultisampledTexture&&); + ~DepthMultisampledTexture() override; + + /// @param symbols the program's symbol table + /// @returns the name for this type that closely resembles how it would be + /// declared in WGSL. + std::string FriendlyName(const SymbolTable& symbols) const override; + + /// Clones this type and all transitive types using the `CloneContext` `ctx`. + /// @param ctx the clone context + /// @return the newly cloned type + const DepthMultisampledTexture* Clone(CloneContext* ctx) const override; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_DEPTH_MULTISAMPLED_TEXTURE_H_
diff --git a/src/tint/ast/depth_multisampled_texture_test.cc b/src/tint/ast/depth_multisampled_texture_test.cc new file mode 100644 index 0000000..540a667 --- /dev/null +++ b/src/tint/ast/depth_multisampled_texture_test.cc
@@ -0,0 +1,37 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/depth_multisampled_texture.h" + +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using AstDepthMultisampledTextureTest = TestHelper; + +TEST_F(AstDepthMultisampledTextureTest, Dim) { + auto* d = create<DepthMultisampledTexture>(TextureDimension::k2d); + EXPECT_EQ(d->dim, TextureDimension::k2d); +} + +TEST_F(AstDepthMultisampledTextureTest, FriendlyName) { + auto* d = create<DepthMultisampledTexture>(TextureDimension::k2d); + EXPECT_EQ(d->FriendlyName(Symbols()), "texture_depth_multisampled_2d"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/depth_texture.cc b/src/tint/ast/depth_texture.cc new file mode 100644 index 0000000..5abfa76 --- /dev/null +++ b/src/tint/ast/depth_texture.cc
@@ -0,0 +1,53 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/depth_texture.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::DepthTexture); + +namespace tint { +namespace ast { +namespace { + +bool IsValidDepthDimension(TextureDimension dim) { + return dim == TextureDimension::k2d || dim == TextureDimension::k2dArray || + dim == TextureDimension::kCube || dim == TextureDimension::kCubeArray; +} + +} // namespace + +DepthTexture::DepthTexture(ProgramID pid, const Source& src, TextureDimension d) + : Base(pid, src, d) { + TINT_ASSERT(AST, IsValidDepthDimension(dim)); +} + +DepthTexture::DepthTexture(DepthTexture&&) = default; + +DepthTexture::~DepthTexture() = default; + +std::string DepthTexture::FriendlyName(const SymbolTable&) const { + std::ostringstream out; + out << "texture_depth_" << dim; + return out.str(); +} + +const DepthTexture* DepthTexture::Clone(CloneContext* ctx) const { + auto src = ctx->Clone(source); + return ctx->dst->create<DepthTexture>(src, dim); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/depth_texture.h b/src/tint/ast/depth_texture.h new file mode 100644 index 0000000..62cd09d --- /dev/null +++ b/src/tint/ast/depth_texture.h
@@ -0,0 +1,51 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_DEPTH_TEXTURE_H_ +#define SRC_TINT_AST_DEPTH_TEXTURE_H_ + +#include <string> + +#include "src/tint/ast/texture.h" + +namespace tint { +namespace ast { + +/// A depth texture type. +class DepthTexture final : public Castable<DepthTexture, Texture> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param dim the dimensionality of the texture + DepthTexture(ProgramID pid, const Source& src, TextureDimension dim); + /// Move constructor + DepthTexture(DepthTexture&&); + ~DepthTexture() override; + + /// @param symbols the program's symbol table + /// @returns the name for this type that closely resembles how it would be + /// declared in WGSL. + std::string FriendlyName(const SymbolTable& symbols) const override; + + /// Clones this type and all transitive types using the `CloneContext` `ctx`. + /// @param ctx the clone context + /// @return the newly cloned type + const DepthTexture* Clone(CloneContext* ctx) const override; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_DEPTH_TEXTURE_H_
diff --git a/src/tint/ast/depth_texture_test.cc b/src/tint/ast/depth_texture_test.cc new file mode 100644 index 0000000..2c4de5e --- /dev/null +++ b/src/tint/ast/depth_texture_test.cc
@@ -0,0 +1,44 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/depth_texture.h" + +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using AstDepthTextureTest = TestHelper; + +TEST_F(AstDepthTextureTest, IsTexture) { + Texture* ty = create<DepthTexture>(TextureDimension::kCube); + EXPECT_TRUE(ty->Is<DepthTexture>()); + EXPECT_FALSE(ty->Is<SampledTexture>()); + EXPECT_FALSE(ty->Is<StorageTexture>()); +} + +TEST_F(AstDepthTextureTest, Dim) { + auto* d = create<DepthTexture>(TextureDimension::kCube); + EXPECT_EQ(d->dim, TextureDimension::kCube); +} + +TEST_F(AstDepthTextureTest, FriendlyName) { + auto* d = create<DepthTexture>(TextureDimension::kCube); + EXPECT_EQ(d->FriendlyName(Symbols()), "texture_depth_cube"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/disable_validation_attribute.cc b/src/tint/ast/disable_validation_attribute.cc new file mode 100644 index 0000000..e474b8e --- /dev/null +++ b/src/tint/ast/disable_validation_attribute.cc
@@ -0,0 +1,57 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/disable_validation_attribute.h" +#include "src/tint/clone_context.h" +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::DisableValidationAttribute); + +namespace tint { +namespace ast { + +DisableValidationAttribute::DisableValidationAttribute(ProgramID pid, + DisabledValidation val) + : Base(pid), validation(val) {} + +DisableValidationAttribute::~DisableValidationAttribute() = default; + +std::string DisableValidationAttribute::InternalName() const { + switch (validation) { + case DisabledValidation::kFunctionHasNoBody: + return "disable_validation__function_has_no_body"; + case DisabledValidation::kBindingPointCollision: + return "disable_validation__binding_point_collision"; + case DisabledValidation::kIgnoreStorageClass: + return "disable_validation__ignore_storage_class"; + case DisabledValidation::kEntryPointParameter: + return "disable_validation__entry_point_parameter"; + case DisabledValidation::kIgnoreConstructibleFunctionParameter: + return "disable_validation__ignore_constructible_function_parameter"; + case DisabledValidation::kIgnoreStrideAttribute: + return "disable_validation__ignore_stride"; + case DisabledValidation::kIgnoreInvalidPointerArgument: + return "disable_validation__ignore_invalid_pointer_argument"; + } + return "<invalid>"; +} + +const DisableValidationAttribute* DisableValidationAttribute::Clone( + CloneContext* ctx) const { + return ctx->dst->ASTNodes().Create<DisableValidationAttribute>(ctx->dst->ID(), + validation); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/disable_validation_attribute.h b/src/tint/ast/disable_validation_attribute.h new file mode 100644 index 0000000..d46edb8 --- /dev/null +++ b/src/tint/ast/disable_validation_attribute.h
@@ -0,0 +1,83 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_AST_DISABLE_VALIDATION_ATTRIBUTE_H_ +#define SRC_TINT_AST_DISABLE_VALIDATION_ATTRIBUTE_H_ + +#include <string> + +#include "src/tint/ast/internal_attribute.h" + +namespace tint { +namespace ast { + +/// Enumerator of validation features that can be disabled with a +/// DisableValidationAttribute attribute. +enum class DisabledValidation { + /// When applied to a function, the validator will not complain there is no + /// body to a function. + kFunctionHasNoBody, + /// When applied to a module-scoped variable, the validator will not complain + /// if two resource variables have the same binding points. + kBindingPointCollision, + /// When applied to a variable, the validator will not complain about the + /// declared storage class. + kIgnoreStorageClass, + /// When applied to an entry-point function parameter, the validator will not + /// check for entry IO attributes. + kEntryPointParameter, + /// When applied to a function parameter, the validator will not + /// check if parameter type is constructible + kIgnoreConstructibleFunctionParameter, + /// When applied to a member attribute, a stride attribute may be applied to + /// non-array types. + kIgnoreStrideAttribute, + /// When applied to a pointer function parameter, the validator will not + /// require a function call argument passed for that parameter to have a + /// certain form. + kIgnoreInvalidPointerArgument, +}; + +/// An internal attribute used to tell the validator to ignore specific +/// violations. Typically generated by transforms that need to produce ASTs that +/// would otherwise cause validation errors. +class DisableValidationAttribute final + : public Castable<DisableValidationAttribute, InternalAttribute> { + public: + /// Constructor + /// @param program_id the identifier of the program that owns this node + /// @param validation the validation to disable + explicit DisableValidationAttribute(ProgramID program_id, + DisabledValidation validation); + + /// Destructor + ~DisableValidationAttribute() override; + + /// @return a short description of the internal attribute which will be + /// displayed in WGSL as `@internal(<name>)` (but is not parsable). + std::string InternalName() const override; + + /// Performs a deep clone of this object using the CloneContext `ctx`. + /// @param ctx the clone context + /// @return the newly cloned object + const DisableValidationAttribute* Clone(CloneContext* ctx) const override; + + /// The validation that this attribute disables + const DisabledValidation validation; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_DISABLE_VALIDATION_ATTRIBUTE_H_
diff --git a/src/tint/ast/discard_statement.cc b/src/tint/ast/discard_statement.cc new file mode 100644 index 0000000..a8f9cf6 --- /dev/null +++ b/src/tint/ast/discard_statement.cc
@@ -0,0 +1,38 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/discard_statement.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::DiscardStatement); + +namespace tint { +namespace ast { + +DiscardStatement::DiscardStatement(ProgramID pid, const Source& src) + : Base(pid, src) {} + +DiscardStatement::DiscardStatement(DiscardStatement&&) = default; + +DiscardStatement::~DiscardStatement() = default; + +const DiscardStatement* DiscardStatement::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + return ctx->dst->create<DiscardStatement>(src); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/discard_statement.h b/src/tint/ast/discard_statement.h new file mode 100644 index 0000000..7e4fcf1 --- /dev/null +++ b/src/tint/ast/discard_statement.h
@@ -0,0 +1,44 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_DISCARD_STATEMENT_H_ +#define SRC_TINT_AST_DISCARD_STATEMENT_H_ + +#include "src/tint/ast/statement.h" + +namespace tint { +namespace ast { + +/// A discard statement +class DiscardStatement final : public Castable<DiscardStatement, Statement> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + DiscardStatement(ProgramID pid, const Source& src); + /// Move constructor + DiscardStatement(DiscardStatement&&); + ~DiscardStatement() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const DiscardStatement* Clone(CloneContext* ctx) const override; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_DISCARD_STATEMENT_H_
diff --git a/src/tint/ast/discard_statement_test.cc b/src/tint/ast/discard_statement_test.cc new file mode 100644 index 0000000..08a0cc3 --- /dev/null +++ b/src/tint/ast/discard_statement_test.cc
@@ -0,0 +1,49 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/discard_statement.h" + +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using DiscardStatementTest = TestHelper; + +TEST_F(DiscardStatementTest, Creation) { + auto* stmt = create<DiscardStatement>(); + EXPECT_EQ(stmt->source.range.begin.line, 0u); + EXPECT_EQ(stmt->source.range.begin.column, 0u); + EXPECT_EQ(stmt->source.range.end.line, 0u); + EXPECT_EQ(stmt->source.range.end.column, 0u); +} + +TEST_F(DiscardStatementTest, Creation_WithSource) { + auto* stmt = create<DiscardStatement>( + Source{Source::Range{Source::Location{20, 2}, Source::Location{20, 5}}}); + EXPECT_EQ(stmt->source.range.begin.line, 20u); + EXPECT_EQ(stmt->source.range.begin.column, 2u); + EXPECT_EQ(stmt->source.range.end.line, 20u); + EXPECT_EQ(stmt->source.range.end.column, 5u); +} + +TEST_F(DiscardStatementTest, IsDiscard) { + auto* stmt = create<DiscardStatement>(); + EXPECT_TRUE(stmt->Is<DiscardStatement>()); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/else_statement.cc b/src/tint/ast/else_statement.cc new file mode 100644 index 0000000..62908b5 --- /dev/null +++ b/src/tint/ast/else_statement.cc
@@ -0,0 +1,47 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/else_statement.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::ElseStatement); + +namespace tint { +namespace ast { + +ElseStatement::ElseStatement(ProgramID pid, + const Source& src, + const Expression* cond, + const BlockStatement* b) + : Base(pid, src), condition(cond), body(b) { + TINT_ASSERT(AST, body); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, condition, program_id); +} + +ElseStatement::ElseStatement(ElseStatement&&) = default; + +ElseStatement::~ElseStatement() = default; + +const ElseStatement* ElseStatement::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* cond = ctx->Clone(condition); + auto* b = ctx->Clone(body); + return ctx->dst->create<ElseStatement>(src, cond, b); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/else_statement.h b/src/tint/ast/else_statement.h new file mode 100644 index 0000000..6f641c7 --- /dev/null +++ b/src/tint/ast/else_statement.h
@@ -0,0 +1,61 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_ELSE_STATEMENT_H_ +#define SRC_TINT_AST_ELSE_STATEMENT_H_ + +#include <vector> + +#include "src/tint/ast/block_statement.h" +#include "src/tint/ast/expression.h" + +namespace tint { +namespace ast { + +/// An else statement +class ElseStatement final : public Castable<ElseStatement, Statement> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param condition the else condition + /// @param body the else body + ElseStatement(ProgramID pid, + const Source& src, + const Expression* condition, + const BlockStatement* body); + /// Move constructor + ElseStatement(ElseStatement&&); + ~ElseStatement() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const ElseStatement* Clone(CloneContext* ctx) const override; + + /// The else condition or nullptr if none set + const Expression* const condition; + + /// The else body + const BlockStatement* const body; +}; + +/// A list of else statements +using ElseStatementList = std::vector<const ElseStatement*>; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_ELSE_STATEMENT_H_
diff --git a/src/tint/ast/else_statement_test.cc b/src/tint/ast/else_statement_test.cc new file mode 100644 index 0000000..27fe9ec --- /dev/null +++ b/src/tint/ast/else_statement_test.cc
@@ -0,0 +1,94 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "gtest/gtest-spi.h" +#include "src/tint/ast/discard_statement.h" +#include "src/tint/ast/if_statement.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using ElseStatementTest = TestHelper; + +TEST_F(ElseStatementTest, Creation) { + auto* cond = Expr(true); + auto* body = create<BlockStatement>(StatementList{ + create<DiscardStatement>(), + }); + auto* discard = body->statements[0]; + + auto* e = create<ElseStatement>(cond, body); + EXPECT_EQ(e->condition, cond); + ASSERT_EQ(e->body->statements.size(), 1u); + EXPECT_EQ(e->body->statements[0], discard); +} + +TEST_F(ElseStatementTest, Creation_WithSource) { + auto* e = create<ElseStatement>(Source{Source::Location{20, 2}}, Expr(true), + Block()); + auto src = e->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(ElseStatementTest, IsElse) { + auto* e = create<ElseStatement>(nullptr, Block()); + EXPECT_TRUE(e->Is<ElseStatement>()); +} + +TEST_F(ElseStatementTest, HasCondition) { + auto* cond = Expr(true); + auto* e = create<ElseStatement>(cond, Block()); + EXPECT_TRUE(e->condition); +} + +TEST_F(ElseStatementTest, HasContition_NullCondition) { + auto* e = create<ElseStatement>(nullptr, Block()); + EXPECT_FALSE(e->condition); +} + +TEST_F(ElseStatementTest, Assert_Null_Body) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<ElseStatement>(b.Expr(true), nullptr); + }, + "internal compiler error"); +} + +TEST_F(ElseStatementTest, Assert_DifferentProgramID_Condition) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<ElseStatement>(b2.Expr(true), b1.Block()); + }, + "internal compiler error"); +} + +TEST_F(ElseStatementTest, Assert_DifferentProgramID_Body) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<ElseStatement>(b1.Expr(true), b2.Block()); + }, + "internal compiler error"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/expression.cc b/src/tint/ast/expression.cc new file mode 100644 index 0000000..a432326 --- /dev/null +++ b/src/tint/ast/expression.cc
@@ -0,0 +1,32 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/expression.h" + +#include "src/tint/sem/expression.h" +#include "src/tint/sem/info.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::Expression); + +namespace tint { +namespace ast { + +Expression::Expression(ProgramID pid, const Source& src) : Base(pid, src) {} + +Expression::Expression(Expression&&) = default; + +Expression::~Expression() = default; + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/expression.h b/src/tint/ast/expression.h new file mode 100644 index 0000000..15bdacc --- /dev/null +++ b/src/tint/ast/expression.h
@@ -0,0 +1,47 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_EXPRESSION_H_ +#define SRC_TINT_AST_EXPRESSION_H_ + +#include <string> +#include <vector> + +#include "src/tint/ast/node.h" +#include "src/tint/sem/type.h" + +namespace tint { +namespace ast { + +/// Base expression class +class Expression : public Castable<Expression, Node> { + public: + ~Expression() override; + + protected: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + Expression(ProgramID pid, const Source& src); + /// Move constructor + Expression(Expression&&); +}; + +/// A list of expressions +using ExpressionList = std::vector<const Expression*>; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_EXPRESSION_H_
diff --git a/src/tint/ast/external_texture.cc b/src/tint/ast/external_texture.cc new file mode 100644 index 0000000..a5a703d --- /dev/null +++ b/src/tint/ast/external_texture.cc
@@ -0,0 +1,41 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/external_texture.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::ExternalTexture); + +namespace tint { +namespace ast { + +// ExternalTexture::ExternalTexture() : Base(ast::TextureDimension::k2d) {} +ExternalTexture::ExternalTexture(ProgramID pid, const Source& src) + : Base(pid, src, ast::TextureDimension::k2d) {} + +ExternalTexture::ExternalTexture(ExternalTexture&&) = default; + +ExternalTexture::~ExternalTexture() = default; + +std::string ExternalTexture::FriendlyName(const SymbolTable&) const { + return "texture_external"; +} + +const ExternalTexture* ExternalTexture::Clone(CloneContext* ctx) const { + return ctx->dst->create<ExternalTexture>(); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/external_texture.h b/src/tint/ast/external_texture.h new file mode 100644 index 0000000..cf9f60d --- /dev/null +++ b/src/tint/ast/external_texture.h
@@ -0,0 +1,51 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_AST_EXTERNAL_TEXTURE_H_ +#define SRC_TINT_AST_EXTERNAL_TEXTURE_H_ + +#include <string> + +#include "src/tint/ast/texture.h" + +namespace tint { +namespace ast { + +/// An external texture type +class ExternalTexture final : public Castable<ExternalTexture, Texture> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + ExternalTexture(ProgramID pid, const Source& src); + + /// Move constructor + ExternalTexture(ExternalTexture&&); + ~ExternalTexture() override; + + /// @param symbols the program's symbol table + /// @returns the name for this type that closely resembles how it would be + /// declared in WGSL. + std::string FriendlyName(const SymbolTable& symbols) const override; + + /// Clones this type and all transitive types using the `CloneContext` `ctx`. + /// @param ctx the clone context + /// @return the newly cloned type + const ExternalTexture* Clone(CloneContext* ctx) const override; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_EXTERNAL_TEXTURE_H_
diff --git a/src/tint/ast/external_texture_test.cc b/src/tint/ast/external_texture_test.cc new file mode 100644 index 0000000..af25ac2 --- /dev/null +++ b/src/tint/ast/external_texture_test.cc
@@ -0,0 +1,46 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/external_texture.h" + +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using AstExternalTextureTest = TestHelper; + +TEST_F(AstExternalTextureTest, IsTexture) { + Texture* ty = create<ExternalTexture>(); + EXPECT_FALSE(ty->Is<DepthTexture>()); + EXPECT_TRUE(ty->Is<ExternalTexture>()); + EXPECT_FALSE(ty->Is<MultisampledTexture>()); + EXPECT_FALSE(ty->Is<SampledTexture>()); + EXPECT_FALSE(ty->Is<StorageTexture>()); +} + +TEST_F(AstExternalTextureTest, Dim) { + auto* ty = create<ExternalTexture>(); + EXPECT_EQ(ty->dim, ast::TextureDimension::k2d); +} + +TEST_F(AstExternalTextureTest, FriendlyName) { + auto* ty = create<ExternalTexture>(); + EXPECT_EQ(ty->FriendlyName(Symbols()), "texture_external"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/f32.cc b/src/tint/ast/f32.cc new file mode 100644 index 0000000..319822c --- /dev/null +++ b/src/tint/ast/f32.cc
@@ -0,0 +1,40 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/f32.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::F32); + +namespace tint { +namespace ast { + +F32::F32(ProgramID pid, const Source& src) : Base(pid, src) {} + +F32::F32(F32&&) = default; + +F32::~F32() = default; + +std::string F32::FriendlyName(const SymbolTable&) const { + return "f32"; +} + +const F32* F32::Clone(CloneContext* ctx) const { + auto src = ctx->Clone(source); + return ctx->dst->create<F32>(src); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/f32.h b/src/tint/ast/f32.h new file mode 100644 index 0000000..58019c9 --- /dev/null +++ b/src/tint/ast/f32.h
@@ -0,0 +1,50 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_F32_H_ +#define SRC_TINT_AST_F32_H_ + +#include <string> + +#include "src/tint/ast/type.h" + +namespace tint { +namespace ast { + +/// A float 32 type +class F32 final : public Castable<F32, Type> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + F32(ProgramID pid, const Source& src); + /// Move constructor + F32(F32&&); + ~F32() override; + + /// @param symbols the program's symbol table + /// @returns the name for this type that closely resembles how it would be + /// declared in WGSL. + std::string FriendlyName(const SymbolTable& symbols) const override; + + /// Clones this type and all transitive types using the `CloneContext` `ctx`. + /// @param ctx the clone context + /// @return the newly cloned type + const F32* Clone(CloneContext* ctx) const override; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_F32_H_
diff --git a/src/tint/ast/f32_test.cc b/src/tint/ast/f32_test.cc new file mode 100644 index 0000000..ec6d62e --- /dev/null +++ b/src/tint/ast/f32_test.cc
@@ -0,0 +1,32 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/f32.h" + +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using AstF32Test = TestHelper; + +TEST_F(AstF32Test, FriendlyName) { + auto* f = create<F32>(); + EXPECT_EQ(f->FriendlyName(Symbols()), "f32"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/fallthrough_statement.cc b/src/tint/ast/fallthrough_statement.cc new file mode 100644 index 0000000..463d2ad --- /dev/null +++ b/src/tint/ast/fallthrough_statement.cc
@@ -0,0 +1,39 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/fallthrough_statement.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::FallthroughStatement); + +namespace tint { +namespace ast { + +FallthroughStatement::FallthroughStatement(ProgramID pid, const Source& src) + : Base(pid, src) {} + +FallthroughStatement::FallthroughStatement(FallthroughStatement&&) = default; + +FallthroughStatement::~FallthroughStatement() = default; + +const FallthroughStatement* FallthroughStatement::Clone( + CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + return ctx->dst->create<FallthroughStatement>(src); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/fallthrough_statement.h b/src/tint/ast/fallthrough_statement.h new file mode 100644 index 0000000..f1cc716 --- /dev/null +++ b/src/tint/ast/fallthrough_statement.h
@@ -0,0 +1,45 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_FALLTHROUGH_STATEMENT_H_ +#define SRC_TINT_AST_FALLTHROUGH_STATEMENT_H_ + +#include "src/tint/ast/statement.h" + +namespace tint { +namespace ast { + +/// An fallthrough statement +class FallthroughStatement final + : public Castable<FallthroughStatement, Statement> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + FallthroughStatement(ProgramID pid, const Source& src); + /// Move constructor + FallthroughStatement(FallthroughStatement&&); + ~FallthroughStatement() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const FallthroughStatement* Clone(CloneContext* ctx) const override; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_FALLTHROUGH_STATEMENT_H_
diff --git a/src/tint/ast/fallthrough_statement_test.cc b/src/tint/ast/fallthrough_statement_test.cc new file mode 100644 index 0000000..5adda3d --- /dev/null +++ b/src/tint/ast/fallthrough_statement_test.cc
@@ -0,0 +1,47 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/fallthrough_statement.h" + +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using FallthroughStatementTest = TestHelper; + +TEST_F(FallthroughStatementTest, Creation) { + auto* stmt = create<FallthroughStatement>(); + EXPECT_EQ(stmt->source.range.begin.line, 0u); + EXPECT_EQ(stmt->source.range.begin.column, 0u); + EXPECT_EQ(stmt->source.range.end.line, 0u); + EXPECT_EQ(stmt->source.range.end.column, 0u); +} + +TEST_F(FallthroughStatementTest, Creation_WithSource) { + auto* stmt = create<FallthroughStatement>(Source{Source::Location{20, 2}}); + auto src = stmt->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(FallthroughStatementTest, IsFallthrough) { + auto* stmt = create<FallthroughStatement>(); + EXPECT_TRUE(stmt->Is<FallthroughStatement>()); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/float_literal_expression.cc b/src/tint/ast/float_literal_expression.cc new file mode 100644 index 0000000..b505f67 --- /dev/null +++ b/src/tint/ast/float_literal_expression.cc
@@ -0,0 +1,41 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/float_literal_expression.h" + +#include <limits> + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::FloatLiteralExpression); + +namespace tint { +namespace ast { + +FloatLiteralExpression::FloatLiteralExpression(ProgramID pid, + const Source& src, + float val) + : Base(pid, src), value(val) {} + +FloatLiteralExpression::~FloatLiteralExpression() = default; + +const FloatLiteralExpression* FloatLiteralExpression::Clone( + CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + return ctx->dst->create<FloatLiteralExpression>(src, value); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/float_literal_expression.h b/src/tint/ast/float_literal_expression.h new file mode 100644 index 0000000..ffa3c2f --- /dev/null +++ b/src/tint/ast/float_literal_expression.h
@@ -0,0 +1,49 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_FLOAT_LITERAL_EXPRESSION_H_ +#define SRC_TINT_AST_FLOAT_LITERAL_EXPRESSION_H_ + +#include <string> + +#include "src/tint/ast/literal_expression.h" + +namespace tint { +namespace ast { + +/// A float literal +class FloatLiteralExpression final + : public Castable<FloatLiteralExpression, LiteralExpression> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param value the float literals value + FloatLiteralExpression(ProgramID pid, const Source& src, float value); + ~FloatLiteralExpression() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const FloatLiteralExpression* Clone(CloneContext* ctx) const override; + + /// The float literal value + const float value; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_FLOAT_LITERAL_EXPRESSION_H_
diff --git a/src/tint/ast/float_literal_expression_test.cc b/src/tint/ast/float_literal_expression_test.cc new file mode 100644 index 0000000..7a91da9 --- /dev/null +++ b/src/tint/ast/float_literal_expression_test.cc
@@ -0,0 +1,31 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using FloatLiteralExpressionTest = TestHelper; + +TEST_F(FloatLiteralExpressionTest, Value) { + auto* f = create<FloatLiteralExpression>(47.2f); + ASSERT_TRUE(f->Is<FloatLiteralExpression>()); + EXPECT_EQ(f->value, 47.2f); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/for_loop_statement.cc b/src/tint/ast/for_loop_statement.cc new file mode 100644 index 0000000..7bb9389 --- /dev/null +++ b/src/tint/ast/for_loop_statement.cc
@@ -0,0 +1,59 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/for_loop_statement.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::ForLoopStatement); + +namespace tint { +namespace ast { + +ForLoopStatement::ForLoopStatement(ProgramID pid, + const Source& src, + const Statement* init, + const Expression* cond, + const Statement* cont, + const BlockStatement* b) + : Base(pid, src), + initializer(init), + condition(cond), + continuing(cont), + body(b) { + TINT_ASSERT(AST, body); + + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, initializer, program_id); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, condition, program_id); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, continuing, program_id); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id); +} + +ForLoopStatement::ForLoopStatement(ForLoopStatement&&) = default; + +ForLoopStatement::~ForLoopStatement() = default; + +const ForLoopStatement* ForLoopStatement::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + + auto* init = ctx->Clone(initializer); + auto* cond = ctx->Clone(condition); + auto* cont = ctx->Clone(continuing); + auto* b = ctx->Clone(body); + return ctx->dst->create<ForLoopStatement>(src, init, cond, cont, b); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/for_loop_statement.h b/src/tint/ast/for_loop_statement.h new file mode 100644 index 0000000..040ba52 --- /dev/null +++ b/src/tint/ast/for_loop_statement.h
@@ -0,0 +1,67 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_AST_FOR_LOOP_STATEMENT_H_ +#define SRC_TINT_AST_FOR_LOOP_STATEMENT_H_ + +#include "src/tint/ast/block_statement.h" + +namespace tint { +namespace ast { + +class Expression; + +/// A for loop statement +class ForLoopStatement final : public Castable<ForLoopStatement, Statement> { + public: + /// Constructor + /// @param program_id the identifier of the program that owns this node + /// @param source the for loop statement source + /// @param initializer the optional loop initializer statement + /// @param condition the optional loop condition expression + /// @param continuing the optional continuing statement + /// @param body the loop body + ForLoopStatement(ProgramID program_id, + Source const& source, + const Statement* initializer, + const Expression* condition, + const Statement* continuing, + const BlockStatement* body); + /// Move constructor + ForLoopStatement(ForLoopStatement&&); + ~ForLoopStatement() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const ForLoopStatement* Clone(CloneContext* ctx) const override; + + /// The initializer statement + const Statement* const initializer; + + /// The condition expression + const Expression* const condition; + + /// The continuing statement + const Statement* const continuing; + + /// The loop body block + const BlockStatement* const body; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_FOR_LOOP_STATEMENT_H_
diff --git a/src/tint/ast/for_loop_statement_test.cc b/src/tint/ast/for_loop_statement_test.cc new file mode 100644 index 0000000..3e5e585 --- /dev/null +++ b/src/tint/ast/for_loop_statement_test.cc
@@ -0,0 +1,104 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "gtest/gtest-spi.h" +#include "src/tint/ast/binary_expression.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using ForLoopStatementTest = TestHelper; + +TEST_F(ForLoopStatementTest, Creation) { + auto* init = Decl(Var("i", ty.u32())); + auto* cond = + create<BinaryExpression>(BinaryOp::kLessThan, Expr("i"), Expr(5u)); + auto* cont = Assign("i", Add("i", 1)); + auto* body = Block(Return()); + auto* l = For(init, cond, cont, body); + + EXPECT_EQ(l->initializer, init); + EXPECT_EQ(l->condition, cond); + EXPECT_EQ(l->continuing, cont); + EXPECT_EQ(l->body, body); +} + +TEST_F(ForLoopStatementTest, Creation_WithSource) { + auto* body = Block(Return()); + auto* l = For(Source{{20u, 2u}}, nullptr, nullptr, nullptr, body); + auto src = l->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(ForLoopStatementTest, Creation_Null_InitCondCont) { + auto* body = Block(Return()); + auto* l = For(nullptr, nullptr, nullptr, body); + EXPECT_EQ(l->body, body); +} + +TEST_F(ForLoopStatementTest, Assert_Null_Body) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.For(nullptr, nullptr, nullptr, nullptr); + }, + "internal compiler error"); +} + +TEST_F(ForLoopStatementTest, Assert_DifferentProgramID_Initializer) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.For(b2.Block(), nullptr, nullptr, b1.Block()); + }, + "internal compiler error"); +} + +TEST_F(ForLoopStatementTest, Assert_DifferentProgramID_Condition) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.For(nullptr, b2.Expr(true), nullptr, b1.Block()); + }, + "internal compiler error"); +} + +TEST_F(ForLoopStatementTest, Assert_DifferentProgramID_Continuing) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.For(nullptr, nullptr, b2.Block(), b1.Block()); + }, + "internal compiler error"); +} + +TEST_F(ForLoopStatementTest, Assert_DifferentProgramID_Body) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.For(nullptr, nullptr, nullptr, b2.Block()); + }, + "internal compiler error"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/function.cc b/src/tint/ast/function.cc new file mode 100644 index 0000000..cba8091 --- /dev/null +++ b/src/tint/ast/function.cc
@@ -0,0 +1,108 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/function.h" + +#include "src/tint/ast/stage_attribute.h" +#include "src/tint/ast/workgroup_attribute.h" +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::Function); + +namespace tint { +namespace ast { + +Function::Function(ProgramID pid, + const Source& src, + Symbol sym, + VariableList parameters, + const Type* return_ty, + const BlockStatement* b, + AttributeList attrs, + AttributeList return_type_attrs) + : Base(pid, src), + symbol(sym), + params(std::move(parameters)), + return_type(return_ty), + body(b), + attributes(std::move(attrs)), + return_type_attributes(std::move(return_type_attrs)) { + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, symbol, program_id); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id); + for (auto* param : params) { + TINT_ASSERT(AST, param && param->is_const); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, param, program_id); + } + TINT_ASSERT(AST, symbol.IsValid()); + TINT_ASSERT(AST, return_type); + for (auto* attr : attributes) { + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, attr, program_id); + } + for (auto* attr : return_type_attributes) { + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, attr, program_id); + } +} + +Function::Function(Function&&) = default; + +Function::~Function() = default; + +PipelineStage Function::PipelineStage() const { + if (auto* stage = GetAttribute<StageAttribute>(attributes)) { + return stage->stage; + } + return PipelineStage::kNone; +} + +const Function* Function::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto sym = ctx->Clone(symbol); + auto p = ctx->Clone(params); + auto* ret = ctx->Clone(return_type); + auto* b = ctx->Clone(body); + auto attrs = ctx->Clone(attributes); + auto ret_attrs = ctx->Clone(return_type_attributes); + return ctx->dst->create<Function>(src, sym, p, ret, b, attrs, ret_attrs); +} + +const Function* FunctionList::Find(Symbol sym) const { + for (auto* func : *this) { + if (func->symbol == sym) { + return func; + } + } + return nullptr; +} + +const Function* FunctionList::Find(Symbol sym, PipelineStage stage) const { + for (auto* func : *this) { + if (func->symbol == sym && func->PipelineStage() == stage) { + return func; + } + } + return nullptr; +} + +bool FunctionList::HasStage(ast::PipelineStage stage) const { + for (auto* func : *this) { + if (func->PipelineStage() == stage) { + return true; + } + } + return false; +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/function.h b/src/tint/ast/function.h new file mode 100644 index 0000000..10ad7a6 --- /dev/null +++ b/src/tint/ast/function.h
@@ -0,0 +1,118 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_FUNCTION_H_ +#define SRC_TINT_AST_FUNCTION_H_ + +#include <string> +#include <tuple> +#include <utility> +#include <vector> + +#include "src/tint/ast/attribute.h" +#include "src/tint/ast/binding_attribute.h" +#include "src/tint/ast/block_statement.h" +#include "src/tint/ast/builtin_attribute.h" +#include "src/tint/ast/group_attribute.h" +#include "src/tint/ast/location_attribute.h" +#include "src/tint/ast/pipeline_stage.h" +#include "src/tint/ast/variable.h" + +namespace tint { +namespace ast { + +/// A Function statement. +class Function final : public Castable<Function, Node> { + public: + /// Create a function + /// @param program_id the identifier of the program that owns this node + /// @param source the variable source + /// @param symbol the function symbol + /// @param params the function parameters + /// @param return_type the return type + /// @param body the function body + /// @param attributes the function attributes + /// @param return_type_attributes the return type attributes + Function(ProgramID program_id, + const Source& source, + Symbol symbol, + VariableList params, + const Type* return_type, + const BlockStatement* body, + AttributeList attributes, + AttributeList return_type_attributes); + /// Move constructor + Function(Function&&); + + ~Function() override; + + /// @returns the functions pipeline stage or None if not set + ast::PipelineStage PipelineStage() const; + + /// @returns true if this function is an entry point + bool IsEntryPoint() const { return PipelineStage() != PipelineStage::kNone; } + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const Function* Clone(CloneContext* ctx) const override; + + /// The function symbol + const Symbol symbol; + + /// The function params + const VariableList params; + + /// The function return type + const Type* const return_type; + + /// The function body + const BlockStatement* const body; + + /// The attributes attached to this function + const AttributeList attributes; + + /// The attributes attached to the function return type. + const AttributeList return_type_attributes; +}; + +/// A list of functions +class FunctionList : public std::vector<const Function*> { + public: + /// Appends f to the end of the list + /// @param f the function to append to this list + void Add(const Function* f) { this->emplace_back(f); } + + /// Returns the function with the given name + /// @param sym the function symbol to search for + /// @returns the associated function or nullptr if none exists + const Function* Find(Symbol sym) const; + + /// Returns the function with the given name + /// @param sym the function symbol to search for + /// @param stage the pipeline stage + /// @returns the associated function or nullptr if none exists + const Function* Find(Symbol sym, PipelineStage stage) const; + + /// @param stage the pipeline stage + /// @returns true if the Builder contains an entrypoint function with + /// the given stage + bool HasStage(PipelineStage stage) const; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_FUNCTION_H_
diff --git a/src/tint/ast/function_test.cc b/src/tint/ast/function_test.cc new file mode 100644 index 0000000..d4077bd --- /dev/null +++ b/src/tint/ast/function_test.cc
@@ -0,0 +1,196 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "gtest/gtest-spi.h" +#include "src/tint/ast/discard_statement.h" +#include "src/tint/ast/stage_attribute.h" +#include "src/tint/ast/test_helper.h" +#include "src/tint/ast/workgroup_attribute.h" + +namespace tint { +namespace ast { +namespace { + +using FunctionTest = TestHelper; + +TEST_F(FunctionTest, Creation) { + VariableList params; + params.push_back(Param("var", ty.i32())); + auto* var = params[0]; + + auto* f = Func("func", params, ty.void_(), StatementList{}, AttributeList{}); + EXPECT_EQ(f->symbol, Symbols().Get("func")); + ASSERT_EQ(f->params.size(), 1u); + EXPECT_TRUE(f->return_type->Is<ast::Void>()); + EXPECT_EQ(f->params[0], var); +} + +TEST_F(FunctionTest, Creation_WithSource) { + VariableList params; + params.push_back(Param("var", ty.i32())); + + auto* f = Func(Source{Source::Location{20, 2}}, "func", params, ty.void_(), + StatementList{}, AttributeList{}); + auto src = f->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(FunctionTest, Assert_InvalidName) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.Func("", VariableList{}, b.ty.void_(), StatementList{}, + AttributeList{}); + }, + "internal compiler error"); +} + +TEST_F(FunctionTest, Assert_Null_ReturnType) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.Func("f", VariableList{}, nullptr, StatementList{}, AttributeList{}); + }, + "internal compiler error"); +} + +TEST_F(FunctionTest, Assert_Null_Param) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + VariableList params; + params.push_back(b.Param("var", b.ty.i32())); + params.push_back(nullptr); + + b.Func("f", params, b.ty.void_(), StatementList{}, AttributeList{}); + }, + "internal compiler error"); +} + +TEST_F(FunctionTest, Assert_DifferentProgramID_Symbol) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.Func(b2.Sym("func"), VariableList{}, b1.ty.void_(), StatementList{}); + }, + "internal compiler error"); +} + +TEST_F(FunctionTest, Assert_DifferentProgramID_Param) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.Func("func", VariableList{b2.Param("var", b2.ty.i32())}, + b1.ty.void_(), StatementList{}); + }, + "internal compiler error"); +} + +TEST_F(FunctionTest, Assert_DifferentProgramID_Attr) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.Func("func", VariableList{}, b1.ty.void_(), StatementList{}, + AttributeList{ + b2.WorkgroupSize(2, 4, 6), + }); + }, + "internal compiler error"); +} + +TEST_F(FunctionTest, Assert_DifferentProgramID_ReturnAttr) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.Func("func", VariableList{}, b1.ty.void_(), StatementList{}, + AttributeList{}, + AttributeList{ + b2.WorkgroupSize(2, 4, 6), + }); + }, + "internal compiler error"); +} + +TEST_F(FunctionTest, Assert_NonConstParam) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + VariableList params; + params.push_back(b.Var("var", b.ty.i32(), ast::StorageClass::kNone)); + + b.Func("f", params, b.ty.void_(), StatementList{}, AttributeList{}); + }, + "internal compiler error"); +} + +using FunctionListTest = TestHelper; + +TEST_F(FunctionListTest, FindSymbol) { + auto* func = Func("main", VariableList{}, ty.f32(), StatementList{}, + ast::AttributeList{}); + FunctionList list; + list.Add(func); + EXPECT_EQ(func, list.Find(Symbols().Register("main"))); +} + +TEST_F(FunctionListTest, FindSymbolMissing) { + FunctionList list; + EXPECT_EQ(nullptr, list.Find(Symbols().Register("Missing"))); +} + +TEST_F(FunctionListTest, FindSymbolStage) { + auto* fs = Func("main", VariableList{}, ty.f32(), StatementList{}, + ast::AttributeList{ + Stage(PipelineStage::kFragment), + }); + auto* vs = Func("main", VariableList{}, ty.f32(), StatementList{}, + ast::AttributeList{ + Stage(PipelineStage::kVertex), + }); + FunctionList list; + list.Add(fs); + list.Add(vs); + EXPECT_EQ(fs, + list.Find(Symbols().Register("main"), PipelineStage::kFragment)); + EXPECT_EQ(vs, list.Find(Symbols().Register("main"), PipelineStage::kVertex)); +} + +TEST_F(FunctionListTest, FindSymbolStageMissing) { + FunctionList list; + list.Add(Func("main", VariableList{}, ty.f32(), StatementList{}, + ast::AttributeList{ + Stage(PipelineStage::kFragment), + })); + EXPECT_EQ(nullptr, + list.Find(Symbols().Register("main"), PipelineStage::kVertex)); +} + +TEST_F(FunctionListTest, HasStage) { + FunctionList list; + list.Add(Func("main", VariableList{}, ty.f32(), StatementList{}, + ast::AttributeList{ + Stage(PipelineStage::kFragment), + })); + EXPECT_TRUE(list.HasStage(PipelineStage::kFragment)); + EXPECT_FALSE(list.HasStage(PipelineStage::kVertex)); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/group_attribute.cc b/src/tint/ast/group_attribute.cc new file mode 100644 index 0000000..58714e8 --- /dev/null +++ b/src/tint/ast/group_attribute.cc
@@ -0,0 +1,42 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/group_attribute.h" + +#include <string> + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::GroupAttribute); + +namespace tint { +namespace ast { + +GroupAttribute::GroupAttribute(ProgramID pid, const Source& src, uint32_t val) + : Base(pid, src), value(val) {} + +GroupAttribute::~GroupAttribute() = default; + +std::string GroupAttribute::Name() const { + return "group"; +} + +const GroupAttribute* GroupAttribute::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + return ctx->dst->create<GroupAttribute>(src, value); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/group_attribute.h b/src/tint/ast/group_attribute.h new file mode 100644 index 0000000..be5c61e --- /dev/null +++ b/src/tint/ast/group_attribute.h
@@ -0,0 +1,51 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_GROUP_ATTRIBUTE_H_ +#define SRC_TINT_AST_GROUP_ATTRIBUTE_H_ + +#include <string> + +#include "src/tint/ast/attribute.h" + +namespace tint { +namespace ast { + +/// A group attribute +class GroupAttribute final : public Castable<GroupAttribute, Attribute> { + public: + /// constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param value the group value + GroupAttribute(ProgramID pid, const Source& src, uint32_t value); + ~GroupAttribute() override; + + /// @returns the WGSL name for the attribute + std::string Name() const override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const GroupAttribute* Clone(CloneContext* ctx) const override; + + /// The group value + const uint32_t value; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_GROUP_ATTRIBUTE_H_
diff --git a/src/tint/ast/group_attribute_test.cc b/src/tint/ast/group_attribute_test.cc new file mode 100644 index 0000000..fd7c18b --- /dev/null +++ b/src/tint/ast/group_attribute_test.cc
@@ -0,0 +1,30 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using GroupAttributeTest = TestHelper; + +TEST_F(GroupAttributeTest, Creation) { + auto* d = create<GroupAttribute>(2); + EXPECT_EQ(2u, d->value); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/i32.cc b/src/tint/ast/i32.cc new file mode 100644 index 0000000..83e4524 --- /dev/null +++ b/src/tint/ast/i32.cc
@@ -0,0 +1,40 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/i32.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::I32); + +namespace tint { +namespace ast { + +I32::I32(ProgramID pid, const Source& src) : Base(pid, src) {} + +I32::I32(I32&&) = default; + +I32::~I32() = default; + +std::string I32::FriendlyName(const SymbolTable&) const { + return "i32"; +} + +const I32* I32::Clone(CloneContext* ctx) const { + auto src = ctx->Clone(source); + return ctx->dst->create<I32>(src); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/i32.h b/src/tint/ast/i32.h new file mode 100644 index 0000000..335bc98 --- /dev/null +++ b/src/tint/ast/i32.h
@@ -0,0 +1,50 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_I32_H_ +#define SRC_TINT_AST_I32_H_ + +#include <string> + +#include "src/tint/ast/type.h" + +namespace tint { +namespace ast { + +/// A signed int 32 type. +class I32 final : public Castable<I32, Type> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + I32(ProgramID pid, const Source& src); + /// Move constructor + I32(I32&&); + ~I32() override; + + /// @param symbols the program's symbol table + /// @returns the name for this type that closely resembles how it would be + /// declared in WGSL. + std::string FriendlyName(const SymbolTable& symbols) const override; + + /// Clones this type and all transitive types using the `CloneContext` `ctx`. + /// @param ctx the clone context + /// @return the newly cloned type + const I32* Clone(CloneContext* ctx) const override; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_I32_H_
diff --git a/src/tint/ast/i32_test.cc b/src/tint/ast/i32_test.cc new file mode 100644 index 0000000..7e1c265 --- /dev/null +++ b/src/tint/ast/i32_test.cc
@@ -0,0 +1,32 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/i32.h" + +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using AstI32Test = TestHelper; + +TEST_F(AstI32Test, FriendlyName) { + auto* i = create<I32>(); + EXPECT_EQ(i->FriendlyName(Symbols()), "i32"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/id_attribute.cc b/src/tint/ast/id_attribute.cc new file mode 100644 index 0000000..b94b450 --- /dev/null +++ b/src/tint/ast/id_attribute.cc
@@ -0,0 +1,42 @@ +// Copyright 2022 The Tint Authors. +// +// 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 "src/tint/ast/id_attribute.h" + +#include <string> + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::IdAttribute); + +namespace tint { +namespace ast { + +IdAttribute::IdAttribute(ProgramID pid, const Source& src, uint32_t val) + : Base(pid, src), value(val) {} + +IdAttribute::~IdAttribute() = default; + +std::string IdAttribute::Name() const { + return "id"; +} + +const IdAttribute* IdAttribute::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + return ctx->dst->create<IdAttribute>(src, value); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/id_attribute.h b/src/tint/ast/id_attribute.h new file mode 100644 index 0000000..7fbac0e --- /dev/null +++ b/src/tint/ast/id_attribute.h
@@ -0,0 +1,51 @@ +// Copyright 2022 The Tint Authors. +// +// 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 SRC_TINT_AST_ID_ATTRIBUTE_H_ +#define SRC_TINT_AST_ID_ATTRIBUTE_H_ + +#include <string> + +#include "src/tint/ast/attribute.h" + +namespace tint { +namespace ast { + +/// An id attribute for pipeline-overridable constants +class IdAttribute final : public Castable<IdAttribute, Attribute> { + public: + /// Create an id attribute. + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param val the numeric id value + IdAttribute(ProgramID pid, const Source& src, uint32_t val); + ~IdAttribute() override; + + /// @returns the WGSL name for the attribute + std::string Name() const override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const IdAttribute* Clone(CloneContext* ctx) const override; + + /// The id value + const uint32_t value; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_ID_ATTRIBUTE_H_
diff --git a/src/tint/ast/id_attribute_test.cc b/src/tint/ast/id_attribute_test.cc new file mode 100644 index 0000000..c3c372b --- /dev/null +++ b/src/tint/ast/id_attribute_test.cc
@@ -0,0 +1,32 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/id_attribute.h" + +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using IdAttributeTest = TestHelper; + +TEST_F(IdAttributeTest, Creation) { + auto* d = create<IdAttribute>(12); + EXPECT_EQ(12u, d->value); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/identifier_expression.cc b/src/tint/ast/identifier_expression.cc new file mode 100644 index 0000000..1f73a45 --- /dev/null +++ b/src/tint/ast/identifier_expression.cc
@@ -0,0 +1,45 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/identifier_expression.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::IdentifierExpression); + +namespace tint { +namespace ast { + +IdentifierExpression::IdentifierExpression(ProgramID pid, + const Source& src, + Symbol sym) + : Base(pid, src), symbol(sym) { + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, symbol, program_id); + TINT_ASSERT(AST, symbol.IsValid()); +} + +IdentifierExpression::IdentifierExpression(IdentifierExpression&&) = default; + +IdentifierExpression::~IdentifierExpression() = default; + +const IdentifierExpression* IdentifierExpression::Clone( + CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto sym = ctx->Clone(symbol); + return ctx->dst->create<IdentifierExpression>(src, sym); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/identifier_expression.h b/src/tint/ast/identifier_expression.h new file mode 100644 index 0000000..6c3c8b3 --- /dev/null +++ b/src/tint/ast/identifier_expression.h
@@ -0,0 +1,49 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_IDENTIFIER_EXPRESSION_H_ +#define SRC_TINT_AST_IDENTIFIER_EXPRESSION_H_ + +#include "src/tint/ast/expression.h" + +namespace tint { +namespace ast { + +/// An identifier expression +class IdentifierExpression final + : public Castable<IdentifierExpression, Expression> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param sym the symbol for the identifier + IdentifierExpression(ProgramID pid, const Source& src, Symbol sym); + /// Move constructor + IdentifierExpression(IdentifierExpression&&); + ~IdentifierExpression() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const IdentifierExpression* Clone(CloneContext* ctx) const override; + + /// The symbol for the identifier + const Symbol symbol; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_IDENTIFIER_EXPRESSION_H_
diff --git a/src/tint/ast/identifier_expression_test.cc b/src/tint/ast/identifier_expression_test.cc new file mode 100644 index 0000000..b8c3a2a --- /dev/null +++ b/src/tint/ast/identifier_expression_test.cc
@@ -0,0 +1,64 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "gtest/gtest-spi.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using IdentifierExpressionTest = TestHelper; + +TEST_F(IdentifierExpressionTest, Creation) { + auto* i = Expr("ident"); + EXPECT_EQ(i->symbol, Symbol(1, ID())); +} + +TEST_F(IdentifierExpressionTest, Creation_WithSource) { + auto* i = Expr(Source{Source::Location{20, 2}}, "ident"); + EXPECT_EQ(i->symbol, Symbol(1, ID())); + + auto src = i->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(IdentifierExpressionTest, IsIdentifier) { + auto* i = Expr("ident"); + EXPECT_TRUE(i->Is<IdentifierExpression>()); +} + +TEST_F(IdentifierExpressionTest, Assert_InvalidSymbol) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.Expr(""); + }, + "internal compiler error"); +} + +TEST_F(IdentifierExpressionTest, Assert_DifferentProgramID_Symbol) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.Expr(b2.Sym("b2")); + }, + "internal compiler error"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/if_statement.cc b/src/tint/ast/if_statement.cc new file mode 100644 index 0000000..276d5d2 --- /dev/null +++ b/src/tint/ast/if_statement.cc
@@ -0,0 +1,57 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/if_statement.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::IfStatement); + +namespace tint { +namespace ast { + +IfStatement::IfStatement(ProgramID pid, + const Source& src, + const Expression* cond, + const BlockStatement* b, + ElseStatementList else_stmts) + : Base(pid, src), + condition(cond), + body(b), + else_statements(std::move(else_stmts)) { + TINT_ASSERT(AST, condition); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, condition, program_id); + TINT_ASSERT(AST, body); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id); + for (auto* el : else_statements) { + TINT_ASSERT(AST, el); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, el, program_id); + } +} + +IfStatement::IfStatement(IfStatement&&) = default; + +IfStatement::~IfStatement() = default; + +const IfStatement* IfStatement::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* cond = ctx->Clone(condition); + auto* b = ctx->Clone(body); + auto el = ctx->Clone(else_statements); + return ctx->dst->create<IfStatement>(src, cond, b, el); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/if_statement.h b/src/tint/ast/if_statement.h new file mode 100644 index 0000000..dae9fd7 --- /dev/null +++ b/src/tint/ast/if_statement.h
@@ -0,0 +1,62 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_IF_STATEMENT_H_ +#define SRC_TINT_AST_IF_STATEMENT_H_ + +#include <utility> + +#include "src/tint/ast/else_statement.h" + +namespace tint { +namespace ast { + +/// An if statement +class IfStatement final : public Castable<IfStatement, Statement> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param condition the if condition + /// @param body the if body + /// @param else_stmts the else statements + IfStatement(ProgramID pid, + const Source& src, + const Expression* condition, + const BlockStatement* body, + ElseStatementList else_stmts); + /// Move constructor + IfStatement(IfStatement&&); + ~IfStatement() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const IfStatement* Clone(CloneContext* ctx) const override; + + /// The if condition or nullptr if none set + const Expression* const condition; + + /// The if body + const BlockStatement* const body; + + /// The else statements + const ElseStatementList else_statements; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_IF_STATEMENT_H_
diff --git a/src/tint/ast/if_statement_test.cc b/src/tint/ast/if_statement_test.cc new file mode 100644 index 0000000..6090eca --- /dev/null +++ b/src/tint/ast/if_statement_test.cc
@@ -0,0 +1,106 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/if_statement.h" + +#include "gtest/gtest-spi.h" +#include "src/tint/ast/discard_statement.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using IfStatementTest = TestHelper; + +TEST_F(IfStatementTest, Creation) { + auto* cond = Expr("cond"); + auto* stmt = create<IfStatement>(Source{Source::Location{20, 2}}, cond, + Block(create<DiscardStatement>()), + ElseStatementList{}); + auto src = stmt->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(IfStatementTest, IsIf) { + auto* stmt = create<IfStatement>(Expr(true), Block(), ElseStatementList{}); + EXPECT_TRUE(stmt->Is<IfStatement>()); +} + +TEST_F(IfStatementTest, Assert_Null_Condition) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<IfStatement>(nullptr, b.Block(), ElseStatementList{}); + }, + "internal compiler error"); +} + +TEST_F(IfStatementTest, Assert_Null_Body) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<IfStatement>(b.Expr(true), nullptr, ElseStatementList{}); + }, + "internal compiler error"); +} + +TEST_F(IfStatementTest, Assert_Null_ElseStatement) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + auto* body = b.create<BlockStatement>(StatementList{}); + b.create<IfStatement>(b.Expr(true), body, ElseStatementList{nullptr}); + }, + "internal compiler error"); +} + +TEST_F(IfStatementTest, Assert_DifferentProgramID_Cond) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<IfStatement>(b2.Expr(true), b1.Block(), ElseStatementList{}); + }, + "internal compiler error"); +} + +TEST_F(IfStatementTest, Assert_DifferentProgramID_Body) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<IfStatement>(b1.Expr(true), b2.Block(), ElseStatementList{}); + }, + "internal compiler error"); +} + +TEST_F(IfStatementTest, Assert_DifferentProgramID_ElseStatement) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<IfStatement>( + b1.Expr(true), b1.Block(), + ElseStatementList{ + b2.create<ElseStatement>(b2.Expr("ident"), b2.Block()), + }); + }, + "internal compiler error"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/index_accessor_expression.cc b/src/tint/ast/index_accessor_expression.cc new file mode 100644 index 0000000..afd73fd --- /dev/null +++ b/src/tint/ast/index_accessor_expression.cc
@@ -0,0 +1,50 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/index_accessor_expression.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::IndexAccessorExpression); + +namespace tint { +namespace ast { + +IndexAccessorExpression::IndexAccessorExpression(ProgramID pid, + const Source& src, + const Expression* obj, + const Expression* idx) + : Base(pid, src), object(obj), index(idx) { + TINT_ASSERT(AST, object); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, object, program_id); + TINT_ASSERT(AST, idx); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, idx, program_id); +} + +IndexAccessorExpression::IndexAccessorExpression(IndexAccessorExpression&&) = + default; + +IndexAccessorExpression::~IndexAccessorExpression() = default; + +const IndexAccessorExpression* IndexAccessorExpression::Clone( + CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* obj = ctx->Clone(object); + auto* idx = ctx->Clone(index); + return ctx->dst->create<IndexAccessorExpression>(src, obj, idx); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/index_accessor_expression.h b/src/tint/ast/index_accessor_expression.h new file mode 100644 index 0000000..6fabada --- /dev/null +++ b/src/tint/ast/index_accessor_expression.h
@@ -0,0 +1,56 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_INDEX_ACCESSOR_EXPRESSION_H_ +#define SRC_TINT_AST_INDEX_ACCESSOR_EXPRESSION_H_ + +#include "src/tint/ast/expression.h" + +namespace tint { +namespace ast { + +/// An index accessor expression +class IndexAccessorExpression final + : public Castable<IndexAccessorExpression, Expression> { + public: + /// Constructor + /// @param program_id the identifier of the program that owns this node + /// @param source the index accessor source + /// @param obj the object + /// @param idx the index expression + IndexAccessorExpression(ProgramID program_id, + const Source& source, + const Expression* obj, + const Expression* idx); + /// Move constructor + IndexAccessorExpression(IndexAccessorExpression&&); + ~IndexAccessorExpression() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const IndexAccessorExpression* Clone(CloneContext* ctx) const override; + + /// the array, vector or matrix + const Expression* const object; + + /// the index expression + const Expression* const index; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_INDEX_ACCESSOR_EXPRESSION_H_
diff --git a/src/tint/ast/index_accessor_expression_test.cc b/src/tint/ast/index_accessor_expression_test.cc new file mode 100644 index 0000000..8b91239 --- /dev/null +++ b/src/tint/ast/index_accessor_expression_test.cc
@@ -0,0 +1,91 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "gtest/gtest-spi.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using IndexAccessorExpressionTest = TestHelper; + +TEST_F(IndexAccessorExpressionTest, Create) { + auto* obj = Expr("obj"); + auto* idx = Expr("idx"); + + auto* exp = IndexAccessor(obj, idx); + ASSERT_EQ(exp->object, obj); + ASSERT_EQ(exp->index, idx); +} + +TEST_F(IndexAccessorExpressionTest, CreateWithSource) { + auto* obj = Expr("obj"); + auto* idx = Expr("idx"); + + auto* exp = IndexAccessor(Source{{20, 2}}, obj, idx); + auto src = exp->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(IndexAccessorExpressionTest, IsIndexAccessor) { + auto* obj = Expr("obj"); + auto* idx = Expr("idx"); + + auto* exp = IndexAccessor(obj, idx); + EXPECT_TRUE(exp->Is<IndexAccessorExpression>()); +} + +TEST_F(IndexAccessorExpressionTest, Assert_Null_Array) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.IndexAccessor(nullptr, b.Expr("idx")); + }, + "internal compiler error"); +} + +TEST_F(IndexAccessorExpressionTest, Assert_Null_Index) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.IndexAccessor(b.Expr("arr"), nullptr); + }, + "internal compiler error"); +} + +TEST_F(IndexAccessorExpressionTest, Assert_DifferentProgramID_Array) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.IndexAccessor(b2.Expr("arr"), b1.Expr("idx")); + }, + "internal compiler error"); +} + +TEST_F(IndexAccessorExpressionTest, Assert_DifferentProgramID_Index) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.IndexAccessor(b1.Expr("arr"), b2.Expr("idx")); + }, + "internal compiler error"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/int_literal_expression.cc b/src/tint/ast/int_literal_expression.cc new file mode 100644 index 0000000..05c9f2d --- /dev/null +++ b/src/tint/ast/int_literal_expression.cc
@@ -0,0 +1,28 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/int_literal_expression.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::IntLiteralExpression); + +namespace tint { +namespace ast { + +IntLiteralExpression::IntLiteralExpression(ProgramID pid, const Source& src) + : Base(pid, src) {} + +IntLiteralExpression::~IntLiteralExpression() = default; + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/int_literal_expression.h b/src/tint/ast/int_literal_expression.h new file mode 100644 index 0000000..9da44bb --- /dev/null +++ b/src/tint/ast/int_literal_expression.h
@@ -0,0 +1,45 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_INT_LITERAL_EXPRESSION_H_ +#define SRC_TINT_AST_INT_LITERAL_EXPRESSION_H_ + +#include "src/tint/ast/literal_expression.h" + +namespace tint { +namespace ast { + +/// An integer literal. This could be either signed or unsigned. +class IntLiteralExpression + : public Castable<IntLiteralExpression, LiteralExpression> { + public: + ~IntLiteralExpression() override; + + /// @returns the literal value as a u32 + virtual uint32_t ValueAsU32() const = 0; + + /// @returns the literal value as an i32 + int32_t ValueAsI32() const { return static_cast<int32_t>(ValueAsU32()); } + + protected: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + IntLiteralExpression(ProgramID pid, const Source& src); +}; // namespace ast + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_INT_LITERAL_EXPRESSION_H_
diff --git a/src/tint/ast/int_literal_expression_test.cc b/src/tint/ast/int_literal_expression_test.cc new file mode 100644 index 0000000..e64d456 --- /dev/null +++ b/src/tint/ast/int_literal_expression_test.cc
@@ -0,0 +1,35 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using IntLiteralExpressionTest = TestHelper; + +TEST_F(IntLiteralExpressionTest, Sint_IsInt) { + auto* i = create<SintLiteralExpression>(47); + ASSERT_TRUE(i->Is<IntLiteralExpression>()); +} + +TEST_F(IntLiteralExpressionTest, Uint_IsInt) { + auto* i = create<UintLiteralExpression>(42); + EXPECT_TRUE(i->Is<IntLiteralExpression>()); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/internal_attribute.cc b/src/tint/ast/internal_attribute.cc new file mode 100644 index 0000000..7f8c84be --- /dev/null +++ b/src/tint/ast/internal_attribute.cc
@@ -0,0 +1,31 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/internal_attribute.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::InternalAttribute); + +namespace tint { +namespace ast { + +InternalAttribute::InternalAttribute(ProgramID pid) : Base(pid, Source{}) {} + +InternalAttribute::~InternalAttribute() = default; + +std::string InternalAttribute::Name() const { + return "internal"; +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/internal_attribute.h b/src/tint/ast/internal_attribute.h new file mode 100644 index 0000000..6a9fd4a --- /dev/null +++ b/src/tint/ast/internal_attribute.h
@@ -0,0 +1,48 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_AST_INTERNAL_ATTRIBUTE_H_ +#define SRC_TINT_AST_INTERNAL_ATTRIBUTE_H_ + +#include <string> + +#include "src/tint/ast/attribute.h" + +namespace tint { +namespace ast { + +/// An attribute used to indicate that a function is tint-internal. +/// These attributes are not produced by generators, but instead are usually +/// created by transforms for consumption by a particular backend. +class InternalAttribute : public Castable<InternalAttribute, Attribute> { + public: + /// Constructor + /// @param program_id the identifier of the program that owns this node + explicit InternalAttribute(ProgramID program_id); + + /// Destructor + ~InternalAttribute() override; + + /// @return a short description of the internal attribute which will be + /// displayed in WGSL as `@internal(<name>)` (but is not parsable). + virtual std::string InternalName() const = 0; + + /// @returns the WGSL name for the attribute + std::string Name() const override; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_INTERNAL_ATTRIBUTE_H_
diff --git a/src/tint/ast/interpolate_attribute.cc b/src/tint/ast/interpolate_attribute.cc new file mode 100644 index 0000000..1cf62c6 --- /dev/null +++ b/src/tint/ast/interpolate_attribute.cc
@@ -0,0 +1,86 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/interpolate_attribute.h" + +#include <string> + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::InterpolateAttribute); + +namespace tint { +namespace ast { + +InterpolateAttribute::InterpolateAttribute(ProgramID pid, + const Source& src, + InterpolationType ty, + InterpolationSampling smpl) + : Base(pid, src), type(ty), sampling(smpl) {} + +InterpolateAttribute::~InterpolateAttribute() = default; + +std::string InterpolateAttribute::Name() const { + return "interpolate"; +} + +const InterpolateAttribute* InterpolateAttribute::Clone( + CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + return ctx->dst->create<InterpolateAttribute>(src, type, sampling); +} + +std::ostream& operator<<(std::ostream& out, InterpolationType type) { + switch (type) { + case InterpolationType::kPerspective: { + out << "perspective"; + break; + } + case InterpolationType::kLinear: { + out << "linear"; + break; + } + case InterpolationType::kFlat: { + out << "flat"; + break; + } + } + return out; +} + +std::ostream& operator<<(std::ostream& out, InterpolationSampling sampling) { + switch (sampling) { + case InterpolationSampling::kNone: { + out << "none"; + break; + } + case InterpolationSampling::kCenter: { + out << "center"; + break; + } + case InterpolationSampling::kCentroid: { + out << "centroid"; + break; + } + case InterpolationSampling::kSample: { + out << "sample"; + break; + } + } + return out; +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/interpolate_attribute.h b/src/tint/ast/interpolate_attribute.h new file mode 100644 index 0000000..ac3b49d --- /dev/null +++ b/src/tint/ast/interpolate_attribute.h
@@ -0,0 +1,76 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_AST_INTERPOLATE_ATTRIBUTE_H_ +#define SRC_TINT_AST_INTERPOLATE_ATTRIBUTE_H_ + +#include <ostream> +#include <string> + +#include "src/tint/ast/attribute.h" + +namespace tint { +namespace ast { + +/// The interpolation type. +enum class InterpolationType { kPerspective, kLinear, kFlat }; + +/// The interpolation sampling. +enum class InterpolationSampling { kNone = -1, kCenter, kCentroid, kSample }; + +/// An interpolate attribute +class InterpolateAttribute final + : public Castable<InterpolateAttribute, Attribute> { + public: + /// Create an interpolate attribute. + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param type the interpolation type + /// @param sampling the interpolation sampling + InterpolateAttribute(ProgramID pid, + const Source& src, + InterpolationType type, + InterpolationSampling sampling); + ~InterpolateAttribute() override; + + /// @returns the WGSL name for the attribute + std::string Name() const override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const InterpolateAttribute* Clone(CloneContext* ctx) const override; + + /// The interpolation type + const InterpolationType type; + + /// The interpolation sampling + const InterpolationSampling sampling; +}; + +/// @param out the std::ostream to write to +/// @param type the interpolation type +/// @return the std::ostream so calls can be chained +std::ostream& operator<<(std::ostream& out, InterpolationType type); + +/// @param out the std::ostream to write to +/// @param sampling the interpolation sampling +/// @return the std::ostream so calls can be chained +std::ostream& operator<<(std::ostream& out, InterpolationSampling sampling); + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_INTERPOLATE_ATTRIBUTE_H_
diff --git a/src/tint/ast/interpolate_attribute_test.cc b/src/tint/ast/interpolate_attribute_test.cc new file mode 100644 index 0000000..e8417b2 --- /dev/null +++ b/src/tint/ast/interpolate_attribute_test.cc
@@ -0,0 +1,34 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/interpolate_attribute.h" + +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using InterpolateAttributeTest = TestHelper; + +TEST_F(InterpolateAttributeTest, Creation) { + auto* d = create<InterpolateAttribute>(InterpolationType::kLinear, + InterpolationSampling::kCenter); + EXPECT_EQ(InterpolationType::kLinear, d->type); + EXPECT_EQ(InterpolationSampling::kCenter, d->sampling); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/invariant_attribute.cc b/src/tint/ast/invariant_attribute.cc new file mode 100644 index 0000000..f893b8a --- /dev/null +++ b/src/tint/ast/invariant_attribute.cc
@@ -0,0 +1,40 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/invariant_attribute.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::InvariantAttribute); + +namespace tint { +namespace ast { + +InvariantAttribute::InvariantAttribute(ProgramID pid, const Source& src) + : Base(pid, src) {} + +InvariantAttribute::~InvariantAttribute() = default; + +std::string InvariantAttribute::Name() const { + return "invariant"; +} + +const InvariantAttribute* InvariantAttribute::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + return ctx->dst->create<InvariantAttribute>(src); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/invariant_attribute.h b/src/tint/ast/invariant_attribute.h new file mode 100644 index 0000000..08a58e8 --- /dev/null +++ b/src/tint/ast/invariant_attribute.h
@@ -0,0 +1,48 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_AST_INVARIANT_ATTRIBUTE_H_ +#define SRC_TINT_AST_INVARIANT_ATTRIBUTE_H_ + +#include <string> + +#include "src/tint/ast/attribute.h" + +namespace tint { +namespace ast { + +/// The invariant attribute +class InvariantAttribute final + : public Castable<InvariantAttribute, Attribute> { + public: + /// constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + InvariantAttribute(ProgramID pid, const Source& src); + ~InvariantAttribute() override; + + /// @returns the WGSL name for the attribute + std::string Name() const override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const InvariantAttribute* Clone(CloneContext* ctx) const override; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_INVARIANT_ATTRIBUTE_H_
diff --git a/src/tint/ast/invariant_attribute_test.cc b/src/tint/ast/invariant_attribute_test.cc new file mode 100644 index 0000000..7b09ed6 --- /dev/null +++ b/src/tint/ast/invariant_attribute_test.cc
@@ -0,0 +1,27 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/invariant_attribute.h" + +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using InvariantAttributeTest = TestHelper; + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/literal_expression.cc b/src/tint/ast/literal_expression.cc new file mode 100644 index 0000000..25aa908 --- /dev/null +++ b/src/tint/ast/literal_expression.cc
@@ -0,0 +1,28 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/literal_expression.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::LiteralExpression); + +namespace tint { +namespace ast { + +LiteralExpression::LiteralExpression(ProgramID pid, const Source& src) + : Base(pid, src) {} + +LiteralExpression::~LiteralExpression() = default; + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/literal_expression.h b/src/tint/ast/literal_expression.h new file mode 100644 index 0000000..0b1c708 --- /dev/null +++ b/src/tint/ast/literal_expression.h
@@ -0,0 +1,40 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_LITERAL_EXPRESSION_H_ +#define SRC_TINT_AST_LITERAL_EXPRESSION_H_ + +#include <string> + +#include "src/tint/ast/expression.h" + +namespace tint { +namespace ast { + +/// Base class for a literal value expressions +class LiteralExpression : public Castable<LiteralExpression, Expression> { + public: + ~LiteralExpression() override; + + protected: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the input source + LiteralExpression(ProgramID pid, const Source& src); +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_LITERAL_EXPRESSION_H_
diff --git a/src/tint/ast/location_attribute.cc b/src/tint/ast/location_attribute.cc new file mode 100644 index 0000000..9f3a53d --- /dev/null +++ b/src/tint/ast/location_attribute.cc
@@ -0,0 +1,44 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/location_attribute.h" + +#include <string> + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::LocationAttribute); + +namespace tint { +namespace ast { + +LocationAttribute::LocationAttribute(ProgramID pid, + const Source& src, + uint32_t val) + : Base(pid, src), value(val) {} + +LocationAttribute::~LocationAttribute() = default; + +std::string LocationAttribute::Name() const { + return "location"; +} + +const LocationAttribute* LocationAttribute::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + return ctx->dst->create<LocationAttribute>(src, value); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/location_attribute.h b/src/tint/ast/location_attribute.h new file mode 100644 index 0000000..3c8078d --- /dev/null +++ b/src/tint/ast/location_attribute.h
@@ -0,0 +1,51 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_LOCATION_ATTRIBUTE_H_ +#define SRC_TINT_AST_LOCATION_ATTRIBUTE_H_ + +#include <string> + +#include "src/tint/ast/attribute.h" + +namespace tint { +namespace ast { + +/// A location attribute +class LocationAttribute final : public Castable<LocationAttribute, Attribute> { + public: + /// constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param value the location value + LocationAttribute(ProgramID pid, const Source& src, uint32_t value); + ~LocationAttribute() override; + + /// @returns the WGSL name for the attribute + std::string Name() const override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const LocationAttribute* Clone(CloneContext* ctx) const override; + + /// The location value + const uint32_t value; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_LOCATION_ATTRIBUTE_H_
diff --git a/src/tint/ast/location_attribute_test.cc b/src/tint/ast/location_attribute_test.cc new file mode 100644 index 0000000..e826a1c --- /dev/null +++ b/src/tint/ast/location_attribute_test.cc
@@ -0,0 +1,30 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using LocationAttributeTest = TestHelper; + +TEST_F(LocationAttributeTest, Creation) { + auto* d = create<LocationAttribute>(2); + EXPECT_EQ(2u, d->value); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/loop_statement.cc b/src/tint/ast/loop_statement.cc new file mode 100644 index 0000000..4e490fd2 --- /dev/null +++ b/src/tint/ast/loop_statement.cc
@@ -0,0 +1,47 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/loop_statement.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::LoopStatement); + +namespace tint { +namespace ast { + +LoopStatement::LoopStatement(ProgramID pid, + const Source& src, + const BlockStatement* b, + const BlockStatement* cont) + : Base(pid, src), body(b), continuing(cont) { + TINT_ASSERT(AST, body); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, continuing, program_id); +} + +LoopStatement::LoopStatement(LoopStatement&&) = default; + +LoopStatement::~LoopStatement() = default; + +const LoopStatement* LoopStatement::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* b = ctx->Clone(body); + auto* cont = ctx->Clone(continuing); + return ctx->dst->create<LoopStatement>(src, b, cont); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/loop_statement.h b/src/tint/ast/loop_statement.h new file mode 100644 index 0000000..be29fc0 --- /dev/null +++ b/src/tint/ast/loop_statement.h
@@ -0,0 +1,55 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_LOOP_STATEMENT_H_ +#define SRC_TINT_AST_LOOP_STATEMENT_H_ + +#include "src/tint/ast/block_statement.h" + +namespace tint { +namespace ast { + +/// A loop statement +class LoopStatement final : public Castable<LoopStatement, Statement> { + public: + /// Constructor + /// @param program_id the identifier of the program that owns this node + /// @param source the loop statement source + /// @param body the body statements + /// @param continuing the continuing statements + LoopStatement(ProgramID program_id, + const Source& source, + const BlockStatement* body, + const BlockStatement* continuing); + /// Move constructor + LoopStatement(LoopStatement&&); + ~LoopStatement() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const LoopStatement* Clone(CloneContext* ctx) const override; + + /// The loop body + const BlockStatement* const body; + + /// The continuing statements + const BlockStatement* const continuing; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_LOOP_STATEMENT_H_
diff --git a/src/tint/ast/loop_statement_test.cc b/src/tint/ast/loop_statement_test.cc new file mode 100644 index 0000000..17cf7e9 --- /dev/null +++ b/src/tint/ast/loop_statement_test.cc
@@ -0,0 +1,105 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/loop_statement.h" + +#include "gtest/gtest-spi.h" +#include "src/tint/ast/discard_statement.h" +#include "src/tint/ast/if_statement.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using LoopStatementTest = TestHelper; + +TEST_F(LoopStatementTest, Creation) { + auto* body = Block(create<DiscardStatement>()); + auto* b = body->Last(); + + auto* continuing = Block(create<DiscardStatement>()); + + auto* l = create<LoopStatement>(body, continuing); + ASSERT_EQ(l->body->statements.size(), 1u); + EXPECT_EQ(l->body->statements[0], b); + ASSERT_EQ(l->continuing->statements.size(), 1u); + EXPECT_EQ(l->continuing->statements[0], continuing->Last()); +} + +TEST_F(LoopStatementTest, Creation_WithSource) { + auto* body = Block(create<DiscardStatement>()); + + auto* continuing = Block(create<DiscardStatement>()); + + auto* l = + create<LoopStatement>(Source{Source::Location{20, 2}}, body, continuing); + auto src = l->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(LoopStatementTest, IsLoop) { + auto* l = create<LoopStatement>(Block(), Block()); + EXPECT_TRUE(l->Is<LoopStatement>()); +} + +TEST_F(LoopStatementTest, HasContinuing_WithoutContinuing) { + auto* body = Block(create<DiscardStatement>()); + + auto* l = create<LoopStatement>(body, nullptr); + EXPECT_FALSE(l->continuing); +} + +TEST_F(LoopStatementTest, HasContinuing_WithContinuing) { + auto* body = Block(create<DiscardStatement>()); + + auto* continuing = Block(create<DiscardStatement>()); + + auto* l = create<LoopStatement>(body, continuing); + EXPECT_TRUE(l->continuing); +} + +TEST_F(LoopStatementTest, Assert_Null_Body) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<LoopStatement>(nullptr, nullptr); + }, + "internal compiler error"); +} + +TEST_F(LoopStatementTest, Assert_DifferentProgramID_Body) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<LoopStatement>(b2.Block(), b1.Block()); + }, + "internal compiler error"); +} + +TEST_F(LoopStatementTest, Assert_DifferentProgramID_Continuing) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<LoopStatement>(b1.Block(), b2.Block()); + }, + "internal compiler error"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/matrix.cc b/src/tint/ast/matrix.cc new file mode 100644 index 0000000..ce65483 --- /dev/null +++ b/src/tint/ast/matrix.cc
@@ -0,0 +1,56 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/matrix.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::Matrix); + +namespace tint { +namespace ast { + +Matrix::Matrix(ProgramID pid, + const Source& src, + const Type* subtype, + uint32_t r, + uint32_t c) + : Base(pid, src), type(subtype), rows(r), columns(c) { + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, subtype, program_id); + TINT_ASSERT(AST, rows > 1); + TINT_ASSERT(AST, rows < 5); + TINT_ASSERT(AST, columns > 1); + TINT_ASSERT(AST, columns < 5); +} + +Matrix::Matrix(Matrix&&) = default; + +Matrix::~Matrix() = default; + +std::string Matrix::FriendlyName(const SymbolTable& symbols) const { + std::ostringstream out; + out << "mat" << columns << "x" << rows << "<" << type->FriendlyName(symbols) + << ">"; + return out.str(); +} + +const Matrix* Matrix::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* ty = ctx->Clone(type); + return ctx->dst->create<Matrix>(src, ty, rows, columns); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/matrix.h b/src/tint/ast/matrix.h new file mode 100644 index 0000000..0ba418c --- /dev/null +++ b/src/tint/ast/matrix.h
@@ -0,0 +1,70 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_MATRIX_H_ +#define SRC_TINT_AST_MATRIX_H_ + +#include <string> + +#include "src/tint/ast/type.h" + +namespace tint { +namespace ast { + +/// A matrix type +class Matrix final : public Castable<Matrix, Type> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param subtype the declared type of the matrix components. May be null for + /// matrix constructors, where the element type will be inferred from + /// the constructor arguments + /// @param rows the number of rows in the matrix + /// @param columns the number of columns in the matrix + Matrix(ProgramID pid, + const Source& src, + const Type* subtype, + uint32_t rows, + uint32_t columns); + /// Move constructor + Matrix(Matrix&&); + ~Matrix() override; + + /// @param symbols the program's symbol table + /// @returns the name for this type that closely resembles how it would be + /// declared in WGSL. + std::string FriendlyName(const SymbolTable& symbols) const override; + + /// Clones this type and all transitive types using the `CloneContext` `ctx`. + /// @param ctx the clone context + /// @return the newly cloned type + const Matrix* Clone(CloneContext* ctx) const override; + + /// The declared type of the matrix components. May be null for matrix + /// constructors, where the element type will be inferred from the constructor + /// arguments + const Type* const type; + + /// The number of rows in the matrix + const uint32_t rows; + + /// The number of columns in the matrix + const uint32_t columns; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_MATRIX_H_
diff --git a/src/tint/ast/matrix_test.cc b/src/tint/ast/matrix_test.cc new file mode 100644 index 0000000..9bb4b8c --- /dev/null +++ b/src/tint/ast/matrix_test.cc
@@ -0,0 +1,52 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/matrix.h" +#include "src/tint/ast/access.h" +#include "src/tint/ast/alias.h" +#include "src/tint/ast/array.h" +#include "src/tint/ast/bool.h" +#include "src/tint/ast/f32.h" +#include "src/tint/ast/i32.h" +#include "src/tint/ast/pointer.h" +#include "src/tint/ast/sampler.h" +#include "src/tint/ast/struct.h" +#include "src/tint/ast/test_helper.h" +#include "src/tint/ast/texture.h" +#include "src/tint/ast/u32.h" +#include "src/tint/ast/vector.h" + +namespace tint { +namespace ast { +namespace { + +using AstMatrixTest = TestHelper; + +TEST_F(AstMatrixTest, Creation) { + auto* i32 = create<I32>(); + auto* m = create<Matrix>(i32, 2, 4); + EXPECT_EQ(m->type, i32); + EXPECT_EQ(m->rows, 2u); + EXPECT_EQ(m->columns, 4u); +} + +TEST_F(AstMatrixTest, FriendlyName) { + auto* i32 = create<I32>(); + auto* m = create<Matrix>(i32, 3, 2); + EXPECT_EQ(m->FriendlyName(Symbols()), "mat2x3<i32>"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/member_accessor_expression.cc b/src/tint/ast/member_accessor_expression.cc new file mode 100644 index 0000000..1b5a724 --- /dev/null +++ b/src/tint/ast/member_accessor_expression.cc
@@ -0,0 +1,51 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/member_accessor_expression.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::MemberAccessorExpression); + +namespace tint { +namespace ast { + +MemberAccessorExpression::MemberAccessorExpression( + ProgramID pid, + const Source& src, + const Expression* str, + const IdentifierExpression* mem) + : Base(pid, src), structure(str), member(mem) { + TINT_ASSERT(AST, structure); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, structure, program_id); + TINT_ASSERT(AST, member); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, member, program_id); +} + +MemberAccessorExpression::MemberAccessorExpression(MemberAccessorExpression&&) = + default; + +MemberAccessorExpression::~MemberAccessorExpression() = default; + +const MemberAccessorExpression* MemberAccessorExpression::Clone( + CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* str = ctx->Clone(structure); + auto* mem = ctx->Clone(member); + return ctx->dst->create<MemberAccessorExpression>(src, str, mem); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/member_accessor_expression.h b/src/tint/ast/member_accessor_expression.h new file mode 100644 index 0000000..8a82ba1 --- /dev/null +++ b/src/tint/ast/member_accessor_expression.h
@@ -0,0 +1,56 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_MEMBER_ACCESSOR_EXPRESSION_H_ +#define SRC_TINT_AST_MEMBER_ACCESSOR_EXPRESSION_H_ + +#include "src/tint/ast/identifier_expression.h" + +namespace tint { +namespace ast { + +/// A member accessor expression +class MemberAccessorExpression final + : public Castable<MemberAccessorExpression, Expression> { + public: + /// Constructor + /// @param program_id the identifier of the program that owns this node + /// @param source the member accessor expression source + /// @param structure the structure + /// @param member the member + MemberAccessorExpression(ProgramID program_id, + const Source& source, + const Expression* structure, + const IdentifierExpression* member); + /// Move constructor + MemberAccessorExpression(MemberAccessorExpression&&); + ~MemberAccessorExpression() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const MemberAccessorExpression* Clone(CloneContext* ctx) const override; + + /// The structure + const Expression* const structure; + + /// The member expression + const IdentifierExpression* const member; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_MEMBER_ACCESSOR_EXPRESSION_H_
diff --git a/src/tint/ast/member_accessor_expression_test.cc b/src/tint/ast/member_accessor_expression_test.cc new file mode 100644 index 0000000..12c4e1f --- /dev/null +++ b/src/tint/ast/member_accessor_expression_test.cc
@@ -0,0 +1,89 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "gtest/gtest-spi.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using MemberAccessorExpressionTest = TestHelper; + +TEST_F(MemberAccessorExpressionTest, Creation) { + auto* str = Expr("structure"); + auto* mem = Expr("member"); + + auto* stmt = create<MemberAccessorExpression>(str, mem); + EXPECT_EQ(stmt->structure, str); + EXPECT_EQ(stmt->member, mem); +} + +TEST_F(MemberAccessorExpressionTest, Creation_WithSource) { + auto* stmt = create<MemberAccessorExpression>( + Source{Source::Location{20, 2}}, Expr("structure"), Expr("member")); + auto src = stmt->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(MemberAccessorExpressionTest, IsMemberAccessor) { + auto* stmt = + create<MemberAccessorExpression>(Expr("structure"), Expr("member")); + EXPECT_TRUE(stmt->Is<MemberAccessorExpression>()); +} + +TEST_F(MemberAccessorExpressionTest, Assert_Null_Struct) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<MemberAccessorExpression>(nullptr, b.Expr("member")); + }, + "internal compiler error"); +} + +TEST_F(MemberAccessorExpressionTest, Assert_Null_Member) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<MemberAccessorExpression>(b.Expr("struct"), nullptr); + }, + "internal compiler error"); +} + +TEST_F(MemberAccessorExpressionTest, Assert_DifferentProgramID_Struct) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<MemberAccessorExpression>(b2.Expr("structure"), + b1.Expr("member")); + }, + "internal compiler error"); +} + +TEST_F(MemberAccessorExpressionTest, Assert_DifferentProgramID_Member) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<MemberAccessorExpression>(b1.Expr("structure"), + b2.Expr("member")); + }, + "internal compiler error"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/module.cc b/src/tint/ast/module.cc new file mode 100644 index 0000000..49634f8 --- /dev/null +++ b/src/tint/ast/module.cc
@@ -0,0 +1,127 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/module.h" + +#include <utility> + +#include "src/tint/ast/type_decl.h" +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::Module); + +namespace tint { +namespace ast { + +Module::Module(ProgramID pid, const Source& src) : Base(pid, src) {} + +Module::Module(ProgramID pid, + const Source& src, + std::vector<const ast::Node*> global_decls) + : Base(pid, src), global_declarations_(std::move(global_decls)) { + for (auto* decl : global_declarations_) { + if (decl == nullptr) { + continue; + } + diag::List diags; + BinGlobalDeclaration(decl, diags); + } +} + +Module::~Module() = default; + +const ast::TypeDecl* Module::LookupType(Symbol name) const { + for (auto* ty : TypeDecls()) { + if (ty->name == name) { + return ty; + } + } + return nullptr; +} + +void Module::AddGlobalDeclaration(const tint::ast::Node* decl) { + diag::List diags; + BinGlobalDeclaration(decl, diags); + global_declarations_.emplace_back(decl); +} + +void Module::BinGlobalDeclaration(const tint::ast::Node* decl, + diag::List& diags) { + Switch( + decl, // + [&](const ast::TypeDecl* type) { + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, type, program_id); + type_decls_.push_back(type); + }, + [&](const Function* func) { + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, func, program_id); + functions_.push_back(func); + }, + [&](const Variable* var) { + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, var, program_id); + global_variables_.push_back(var); + }, + [&](Default) { + TINT_ICE(AST, diags) << "Unknown global declaration type"; + }); +} + +void Module::AddGlobalVariable(const ast::Variable* var) { + TINT_ASSERT(AST, var); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, var, program_id); + global_variables_.push_back(var); + global_declarations_.push_back(var); +} + +void Module::AddTypeDecl(const ast::TypeDecl* type) { + TINT_ASSERT(AST, type); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, type, program_id); + type_decls_.push_back(type); + global_declarations_.push_back(type); +} + +void Module::AddFunction(const ast::Function* func) { + TINT_ASSERT(AST, func); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, func, program_id); + functions_.push_back(func); + global_declarations_.push_back(func); +} + +const Module* Module::Clone(CloneContext* ctx) const { + auto* out = ctx->dst->create<Module>(); + out->Copy(ctx, this); + return out; +} + +void Module::Copy(CloneContext* ctx, const Module* src) { + ctx->Clone(global_declarations_, src->global_declarations_); + + // During the clone, declarations may have been placed into the module. + // Clear everything out, as we're about to re-bin the declarations. + type_decls_.clear(); + functions_.clear(); + global_variables_.clear(); + + for (auto* decl : global_declarations_) { + if (!decl) { + TINT_ICE(AST, ctx->dst->Diagnostics()) + << "src global declaration was nullptr"; + continue; + } + BinGlobalDeclaration(decl, ctx->dst->Diagnostics()); + } +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/module.h b/src/tint/ast/module.h new file mode 100644 index 0000000..b98013d --- /dev/null +++ b/src/tint/ast/module.h
@@ -0,0 +1,125 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_AST_MODULE_H_ +#define SRC_TINT_AST_MODULE_H_ + +#include <string> +#include <vector> + +#include "src/tint/ast/function.h" +#include "src/tint/ast/type.h" + +namespace tint { +namespace ast { + +class TypeDecl; + +/// Module holds the top-level AST types, functions and global variables used by +/// a Program. +class Module final : public Castable<Module, Node> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + Module(ProgramID pid, const Source& src); + + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param global_decls the list of global types, functions, and variables, in + /// the order they were declared in the source program + Module(ProgramID pid, + const Source& src, + std::vector<const Node*> global_decls); + + /// Destructor + ~Module() override; + + /// @returns the declaration-ordered global declarations for the module + const std::vector<const Node*>& GlobalDeclarations() const { + return global_declarations_; + } + + /// Add a global variable to the Builder + /// @param var the variable to add + void AddGlobalVariable(const Variable* var); + + /// @returns true if the module has the global declaration `decl` + /// @param decl the declaration to check + bool HasGlobalDeclaration(Node* decl) const { + for (auto* d : global_declarations_) { + if (d == decl) { + return true; + } + } + return false; + } + + /// Adds a global declaration to the Builder. + /// @param decl the declaration to add + void AddGlobalDeclaration(const tint::ast::Node* decl); + + /// @returns the global variables for the module + const VariableList& GlobalVariables() const { return global_variables_; } + + /// @returns the global variables for the module + VariableList& GlobalVariables() { return global_variables_; } + + /// Adds a type declaration to the Builder. + /// @param decl the type declaration to add + void AddTypeDecl(const TypeDecl* decl); + + /// @returns the TypeDecl registered as a TypeDecl() + /// @param name the name of the type to search for + const TypeDecl* LookupType(Symbol name) const; + + /// @returns the declared types in the module + const std::vector<const TypeDecl*>& TypeDecls() const { return type_decls_; } + + /// Add a function to the Builder + /// @param func the function to add + void AddFunction(const Function* func); + + /// @returns the functions declared in the module + const FunctionList& Functions() const { return functions_; } + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const Module* Clone(CloneContext* ctx) const override; + + /// Copy copies the content of the Module src into this module. + /// @param ctx the clone context + /// @param src the module to copy into this module + void Copy(CloneContext* ctx, const Module* src); + + private: + /// Adds `decl` to either: + /// * #global_declarations_ + /// * #type_decls_ + /// * #functions_ + void BinGlobalDeclaration(const tint::ast::Node* decl, diag::List& diags); + + std::vector<const Node*> global_declarations_; + std::vector<const TypeDecl*> type_decls_; + FunctionList functions_; + VariableList global_variables_; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_MODULE_H_
diff --git a/src/tint/ast/module_clone_test.cc b/src/tint/ast/module_clone_test.cc new file mode 100644 index 0000000..4e92f25 --- /dev/null +++ b/src/tint/ast/module_clone_test.cc
@@ -0,0 +1,181 @@ +// Copyright 2020 The Tint Authors. +// +// 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 <unordered_set> + +#include "gtest/gtest.h" +#include "src/tint/reader/wgsl/parser.h" +#include "src/tint/writer/wgsl/generator.h" + +namespace tint { +namespace ast { +namespace { + +TEST(ModuleCloneTest, Clone) { +#if TINT_BUILD_WGSL_READER && TINT_BUILD_WGSL_WRITER + // Shader that exercises the bulk of the AST nodes and types. + // See also fuzzers/tint_ast_clone_fuzzer.cc for further coverage of cloning. + Source::File file("test.wgsl", R"(struct S0 { + @size(4) + m0 : u32, + m1 : array<u32>, +}; + +struct S1 { + @size(4) + m0 : u32, + m1 : array<u32, 6>, +}; + +let c0 : i32 = 10; +let c1 : bool = true; + +type t0 = array<vec4<f32>>; +type t1 = array<vec4<f32>>; + +var<private> g0 : u32 = 20u; +var<private> g1 : f32 = 123.0; +@group(0) @binding(0) var g2 : texture_2d<f32>; +@group(1) @binding(0) var g3 : texture_depth_2d; +@group(2) @binding(0) var g4 : texture_storage_2d<rg32float, write>; +@group(3) @binding(0) var g5 : texture_depth_cube_array; +@group(4) @binding(0) var g6 : texture_external; + +var<private> g7 : vec3<f32>; +@group(0) @binding(1) var<storage, write> g8 : S0; +@group(1) @binding(1) var<storage, read> g9 : S0; +@group(2) @binding(1) var<storage, read_write> g10 : S0; + +fn f0(p0 : bool) -> f32 { + if (p0) { + return 1.0; + } + return 0.0; +} + +fn f1(p0 : f32, p1 : i32) -> f32 { + var l0 : i32 = 3; + var l1 : f32 = 8.0; + var l2 : u32 = bitcast<u32>(4); + var l3 : vec2<u32> = vec2<u32>(u32(l0), u32(l1)); + var l4 : S1; + var l5 : u32 = l4.m1[5]; + let l6 : ptr<private, u32> = &g0; + loop { + l0 = (p1 + 2); + if (((l0 % 4) == 0)) { + break; + } + + continuing { + if (1 == 2) { + l0 = l0 - 1; + } else { + l0 = l0 - 2; + } + } + } + switch(l2) { + case 0u: { + break; + } + case 1u: { + return f0(true); + } + default: { + discard; + } + } + return 1.0; +} + +@stage(fragment) +fn main() { + f1(1.0, 2); +} + +let declaration_order_check_0 : i32 = 1; + +type declaration_order_check_1 = f32; + +fn declaration_order_check_2() {} + +type declaration_order_check_3 = f32; + +let declaration_order_check_4 : i32 = 1; + +)"); + + // Parse the wgsl, create the src program + auto src = reader::wgsl::Parse(&file); + + ASSERT_TRUE(src.IsValid()) << diag::Formatter().format(src.Diagnostics()); + + // Clone the src program to dst + Program dst(src.Clone()); + + ASSERT_TRUE(dst.IsValid()) << diag::Formatter().format(dst.Diagnostics()); + + // Expect the printed strings to match + EXPECT_EQ(Program::printer(&src), Program::printer(&dst)); + + // Check that none of the AST nodes or type pointers in dst are found in src + std::unordered_set<const ast::Node*> src_nodes; + for (auto* src_node : src.ASTNodes().Objects()) { + src_nodes.emplace(src_node); + } + std::unordered_set<const sem::Type*> src_types; + for (auto* src_type : src.Types()) { + src_types.emplace(src_type); + } + for (auto* dst_node : dst.ASTNodes().Objects()) { + ASSERT_EQ(src_nodes.count(dst_node), 0u); + } + for (auto* dst_type : dst.Types()) { + ASSERT_EQ(src_types.count(dst_type), 0u); + } + + // Regenerate the wgsl for the src program. We use this instead of the + // original source so that reformatting doesn't impact the final wgsl + // comparison. + writer::wgsl::Options options; + std::string src_wgsl; + { + auto result = writer::wgsl::Generate(&src, options); + ASSERT_TRUE(result.success) << result.error; + src_wgsl = result.wgsl; + + // Move the src program to a temporary that'll be dropped, so that the src + // program is released before we attempt to print the dst program. This + // guarantee that all the source program nodes and types are destructed and + // freed. ASAN should error if there's any remaining references in dst when + // we try to reconstruct the WGSL. + auto tmp = std::move(src); + } + + // Print the dst module, check it matches the original source + auto result = writer::wgsl::Generate(&dst, options); + ASSERT_TRUE(result.success); + auto dst_wgsl = result.wgsl; + ASSERT_EQ(src_wgsl, dst_wgsl); + +#else // #if TINT_BUILD_WGSL_READER && TINT_BUILD_WGSL_WRITER + GTEST_SKIP() << "ModuleCloneTest requires TINT_BUILD_WGSL_READER and " + "TINT_BUILD_WGSL_WRITER to be enabled"; +#endif +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/module_test.cc b/src/tint/ast/module_test.cc new file mode 100644 index 0000000..21b48dc --- /dev/null +++ b/src/tint/ast/module_test.cc
@@ -0,0 +1,143 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "gtest/gtest-spi.h" +#include "src/tint/ast/test_helper.h" +#include "src/tint/clone_context.h" + +namespace tint { +namespace ast { +namespace { + +using ModuleTest = TestHelper; + +TEST_F(ModuleTest, Creation) { + EXPECT_EQ(Program(std::move(*this)).AST().Functions().size(), 0u); +} + +TEST_F(ModuleTest, LookupFunction) { + auto* func = Func("main", VariableList{}, ty.f32(), StatementList{}, + ast::AttributeList{}); + + Program program(std::move(*this)); + EXPECT_EQ(func, + program.AST().Functions().Find(program.Symbols().Get("main"))); +} + +TEST_F(ModuleTest, LookupFunctionMissing) { + Program program(std::move(*this)); + EXPECT_EQ(nullptr, + program.AST().Functions().Find(program.Symbols().Get("Missing"))); +} + +TEST_F(ModuleTest, Assert_Null_GlobalVariable) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder builder; + builder.AST().AddGlobalVariable(nullptr); + }, + "internal compiler error"); +} + +TEST_F(ModuleTest, Assert_Null_TypeDecl) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder builder; + builder.AST().AddTypeDecl(nullptr); + }, + "internal compiler error"); +} + +TEST_F(ModuleTest, Assert_DifferentProgramID_Function) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.AST().AddFunction(b2.create<ast::Function>( + b2.Symbols().Register("func"), VariableList{}, b2.ty.f32(), + b2.Block(), AttributeList{}, AttributeList{})); + }, + "internal compiler error"); +} + +TEST_F(ModuleTest, Assert_DifferentProgramID_GlobalVariable) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.AST().AddGlobalVariable( + b2.Var("var", b2.ty.i32(), ast::StorageClass::kPrivate)); + }, + "internal compiler error"); +} + +TEST_F(ModuleTest, Assert_Null_Function) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder builder; + builder.AST().AddFunction(nullptr); + }, + "internal compiler error"); +} + +TEST_F(ModuleTest, CloneOrder) { + // Create a program with a function, alias decl and var decl. + Program p = [] { + ProgramBuilder b; + b.Func("F", {}, b.ty.void_(), {}); + b.Alias("A", b.ty.u32()); + b.Global("V", b.ty.i32(), ast::StorageClass::kPrivate); + return Program(std::move(b)); + }(); + + // Clone the program, using ReplaceAll() to create new module-scope + // declarations. We want to test that these are added just before the + // declaration that triggered the ReplaceAll(). + ProgramBuilder cloned; + CloneContext ctx(&cloned, &p); + ctx.ReplaceAll([&](const ast::Function*) -> const ast::Function* { + ctx.dst->Alias("inserted_before_F", cloned.ty.u32()); + return nullptr; + }); + ctx.ReplaceAll([&](const ast::Alias*) -> const ast::Alias* { + ctx.dst->Alias("inserted_before_A", cloned.ty.u32()); + return nullptr; + }); + ctx.ReplaceAll([&](const ast::Variable*) -> const ast::Variable* { + ctx.dst->Alias("inserted_before_V", cloned.ty.u32()); + return nullptr; + }); + ctx.Clone(); + + auto& decls = cloned.AST().GlobalDeclarations(); + ASSERT_EQ(decls.size(), 6u); + EXPECT_TRUE(decls[1]->Is<ast::Function>()); + EXPECT_TRUE(decls[3]->Is<ast::Alias>()); + EXPECT_TRUE(decls[5]->Is<ast::Variable>()); + + ASSERT_TRUE(decls[0]->Is<ast::Alias>()); + ASSERT_TRUE(decls[2]->Is<ast::Alias>()); + ASSERT_TRUE(decls[4]->Is<ast::Alias>()); + + ASSERT_EQ(cloned.Symbols().NameFor(decls[0]->As<ast::Alias>()->name), + "inserted_before_F"); + ASSERT_EQ(cloned.Symbols().NameFor(decls[2]->As<ast::Alias>()->name), + "inserted_before_A"); + ASSERT_EQ(cloned.Symbols().NameFor(decls[4]->As<ast::Alias>()->name), + "inserted_before_V"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/multisampled_texture.cc b/src/tint/ast/multisampled_texture.cc new file mode 100644 index 0000000..f216a3c --- /dev/null +++ b/src/tint/ast/multisampled_texture.cc
@@ -0,0 +1,52 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/multisampled_texture.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::MultisampledTexture); + +namespace tint { +namespace ast { + +MultisampledTexture::MultisampledTexture(ProgramID pid, + const Source& src, + TextureDimension d, + const Type* ty) + : Base(pid, src, d), type(ty) { + TINT_ASSERT(AST, type); +} + +MultisampledTexture::MultisampledTexture(MultisampledTexture&&) = default; + +MultisampledTexture::~MultisampledTexture() = default; + +std::string MultisampledTexture::FriendlyName( + const SymbolTable& symbols) const { + std::ostringstream out; + out << "texture_multisampled_" << dim << "<" << type->FriendlyName(symbols) + << ">"; + return out.str(); +} + +const MultisampledTexture* MultisampledTexture::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* ty = ctx->Clone(type); + return ctx->dst->create<MultisampledTexture>(src, dim, ty); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/multisampled_texture.h b/src/tint/ast/multisampled_texture.h new file mode 100644 index 0000000..fbc3db0 --- /dev/null +++ b/src/tint/ast/multisampled_texture.h
@@ -0,0 +1,59 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_MULTISAMPLED_TEXTURE_H_ +#define SRC_TINT_AST_MULTISAMPLED_TEXTURE_H_ + +#include <string> + +#include "src/tint/ast/texture.h" + +namespace tint { +namespace ast { + +/// A multisampled texture type. +class MultisampledTexture final + : public Castable<MultisampledTexture, Texture> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param dim the dimensionality of the texture + /// @param type the data type of the multisampled texture + MultisampledTexture(ProgramID pid, + const Source& src, + TextureDimension dim, + const Type* type); + /// Move constructor + MultisampledTexture(MultisampledTexture&&); + ~MultisampledTexture() override; + + /// @param symbols the program's symbol table + /// @returns the name for this type that closely resembles how it would be + /// declared in WGSL. + std::string FriendlyName(const SymbolTable& symbols) const override; + + /// Clones this type and all transitive types using the `CloneContext` `ctx`. + /// @param ctx the clone context + /// @return the newly cloned type + const MultisampledTexture* Clone(CloneContext* ctx) const override; + + /// The subtype of the multisampled texture + const Type* const type; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_MULTISAMPLED_TEXTURE_H_
diff --git a/src/tint/ast/multisampled_texture_test.cc b/src/tint/ast/multisampled_texture_test.cc new file mode 100644 index 0000000..ba7ffb5 --- /dev/null +++ b/src/tint/ast/multisampled_texture_test.cc
@@ -0,0 +1,70 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/multisampled_texture.h" + +#include "src/tint/ast/access.h" +#include "src/tint/ast/alias.h" +#include "src/tint/ast/array.h" +#include "src/tint/ast/bool.h" +#include "src/tint/ast/depth_texture.h" +#include "src/tint/ast/f32.h" +#include "src/tint/ast/i32.h" +#include "src/tint/ast/matrix.h" +#include "src/tint/ast/pointer.h" +#include "src/tint/ast/sampled_texture.h" +#include "src/tint/ast/sampler.h" +#include "src/tint/ast/storage_texture.h" +#include "src/tint/ast/struct.h" +#include "src/tint/ast/test_helper.h" +#include "src/tint/ast/texture.h" +#include "src/tint/ast/u32.h" +#include "src/tint/ast/vector.h" + +namespace tint { +namespace ast { +namespace { + +using AstMultisampledTextureTest = TestHelper; + +TEST_F(AstMultisampledTextureTest, IsTexture) { + auto* f32 = create<F32>(); + Texture* ty = create<MultisampledTexture>(TextureDimension::kCube, f32); + EXPECT_FALSE(ty->Is<DepthTexture>()); + EXPECT_TRUE(ty->Is<MultisampledTexture>()); + EXPECT_FALSE(ty->Is<SampledTexture>()); + EXPECT_FALSE(ty->Is<StorageTexture>()); +} + +TEST_F(AstMultisampledTextureTest, Dim) { + auto* f32 = create<F32>(); + auto* s = create<MultisampledTexture>(TextureDimension::k3d, f32); + EXPECT_EQ(s->dim, TextureDimension::k3d); +} + +TEST_F(AstMultisampledTextureTest, Type) { + auto* f32 = create<F32>(); + auto* s = create<MultisampledTexture>(TextureDimension::k3d, f32); + EXPECT_EQ(s->type, f32); +} + +TEST_F(AstMultisampledTextureTest, FriendlyName) { + auto* f32 = create<F32>(); + auto* s = create<MultisampledTexture>(TextureDimension::k3d, f32); + EXPECT_EQ(s->FriendlyName(Symbols()), "texture_multisampled_3d<f32>"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/node.cc b/src/tint/ast/node.cc new file mode 100644 index 0000000..b90f0ef --- /dev/null +++ b/src/tint/ast/node.cc
@@ -0,0 +1,29 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/node.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::Node); + +namespace tint { +namespace ast { + +Node::Node(ProgramID pid, const Source& src) : program_id(pid), source(src) {} + +Node::Node(Node&&) = default; + +Node::~Node() = default; + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/node.h b/src/tint/ast/node.h new file mode 100644 index 0000000..01b017b5 --- /dev/null +++ b/src/tint/ast/node.h
@@ -0,0 +1,68 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_NODE_H_ +#define SRC_TINT_AST_NODE_H_ + +#include <string> + +#include "src/tint/clone_context.h" + +namespace tint { + +// Forward declarations +class CloneContext; +namespace sem { +class Type; +} +namespace sem { +class Info; +} + +namespace ast { + +/// AST base class node +class Node : public Castable<Node, Cloneable> { + public: + ~Node() override; + + /// The identifier of the program that owns this node + const ProgramID program_id; + + /// The node source data + const Source source; + + protected: + /// Create a new node + /// @param pid the identifier of the program that owns this node + /// @param src the input source for the node + Node(ProgramID pid, const Source& src); + /// Move constructor + Node(Node&&); + + private: + Node(const Node&) = delete; +}; + +} // namespace ast + +/// @param node a pointer to an AST node +/// @returns the ProgramID of the given AST node. +inline ProgramID ProgramIDOf(const ast::Node* node) { + return node ? node->program_id : ProgramID(); +} + +} // namespace tint + +#endif // SRC_TINT_AST_NODE_H_
diff --git a/src/tint/ast/phony_expression.cc b/src/tint/ast/phony_expression.cc new file mode 100644 index 0000000..c05fb1d --- /dev/null +++ b/src/tint/ast/phony_expression.cc
@@ -0,0 +1,38 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/phony_expression.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::PhonyExpression); + +namespace tint { +namespace ast { + +PhonyExpression::PhonyExpression(ProgramID pid, const Source& src) + : Base(pid, src) {} + +PhonyExpression::PhonyExpression(PhonyExpression&&) = default; + +PhonyExpression::~PhonyExpression() = default; + +const PhonyExpression* PhonyExpression::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + return ctx->dst->create<PhonyExpression>(src); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/phony_expression.h b/src/tint/ast/phony_expression.h new file mode 100644 index 0000000..e021846 --- /dev/null +++ b/src/tint/ast/phony_expression.h
@@ -0,0 +1,45 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_AST_PHONY_EXPRESSION_H_ +#define SRC_TINT_AST_PHONY_EXPRESSION_H_ + +#include "src/tint/ast/expression.h" + +namespace tint { +namespace ast { + +/// Represents the `_` of a phony assignment `_ = <expr>` +/// @see https://www.w3.org/TR/WGSL/#phony-assignment-section +class PhonyExpression final : public Castable<PhonyExpression, Expression> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + PhonyExpression(ProgramID pid, const Source& src); + /// Move constructor + PhonyExpression(PhonyExpression&&); + ~PhonyExpression() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const PhonyExpression* Clone(CloneContext* ctx) const override; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_PHONY_EXPRESSION_H_
diff --git a/src/tint/ast/phony_expression_test.cc b/src/tint/ast/phony_expression_test.cc new file mode 100644 index 0000000..568a2d0 --- /dev/null +++ b/src/tint/ast/phony_expression_test.cc
@@ -0,0 +1,42 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using IdentifierExpressionTest = TestHelper; + +TEST_F(IdentifierExpressionTest, Creation) { + EXPECT_NE(Phony(), nullptr); +} + +TEST_F(IdentifierExpressionTest, Creation_WithSource) { + auto* p = Phony(Source{{20, 2}}); + + auto src = p->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(IdentifierExpressionTest, IsPhony) { + auto* p = Phony(); + EXPECT_TRUE(p->Is<PhonyExpression>()); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/pipeline_stage.cc b/src/tint/ast/pipeline_stage.cc new file mode 100644 index 0000000..9f5203d --- /dev/null +++ b/src/tint/ast/pipeline_stage.cc
@@ -0,0 +1,43 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/pipeline_stage.h" + +namespace tint { +namespace ast { + +std::ostream& operator<<(std::ostream& out, PipelineStage stage) { + switch (stage) { + case PipelineStage::kNone: { + out << "none"; + break; + } + case PipelineStage::kVertex: { + out << "vertex"; + break; + } + case PipelineStage::kFragment: { + out << "fragment"; + break; + } + case PipelineStage::kCompute: { + out << "compute"; + break; + } + } + return out; +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/pipeline_stage.h b/src/tint/ast/pipeline_stage.h new file mode 100644 index 0000000..37e5030 --- /dev/null +++ b/src/tint/ast/pipeline_stage.h
@@ -0,0 +1,34 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_PIPELINE_STAGE_H_ +#define SRC_TINT_AST_PIPELINE_STAGE_H_ + +#include <ostream> + +namespace tint { +namespace ast { + +/// The pipeline stage +enum class PipelineStage { kNone = -1, kVertex, kFragment, kCompute }; + +/// @param out the std::ostream to write to +/// @param stage the PipelineStage +/// @return the std::ostream so calls can be chained +std::ostream& operator<<(std::ostream& out, PipelineStage stage); + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_PIPELINE_STAGE_H_
diff --git a/src/tint/ast/pointer.cc b/src/tint/ast/pointer.cc new file mode 100644 index 0000000..1db6fc0 --- /dev/null +++ b/src/tint/ast/pointer.cc
@@ -0,0 +1,57 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/pointer.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::Pointer); + +namespace tint { +namespace ast { + +Pointer::Pointer(ProgramID pid, + const Source& src, + const Type* const subtype, + ast::StorageClass sc, + ast::Access ac) + : Base(pid, src), type(subtype), storage_class(sc), access(ac) {} + +std::string Pointer::FriendlyName(const SymbolTable& symbols) const { + std::ostringstream out; + out << "ptr<"; + if (storage_class != ast::StorageClass::kNone) { + out << storage_class << ", "; + } + out << type->FriendlyName(symbols); + if (access != ast::Access::kUndefined) { + out << ", " << access; + } + out << ">"; + return out.str(); +} + +Pointer::Pointer(Pointer&&) = default; + +Pointer::~Pointer() = default; + +const Pointer* Pointer::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* ty = ctx->Clone(type); + return ctx->dst->create<Pointer>(src, ty, storage_class, access); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/pointer.h b/src/tint/ast/pointer.h new file mode 100644 index 0000000..c73eb40 --- /dev/null +++ b/src/tint/ast/pointer.h
@@ -0,0 +1,68 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_POINTER_H_ +#define SRC_TINT_AST_POINTER_H_ + +#include <string> + +#include "src/tint/ast/access.h" +#include "src/tint/ast/storage_class.h" +#include "src/tint/ast/type.h" + +namespace tint { +namespace ast { + +/// A pointer type. +class Pointer final : public Castable<Pointer, Type> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param subtype the pointee type + /// @param storage_class the storage class of the pointer + /// @param access the access control of the pointer + Pointer(ProgramID pid, + const Source& src, + const Type* const subtype, + ast::StorageClass storage_class, + ast::Access access); + /// Move constructor + Pointer(Pointer&&); + ~Pointer() override; + + /// @param symbols the program's symbol table + /// @returns the name for this type that closely resembles how it would be + /// declared in WGSL. + std::string FriendlyName(const SymbolTable& symbols) const override; + + /// Clones this type and all transitive types using the `CloneContext` `ctx`. + /// @param ctx the clone context + /// @return the newly cloned type + const Pointer* Clone(CloneContext* ctx) const override; + + /// The pointee type + const Type* const type; + + /// The storage class of the pointer + ast::StorageClass const storage_class; + + /// The access control of the pointer + ast::Access const access; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_POINTER_H_
diff --git a/src/tint/ast/pointer_test.cc b/src/tint/ast/pointer_test.cc new file mode 100644 index 0000000..ec5520c --- /dev/null +++ b/src/tint/ast/pointer_test.cc
@@ -0,0 +1,50 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/pointer.h" + +#include "src/tint/ast/i32.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using AstPointerTest = TestHelper; + +TEST_F(AstPointerTest, Creation) { + auto* i32 = create<I32>(); + auto* p = create<Pointer>(i32, ast::StorageClass::kStorage, Access::kRead); + EXPECT_EQ(p->type, i32); + EXPECT_EQ(p->storage_class, ast::StorageClass::kStorage); + EXPECT_EQ(p->access, Access::kRead); +} + +TEST_F(AstPointerTest, FriendlyName) { + auto* i32 = create<I32>(); + auto* p = + create<Pointer>(i32, ast::StorageClass::kWorkgroup, Access::kUndefined); + EXPECT_EQ(p->FriendlyName(Symbols()), "ptr<workgroup, i32>"); +} + +TEST_F(AstPointerTest, FriendlyNameWithAccess) { + auto* i32 = create<I32>(); + auto* p = + create<Pointer>(i32, ast::StorageClass::kStorage, Access::kReadWrite); + EXPECT_EQ(p->FriendlyName(Symbols()), "ptr<storage, i32, read_write>"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/return_statement.cc b/src/tint/ast/return_statement.cc new file mode 100644 index 0000000..61b6d45 --- /dev/null +++ b/src/tint/ast/return_statement.cc
@@ -0,0 +1,46 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/return_statement.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::ReturnStatement); + +namespace tint { +namespace ast { + +ReturnStatement::ReturnStatement(ProgramID pid, const Source& src) + : Base(pid, src), value(nullptr) {} + +ReturnStatement::ReturnStatement(ProgramID pid, + const Source& src, + const Expression* val) + : Base(pid, src), value(val) { + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, value, program_id); +} + +ReturnStatement::ReturnStatement(ReturnStatement&&) = default; + +ReturnStatement::~ReturnStatement() = default; + +const ReturnStatement* ReturnStatement::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* ret = ctx->Clone(value); + return ctx->dst->create<ReturnStatement>(src, ret); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/return_statement.h b/src/tint/ast/return_statement.h new file mode 100644 index 0000000..5a20ff4 --- /dev/null +++ b/src/tint/ast/return_statement.h
@@ -0,0 +1,54 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_RETURN_STATEMENT_H_ +#define SRC_TINT_AST_RETURN_STATEMENT_H_ + +#include "src/tint/ast/expression.h" +#include "src/tint/ast/statement.h" + +namespace tint { +namespace ast { + +/// A return statement +class ReturnStatement final : public Castable<ReturnStatement, Statement> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + ReturnStatement(ProgramID pid, const Source& src); + + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param value the return value + ReturnStatement(ProgramID pid, const Source& src, const Expression* value); + /// Move constructor + ReturnStatement(ReturnStatement&&); + ~ReturnStatement() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const ReturnStatement* Clone(CloneContext* ctx) const override; + + /// The value returned. May be null. + const Expression* const value; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_RETURN_STATEMENT_H_
diff --git a/src/tint/ast/return_statement_test.cc b/src/tint/ast/return_statement_test.cc new file mode 100644 index 0000000..93c75a7 --- /dev/null +++ b/src/tint/ast/return_statement_test.cc
@@ -0,0 +1,68 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/return_statement.h" + +#include "gtest/gtest-spi.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using ReturnStatementTest = TestHelper; + +TEST_F(ReturnStatementTest, Creation) { + auto* expr = Expr("expr"); + + auto* r = create<ReturnStatement>(expr); + EXPECT_EQ(r->value, expr); +} + +TEST_F(ReturnStatementTest, Creation_WithSource) { + auto* r = create<ReturnStatement>(Source{Source::Location{20, 2}}); + auto src = r->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(ReturnStatementTest, IsReturn) { + auto* r = create<ReturnStatement>(); + EXPECT_TRUE(r->Is<ReturnStatement>()); +} + +TEST_F(ReturnStatementTest, WithoutValue) { + auto* r = create<ReturnStatement>(); + EXPECT_EQ(r->value, nullptr); +} + +TEST_F(ReturnStatementTest, WithValue) { + auto* expr = Expr("expr"); + auto* r = create<ReturnStatement>(expr); + EXPECT_NE(r->value, nullptr); +} + +TEST_F(ReturnStatementTest, Assert_DifferentProgramID_Expr) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<ReturnStatement>(b2.Expr(true)); + }, + "internal compiler error"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/sampled_texture.cc b/src/tint/ast/sampled_texture.cc new file mode 100644 index 0000000..7937ad5 --- /dev/null +++ b/src/tint/ast/sampled_texture.cc
@@ -0,0 +1,50 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/sampled_texture.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::SampledTexture); + +namespace tint { +namespace ast { + +SampledTexture::SampledTexture(ProgramID pid, + const Source& src, + TextureDimension d, + const Type* ty) + : Base(pid, src, d), type(ty) { + TINT_ASSERT(AST, type); +} + +SampledTexture::SampledTexture(SampledTexture&&) = default; + +SampledTexture::~SampledTexture() = default; + +std::string SampledTexture::FriendlyName(const SymbolTable& symbols) const { + std::ostringstream out; + out << "texture_" << dim << "<" << type->FriendlyName(symbols) << ">"; + return out.str(); +} + +const SampledTexture* SampledTexture::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* ty = ctx->Clone(type); + return ctx->dst->create<SampledTexture>(src, dim, ty); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/sampled_texture.h b/src/tint/ast/sampled_texture.h new file mode 100644 index 0000000..6d0820c --- /dev/null +++ b/src/tint/ast/sampled_texture.h
@@ -0,0 +1,58 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_SAMPLED_TEXTURE_H_ +#define SRC_TINT_AST_SAMPLED_TEXTURE_H_ + +#include <string> + +#include "src/tint/ast/texture.h" + +namespace tint { +namespace ast { + +/// A sampled texture type. +class SampledTexture final : public Castable<SampledTexture, Texture> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param dim the dimensionality of the texture + /// @param type the data type of the sampled texture + SampledTexture(ProgramID pid, + const Source& src, + TextureDimension dim, + const Type* type); + /// Move constructor + SampledTexture(SampledTexture&&); + ~SampledTexture() override; + + /// @param symbols the program's symbol table + /// @returns the name for this type that closely resembles how it would be + /// declared in WGSL. + std::string FriendlyName(const SymbolTable& symbols) const override; + + /// Clones this type and all transitive types using the `CloneContext` `ctx`. + /// @param ctx the clone context + /// @return the newly cloned type + const SampledTexture* Clone(CloneContext* ctx) const override; + + /// The subtype of the sampled texture + const Type* const type; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_SAMPLED_TEXTURE_H_
diff --git a/src/tint/ast/sampled_texture_test.cc b/src/tint/ast/sampled_texture_test.cc new file mode 100644 index 0000000..033751d --- /dev/null +++ b/src/tint/ast/sampled_texture_test.cc
@@ -0,0 +1,54 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/sampled_texture.h" + +#include "src/tint/ast/f32.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using AstSampledTextureTest = TestHelper; + +TEST_F(AstSampledTextureTest, IsTexture) { + auto* f32 = create<F32>(); + Texture* ty = create<SampledTexture>(TextureDimension::kCube, f32); + EXPECT_FALSE(ty->Is<DepthTexture>()); + EXPECT_TRUE(ty->Is<SampledTexture>()); + EXPECT_FALSE(ty->Is<StorageTexture>()); +} + +TEST_F(AstSampledTextureTest, Dim) { + auto* f32 = create<F32>(); + auto* s = create<SampledTexture>(TextureDimension::k3d, f32); + EXPECT_EQ(s->dim, TextureDimension::k3d); +} + +TEST_F(AstSampledTextureTest, Type) { + auto* f32 = create<F32>(); + auto* s = create<SampledTexture>(TextureDimension::k3d, f32); + EXPECT_EQ(s->type, f32); +} + +TEST_F(AstSampledTextureTest, FriendlyName) { + auto* f32 = create<F32>(); + auto* s = create<SampledTexture>(TextureDimension::k3d, f32); + EXPECT_EQ(s->FriendlyName(Symbols()), "texture_3d<f32>"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/sampler.cc b/src/tint/ast/sampler.cc new file mode 100644 index 0000000..2db571d --- /dev/null +++ b/src/tint/ast/sampler.cc
@@ -0,0 +1,53 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/sampler.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::Sampler); + +namespace tint { +namespace ast { + +std::ostream& operator<<(std::ostream& out, SamplerKind kind) { + switch (kind) { + case SamplerKind::kSampler: + out << "sampler"; + break; + case SamplerKind::kComparisonSampler: + out << "comparison_sampler"; + break; + } + return out; +} + +Sampler::Sampler(ProgramID pid, const Source& src, SamplerKind k) + : Base(pid, src), kind(k) {} + +Sampler::Sampler(Sampler&&) = default; + +Sampler::~Sampler() = default; + +std::string Sampler::FriendlyName(const SymbolTable&) const { + return kind == SamplerKind::kSampler ? "sampler" : "sampler_comparison"; +} + +const Sampler* Sampler::Clone(CloneContext* ctx) const { + auto src = ctx->Clone(source); + return ctx->dst->create<Sampler>(src, kind); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/sampler.h b/src/tint/ast/sampler.h new file mode 100644 index 0000000..8724384 --- /dev/null +++ b/src/tint/ast/sampler.h
@@ -0,0 +1,70 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_SAMPLER_H_ +#define SRC_TINT_AST_SAMPLER_H_ + +#include <string> + +#include "src/tint/ast/type.h" + +namespace tint { +namespace ast { + +/// The different kinds of samplers +enum class SamplerKind { + /// A regular sampler + kSampler, + /// A comparison sampler + kComparisonSampler +}; + +/// @param out the std::ostream to write to +/// @param kind the SamplerKind +/// @return the std::ostream so calls can be chained +std::ostream& operator<<(std::ostream& out, SamplerKind kind); + +/// A sampler type. +class Sampler final : public Castable<Sampler, Type> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param kind the kind of sampler + Sampler(ProgramID pid, const Source& src, SamplerKind kind); + /// Move constructor + Sampler(Sampler&&); + ~Sampler() override; + + /// @returns true if this is a comparison sampler + bool IsComparison() const { return kind == SamplerKind::kComparisonSampler; } + + /// @param symbols the program's symbol table + /// @returns the name for this type that closely resembles how it would be + /// declared in WGSL. + std::string FriendlyName(const SymbolTable& symbols) const override; + + /// Clones this type and all transitive types using the `CloneContext` `ctx`. + /// @param ctx the clone context + /// @return the newly cloned type + const Sampler* Clone(CloneContext* ctx) const override; + + /// The sampler type + const SamplerKind kind; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_SAMPLER_H_
diff --git a/src/tint/ast/sampler_test.cc b/src/tint/ast/sampler_test.cc new file mode 100644 index 0000000..2ae7d42 --- /dev/null +++ b/src/tint/ast/sampler_test.cc
@@ -0,0 +1,48 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/sampler.h" + +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using AstSamplerTest = TestHelper; + +TEST_F(AstSamplerTest, Creation) { + auto* s = create<Sampler>(SamplerKind::kSampler); + EXPECT_EQ(s->kind, SamplerKind::kSampler); +} + +TEST_F(AstSamplerTest, Creation_ComparisonSampler) { + auto* s = create<Sampler>(SamplerKind::kComparisonSampler); + EXPECT_EQ(s->kind, SamplerKind::kComparisonSampler); + EXPECT_TRUE(s->IsComparison()); +} + +TEST_F(AstSamplerTest, FriendlyNameSampler) { + auto* s = create<Sampler>(SamplerKind::kSampler); + EXPECT_EQ(s->FriendlyName(Symbols()), "sampler"); +} + +TEST_F(AstSamplerTest, FriendlyNameComparisonSampler) { + auto* s = create<Sampler>(SamplerKind::kComparisonSampler); + EXPECT_EQ(s->FriendlyName(Symbols()), "sampler_comparison"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/sint_literal_expression.cc b/src/tint/ast/sint_literal_expression.cc new file mode 100644 index 0000000..fc0a4b3 --- /dev/null +++ b/src/tint/ast/sint_literal_expression.cc
@@ -0,0 +1,43 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/sint_literal_expression.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::SintLiteralExpression); + +namespace tint { +namespace ast { + +SintLiteralExpression::SintLiteralExpression(ProgramID pid, + const Source& src, + int32_t val) + : Base(pid, src), value(val) {} + +SintLiteralExpression::~SintLiteralExpression() = default; + +uint32_t SintLiteralExpression::ValueAsU32() const { + return static_cast<uint32_t>(value); +} + +const SintLiteralExpression* SintLiteralExpression::Clone( + CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + return ctx->dst->create<SintLiteralExpression>(src, value); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/sint_literal_expression.h b/src/tint/ast/sint_literal_expression.h new file mode 100644 index 0000000..1431dd2 --- /dev/null +++ b/src/tint/ast/sint_literal_expression.h
@@ -0,0 +1,52 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_SINT_LITERAL_EXPRESSION_H_ +#define SRC_TINT_AST_SINT_LITERAL_EXPRESSION_H_ + +#include <string> + +#include "src/tint/ast/int_literal_expression.h" + +namespace tint { +namespace ast { + +/// A signed int literal +class SintLiteralExpression final + : public Castable<SintLiteralExpression, IntLiteralExpression> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param value the signed int literals value + SintLiteralExpression(ProgramID pid, const Source& src, int32_t value); + ~SintLiteralExpression() override; + + /// @returns the literal value as a u32 + uint32_t ValueAsU32() const override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const SintLiteralExpression* Clone(CloneContext* ctx) const override; + + /// The int literal value + const int32_t value; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_SINT_LITERAL_EXPRESSION_H_
diff --git a/src/tint/ast/sint_literal_expression_test.cc b/src/tint/ast/sint_literal_expression_test.cc new file mode 100644 index 0000000..f19fff9 --- /dev/null +++ b/src/tint/ast/sint_literal_expression_test.cc
@@ -0,0 +1,31 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using SintLiteralExpressionTest = TestHelper; + +TEST_F(SintLiteralExpressionTest, Value) { + auto* i = create<SintLiteralExpression>(47); + ASSERT_TRUE(i->Is<SintLiteralExpression>()); + EXPECT_EQ(i->value, 47); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/stage_attribute.cc b/src/tint/ast/stage_attribute.cc new file mode 100644 index 0000000..92cc802 --- /dev/null +++ b/src/tint/ast/stage_attribute.cc
@@ -0,0 +1,44 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/stage_attribute.h" + +#include <string> + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::StageAttribute); + +namespace tint { +namespace ast { + +StageAttribute::StageAttribute(ProgramID pid, + const Source& src, + PipelineStage s) + : Base(pid, src), stage(s) {} + +StageAttribute::~StageAttribute() = default; + +std::string StageAttribute::Name() const { + return "stage"; +} + +const StageAttribute* StageAttribute::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + return ctx->dst->create<StageAttribute>(src, stage); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/stage_attribute.h b/src/tint/ast/stage_attribute.h new file mode 100644 index 0000000..7bf918c --- /dev/null +++ b/src/tint/ast/stage_attribute.h
@@ -0,0 +1,54 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_STAGE_ATTRIBUTE_H_ +#define SRC_TINT_AST_STAGE_ATTRIBUTE_H_ + +#include <string> + +#include "src/tint/ast/attribute.h" +#include "src/tint/ast/pipeline_stage.h" + +namespace tint { +namespace ast { + +/// A workgroup attribute +class StageAttribute final : public Castable<StageAttribute, Attribute> { + public: + /// constructor + /// @param program_id the identifier of the program that owns this node + /// @param stage the pipeline stage + /// @param source the source of this attribute + StageAttribute(ProgramID program_id, + const Source& source, + PipelineStage stage); + ~StageAttribute() override; + + /// @returns the WGSL name for the attribute + std::string Name() const override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const StageAttribute* Clone(CloneContext* ctx) const override; + + /// The pipeline stage + const PipelineStage stage; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_STAGE_ATTRIBUTE_H_
diff --git a/src/tint/ast/stage_attribute_test.cc b/src/tint/ast/stage_attribute_test.cc new file mode 100644 index 0000000..e1cc93a --- /dev/null +++ b/src/tint/ast/stage_attribute_test.cc
@@ -0,0 +1,33 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/stage_attribute.h" + +#include "src/tint/ast/test_helper.h" +#include "src/tint/ast/workgroup_attribute.h" + +namespace tint { +namespace ast { +namespace { + +using StageAttributeTest = TestHelper; + +TEST_F(StageAttributeTest, Creation_1param) { + auto* d = create<StageAttribute>(PipelineStage::kFragment); + EXPECT_EQ(d->stage, PipelineStage::kFragment); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/statement.cc b/src/tint/ast/statement.cc new file mode 100644 index 0000000..de02bbd --- /dev/null +++ b/src/tint/ast/statement.cc
@@ -0,0 +1,87 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/statement.h" + +#include "src/tint/ast/assignment_statement.h" +#include "src/tint/ast/break_statement.h" +#include "src/tint/ast/call_statement.h" +#include "src/tint/ast/continue_statement.h" +#include "src/tint/ast/discard_statement.h" +#include "src/tint/ast/fallthrough_statement.h" +#include "src/tint/ast/if_statement.h" +#include "src/tint/ast/loop_statement.h" +#include "src/tint/ast/return_statement.h" +#include "src/tint/ast/switch_statement.h" +#include "src/tint/ast/variable_decl_statement.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::Statement); + +namespace tint { +namespace ast { + +Statement::Statement(ProgramID pid, const Source& src) : Base(pid, src) {} + +Statement::Statement(Statement&&) = default; + +Statement::~Statement() = default; + +const char* Statement::Name() const { + if (Is<AssignmentStatement>()) { + return "assignment statement"; + } + if (Is<BlockStatement>()) { + return "block statement"; + } + if (Is<BreakStatement>()) { + return "break statement"; + } + if (Is<CaseStatement>()) { + return "case statement"; + } + if (Is<CallStatement>()) { + return "function call"; + } + if (Is<ContinueStatement>()) { + return "continue statement"; + } + if (Is<DiscardStatement>()) { + return "discard statement"; + } + if (Is<ElseStatement>()) { + return "else statement"; + } + if (Is<FallthroughStatement>()) { + return "fallthrough statement"; + } + if (Is<IfStatement>()) { + return "if statement"; + } + if (Is<LoopStatement>()) { + return "loop statement"; + } + if (Is<ReturnStatement>()) { + return "return statement"; + } + if (Is<SwitchStatement>()) { + return "switch statement"; + } + if (Is<VariableDeclStatement>()) { + return "variable declaration"; + } + return "statement"; +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/statement.h b/src/tint/ast/statement.h new file mode 100644 index 0000000..e6c9a1c --- /dev/null +++ b/src/tint/ast/statement.h
@@ -0,0 +1,48 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_STATEMENT_H_ +#define SRC_TINT_AST_STATEMENT_H_ + +#include <vector> + +#include "src/tint/ast/node.h" + +namespace tint { +namespace ast { + +/// Base statement class +class Statement : public Castable<Statement, Node> { + public: + ~Statement() override; + + /// @returns the human readable name for the statement type. + const char* Name() const; + + protected: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of the expression + Statement(ProgramID pid, const Source& src); + /// Move constructor + Statement(Statement&&); +}; + +/// A list of statements +using StatementList = std::vector<const Statement*>; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_STATEMENT_H_
diff --git a/src/tint/ast/storage_class.cc b/src/tint/ast/storage_class.cc new file mode 100644 index 0000000..b760647 --- /dev/null +++ b/src/tint/ast/storage_class.cc
@@ -0,0 +1,51 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/storage_class.h" + +namespace tint { +namespace ast { + +const char* ToString(StorageClass sc) { + switch (sc) { + case StorageClass::kInvalid: + return "invalid"; + case StorageClass::kNone: + return "none"; + case StorageClass::kInput: + return "in"; + case StorageClass::kOutput: + return "out"; + case StorageClass::kUniform: + return "uniform"; + case StorageClass::kWorkgroup: + return "workgroup"; + case StorageClass::kUniformConstant: + return "uniform_constant"; + case StorageClass::kStorage: + return "storage"; + case StorageClass::kPrivate: + return "private"; + case StorageClass::kFunction: + return "function"; + } + return "<unknown>"; +} +std::ostream& operator<<(std::ostream& out, StorageClass sc) { + out << ToString(sc); + return out; +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/storage_class.h b/src/tint/ast/storage_class.h new file mode 100644 index 0000000..bc22468 --- /dev/null +++ b/src/tint/ast/storage_class.h
@@ -0,0 +1,56 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_STORAGE_CLASS_H_ +#define SRC_TINT_AST_STORAGE_CLASS_H_ + +#include <ostream> + +namespace tint { +namespace ast { + +/// Storage class of a given pointer. +enum class StorageClass { + kInvalid = -1, + kNone, + kInput, + kOutput, + kUniform, + kWorkgroup, + kUniformConstant, + kStorage, + kPrivate, + kFunction +}; + +/// @returns true if the StorageClass is host-shareable +/// @param sc the StorageClass +/// @see https://gpuweb.github.io/gpuweb/wgsl.html#host-shareable +inline bool IsHostShareable(StorageClass sc) { + return sc == ast::StorageClass::kUniform || sc == ast::StorageClass::kStorage; +} + +/// @param sc the StorageClass +/// @return the name of the given storage class +const char* ToString(StorageClass sc); + +/// @param out the std::ostream to write to +/// @param sc the StorageClass +/// @return the std::ostream so calls can be chained +std::ostream& operator<<(std::ostream& out, StorageClass sc); + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_STORAGE_CLASS_H_
diff --git a/src/tint/ast/storage_texture.cc b/src/tint/ast/storage_texture.cc new file mode 100644 index 0000000..9f818ec --- /dev/null +++ b/src/tint/ast/storage_texture.cc
@@ -0,0 +1,146 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/storage_texture.h" + +#include "src/tint/ast/f32.h" +#include "src/tint/ast/i32.h" +#include "src/tint/ast/u32.h" +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::StorageTexture); + +namespace tint { +namespace ast { + +// Note, these names match the names in the WGSL spec. This behaviour is used +// in the WGSL writer to emit the texture format names. +std::ostream& operator<<(std::ostream& out, TexelFormat format) { + switch (format) { + case TexelFormat::kNone: + out << "none"; + break; + case TexelFormat::kR32Uint: + out << "r32uint"; + break; + case TexelFormat::kR32Sint: + out << "r32sint"; + break; + case TexelFormat::kR32Float: + out << "r32float"; + break; + case TexelFormat::kRgba8Unorm: + out << "rgba8unorm"; + break; + case TexelFormat::kRgba8Snorm: + out << "rgba8snorm"; + break; + case TexelFormat::kRgba8Uint: + out << "rgba8uint"; + break; + case TexelFormat::kRgba8Sint: + out << "rgba8sint"; + break; + case TexelFormat::kRg32Uint: + out << "rg32uint"; + break; + case TexelFormat::kRg32Sint: + out << "rg32sint"; + break; + case TexelFormat::kRg32Float: + out << "rg32float"; + break; + case TexelFormat::kRgba16Uint: + out << "rgba16uint"; + break; + case TexelFormat::kRgba16Sint: + out << "rgba16sint"; + break; + case TexelFormat::kRgba16Float: + out << "rgba16float"; + break; + case TexelFormat::kRgba32Uint: + out << "rgba32uint"; + break; + case TexelFormat::kRgba32Sint: + out << "rgba32sint"; + break; + case TexelFormat::kRgba32Float: + out << "rgba32float"; + break; + } + return out; +} + +StorageTexture::StorageTexture(ProgramID pid, + const Source& src, + TextureDimension d, + TexelFormat fmt, + const Type* subtype, + Access ac) + : Base(pid, src, d), format(fmt), type(subtype), access(ac) {} + +StorageTexture::StorageTexture(StorageTexture&&) = default; + +StorageTexture::~StorageTexture() = default; + +std::string StorageTexture::FriendlyName(const SymbolTable&) const { + std::ostringstream out; + out << "texture_storage_" << dim << "<" << format << ", " << access << ">"; + return out.str(); +} + +const StorageTexture* StorageTexture::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* ty = ctx->Clone(type); + return ctx->dst->create<StorageTexture>(src, dim, format, ty, access); +} + +Type* StorageTexture::SubtypeFor(TexelFormat format, ProgramBuilder& builder) { + switch (format) { + case TexelFormat::kR32Uint: + case TexelFormat::kRgba8Uint: + case TexelFormat::kRg32Uint: + case TexelFormat::kRgba16Uint: + case TexelFormat::kRgba32Uint: { + return builder.create<U32>(); + } + + case TexelFormat::kR32Sint: + case TexelFormat::kRgba8Sint: + case TexelFormat::kRg32Sint: + case TexelFormat::kRgba16Sint: + case TexelFormat::kRgba32Sint: { + return builder.create<I32>(); + } + + case TexelFormat::kRgba8Unorm: + case TexelFormat::kRgba8Snorm: + case TexelFormat::kR32Float: + case TexelFormat::kRg32Float: + case TexelFormat::kRgba16Float: + case TexelFormat::kRgba32Float: { + return builder.create<F32>(); + } + + case TexelFormat::kNone: + break; + } + + return nullptr; +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/storage_texture.h b/src/tint/ast/storage_texture.h new file mode 100644 index 0000000..9af93af --- /dev/null +++ b/src/tint/ast/storage_texture.h
@@ -0,0 +1,101 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_STORAGE_TEXTURE_H_ +#define SRC_TINT_AST_STORAGE_TEXTURE_H_ + +#include <string> + +#include "src/tint/ast/access.h" +#include "src/tint/ast/texture.h" + +namespace tint { +namespace ast { + +/// The texel format in the storage texture +enum class TexelFormat { + kNone = -1, + kRgba8Unorm, + kRgba8Snorm, + kRgba8Uint, + kRgba8Sint, + kRgba16Uint, + kRgba16Sint, + kRgba16Float, + kR32Uint, + kR32Sint, + kR32Float, + kRg32Uint, + kRg32Sint, + kRg32Float, + kRgba32Uint, + kRgba32Sint, + kRgba32Float, +}; + +/// @param out the std::ostream to write to +/// @param format the TexelFormat +/// @return the std::ostream so calls can be chained +std::ostream& operator<<(std::ostream& out, TexelFormat format); + +/// A storage texture type. +class StorageTexture final : public Castable<StorageTexture, Texture> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param dim the dimensionality of the texture + /// @param format the image format of the texture + /// @param subtype the storage subtype. Use SubtypeFor() to calculate this. + /// @param access_control the access control for the texture. + StorageTexture(ProgramID pid, + const Source& src, + TextureDimension dim, + TexelFormat format, + const Type* subtype, + Access access_control); + + /// Move constructor + StorageTexture(StorageTexture&&); + ~StorageTexture() override; + + /// @param symbols the program's symbol table + /// @returns the name for this type that closely resembles how it would be + /// declared in WGSL. + std::string FriendlyName(const SymbolTable& symbols) const override; + + /// Clones this type and all transitive types using the `CloneContext` `ctx`. + /// @param ctx the clone context + /// @return the newly cloned type + const StorageTexture* Clone(CloneContext* ctx) const override; + + /// @param format the storage texture image format + /// @param builder the ProgramBuilder used to build the returned type + /// @returns the storage texture subtype for the given TexelFormat + static Type* SubtypeFor(TexelFormat format, ProgramBuilder& builder); + + /// The image format + const TexelFormat format; + + /// The storage subtype + const Type* const type; + + /// The access control + const Access access; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_STORAGE_TEXTURE_H_
diff --git a/src/tint/ast/storage_texture_test.cc b/src/tint/ast/storage_texture_test.cc new file mode 100644 index 0000000..5186b7e --- /dev/null +++ b/src/tint/ast/storage_texture_test.cc
@@ -0,0 +1,95 @@ +// Copyright 2020 The Tint Authors-> +// +// 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 "src/tint/ast/storage_texture.h" + +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using AstStorageTextureTest = TestHelper; + +TEST_F(AstStorageTextureTest, IsTexture) { + auto* subtype = StorageTexture::SubtypeFor(TexelFormat::kRgba32Float, *this); + Texture* ty = + create<StorageTexture>(TextureDimension::k2dArray, + TexelFormat::kRgba32Float, subtype, Access::kRead); + EXPECT_FALSE(ty->Is<DepthTexture>()); + EXPECT_FALSE(ty->Is<SampledTexture>()); + EXPECT_TRUE(ty->Is<StorageTexture>()); +} + +TEST_F(AstStorageTextureTest, Dim) { + auto* subtype = StorageTexture::SubtypeFor(TexelFormat::kRgba32Float, *this); + auto* s = + create<StorageTexture>(TextureDimension::k2dArray, + TexelFormat::kRgba32Float, subtype, Access::kRead); + EXPECT_EQ(s->dim, TextureDimension::k2dArray); +} + +TEST_F(AstStorageTextureTest, Format) { + auto* subtype = StorageTexture::SubtypeFor(TexelFormat::kRgba32Float, *this); + auto* s = + create<StorageTexture>(TextureDimension::k2dArray, + TexelFormat::kRgba32Float, subtype, Access::kRead); + EXPECT_EQ(s->format, TexelFormat::kRgba32Float); +} + +TEST_F(AstStorageTextureTest, FriendlyName) { + auto* subtype = StorageTexture::SubtypeFor(TexelFormat::kRgba32Float, *this); + auto* s = + create<StorageTexture>(TextureDimension::k2dArray, + TexelFormat::kRgba32Float, subtype, Access::kRead); + EXPECT_EQ(s->FriendlyName(Symbols()), + "texture_storage_2d_array<rgba32float, read>"); +} + +TEST_F(AstStorageTextureTest, F32) { + auto* subtype = StorageTexture::SubtypeFor(TexelFormat::kRgba32Float, *this); + Type* s = + create<StorageTexture>(TextureDimension::k2dArray, + TexelFormat::kRgba32Float, subtype, Access::kRead); + + ASSERT_TRUE(s->Is<Texture>()); + ASSERT_TRUE(s->Is<StorageTexture>()); + EXPECT_TRUE(s->As<StorageTexture>()->type->Is<F32>()); +} + +TEST_F(AstStorageTextureTest, U32) { + auto* subtype = StorageTexture::SubtypeFor(TexelFormat::kRg32Uint, *this); + Type* s = + create<StorageTexture>(TextureDimension::k2dArray, TexelFormat::kRg32Uint, + subtype, Access::kRead); + + ASSERT_TRUE(s->Is<Texture>()); + ASSERT_TRUE(s->Is<StorageTexture>()); + EXPECT_TRUE(s->As<StorageTexture>()->type->Is<U32>()); +} + +TEST_F(AstStorageTextureTest, I32) { + auto* subtype = StorageTexture::SubtypeFor(TexelFormat::kRgba32Sint, *this); + Type* s = + create<StorageTexture>(TextureDimension::k2dArray, + TexelFormat::kRgba32Sint, subtype, Access::kRead); + + ASSERT_TRUE(s->Is<Texture>()); + ASSERT_TRUE(s->Is<StorageTexture>()); + EXPECT_TRUE(s->As<StorageTexture>()->type->Is<I32>()); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/stride_attribute.cc b/src/tint/ast/stride_attribute.cc new file mode 100644 index 0000000..1c763ac --- /dev/null +++ b/src/tint/ast/stride_attribute.cc
@@ -0,0 +1,42 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/stride_attribute.h" + +#include <string> + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::StrideAttribute); + +namespace tint { +namespace ast { + +StrideAttribute::StrideAttribute(ProgramID pid, const Source& src, uint32_t s) + : Base(pid, src), stride(s) {} + +StrideAttribute::~StrideAttribute() = default; + +std::string StrideAttribute::Name() const { + return "stride"; +} + +const StrideAttribute* StrideAttribute::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + return ctx->dst->create<StrideAttribute>(src, stride); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/stride_attribute.h b/src/tint/ast/stride_attribute.h new file mode 100644 index 0000000..0aa3baf --- /dev/null +++ b/src/tint/ast/stride_attribute.h
@@ -0,0 +1,53 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_STRIDE_ATTRIBUTE_H_ +#define SRC_TINT_AST_STRIDE_ATTRIBUTE_H_ + +#include <string> + +#include "src/tint/ast/attribute.h" +#include "src/tint/ast/internal_attribute.h" + +namespace tint { +namespace ast { + +/// A stride attribute used by the SPIR-V reader for strided arrays and +/// matrices. +class StrideAttribute final : public Castable<StrideAttribute, Attribute> { + public: + /// constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param stride the stride value + StrideAttribute(ProgramID pid, const Source& src, uint32_t stride); + ~StrideAttribute() override; + + /// @returns the WGSL name for the attribute + std::string Name() const override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const StrideAttribute* Clone(CloneContext* ctx) const override; + + /// The stride value + const uint32_t stride; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_STRIDE_ATTRIBUTE_H_
diff --git a/src/tint/ast/stride_attribute_test.cc b/src/tint/ast/stride_attribute_test.cc new file mode 100644 index 0000000..f8549ec --- /dev/null +++ b/src/tint/ast/stride_attribute_test.cc
@@ -0,0 +1,39 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using StrideAttributeTest = TestHelper; + +TEST_F(StrideAttributeTest, Creation) { + auto* d = create<StrideAttribute>(2); + EXPECT_EQ(2u, d->stride); +} + +TEST_F(StrideAttributeTest, Source) { + auto* d = create<StrideAttribute>( + Source{Source::Range{Source::Location{1, 2}, Source::Location{3, 4}}}, 2); + EXPECT_EQ(d->source.range.begin.line, 1u); + EXPECT_EQ(d->source.range.begin.column, 2u); + EXPECT_EQ(d->source.range.end.line, 3u); + EXPECT_EQ(d->source.range.end.column, 4u); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/struct.cc b/src/tint/ast/struct.cc new file mode 100644 index 0000000..c33c07d --- /dev/null +++ b/src/tint/ast/struct.cc
@@ -0,0 +1,56 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/struct.h" + +#include <string> + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::Struct); + +namespace tint { +namespace ast { + +Struct::Struct(ProgramID pid, + const Source& src, + Symbol n, + StructMemberList m, + AttributeList attrs) + : Base(pid, src, n), members(std::move(m)), attributes(std::move(attrs)) { + for (auto* mem : members) { + TINT_ASSERT(AST, mem); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, mem, program_id); + } + for (auto* attr : attributes) { + TINT_ASSERT(AST, attr); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, attr, program_id); + } +} + +Struct::Struct(Struct&&) = default; + +Struct::~Struct() = default; + +const Struct* Struct::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto n = ctx->Clone(name); + auto mem = ctx->Clone(members); + auto attrs = ctx->Clone(attributes); + return ctx->dst->create<Struct>(src, n, mem, attrs); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/struct.h b/src/tint/ast/struct.h new file mode 100644 index 0000000..076c46d --- /dev/null +++ b/src/tint/ast/struct.h
@@ -0,0 +1,63 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_STRUCT_H_ +#define SRC_TINT_AST_STRUCT_H_ + +#include <string> +#include <utility> + +#include "src/tint/ast/attribute.h" +#include "src/tint/ast/struct_member.h" +#include "src/tint/ast/type_decl.h" + +namespace tint { +namespace ast { + +/// A struct statement. +class Struct final : public Castable<Struct, TypeDecl> { + public: + /// Create a new struct statement + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node for the import statement + /// @param name The name of the structure + /// @param members The struct members + /// @param attributes The struct attributes + Struct(ProgramID pid, + const Source& src, + Symbol name, + StructMemberList members, + AttributeList attributes); + /// Move constructor + Struct(Struct&&); + + ~Struct() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const Struct* Clone(CloneContext* ctx) const override; + + /// The members + const StructMemberList members; + + /// The struct attributes + const AttributeList attributes; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_STRUCT_H_
diff --git a/src/tint/ast/struct_member.cc b/src/tint/ast/struct_member.cc new file mode 100644 index 0000000..2afb10d --- /dev/null +++ b/src/tint/ast/struct_member.cc
@@ -0,0 +1,53 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/struct_member.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::StructMember); + +namespace tint { +namespace ast { + +StructMember::StructMember(ProgramID pid, + const Source& src, + const Symbol& sym, + const ast::Type* ty, + AttributeList attrs) + : Base(pid, src), symbol(sym), type(ty), attributes(std::move(attrs)) { + TINT_ASSERT(AST, type); + TINT_ASSERT(AST, symbol.IsValid()); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, symbol, program_id); + for (auto* attr : attributes) { + TINT_ASSERT(AST, attr); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, attr, program_id); + } +} + +StructMember::StructMember(StructMember&&) = default; + +StructMember::~StructMember() = default; + +const StructMember* StructMember::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto sym = ctx->Clone(symbol); + auto* ty = ctx->Clone(type); + auto attrs = ctx->Clone(attributes); + return ctx->dst->create<StructMember>(src, sym, ty, attrs); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/struct_member.h b/src/tint/ast/struct_member.h new file mode 100644 index 0000000..4be9256 --- /dev/null +++ b/src/tint/ast/struct_member.h
@@ -0,0 +1,70 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_STRUCT_MEMBER_H_ +#define SRC_TINT_AST_STRUCT_MEMBER_H_ + +#include <utility> +#include <vector> + +#include "src/tint/ast/attribute.h" + +namespace tint { +namespace ast { + +// Forward declaration +class Type; + +/// A struct member statement. +class StructMember final : public Castable<StructMember, Node> { + public: + /// Create a new struct member statement + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node for the struct member statement + /// @param sym The struct member symbol + /// @param type The struct member type + /// @param attributes The struct member attributes + StructMember(ProgramID pid, + const Source& src, + const Symbol& sym, + const ast::Type* type, + AttributeList attributes); + /// Move constructor + StructMember(StructMember&&); + + ~StructMember() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const StructMember* Clone(CloneContext* ctx) const override; + + /// The symbol + const Symbol symbol; + + /// The type + const ast::Type* const type; + + /// The attributes + const AttributeList attributes; +}; + +/// A list of struct members +using StructMemberList = std::vector<const StructMember*>; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_STRUCT_MEMBER_H_
diff --git a/src/tint/ast/struct_member_align_attribute.cc b/src/tint/ast/struct_member_align_attribute.cc new file mode 100644 index 0000000..7790800 --- /dev/null +++ b/src/tint/ast/struct_member_align_attribute.cc
@@ -0,0 +1,46 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/struct_member_align_attribute.h" + +#include <string> + +#include "src/tint/clone_context.h" +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::StructMemberAlignAttribute); + +namespace tint { +namespace ast { + +StructMemberAlignAttribute::StructMemberAlignAttribute(ProgramID pid, + const Source& src, + uint32_t a) + : Base(pid, src), align(a) {} + +StructMemberAlignAttribute::~StructMemberAlignAttribute() = default; + +std::string StructMemberAlignAttribute::Name() const { + return "align"; +} + +const StructMemberAlignAttribute* StructMemberAlignAttribute::Clone( + CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + return ctx->dst->create<StructMemberAlignAttribute>(src, align); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/struct_member_align_attribute.h b/src/tint/ast/struct_member_align_attribute.h new file mode 100644 index 0000000..a1b455b --- /dev/null +++ b/src/tint/ast/struct_member_align_attribute.h
@@ -0,0 +1,53 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_AST_STRUCT_MEMBER_ALIGN_ATTRIBUTE_H_ +#define SRC_TINT_AST_STRUCT_MEMBER_ALIGN_ATTRIBUTE_H_ + +#include <stddef.h> +#include <string> + +#include "src/tint/ast/attribute.h" + +namespace tint { +namespace ast { + +/// A struct member align attribute +class StructMemberAlignAttribute final + : public Castable<StructMemberAlignAttribute, Attribute> { + public: + /// constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param align the align value + StructMemberAlignAttribute(ProgramID pid, const Source& src, uint32_t align); + ~StructMemberAlignAttribute() override; + + /// @returns the WGSL name for the attribute + std::string Name() const override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const StructMemberAlignAttribute* Clone(CloneContext* ctx) const override; + + /// The align value + const uint32_t align; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_STRUCT_MEMBER_ALIGN_ATTRIBUTE_H_
diff --git a/src/tint/ast/struct_member_align_attribute_test.cc b/src/tint/ast/struct_member_align_attribute_test.cc new file mode 100644 index 0000000..9dcddd4 --- /dev/null +++ b/src/tint/ast/struct_member_align_attribute_test.cc
@@ -0,0 +1,32 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/struct_member_align_attribute.h" + +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using StructMemberAlignAttributeTest = TestHelper; + +TEST_F(StructMemberAlignAttributeTest, Creation) { + auto* d = create<StructMemberAlignAttribute>(2); + EXPECT_EQ(2u, d->align); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/struct_member_offset_attribute.cc b/src/tint/ast/struct_member_offset_attribute.cc new file mode 100644 index 0000000..a854f8c --- /dev/null +++ b/src/tint/ast/struct_member_offset_attribute.cc
@@ -0,0 +1,45 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/struct_member_offset_attribute.h" + +#include <string> + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::StructMemberOffsetAttribute); + +namespace tint { +namespace ast { + +StructMemberOffsetAttribute::StructMemberOffsetAttribute(ProgramID pid, + const Source& src, + uint32_t o) + : Base(pid, src), offset(o) {} + +StructMemberOffsetAttribute::~StructMemberOffsetAttribute() = default; + +std::string StructMemberOffsetAttribute::Name() const { + return "offset"; +} + +const StructMemberOffsetAttribute* StructMemberOffsetAttribute::Clone( + CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + return ctx->dst->create<StructMemberOffsetAttribute>(src, offset); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/struct_member_offset_attribute.h b/src/tint/ast/struct_member_offset_attribute.h new file mode 100644 index 0000000..63db959 --- /dev/null +++ b/src/tint/ast/struct_member_offset_attribute.h
@@ -0,0 +1,63 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_STRUCT_MEMBER_OFFSET_ATTRIBUTE_H_ +#define SRC_TINT_AST_STRUCT_MEMBER_OFFSET_ATTRIBUTE_H_ + +#include <string> + +#include "src/tint/ast/attribute.h" + +namespace tint { +namespace ast { + +/// A struct member offset attribute +/// @note The WGSL spec removed the `@offset(n)` attribute for `@size(n)` +/// and `@align(n)` in https://github.com/gpuweb/gpuweb/pull/1447. However +/// this attribute is kept because the SPIR-V reader has to deal with absolute +/// offsets, and transforming these to size / align is complex and can be done +/// in a number of ways. The Resolver is responsible for consuming the size and +/// align attributes and transforming these into absolute offsets. It is +/// trivial for the Resolver to handle `@offset(n)` or `@size(n)` / +/// `@align(n)` attributes, so this is what we do, keeping all the layout +/// logic in one place. +class StructMemberOffsetAttribute final + : public Castable<StructMemberOffsetAttribute, Attribute> { + public: + /// constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param offset the offset value + StructMemberOffsetAttribute(ProgramID pid, + const Source& src, + uint32_t offset); + ~StructMemberOffsetAttribute() override; + + /// @returns the WGSL name for the attribute + std::string Name() const override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const StructMemberOffsetAttribute* Clone(CloneContext* ctx) const override; + + /// The offset value + const uint32_t offset; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_STRUCT_MEMBER_OFFSET_ATTRIBUTE_H_
diff --git a/src/tint/ast/struct_member_offset_attribute_test.cc b/src/tint/ast/struct_member_offset_attribute_test.cc new file mode 100644 index 0000000..022821a --- /dev/null +++ b/src/tint/ast/struct_member_offset_attribute_test.cc
@@ -0,0 +1,30 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using StructMemberOffsetAttributeTest = TestHelper; + +TEST_F(StructMemberOffsetAttributeTest, Creation) { + auto* d = create<StructMemberOffsetAttribute>(2); + EXPECT_EQ(2u, d->offset); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/struct_member_size_attribute.cc b/src/tint/ast/struct_member_size_attribute.cc new file mode 100644 index 0000000..d76820a --- /dev/null +++ b/src/tint/ast/struct_member_size_attribute.cc
@@ -0,0 +1,46 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/struct_member_size_attribute.h" + +#include <string> + +#include "src/tint/clone_context.h" +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::StructMemberSizeAttribute); + +namespace tint { +namespace ast { + +StructMemberSizeAttribute::StructMemberSizeAttribute(ProgramID pid, + const Source& src, + uint32_t sz) + : Base(pid, src), size(sz) {} + +StructMemberSizeAttribute::~StructMemberSizeAttribute() = default; + +std::string StructMemberSizeAttribute::Name() const { + return "size"; +} + +const StructMemberSizeAttribute* StructMemberSizeAttribute::Clone( + CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + return ctx->dst->create<StructMemberSizeAttribute>(src, size); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/struct_member_size_attribute.h b/src/tint/ast/struct_member_size_attribute.h new file mode 100644 index 0000000..c2a6e50 --- /dev/null +++ b/src/tint/ast/struct_member_size_attribute.h
@@ -0,0 +1,53 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_AST_STRUCT_MEMBER_SIZE_ATTRIBUTE_H_ +#define SRC_TINT_AST_STRUCT_MEMBER_SIZE_ATTRIBUTE_H_ + +#include <stddef.h> +#include <string> + +#include "src/tint/ast/attribute.h" + +namespace tint { +namespace ast { + +/// A struct member size attribute +class StructMemberSizeAttribute final + : public Castable<StructMemberSizeAttribute, Attribute> { + public: + /// constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param size the size value + StructMemberSizeAttribute(ProgramID pid, const Source& src, uint32_t size); + ~StructMemberSizeAttribute() override; + + /// @returns the WGSL name for the attribute + std::string Name() const override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const StructMemberSizeAttribute* Clone(CloneContext* ctx) const override; + + /// The size value + const uint32_t size; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_STRUCT_MEMBER_SIZE_ATTRIBUTE_H_
diff --git a/src/tint/ast/struct_member_size_attribute_test.cc b/src/tint/ast/struct_member_size_attribute_test.cc new file mode 100644 index 0000000..346535d --- /dev/null +++ b/src/tint/ast/struct_member_size_attribute_test.cc
@@ -0,0 +1,32 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/struct_member_size_attribute.h" + +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using StructMemberSizeAttributeTest = TestHelper; + +TEST_F(StructMemberSizeAttributeTest, Creation) { + auto* d = create<StructMemberSizeAttribute>(2); + EXPECT_EQ(2u, d->size); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/struct_member_test.cc b/src/tint/ast/struct_member_test.cc new file mode 100644 index 0000000..c675a47 --- /dev/null +++ b/src/tint/ast/struct_member_test.cc
@@ -0,0 +1,98 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "gtest/gtest-spi.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using StructMemberTest = TestHelper; + +TEST_F(StructMemberTest, Creation) { + auto* st = Member("a", ty.i32(), {MemberSize(4)}); + EXPECT_EQ(st->symbol, Symbol(1, ID())); + EXPECT_TRUE(st->type->Is<ast::I32>()); + EXPECT_EQ(st->attributes.size(), 1u); + EXPECT_TRUE(st->attributes[0]->Is<StructMemberSizeAttribute>()); + EXPECT_EQ(st->source.range.begin.line, 0u); + EXPECT_EQ(st->source.range.begin.column, 0u); + EXPECT_EQ(st->source.range.end.line, 0u); + EXPECT_EQ(st->source.range.end.column, 0u); +} + +TEST_F(StructMemberTest, CreationWithSource) { + auto* st = Member( + Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 8}}}, + "a", ty.i32()); + EXPECT_EQ(st->symbol, Symbol(1, ID())); + EXPECT_TRUE(st->type->Is<ast::I32>()); + EXPECT_EQ(st->attributes.size(), 0u); + EXPECT_EQ(st->source.range.begin.line, 27u); + EXPECT_EQ(st->source.range.begin.column, 4u); + EXPECT_EQ(st->source.range.end.line, 27u); + EXPECT_EQ(st->source.range.end.column, 8u); +} + +TEST_F(StructMemberTest, Assert_Empty_Symbol) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.Member("", b.ty.i32()); + }, + "internal compiler error"); +} + +TEST_F(StructMemberTest, Assert_Null_Type) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.Member("a", nullptr); + }, + "internal compiler error"); +} + +TEST_F(StructMemberTest, Assert_Null_Attribute) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.Member("a", b.ty.i32(), {b.MemberSize(4), nullptr}); + }, + "internal compiler error"); +} + +TEST_F(StructMemberTest, Assert_DifferentProgramID_Symbol) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.Member(b2.Sym("a"), b1.ty.i32(), {b1.MemberSize(4)}); + }, + "internal compiler error"); +} + +TEST_F(StructMemberTest, Assert_DifferentProgramID_Attribute) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.Member("a", b1.ty.i32(), {b2.MemberSize(4)}); + }, + "internal compiler error"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/struct_test.cc b/src/tint/ast/struct_test.cc new file mode 100644 index 0000000..3b02767 --- /dev/null +++ b/src/tint/ast/struct_test.cc
@@ -0,0 +1,133 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/struct.h" +#include "gtest/gtest-spi.h" +#include "src/tint/ast/alias.h" +#include "src/tint/ast/array.h" +#include "src/tint/ast/bool.h" +#include "src/tint/ast/f32.h" +#include "src/tint/ast/i32.h" +#include "src/tint/ast/matrix.h" +#include "src/tint/ast/pointer.h" +#include "src/tint/ast/sampler.h" +#include "src/tint/ast/test_helper.h" +#include "src/tint/ast/texture.h" +#include "src/tint/ast/u32.h" +#include "src/tint/ast/vector.h" +#include "src/tint/transform/add_spirv_block_attribute.h" + +namespace tint { +namespace ast { +namespace { + +using AstStructTest = TestHelper; +using SpirvBlockAttribute = + transform::AddSpirvBlockAttribute::SpirvBlockAttribute; + +TEST_F(AstStructTest, Creation) { + auto name = Sym("s"); + auto* s = create<Struct>(name, StructMemberList{Member("a", ty.i32())}, + AttributeList{}); + EXPECT_EQ(s->name, name); + EXPECT_EQ(s->members.size(), 1u); + EXPECT_TRUE(s->attributes.empty()); + EXPECT_EQ(s->source.range.begin.line, 0u); + EXPECT_EQ(s->source.range.begin.column, 0u); + EXPECT_EQ(s->source.range.end.line, 0u); + EXPECT_EQ(s->source.range.end.column, 0u); +} + +TEST_F(AstStructTest, Creation_WithAttributes) { + auto name = Sym("s"); + AttributeList attrs; + attrs.push_back(ASTNodes().Create<SpirvBlockAttribute>(ID())); + + auto* s = + create<Struct>(name, StructMemberList{Member("a", ty.i32())}, attrs); + EXPECT_EQ(s->name, name); + EXPECT_EQ(s->members.size(), 1u); + ASSERT_EQ(s->attributes.size(), 1u); + EXPECT_TRUE(s->attributes[0]->Is<SpirvBlockAttribute>()); + EXPECT_EQ(s->source.range.begin.line, 0u); + EXPECT_EQ(s->source.range.begin.column, 0u); + EXPECT_EQ(s->source.range.end.line, 0u); + EXPECT_EQ(s->source.range.end.column, 0u); +} + +TEST_F(AstStructTest, CreationWithSourceAndAttributes) { + auto name = Sym("s"); + auto* s = create<Struct>( + Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 8}}}, + name, StructMemberList{Member("a", ty.i32())}, + AttributeList{ASTNodes().Create<SpirvBlockAttribute>(ID())}); + EXPECT_EQ(s->name, name); + EXPECT_EQ(s->members.size(), 1u); + ASSERT_EQ(s->attributes.size(), 1u); + EXPECT_TRUE(s->attributes[0]->Is<SpirvBlockAttribute>()); + EXPECT_EQ(s->source.range.begin.line, 27u); + EXPECT_EQ(s->source.range.begin.column, 4u); + EXPECT_EQ(s->source.range.end.line, 27u); + EXPECT_EQ(s->source.range.end.column, 8u); +} + +TEST_F(AstStructTest, Assert_Null_StructMember) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<Struct>(b.Sym("S"), + StructMemberList{b.Member("a", b.ty.i32()), nullptr}, + AttributeList{}); + }, + "internal compiler error"); +} + +TEST_F(AstStructTest, Assert_Null_Attribute) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<Struct>(b.Sym("S"), + StructMemberList{b.Member("a", b.ty.i32())}, + AttributeList{nullptr}); + }, + "internal compiler error"); +} + +TEST_F(AstStructTest, Assert_DifferentProgramID_StructMember) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<Struct>(b1.Sym("S"), + StructMemberList{b2.Member("a", b2.ty.i32())}, + AttributeList{}); + }, + "internal compiler error"); +} + +TEST_F(AstStructTest, Assert_DifferentProgramID_Attribute) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<Struct>( + b1.Sym("S"), StructMemberList{b1.Member("a", b1.ty.i32())}, + AttributeList{b2.ASTNodes().Create<SpirvBlockAttribute>(b2.ID())}); + }, + "internal compiler error"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/switch_statement.cc b/src/tint/ast/switch_statement.cc new file mode 100644 index 0000000..02cbb61 --- /dev/null +++ b/src/tint/ast/switch_statement.cc
@@ -0,0 +1,50 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/switch_statement.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::SwitchStatement); + +namespace tint { +namespace ast { + +SwitchStatement::SwitchStatement(ProgramID pid, + const Source& src, + const Expression* cond, + CaseStatementList b) + : Base(pid, src), condition(cond), body(b) { + TINT_ASSERT(AST, condition); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, condition, program_id); + for (auto* stmt : body) { + TINT_ASSERT(AST, stmt); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, stmt, program_id); + } +} + +SwitchStatement::SwitchStatement(SwitchStatement&&) = default; + +SwitchStatement::~SwitchStatement() = default; + +const SwitchStatement* SwitchStatement::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* cond = ctx->Clone(condition); + auto b = ctx->Clone(body); + return ctx->dst->create<SwitchStatement>(src, cond, b); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/switch_statement.h b/src/tint/ast/switch_statement.h new file mode 100644 index 0000000..aa0cc77 --- /dev/null +++ b/src/tint/ast/switch_statement.h
@@ -0,0 +1,60 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_SWITCH_STATEMENT_H_ +#define SRC_TINT_AST_SWITCH_STATEMENT_H_ + +#include "src/tint/ast/case_statement.h" +#include "src/tint/ast/expression.h" + +namespace tint { +namespace ast { + +/// A switch statement +class SwitchStatement final : public Castable<SwitchStatement, Statement> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param condition the switch condition + /// @param body the switch body + SwitchStatement(ProgramID pid, + const Source& src, + const Expression* condition, + CaseStatementList body); + /// Move constructor + SwitchStatement(SwitchStatement&&); + ~SwitchStatement() override; + + /// @returns true if this is a default statement + bool IsDefault() const { return condition == nullptr; } + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const SwitchStatement* Clone(CloneContext* ctx) const override; + + /// The switch condition or nullptr if none set + const Expression* const condition; + + /// The Switch body + const CaseStatementList body; + SwitchStatement(const SwitchStatement&) = delete; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_SWITCH_STATEMENT_H_
diff --git a/src/tint/ast/switch_statement_test.cc b/src/tint/ast/switch_statement_test.cc new file mode 100644 index 0000000..ecbf68c --- /dev/null +++ b/src/tint/ast/switch_statement_test.cc
@@ -0,0 +1,118 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/switch_statement.h" + +#include "gtest/gtest-spi.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using SwitchStatementTest = TestHelper; + +TEST_F(SwitchStatementTest, Creation) { + CaseSelectorList lit; + lit.push_back(create<SintLiteralExpression>(1)); + + auto* ident = Expr("ident"); + CaseStatementList body; + auto* case_stmt = create<CaseStatement>(lit, Block()); + body.push_back(case_stmt); + + auto* stmt = create<SwitchStatement>(ident, body); + EXPECT_EQ(stmt->condition, ident); + ASSERT_EQ(stmt->body.size(), 1u); + EXPECT_EQ(stmt->body[0], case_stmt); +} + +TEST_F(SwitchStatementTest, Creation_WithSource) { + auto* ident = Expr("ident"); + + auto* stmt = create<SwitchStatement>(Source{Source::Location{20, 2}}, ident, + CaseStatementList()); + auto src = stmt->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(SwitchStatementTest, IsSwitch) { + CaseSelectorList lit; + lit.push_back(create<SintLiteralExpression>(2)); + + auto* ident = Expr("ident"); + CaseStatementList body; + body.push_back(create<CaseStatement>(lit, Block())); + + auto* stmt = create<SwitchStatement>(ident, body); + EXPECT_TRUE(stmt->Is<SwitchStatement>()); +} + +TEST_F(SwitchStatementTest, Assert_Null_Condition) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + CaseStatementList cases; + cases.push_back( + b.create<CaseStatement>(CaseSelectorList{b.Expr(1)}, b.Block())); + b.create<SwitchStatement>(nullptr, cases); + }, + "internal compiler error"); +} + +TEST_F(SwitchStatementTest, Assert_Null_CaseStatement) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<SwitchStatement>(b.Expr(true), CaseStatementList{nullptr}); + }, + "internal compiler error"); +} + +TEST_F(SwitchStatementTest, Assert_DifferentProgramID_Condition) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<SwitchStatement>(b2.Expr(true), CaseStatementList{ + b1.create<CaseStatement>( + CaseSelectorList{ + b1.Expr(1), + }, + b1.Block()), + }); + }, + "internal compiler error"); +} + +TEST_F(SwitchStatementTest, Assert_DifferentProgramID_CaseStatement) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<SwitchStatement>(b1.Expr(true), CaseStatementList{ + b2.create<CaseStatement>( + CaseSelectorList{ + b2.Expr(1), + }, + b2.Block()), + }); + }, + "internal compiler error"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/test_helper.h b/src/tint/ast/test_helper.h new file mode 100644 index 0000000..2c3be98 --- /dev/null +++ b/src/tint/ast/test_helper.h
@@ -0,0 +1,38 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_TEST_HELPER_H_ +#define SRC_TINT_AST_TEST_HELPER_H_ + +#include "gtest/gtest.h" +#include "src/tint/program_builder.h" + +namespace tint { +namespace ast { + +/// Helper base class for testing +template <typename BASE> +class TestHelperBase : public BASE, public ProgramBuilder {}; + +/// Helper class for testing that derives from testing::Test. +using TestHelper = TestHelperBase<testing::Test>; + +/// Helper class for testing that derives from `T`. +template <typename T> +using TestParamHelper = TestHelperBase<testing::TestWithParam<T>>; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_TEST_HELPER_H_
diff --git a/src/tint/ast/texture.cc b/src/tint/ast/texture.cc new file mode 100644 index 0000000..38d16e4 --- /dev/null +++ b/src/tint/ast/texture.cc
@@ -0,0 +1,89 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/texture.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::Texture); + +namespace tint { +namespace ast { + +std::ostream& operator<<(std::ostream& out, TextureDimension dim) { + switch (dim) { + case TextureDimension::kNone: + out << "None"; + break; + case TextureDimension::k1d: + out << "1d"; + break; + case TextureDimension::k2d: + out << "2d"; + break; + case TextureDimension::k2dArray: + out << "2d_array"; + break; + case TextureDimension::k3d: + out << "3d"; + break; + case TextureDimension::kCube: + out << "cube"; + break; + case TextureDimension::kCubeArray: + out << "cube_array"; + break; + } + return out; +} + +bool IsTextureArray(TextureDimension dim) { + switch (dim) { + case TextureDimension::k2dArray: + case TextureDimension::kCubeArray: + return true; + case TextureDimension::k2d: + case TextureDimension::kNone: + case TextureDimension::k1d: + case TextureDimension::k3d: + case TextureDimension::kCube: + return false; + } + return false; +} + +int NumCoordinateAxes(TextureDimension dim) { + switch (dim) { + case TextureDimension::kNone: + return 0; + case TextureDimension::k1d: + return 1; + case TextureDimension::k2d: + case TextureDimension::k2dArray: + return 2; + case TextureDimension::k3d: + case TextureDimension::kCube: + case TextureDimension::kCubeArray: + return 3; + } + return 0; +} + +Texture::Texture(ProgramID pid, const Source& src, TextureDimension d) + : Base(pid, src), dim(d) {} + +Texture::Texture(Texture&&) = default; + +Texture::~Texture() = default; + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/texture.h b/src/tint/ast/texture.h new file mode 100644 index 0000000..41e893a --- /dev/null +++ b/src/tint/ast/texture.h
@@ -0,0 +1,83 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_TEXTURE_H_ +#define SRC_TINT_AST_TEXTURE_H_ + +#include "src/tint/ast/type.h" + +namespace tint { +namespace ast { + +/// The dimensionality of the texture +enum class TextureDimension { + /// Invalid texture + kNone = -1, + /// 1 dimensional texture + k1d, + /// 2 dimensional texture + k2d, + /// 2 dimensional array texture + k2dArray, + /// 3 dimensional texture + k3d, + /// cube texture + kCube, + /// cube array texture + kCubeArray, +}; + +/// @param out the std::ostream to write to +/// @param dim the TextureDimension +/// @return the std::ostream so calls can be chained +std::ostream& operator<<(std::ostream& out, TextureDimension dim); + +/// @param dim the TextureDimension to query +/// @return true if the given TextureDimension is an array texture +bool IsTextureArray(TextureDimension dim); + +/// Returns the number of axes in the coordinate used for accessing +/// the texture, where an access is one of: sampling, fetching, load, +/// or store. +/// None -> 0 +/// 1D -> 1 +/// 2D, 2DArray -> 2 +/// 3D, Cube, CubeArray -> 3 +/// Note: To sample a cube texture, the coordinate has 3 dimensions, +/// but textureDimensions on a cube or cube array returns a 2-element +/// size, representing the (x,y) size of each cube face, in texels. +/// @param dim the TextureDimension to query +/// @return number of dimensions in a coordinate for the dimensionality +int NumCoordinateAxes(TextureDimension dim); + +/// A texture type. +class Texture : public Castable<Texture, Type> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param dim the dimensionality of the texture + Texture(ProgramID pid, const Source& src, TextureDimension dim); + /// Move constructor + Texture(Texture&&); + ~Texture() override; + + /// The texture dimension + const TextureDimension dim; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_TEXTURE_H_
diff --git a/src/tint/ast/texture_test.cc b/src/tint/ast/texture_test.cc new file mode 100644 index 0000000..017d56e --- /dev/null +++ b/src/tint/ast/texture_test.cc
@@ -0,0 +1,58 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/texture.h" + +#include "src/tint/ast/alias.h" +#include "src/tint/ast/array.h" +#include "src/tint/ast/bool.h" +#include "src/tint/ast/f32.h" +#include "src/tint/ast/i32.h" +#include "src/tint/ast/matrix.h" +#include "src/tint/ast/pointer.h" +#include "src/tint/ast/sampler.h" +#include "src/tint/ast/struct.h" +#include "src/tint/ast/test_helper.h" +#include "src/tint/ast/u32.h" +#include "src/tint/ast/vector.h" + +namespace tint { +namespace ast { +namespace { + +using AstTextureTypeTest = TestHelper; + +TEST_F(AstTextureTypeTest, IsTextureArray) { + EXPECT_EQ(false, IsTextureArray(TextureDimension::kNone)); + EXPECT_EQ(false, IsTextureArray(TextureDimension::k1d)); + EXPECT_EQ(false, IsTextureArray(TextureDimension::k2d)); + EXPECT_EQ(true, IsTextureArray(TextureDimension::k2dArray)); + EXPECT_EQ(false, IsTextureArray(TextureDimension::k3d)); + EXPECT_EQ(false, IsTextureArray(TextureDimension::kCube)); + EXPECT_EQ(true, IsTextureArray(TextureDimension::kCubeArray)); +} + +TEST_F(AstTextureTypeTest, NumCoordinateAxes) { + EXPECT_EQ(0, NumCoordinateAxes(TextureDimension::kNone)); + EXPECT_EQ(1, NumCoordinateAxes(TextureDimension::k1d)); + EXPECT_EQ(2, NumCoordinateAxes(TextureDimension::k2d)); + EXPECT_EQ(2, NumCoordinateAxes(TextureDimension::k2dArray)); + EXPECT_EQ(3, NumCoordinateAxes(TextureDimension::k3d)); + EXPECT_EQ(3, NumCoordinateAxes(TextureDimension::kCube)); + EXPECT_EQ(3, NumCoordinateAxes(TextureDimension::kCubeArray)); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/traverse_expressions.h b/src/tint/ast/traverse_expressions.h new file mode 100644 index 0000000..084a201 --- /dev/null +++ b/src/tint/ast/traverse_expressions.h
@@ -0,0 +1,154 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_AST_TRAVERSE_EXPRESSIONS_H_ +#define SRC_TINT_AST_TRAVERSE_EXPRESSIONS_H_ + +#include <vector> + +#include "src/tint/ast/binary_expression.h" +#include "src/tint/ast/bitcast_expression.h" +#include "src/tint/ast/call_expression.h" +#include "src/tint/ast/index_accessor_expression.h" +#include "src/tint/ast/literal_expression.h" +#include "src/tint/ast/member_accessor_expression.h" +#include "src/tint/ast/phony_expression.h" +#include "src/tint/ast/unary_op_expression.h" +#include "src/tint/utils/reverse.h" + +namespace tint { +namespace ast { + +/// The action to perform after calling the TraverseExpressions() callback +/// function. +enum class TraverseAction { + /// Stop traversal immediately. + Stop, + /// Descend into this expression. + Descend, + /// Do not descend into this expression. + Skip, +}; + +/// The order TraverseExpressions() will traverse expressions +enum class TraverseOrder { + /// Expressions will be traversed from left to right + LeftToRight, + /// Expressions will be traversed from right to left + RightToLeft, +}; + +/// TraverseExpressions performs a depth-first traversal of the expression nodes +/// from `root`, calling `callback` for each of the visited expressions that +/// match the predicate parameter type, in pre-ordering (root first). +/// @param root the root expression node +/// @param diags the diagnostics used for error messages +/// @param callback the callback function. Must be of the signature: +/// `TraverseAction(const T*)` where T is an ast::Expression type. +/// @return true on success, false on error +template <TraverseOrder ORDER = TraverseOrder::LeftToRight, typename CALLBACK> +bool TraverseExpressions(const ast::Expression* root, + diag::List& diags, + CALLBACK&& callback) { + using EXPR_TYPE = std::remove_pointer_t<traits::ParameterType<CALLBACK, 0>>; + std::vector<const ast::Expression*> to_visit{root}; + + auto push_pair = [&](const ast::Expression* left, + const ast::Expression* right) { + if (ORDER == TraverseOrder::LeftToRight) { + to_visit.push_back(right); + to_visit.push_back(left); + } else { + to_visit.push_back(left); + to_visit.push_back(right); + } + }; + auto push_list = [&](const std::vector<const ast::Expression*>& exprs) { + if (ORDER == TraverseOrder::LeftToRight) { + for (auto* expr : utils::Reverse(exprs)) { + to_visit.push_back(expr); + } + } else { + for (auto* expr : exprs) { + to_visit.push_back(expr); + } + } + }; + + while (!to_visit.empty()) { + auto* expr = to_visit.back(); + to_visit.pop_back(); + + if (auto* filtered = expr->As<EXPR_TYPE>()) { + switch (callback(filtered)) { + case TraverseAction::Stop: + return true; + case TraverseAction::Skip: + continue; + case TraverseAction::Descend: + break; + } + } + + bool ok = Switch( + expr, + [&](const IndexAccessorExpression* idx) { + push_pair(idx->object, idx->index); + return true; + }, + [&](const BinaryExpression* bin_op) { + push_pair(bin_op->lhs, bin_op->rhs); + return true; + }, + [&](const BitcastExpression* bitcast) { + to_visit.push_back(bitcast->expr); + return true; + }, + [&](const CallExpression* call) { + // TODO(crbug.com/tint/1257): Resolver breaks if we actually include + // the function name in the traversal. to_visit.push_back(call->func); + push_list(call->args); + return true; + }, + [&](const MemberAccessorExpression* member) { + // TODO(crbug.com/tint/1257): Resolver breaks if we actually include + // the member name in the traversal. push_pair(member->structure, + // member->member); + to_visit.push_back(member->structure); + return true; + }, + [&](const UnaryOpExpression* unary) { + to_visit.push_back(unary->expr); + return true; + }, + [&](Default) { + if (expr->IsAnyOf<LiteralExpression, IdentifierExpression, + PhonyExpression>()) { + return true; // Leaf expression + } + TINT_ICE(AST, diags) + << "unhandled expression type: " << expr->TypeInfo().name; + return false; + }); + if (!ok) { + return false; + } + } + return true; +} + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_TRAVERSE_EXPRESSIONS_H_
diff --git a/src/tint/ast/traverse_expressions_test.cc b/src/tint/ast/traverse_expressions_test.cc new file mode 100644 index 0000000..ae839ba --- /dev/null +++ b/src/tint/ast/traverse_expressions_test.cc
@@ -0,0 +1,237 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/traverse_expressions.h" +#include "gmock/gmock.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using ::testing::ElementsAre; + +using TraverseExpressionsTest = TestHelper; + +TEST_F(TraverseExpressionsTest, DescendIndexAccessor) { + std::vector<const ast::Expression*> e = {Expr(1), Expr(1), Expr(1), Expr(1)}; + std::vector<const ast::Expression*> i = {IndexAccessor(e[0], e[1]), + IndexAccessor(e[2], e[3])}; + auto* root = IndexAccessor(i[0], i[1]); + { + std::vector<const ast::Expression*> l2r; + TraverseExpressions<TraverseOrder::LeftToRight>( + root, Diagnostics(), [&](const ast::Expression* expr) { + l2r.push_back(expr); + return ast::TraverseAction::Descend; + }); + EXPECT_THAT(l2r, ElementsAre(root, i[0], e[0], e[1], i[1], e[2], e[3])); + } + { + std::vector<const ast::Expression*> r2l; + TraverseExpressions<TraverseOrder::RightToLeft>( + root, Diagnostics(), [&](const ast::Expression* expr) { + r2l.push_back(expr); + return ast::TraverseAction::Descend; + }); + EXPECT_THAT(r2l, ElementsAre(root, i[1], e[3], e[2], i[0], e[1], e[0])); + } +} + +TEST_F(TraverseExpressionsTest, DescendBinaryExpression) { + std::vector<const ast::Expression*> e = {Expr(1), Expr(1), Expr(1), Expr(1)}; + std::vector<const ast::Expression*> i = {Add(e[0], e[1]), Sub(e[2], e[3])}; + auto* root = Mul(i[0], i[1]); + { + std::vector<const ast::Expression*> l2r; + TraverseExpressions<TraverseOrder::LeftToRight>( + root, Diagnostics(), [&](const ast::Expression* expr) { + l2r.push_back(expr); + return ast::TraverseAction::Descend; + }); + EXPECT_THAT(l2r, ElementsAre(root, i[0], e[0], e[1], i[1], e[2], e[3])); + } + { + std::vector<const ast::Expression*> r2l; + TraverseExpressions<TraverseOrder::RightToLeft>( + root, Diagnostics(), [&](const ast::Expression* expr) { + r2l.push_back(expr); + return ast::TraverseAction::Descend; + }); + EXPECT_THAT(r2l, ElementsAre(root, i[1], e[3], e[2], i[0], e[1], e[0])); + } +} + +TEST_F(TraverseExpressionsTest, DescendBitcastExpression) { + auto* e = Expr(1); + auto* b0 = Bitcast<i32>(e); + auto* b1 = Bitcast<i32>(b0); + auto* b2 = Bitcast<i32>(b1); + auto* root = Bitcast<i32>(b2); + { + std::vector<const ast::Expression*> l2r; + TraverseExpressions<TraverseOrder::LeftToRight>( + root, Diagnostics(), [&](const ast::Expression* expr) { + l2r.push_back(expr); + return ast::TraverseAction::Descend; + }); + EXPECT_THAT(l2r, ElementsAre(root, b2, b1, b0, e)); + } + { + std::vector<const ast::Expression*> r2l; + TraverseExpressions<TraverseOrder::RightToLeft>( + root, Diagnostics(), [&](const ast::Expression* expr) { + r2l.push_back(expr); + return ast::TraverseAction::Descend; + }); + EXPECT_THAT(r2l, ElementsAre(root, b2, b1, b0, e)); + } +} + +TEST_F(TraverseExpressionsTest, DescendCallExpression) { + std::vector<const ast::Expression*> e = {Expr(1), Expr(1), Expr(1), Expr(1)}; + std::vector<const ast::Expression*> c = {Call("a", e[0], e[1]), + Call("b", e[2], e[3])}; + auto* root = Call("c", c[0], c[1]); + { + std::vector<const ast::Expression*> l2r; + TraverseExpressions<TraverseOrder::LeftToRight>( + root, Diagnostics(), [&](const ast::Expression* expr) { + l2r.push_back(expr); + return ast::TraverseAction::Descend; + }); + EXPECT_THAT(l2r, ElementsAre(root, c[0], e[0], e[1], c[1], e[2], e[3])); + } + { + std::vector<const ast::Expression*> r2l; + TraverseExpressions<TraverseOrder::RightToLeft>( + root, Diagnostics(), [&](const ast::Expression* expr) { + r2l.push_back(expr); + return ast::TraverseAction::Descend; + }); + EXPECT_THAT(r2l, ElementsAre(root, c[1], e[3], e[2], c[0], e[1], e[0])); + } +} + +// TODO(crbug.com/tint/1257): Test ignores member accessor 'member' field. +// Replace with the test below when fixed. +TEST_F(TraverseExpressionsTest, DescendMemberIndexExpression) { + auto* e = Expr(1); + auto* m = MemberAccessor(e, Expr("a")); + auto* root = MemberAccessor(m, Expr("b")); + { + std::vector<const ast::Expression*> l2r; + TraverseExpressions<TraverseOrder::LeftToRight>( + root, Diagnostics(), [&](const ast::Expression* expr) { + l2r.push_back(expr); + return ast::TraverseAction::Descend; + }); + EXPECT_THAT(l2r, ElementsAre(root, m, e)); + } + { + std::vector<const ast::Expression*> r2l; + TraverseExpressions<TraverseOrder::RightToLeft>( + root, Diagnostics(), [&](const ast::Expression* expr) { + r2l.push_back(expr); + return ast::TraverseAction::Descend; + }); + EXPECT_THAT(r2l, ElementsAre(root, m, e)); + } +} + +// TODO(crbug.com/tint/1257): The correct test for DescendMemberIndexExpression. +TEST_F(TraverseExpressionsTest, DISABLED_DescendMemberIndexExpression) { + auto* e = Expr(1); + std::vector<const ast::IdentifierExpression*> i = {Expr("a"), Expr("b")}; + auto* m = MemberAccessor(e, i[0]); + auto* root = MemberAccessor(m, i[1]); + { + std::vector<const ast::Expression*> l2r; + TraverseExpressions<TraverseOrder::LeftToRight>( + root, Diagnostics(), [&](const ast::Expression* expr) { + l2r.push_back(expr); + return ast::TraverseAction::Descend; + }); + EXPECT_THAT(l2r, ElementsAre(root, m, e, i[0], i[1])); + } + { + std::vector<const ast::Expression*> r2l; + TraverseExpressions<TraverseOrder::RightToLeft>( + root, Diagnostics(), [&](const ast::Expression* expr) { + r2l.push_back(expr); + return ast::TraverseAction::Descend; + }); + EXPECT_THAT(r2l, ElementsAre(root, i[1], m, i[0], e)); + } +} + +TEST_F(TraverseExpressionsTest, DescendUnaryExpression) { + auto* e = Expr(1); + auto* u0 = AddressOf(e); + auto* u1 = Deref(u0); + auto* u2 = AddressOf(u1); + auto* root = Deref(u2); + { + std::vector<const ast::Expression*> l2r; + TraverseExpressions<TraverseOrder::LeftToRight>( + root, Diagnostics(), [&](const ast::Expression* expr) { + l2r.push_back(expr); + return ast::TraverseAction::Descend; + }); + EXPECT_THAT(l2r, ElementsAre(root, u2, u1, u0, e)); + } + { + std::vector<const ast::Expression*> r2l; + TraverseExpressions<TraverseOrder::RightToLeft>( + root, Diagnostics(), [&](const ast::Expression* expr) { + r2l.push_back(expr); + return ast::TraverseAction::Descend; + }); + EXPECT_THAT(r2l, ElementsAre(root, u2, u1, u0, e)); + } +} + +TEST_F(TraverseExpressionsTest, Skip) { + std::vector<const ast::Expression*> e = {Expr(1), Expr(1), Expr(1), Expr(1)}; + std::vector<const ast::Expression*> i = {IndexAccessor(e[0], e[1]), + IndexAccessor(e[2], e[3])}; + auto* root = IndexAccessor(i[0], i[1]); + std::vector<const ast::Expression*> order; + TraverseExpressions<TraverseOrder::LeftToRight>( + root, Diagnostics(), [&](const ast::Expression* expr) { + order.push_back(expr); + return expr == i[0] ? ast::TraverseAction::Skip + : ast::TraverseAction::Descend; + }); + EXPECT_THAT(order, ElementsAre(root, i[0], i[1], e[2], e[3])); +} + +TEST_F(TraverseExpressionsTest, Stop) { + std::vector<const ast::Expression*> e = {Expr(1), Expr(1), Expr(1), Expr(1)}; + std::vector<const ast::Expression*> i = {IndexAccessor(e[0], e[1]), + IndexAccessor(e[2], e[3])}; + auto* root = IndexAccessor(i[0], i[1]); + std::vector<const ast::Expression*> order; + TraverseExpressions<TraverseOrder::LeftToRight>( + root, Diagnostics(), [&](const ast::Expression* expr) { + order.push_back(expr); + return expr == i[0] ? ast::TraverseAction::Stop + : ast::TraverseAction::Descend; + }); + EXPECT_THAT(order, ElementsAre(root, i[0])); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/type.h b/src/tint/ast/type.h new file mode 100644 index 0000000..8154988 --- /dev/null +++ b/src/tint/ast/type.h
@@ -0,0 +1,53 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_TYPE_H_ +#define SRC_TINT_AST_TYPE_H_ + +#include <string> + +#include "src/tint/ast/node.h" +#include "src/tint/clone_context.h" + +namespace tint { + +// Forward declarations +class ProgramBuilder; +class SymbolTable; + +namespace ast { + +/// Base class for a type in the system +class Type : public Castable<Type, Node> { + public: + /// Move constructor + Type(Type&&); + ~Type() override; + + /// @param symbols the program's symbol table + /// @returns the name for this type that closely resembles how it would be + /// declared in WGSL. + virtual std::string FriendlyName(const SymbolTable& symbols) const = 0; + + protected: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + Type(ProgramID pid, const Source& src); +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_TYPE_H_
diff --git a/src/tint/ast/type_decl.cc b/src/tint/ast/type_decl.cc new file mode 100644 index 0000000..6d0b301 --- /dev/null +++ b/src/tint/ast/type_decl.cc
@@ -0,0 +1,34 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/type_decl.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::TypeDecl); + +namespace tint { +namespace ast { + +TypeDecl::TypeDecl(ProgramID pid, const Source& src, Symbol n) + : Base(pid, src), name(n) { + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, name, program_id); +} + +TypeDecl::TypeDecl(TypeDecl&&) = default; + +TypeDecl::~TypeDecl() = default; + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/type_decl.h b/src/tint/ast/type_decl.h new file mode 100644 index 0000000..0e290cd --- /dev/null +++ b/src/tint/ast/type_decl.h
@@ -0,0 +1,45 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_AST_TYPE_DECL_H_ +#define SRC_TINT_AST_TYPE_DECL_H_ + +#include <string> + +#include "src/tint/ast/type.h" + +namespace tint { +namespace ast { + +/// The base class for type declarations. +class TypeDecl : public Castable<TypeDecl, Node> { + public: + /// Create a new struct statement + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node for the import statement + /// @param name The name of the structure + TypeDecl(ProgramID pid, const Source& src, Symbol name); + /// Move constructor + TypeDecl(TypeDecl&&); + + ~TypeDecl() override; + + /// The name of the type declaration + const Symbol name; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_TYPE_DECL_H_
diff --git a/src/tint/ast/type_name.cc b/src/tint/ast/type_name.cc new file mode 100644 index 0000000..1f58b9e --- /dev/null +++ b/src/tint/ast/type_name.cc
@@ -0,0 +1,42 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/ast/type_name.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::TypeName); + +namespace tint { +namespace ast { + +TypeName::TypeName(ProgramID pid, const Source& src, Symbol n) + : Base(pid, src), name(n) {} + +TypeName::~TypeName() = default; + +TypeName::TypeName(TypeName&&) = default; + +std::string TypeName::FriendlyName(const SymbolTable& symbols) const { + return symbols.NameFor(name); +} + +const TypeName* TypeName::Clone(CloneContext* ctx) const { + auto src = ctx->Clone(source); + auto n = ctx->Clone(name); + return ctx->dst->create<TypeName>(src, n); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/type_name.h b/src/tint/ast/type_name.h new file mode 100644 index 0000000..9bae5d0 --- /dev/null +++ b/src/tint/ast/type_name.h
@@ -0,0 +1,55 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_AST_TYPE_NAME_H_ +#define SRC_TINT_AST_TYPE_NAME_H_ + +#include <string> + +#include "src/tint/ast/type.h" + +namespace tint { +namespace ast { + +/// A named type (i.e. struct or alias) +class TypeName final : public Castable<TypeName, Type> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param name the type name + TypeName(ProgramID pid, const Source& src, Symbol name); + /// Move constructor + TypeName(TypeName&&); + /// Destructor + ~TypeName() override; + + /// @param symbols the program's symbol table + /// @returns the name for this type that closely resembles how it would be + /// declared in WGSL. + std::string FriendlyName(const SymbolTable& symbols) const override; + + /// Clones this type and all transitive types using the `CloneContext` `ctx`. + /// @param ctx the clone context + /// @return the newly cloned type + const TypeName* Clone(CloneContext* ctx) const override; + + /// The type name + Symbol name; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_TYPE_NAME_H_
diff --git a/src/tint/ast/u32.cc b/src/tint/ast/u32.cc new file mode 100644 index 0000000..892289a --- /dev/null +++ b/src/tint/ast/u32.cc
@@ -0,0 +1,40 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/u32.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::U32); + +namespace tint { +namespace ast { + +U32::U32(ProgramID pid, const Source& src) : Base(pid, src) {} + +U32::~U32() = default; + +U32::U32(U32&&) = default; + +std::string U32::FriendlyName(const SymbolTable&) const { + return "u32"; +} + +const U32* U32::Clone(CloneContext* ctx) const { + auto src = ctx->Clone(source); + return ctx->dst->create<U32>(src); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/u32.h b/src/tint/ast/u32.h new file mode 100644 index 0000000..a31222b --- /dev/null +++ b/src/tint/ast/u32.h
@@ -0,0 +1,50 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_U32_H_ +#define SRC_TINT_AST_U32_H_ + +#include <string> + +#include "src/tint/ast/type.h" + +namespace tint { +namespace ast { + +/// A unsigned int 32 type. +class U32 final : public Castable<U32, Type> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + U32(ProgramID pid, const Source& src); + /// Move constructor + U32(U32&&); + ~U32() override; + + /// @param symbols the program's symbol table + /// @returns the name for this type that closely resembles how it would be + /// declared in WGSL. + std::string FriendlyName(const SymbolTable& symbols) const override; + + /// Clones this type and all transitive types using the `CloneContext` `ctx`. + /// @param ctx the clone context + /// @return the newly cloned type + const U32* Clone(CloneContext* ctx) const override; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_U32_H_
diff --git a/src/tint/ast/u32_test.cc b/src/tint/ast/u32_test.cc new file mode 100644 index 0000000..9e8e5a6 --- /dev/null +++ b/src/tint/ast/u32_test.cc
@@ -0,0 +1,32 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/u32.h" + +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using AstU32Test = TestHelper; + +TEST_F(AstU32Test, FriendlyName) { + auto* u = create<U32>(); + EXPECT_EQ(u->FriendlyName(Symbols()), "u32"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/uint_literal_expression.cc b/src/tint/ast/uint_literal_expression.cc new file mode 100644 index 0000000..d53e3eb --- /dev/null +++ b/src/tint/ast/uint_literal_expression.cc
@@ -0,0 +1,43 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/uint_literal_expression.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::UintLiteralExpression); + +namespace tint { +namespace ast { + +UintLiteralExpression::UintLiteralExpression(ProgramID pid, + const Source& src, + uint32_t val) + : Base(pid, src), value(val) {} + +UintLiteralExpression::~UintLiteralExpression() = default; + +uint32_t UintLiteralExpression::ValueAsU32() const { + return value; +} + +const UintLiteralExpression* UintLiteralExpression::Clone( + CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + return ctx->dst->create<UintLiteralExpression>(src, value); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/uint_literal_expression.h b/src/tint/ast/uint_literal_expression.h new file mode 100644 index 0000000..58fdb96 --- /dev/null +++ b/src/tint/ast/uint_literal_expression.h
@@ -0,0 +1,52 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_UINT_LITERAL_EXPRESSION_H_ +#define SRC_TINT_AST_UINT_LITERAL_EXPRESSION_H_ + +#include <string> + +#include "src/tint/ast/int_literal_expression.h" + +namespace tint { +namespace ast { + +/// A uint literal +class UintLiteralExpression final + : public Castable<UintLiteralExpression, IntLiteralExpression> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param value the uint literals value + UintLiteralExpression(ProgramID pid, const Source& src, uint32_t value); + ~UintLiteralExpression() override; + + /// @returns the literal value as a u32 + uint32_t ValueAsU32() const override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const UintLiteralExpression* Clone(CloneContext* ctx) const override; + + /// The int literal value + const uint32_t value; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_UINT_LITERAL_EXPRESSION_H_
diff --git a/src/tint/ast/uint_literal_expression_test.cc b/src/tint/ast/uint_literal_expression_test.cc new file mode 100644 index 0000000..1732dde --- /dev/null +++ b/src/tint/ast/uint_literal_expression_test.cc
@@ -0,0 +1,31 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using UintLiteralExpressionTest = TestHelper; + +TEST_F(UintLiteralExpressionTest, Value) { + auto* u = create<UintLiteralExpression>(47); + ASSERT_TRUE(u->Is<UintLiteralExpression>()); + EXPECT_EQ(u->value, 47u); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/unary_op.cc b/src/tint/ast/unary_op.cc new file mode 100644 index 0000000..4b363f5 --- /dev/null +++ b/src/tint/ast/unary_op.cc
@@ -0,0 +1,47 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/unary_op.h" + +namespace tint { +namespace ast { + +std::ostream& operator<<(std::ostream& out, UnaryOp mod) { + switch (mod) { + case UnaryOp::kAddressOf: { + out << "address-of"; + break; + } + case UnaryOp::kComplement: { + out << "complement"; + break; + } + case UnaryOp::kIndirection: { + out << "indirection"; + break; + } + case UnaryOp::kNegation: { + out << "negation"; + break; + } + case UnaryOp::kNot: { + out << "not"; + break; + } + } + return out; +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/unary_op.h b/src/tint/ast/unary_op.h new file mode 100644 index 0000000..33fdbbf --- /dev/null +++ b/src/tint/ast/unary_op.h
@@ -0,0 +1,40 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_UNARY_OP_H_ +#define SRC_TINT_AST_UNARY_OP_H_ + +#include <ostream> + +namespace tint { +namespace ast { + +/// The unary op +enum class UnaryOp { + kAddressOf, // &EXPR + kComplement, // ~EXPR + kIndirection, // *EXPR + kNegation, // -EXPR + kNot, // !EXPR +}; + +/// @param out the std::ostream to write to +/// @param mod the UnaryOp +/// @return the std::ostream so calls can be chained +std::ostream& operator<<(std::ostream& out, UnaryOp mod); + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_UNARY_OP_H_
diff --git a/src/tint/ast/unary_op_expression.cc b/src/tint/ast/unary_op_expression.cc new file mode 100644 index 0000000..032fa02 --- /dev/null +++ b/src/tint/ast/unary_op_expression.cc
@@ -0,0 +1,45 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/unary_op_expression.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::UnaryOpExpression); + +namespace tint { +namespace ast { + +UnaryOpExpression::UnaryOpExpression(ProgramID pid, + const Source& src, + UnaryOp o, + const Expression* e) + : Base(pid, src), op(o), expr(e) { + TINT_ASSERT(AST, expr); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, expr, program_id); +} + +UnaryOpExpression::UnaryOpExpression(UnaryOpExpression&&) = default; + +UnaryOpExpression::~UnaryOpExpression() = default; + +const UnaryOpExpression* UnaryOpExpression::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* e = ctx->Clone(expr); + return ctx->dst->create<UnaryOpExpression>(src, op, e); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/unary_op_expression.h b/src/tint/ast/unary_op_expression.h new file mode 100644 index 0000000..9b19a77 --- /dev/null +++ b/src/tint/ast/unary_op_expression.h
@@ -0,0 +1,56 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_UNARY_OP_EXPRESSION_H_ +#define SRC_TINT_AST_UNARY_OP_EXPRESSION_H_ + +#include "src/tint/ast/expression.h" +#include "src/tint/ast/unary_op.h" + +namespace tint { +namespace ast { + +/// A unary op expression +class UnaryOpExpression final : public Castable<UnaryOpExpression, Expression> { + public: + /// Constructor + /// @param program_id the identifier of the program that owns this node + /// @param source the unary op expression source + /// @param op the op + /// @param expr the expr + UnaryOpExpression(ProgramID program_id, + const Source& source, + UnaryOp op, + const Expression* expr); + /// Move constructor + UnaryOpExpression(UnaryOpExpression&&); + ~UnaryOpExpression() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const UnaryOpExpression* Clone(CloneContext* ctx) const override; + + /// The op + const UnaryOp op; + + /// The expression + const Expression* const expr; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_UNARY_OP_EXPRESSION_H_
diff --git a/src/tint/ast/unary_op_expression_test.cc b/src/tint/ast/unary_op_expression_test.cc new file mode 100644 index 0000000..3f1b1cf --- /dev/null +++ b/src/tint/ast/unary_op_expression_test.cc
@@ -0,0 +1,70 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/unary_op_expression.h" + +#include "gtest/gtest-spi.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using UnaryOpExpressionTest = TestHelper; + +TEST_F(UnaryOpExpressionTest, Creation) { + auto* ident = Expr("ident"); + + auto* u = create<UnaryOpExpression>(UnaryOp::kNot, ident); + EXPECT_EQ(u->op, UnaryOp::kNot); + EXPECT_EQ(u->expr, ident); +} + +TEST_F(UnaryOpExpressionTest, Creation_WithSource) { + auto* ident = Expr("ident"); + auto* u = create<UnaryOpExpression>(Source{Source::Location{20, 2}}, + UnaryOp::kNot, ident); + auto src = u->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(UnaryOpExpressionTest, IsUnaryOp) { + auto* ident = Expr("ident"); + auto* u = create<UnaryOpExpression>(UnaryOp::kNot, ident); + EXPECT_TRUE(u->Is<UnaryOpExpression>()); +} + +TEST_F(UnaryOpExpressionTest, Assert_Null_Expression) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<UnaryOpExpression>(UnaryOp::kNot, nullptr); + }, + "internal compiler error"); +} + +TEST_F(UnaryOpExpressionTest, Assert_DifferentProgramID_Expression) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<UnaryOpExpression>(UnaryOp::kNot, b2.Expr(true)); + }, + "internal compiler error"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/variable.cc b/src/tint/ast/variable.cc new file mode 100644 index 0000000..62d8dd9 --- /dev/null +++ b/src/tint/ast/variable.cc
@@ -0,0 +1,79 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/variable.h" + +#include "src/tint/program_builder.h" +#include "src/tint/sem/variable.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::Variable); + +namespace tint { +namespace ast { + +Variable::Variable(ProgramID pid, + const Source& src, + const Symbol& sym, + StorageClass dsc, + Access da, + const ast::Type* ty, + bool constant, + bool overridable, + const Expression* ctor, + AttributeList attrs) + : Base(pid, src), + symbol(sym), + type(ty), + is_const(constant), + is_overridable(overridable), + constructor(ctor), + attributes(std::move(attrs)), + declared_storage_class(dsc), + declared_access(da) { + TINT_ASSERT(AST, symbol.IsValid()); + TINT_ASSERT(AST, is_overridable ? is_const : true); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, symbol, program_id); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, constructor, program_id); +} + +Variable::Variable(Variable&&) = default; + +Variable::~Variable() = default; + +VariableBindingPoint Variable::BindingPoint() const { + const GroupAttribute* group = nullptr; + const BindingAttribute* binding = nullptr; + for (auto* attr : attributes) { + if (auto* g = attr->As<GroupAttribute>()) { + group = g; + } else if (auto* b = attr->As<BindingAttribute>()) { + binding = b; + } + } + return VariableBindingPoint{group, binding}; +} + +const Variable* Variable::Clone(CloneContext* ctx) const { + auto src = ctx->Clone(source); + auto sym = ctx->Clone(symbol); + auto* ty = ctx->Clone(type); + auto* ctor = ctx->Clone(constructor); + auto attrs = ctx->Clone(attributes); + return ctx->dst->create<Variable>(src, sym, declared_storage_class, + declared_access, ty, is_const, + is_overridable, ctor, attrs); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/variable.h b/src/tint/ast/variable.h new file mode 100644 index 0000000..88b6ad2 --- /dev/null +++ b/src/tint/ast/variable.h
@@ -0,0 +1,186 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_VARIABLE_H_ +#define SRC_TINT_AST_VARIABLE_H_ + +#include <utility> +#include <vector> + +#include "src/tint/ast/access.h" +#include "src/tint/ast/attribute.h" +#include "src/tint/ast/expression.h" +#include "src/tint/ast/storage_class.h" + +namespace tint { +namespace ast { + +// Forward declarations +class BindingAttribute; +class GroupAttribute; +class LocationAttribute; +class Type; + +/// VariableBindingPoint holds a group and binding attribute. +struct VariableBindingPoint { + /// The `@group` part of the binding point + const GroupAttribute* group = nullptr; + /// The `@binding` part of the binding point + const BindingAttribute* binding = nullptr; + + /// @returns true if the BindingPoint has a valid group and binding + /// attribute. + inline operator bool() const { return group && binding; } +}; + +/// A Variable statement. +/// +/// An instance of this class represents one of four constructs in WGSL: "var" +/// declaration, "let" declaration, "override" declaration, or formal parameter +/// to a function. +/// +/// 1. A "var" declaration is a name for typed storage. Examples: +/// +/// // Declared outside a function, i.e. at module scope, requires +/// // a storage class. +/// var<workgroup> width : i32; // no initializer +/// var<private> height : i32 = 3; // with initializer +/// +/// // A variable declared inside a function doesn't take a storage class, +/// // and maps to SPIR-V Function storage. +/// var computed_depth : i32; +/// var area : i32 = compute_area(width, height); +/// +/// 2. A "let" declaration is a name for a typed value. Examples: +/// +/// let twice_depth : i32 = width + width; // Must have initializer +/// +/// 3. An "override" declaration is a name for a pipeline-overridable constant. +/// Examples: +/// +/// override radius : i32 = 2; // Can be overridden by name. +/// @id(5) override width : i32 = 2; // Can be overridden by ID. +/// override scale : f32; // No default - must be overridden. +/// +/// 4. A formal parameter to a function is a name for a typed value to +/// be passed into a function. Example: +/// +/// fn twice(a: i32) -> i32 { // "a:i32" is the formal parameter +/// return a + a; +/// } +/// +/// From the WGSL draft, about "var":: +/// +/// A variable is a named reference to storage that can contain a value of a +/// particular type. +/// +/// Two types are associated with a variable: its store type (the type of +/// value that may be placed in the referenced storage) and its reference +/// type (the type of the variable itself). If a variable has store type T +/// and storage class S, then its reference type is pointer-to-T-in-S. +/// +/// This class uses the term "type" to refer to: +/// the value type of a "let", +/// the value type of an "override", +/// the value type of the formal parameter, +/// or the store type of the "var". +// +/// Setting is_const: +/// - "var" gets false +/// - "let" gets true +/// - "override" gets true +/// - formal parameter gets true +/// +/// Setting is_overrideable: +/// - "var" gets false +/// - "let" gets false +/// - "override" gets true +/// - formal parameter gets false +/// +/// Setting storage class: +/// - "var" is StorageClass::kNone when using the +/// defaulting syntax for a "var" declared inside a function. +/// - "let" is always StorageClass::kNone. +/// - formal parameter is always StorageClass::kNone. +class Variable final : public Castable<Variable, Node> { + public: + /// Create a variable + /// @param program_id the identifier of the program that owns this node + /// @param source the variable source + /// @param sym the variable symbol + /// @param declared_storage_class the declared storage class + /// @param declared_access the declared access control + /// @param type the declared variable type + /// @param is_const true if the variable is const + /// @param is_overridable true if the variable is pipeline-overridable + /// @param constructor the constructor expression + /// @param attributes the variable attributes + Variable(ProgramID program_id, + const Source& source, + const Symbol& sym, + StorageClass declared_storage_class, + Access declared_access, + const ast::Type* type, + bool is_const, + bool is_overridable, + const Expression* constructor, + AttributeList attributes); + /// Move constructor + Variable(Variable&&); + + ~Variable() override; + + /// @returns the binding point information for the variable + VariableBindingPoint BindingPoint() const; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const Variable* Clone(CloneContext* ctx) const override; + + /// The variable symbol + const Symbol symbol; + + /// The declared variable type. This is null if the type is inferred, e.g.: + /// let f = 1.0; + /// var i = 1; + const ast::Type* const type; + + /// True if this is a constant, false otherwise + const bool is_const; + + /// True if this is a pipeline-overridable constant, false otherwise + const bool is_overridable; + + /// The constructor expression or nullptr if none set + const Expression* const constructor; + + /// The attributes attached to this variable + const AttributeList attributes; + + /// The declared storage class + const StorageClass declared_storage_class; + + /// The declared access control + const Access declared_access; +}; + +/// A list of variables +using VariableList = std::vector<const Variable*>; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_VARIABLE_H_
diff --git a/src/tint/ast/variable_decl_statement.cc b/src/tint/ast/variable_decl_statement.cc new file mode 100644 index 0000000..6adf183 --- /dev/null +++ b/src/tint/ast/variable_decl_statement.cc
@@ -0,0 +1,45 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/variable_decl_statement.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::VariableDeclStatement); + +namespace tint { +namespace ast { + +VariableDeclStatement::VariableDeclStatement(ProgramID pid, + const Source& src, + const Variable* var) + : Base(pid, src), variable(var) { + TINT_ASSERT(AST, variable); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, variable, program_id); +} + +VariableDeclStatement::VariableDeclStatement(VariableDeclStatement&&) = default; + +VariableDeclStatement::~VariableDeclStatement() = default; + +const VariableDeclStatement* VariableDeclStatement::Clone( + CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* var = ctx->Clone(variable); + return ctx->dst->create<VariableDeclStatement>(src, var); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/variable_decl_statement.h b/src/tint/ast/variable_decl_statement.h new file mode 100644 index 0000000..47fb8ab --- /dev/null +++ b/src/tint/ast/variable_decl_statement.h
@@ -0,0 +1,52 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_VARIABLE_DECL_STATEMENT_H_ +#define SRC_TINT_AST_VARIABLE_DECL_STATEMENT_H_ + +#include "src/tint/ast/statement.h" +#include "src/tint/ast/variable.h" + +namespace tint { +namespace ast { + +/// A variable declaration statement +class VariableDeclStatement final + : public Castable<VariableDeclStatement, Statement> { + public: + /// Constructor + /// @param program_id the identifier of the program that owns this node + /// @param source the variable statement source + /// @param variable the variable + VariableDeclStatement(ProgramID program_id, + const Source& source, + const Variable* variable); + /// Move constructor + VariableDeclStatement(VariableDeclStatement&&); + ~VariableDeclStatement() override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const VariableDeclStatement* Clone(CloneContext* ctx) const override; + + /// The variable + const Variable* const variable; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_VARIABLE_DECL_STATEMENT_H_
diff --git a/src/tint/ast/variable_decl_statement_test.cc b/src/tint/ast/variable_decl_statement_test.cc new file mode 100644 index 0000000..9881c66 --- /dev/null +++ b/src/tint/ast/variable_decl_statement_test.cc
@@ -0,0 +1,72 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/variable_decl_statement.h" + +#include "gtest/gtest-spi.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using VariableDeclStatementTest = TestHelper; + +TEST_F(VariableDeclStatementTest, Creation) { + auto* var = Var("a", ty.f32(), StorageClass::kNone); + + auto* stmt = create<VariableDeclStatement>(var); + EXPECT_EQ(stmt->variable, var); +} + +TEST_F(VariableDeclStatementTest, Creation_WithSource) { + auto* var = Var("a", ty.f32(), StorageClass::kNone); + + auto* stmt = + create<VariableDeclStatement>(Source{Source::Location{20, 2}}, var); + auto src = stmt->source; + EXPECT_EQ(src.range.begin.line, 20u); + EXPECT_EQ(src.range.begin.column, 2u); +} + +TEST_F(VariableDeclStatementTest, IsVariableDecl) { + auto* var = Var("a", ty.f32(), StorageClass::kNone); + + auto* stmt = create<VariableDeclStatement>(var); + EXPECT_TRUE(stmt->Is<VariableDeclStatement>()); +} + +TEST_F(VariableDeclStatementTest, Assert_Null_Variable) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.create<VariableDeclStatement>(nullptr); + }, + "internal compiler error"); +} + +TEST_F(VariableDeclStatementTest, Assert_DifferentProgramID_Variable) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.create<VariableDeclStatement>( + b2.Var("a", b2.ty.f32(), StorageClass::kNone)); + }, + "internal compiler error"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/variable_test.cc b/src/tint/ast/variable_test.cc new file mode 100644 index 0000000..b053c0fa --- /dev/null +++ b/src/tint/ast/variable_test.cc
@@ -0,0 +1,156 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "gtest/gtest-spi.h" + +#include "src/tint/ast/id_attribute.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using VariableTest = TestHelper; + +TEST_F(VariableTest, Creation) { + auto* v = Var("my_var", ty.i32(), StorageClass::kFunction); + + EXPECT_EQ(v->symbol, Symbol(1, ID())); + EXPECT_EQ(v->declared_storage_class, StorageClass::kFunction); + EXPECT_TRUE(v->type->Is<ast::I32>()); + EXPECT_EQ(v->source.range.begin.line, 0u); + EXPECT_EQ(v->source.range.begin.column, 0u); + EXPECT_EQ(v->source.range.end.line, 0u); + EXPECT_EQ(v->source.range.end.column, 0u); +} + +TEST_F(VariableTest, CreationWithSource) { + auto* v = Var( + Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 5}}}, + "i", ty.f32(), StorageClass::kPrivate, nullptr, AttributeList{}); + + EXPECT_EQ(v->symbol, Symbol(1, ID())); + EXPECT_EQ(v->declared_storage_class, StorageClass::kPrivate); + EXPECT_TRUE(v->type->Is<ast::F32>()); + EXPECT_EQ(v->source.range.begin.line, 27u); + EXPECT_EQ(v->source.range.begin.column, 4u); + EXPECT_EQ(v->source.range.end.line, 27u); + EXPECT_EQ(v->source.range.end.column, 5u); +} + +TEST_F(VariableTest, CreationEmpty) { + auto* v = Var( + Source{Source::Range{Source::Location{27, 4}, Source::Location{27, 7}}}, + "a_var", ty.i32(), StorageClass::kWorkgroup, nullptr, AttributeList{}); + + EXPECT_EQ(v->symbol, Symbol(1, ID())); + EXPECT_EQ(v->declared_storage_class, StorageClass::kWorkgroup); + EXPECT_TRUE(v->type->Is<ast::I32>()); + EXPECT_EQ(v->source.range.begin.line, 27u); + EXPECT_EQ(v->source.range.begin.column, 4u); + EXPECT_EQ(v->source.range.end.line, 27u); + EXPECT_EQ(v->source.range.end.column, 7u); +} + +TEST_F(VariableTest, Assert_MissingSymbol) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.Var("", b.ty.i32(), StorageClass::kNone); + }, + "internal compiler error"); +} + +TEST_F(VariableTest, Assert_DifferentProgramID_Symbol) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.Var(b2.Sym("x"), b1.ty.f32(), StorageClass::kNone); + }, + "internal compiler error"); +} + +TEST_F(VariableTest, Assert_DifferentProgramID_Constructor) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b1; + ProgramBuilder b2; + b1.Var("x", b1.ty.f32(), StorageClass::kNone, b2.Expr(1.2f)); + }, + "internal compiler error"); +} + +TEST_F(VariableTest, WithAttributes) { + auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr, + AttributeList{ + create<LocationAttribute>(1), + create<BuiltinAttribute>(Builtin::kPosition), + create<IdAttribute>(1200), + }); + + auto& attributes = var->attributes; + EXPECT_TRUE(ast::HasAttribute<ast::LocationAttribute>(attributes)); + EXPECT_TRUE(ast::HasAttribute<ast::BuiltinAttribute>(attributes)); + EXPECT_TRUE(ast::HasAttribute<ast::IdAttribute>(attributes)); + + auto* location = ast::GetAttribute<ast::LocationAttribute>(attributes); + ASSERT_NE(nullptr, location); + EXPECT_EQ(1u, location->value); +} + +TEST_F(VariableTest, BindingPoint) { + auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr, + AttributeList{ + create<BindingAttribute>(2), + create<GroupAttribute>(1), + }); + EXPECT_TRUE(var->BindingPoint()); + ASSERT_NE(var->BindingPoint().binding, nullptr); + ASSERT_NE(var->BindingPoint().group, nullptr); + EXPECT_EQ(var->BindingPoint().binding->value, 2u); + EXPECT_EQ(var->BindingPoint().group->value, 1u); +} + +TEST_F(VariableTest, BindingPointAttributes) { + auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr, + AttributeList{}); + EXPECT_FALSE(var->BindingPoint()); + EXPECT_EQ(var->BindingPoint().group, nullptr); + EXPECT_EQ(var->BindingPoint().binding, nullptr); +} + +TEST_F(VariableTest, BindingPointMissingGroupAttribute) { + auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr, + AttributeList{ + create<BindingAttribute>(2), + }); + EXPECT_FALSE(var->BindingPoint()); + ASSERT_NE(var->BindingPoint().binding, nullptr); + EXPECT_EQ(var->BindingPoint().binding->value, 2u); + EXPECT_EQ(var->BindingPoint().group, nullptr); +} + +TEST_F(VariableTest, BindingPointMissingBindingAttribute) { + auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, nullptr, + AttributeList{create<GroupAttribute>(1)}); + EXPECT_FALSE(var->BindingPoint()); + ASSERT_NE(var->BindingPoint().group, nullptr); + EXPECT_EQ(var->BindingPoint().group->value, 1u); + EXPECT_EQ(var->BindingPoint().binding, nullptr); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/vector.cc b/src/tint/ast/vector.cc new file mode 100644 index 0000000..d63a61b --- /dev/null +++ b/src/tint/ast/vector.cc
@@ -0,0 +1,55 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/vector.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::Vector); + +namespace tint { +namespace ast { + +Vector::Vector(ProgramID pid, + Source const& src, + const Type* subtype, + uint32_t w) + : Base(pid, src), type(subtype), width(w) { + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, subtype, program_id); + TINT_ASSERT(AST, width > 1); + TINT_ASSERT(AST, width < 5); +} + +Vector::Vector(Vector&&) = default; + +Vector::~Vector() = default; + +std::string Vector::FriendlyName(const SymbolTable& symbols) const { + std::ostringstream out; + out << "vec" << width; + if (type) { + out << "<" << type->FriendlyName(symbols) << ">"; + } + return out.str(); +} + +const Vector* Vector::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* ty = ctx->Clone(type); + return ctx->dst->create<Vector>(src, ty, width); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/vector.h b/src/tint/ast/vector.h new file mode 100644 index 0000000..bfc4908 --- /dev/null +++ b/src/tint/ast/vector.h
@@ -0,0 +1,62 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_VECTOR_H_ +#define SRC_TINT_AST_VECTOR_H_ + +#include <string> + +#include "src/tint/ast/type.h" + +namespace tint { +namespace ast { + +/// A vector type. +class Vector final : public Castable<Vector, Type> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param subtype the declared type of the vector components. May be null + /// for vector constructors, where the element type will be inferred + /// from the constructor arguments + /// @param width the number of elements in the vector + Vector(ProgramID pid, Source const& src, const Type* subtype, uint32_t width); + /// Move constructor + Vector(Vector&&); + ~Vector() override; + + /// @param symbols the program's symbol table + /// @returns the name for this type that closely resembles how it would be + /// declared in WGSL. + std::string FriendlyName(const SymbolTable& symbols) const override; + + /// Clones this type and all transitive types using the `CloneContext` `ctx`. + /// @param ctx the clone context + /// @return the newly cloned type + const Vector* Clone(CloneContext* ctx) const override; + + /// The declared type of the vector components. May be null for vector + /// constructors, where the element type will be inferred from the constructor + /// arguments + const Type* const type; + + /// The number of elements in the vector + const uint32_t width; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_VECTOR_H_
diff --git a/src/tint/ast/vector_test.cc b/src/tint/ast/vector_test.cc new file mode 100644 index 0000000..a029e73 --- /dev/null +++ b/src/tint/ast/vector_test.cc
@@ -0,0 +1,41 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/vector.h" + +#include "src/tint/ast/i32.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using AstVectorTest = TestHelper; + +TEST_F(AstVectorTest, Creation) { + auto* i32 = create<I32>(); + auto* v = create<Vector>(i32, 2); + EXPECT_EQ(v->type, i32); + EXPECT_EQ(v->width, 2u); +} + +TEST_F(AstVectorTest, FriendlyName) { + auto* f32 = create<F32>(); + auto* v = create<Vector>(f32, 3); + EXPECT_EQ(v->FriendlyName(Symbols()), "vec3<f32>"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/void.cc b/src/tint/ast/void.cc new file mode 100644 index 0000000..1abb83c --- /dev/null +++ b/src/tint/ast/void.cc
@@ -0,0 +1,40 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/void.h" + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::Void); + +namespace tint { +namespace ast { + +Void::Void(ProgramID pid, const Source& src) : Base(pid, src) {} + +Void::Void(Void&&) = default; + +Void::~Void() = default; + +std::string Void::FriendlyName(const SymbolTable&) const { + return "void"; +} + +const Void* Void::Clone(CloneContext* ctx) const { + auto src = ctx->Clone(source); + return ctx->dst->create<Void>(src); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/void.h b/src/tint/ast/void.h new file mode 100644 index 0000000..94d382f --- /dev/null +++ b/src/tint/ast/void.h
@@ -0,0 +1,50 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_VOID_H_ +#define SRC_TINT_AST_VOID_H_ + +#include <string> + +#include "src/tint/ast/type.h" + +namespace tint { +namespace ast { + +/// A void type +class Void final : public Castable<Void, Type> { + public: + /// Constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + Void(ProgramID pid, const Source& src); + /// Move constructor + Void(Void&&); + ~Void() override; + + /// @param symbols the program's symbol table + /// @returns the name for this type that closely resembles how it would be + /// declared in WGSL. + std::string FriendlyName(const SymbolTable& symbols) const override; + + /// Clones this type and all transitive types using the `CloneContext` `ctx`. + /// @param ctx the clone context + /// @return the newly cloned type + const Void* Clone(CloneContext* ctx) const override; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_VOID_H_
diff --git a/src/tint/ast/workgroup_attribute.cc b/src/tint/ast/workgroup_attribute.cc new file mode 100644 index 0000000..7ff6954 --- /dev/null +++ b/src/tint/ast/workgroup_attribute.cc
@@ -0,0 +1,49 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/workgroup_attribute.h" + +#include <string> + +#include "src/tint/program_builder.h" + +TINT_INSTANTIATE_TYPEINFO(tint::ast::WorkgroupAttribute); + +namespace tint { +namespace ast { + +WorkgroupAttribute::WorkgroupAttribute(ProgramID pid, + const Source& src, + const ast::Expression* x_, + const ast::Expression* y_, + const ast::Expression* z_) + : Base(pid, src), x(x_), y(y_), z(z_) {} + +WorkgroupAttribute::~WorkgroupAttribute() = default; + +std::string WorkgroupAttribute::Name() const { + return "workgroup_size"; +} + +const WorkgroupAttribute* WorkgroupAttribute::Clone(CloneContext* ctx) const { + // Clone arguments outside of create() call to have deterministic ordering + auto src = ctx->Clone(source); + auto* x_ = ctx->Clone(x); + auto* y_ = ctx->Clone(y); + auto* z_ = ctx->Clone(z); + return ctx->dst->create<WorkgroupAttribute>(src, x_, y_, z_); +} + +} // namespace ast +} // namespace tint
diff --git a/src/tint/ast/workgroup_attribute.h b/src/tint/ast/workgroup_attribute.h new file mode 100644 index 0000000..5ffc7c64 --- /dev/null +++ b/src/tint/ast/workgroup_attribute.h
@@ -0,0 +1,70 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_AST_WORKGROUP_ATTRIBUTE_H_ +#define SRC_TINT_AST_WORKGROUP_ATTRIBUTE_H_ + +#include <array> +#include <string> + +#include "src/tint/ast/attribute.h" + +namespace tint { +namespace ast { + +// Forward declaration +class Expression; + +/// A workgroup attribute +class WorkgroupAttribute final + : public Castable<WorkgroupAttribute, Attribute> { + public: + /// constructor + /// @param pid the identifier of the program that owns this node + /// @param src the source of this node + /// @param x the workgroup x dimension expression + /// @param y the optional workgroup y dimension expression + /// @param z the optional workgroup z dimension expression + WorkgroupAttribute(ProgramID pid, + const Source& src, + const ast::Expression* x, + const ast::Expression* y = nullptr, + const ast::Expression* z = nullptr); + + ~WorkgroupAttribute() override; + + /// @returns the workgroup dimensions + std::array<const ast::Expression*, 3> Values() const { return {x, y, z}; } + + /// @returns the WGSL name for the attribute + std::string Name() const override; + + /// Clones this node and all transitive child nodes using the `CloneContext` + /// `ctx`. + /// @param ctx the clone context + /// @return the newly cloned node + const WorkgroupAttribute* Clone(CloneContext* ctx) const override; + + /// The workgroup x dimension. + const ast::Expression* const x; + /// The optional workgroup y dimension. May be null. + const ast::Expression* const y = nullptr; + /// The optional workgroup z dimension. May be null. + const ast::Expression* const z = nullptr; +}; + +} // namespace ast +} // namespace tint + +#endif // SRC_TINT_AST_WORKGROUP_ATTRIBUTE_H_
diff --git a/src/tint/ast/workgroup_attribute_test.cc b/src/tint/ast/workgroup_attribute_test.cc new file mode 100644 index 0000000..ecf7186 --- /dev/null +++ b/src/tint/ast/workgroup_attribute_test.cc
@@ -0,0 +1,80 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/ast/workgroup_attribute.h" + +#include "src/tint/ast/stage_attribute.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace ast { +namespace { + +using WorkgroupAttributeTest = TestHelper; + +TEST_F(WorkgroupAttributeTest, Creation_1param) { + auto* d = WorkgroupSize(2); + auto values = d->Values(); + + ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>()); + EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u); + + EXPECT_EQ(values[1], nullptr); + EXPECT_EQ(values[2], nullptr); +} +TEST_F(WorkgroupAttributeTest, Creation_2param) { + auto* d = WorkgroupSize(2, 4); + auto values = d->Values(); + + ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>()); + EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u); + + ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>()); + EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u); + + EXPECT_EQ(values[2], nullptr); +} + +TEST_F(WorkgroupAttributeTest, Creation_3param) { + auto* d = WorkgroupSize(2, 4, 6); + auto values = d->Values(); + + ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>()); + EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u); + + ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>()); + EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u); + + ASSERT_TRUE(values[2]->Is<ast::IntLiteralExpression>()); + EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->ValueAsU32(), 6u); +} + +TEST_F(WorkgroupAttributeTest, Creation_WithIdentifier) { + auto* d = WorkgroupSize(2, 4, "depth"); + auto values = d->Values(); + + ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>()); + EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u); + + ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>()); + EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u); + + auto* z_ident = As<ast::IdentifierExpression>(values[2]); + ASSERT_TRUE(z_ident); + EXPECT_EQ(Symbols().NameFor(z_ident->symbol), "depth"); +} + +} // namespace +} // namespace ast +} // namespace tint
diff --git a/src/tint/bench/benchmark.cc b/src/tint/bench/benchmark.cc new file mode 100644 index 0000000..4fd96e2 --- /dev/null +++ b/src/tint/bench/benchmark.cc
@@ -0,0 +1,123 @@ +// Copyright 2022 The Tint Authors. +// +// 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 "src/tint/bench/benchmark.h" + +#include <filesystem> +#include <sstream> +#include <utility> +#include <vector> + +namespace tint::bench { +namespace { + +std::filesystem::path kInputFileDir; + +/// Copies the content from the file named `input_file` to `buffer`, +/// assuming each element in the file is of type `T`. If any error occurs, +/// writes error messages to the standard error stream and returns false. +/// Assumes the size of a `T` object is divisible by its required alignment. +/// @returns true if we successfully read the file. +template <typename T> +std::variant<std::vector<T>, Error> ReadFile(const std::string& input_file) { + FILE* file = nullptr; +#if defined(_MSC_VER) + fopen_s(&file, input_file.c_str(), "rb"); +#else + file = fopen(input_file.c_str(), "rb"); +#endif + if (!file) { + return Error{"Failed to open " + input_file}; + } + + fseek(file, 0, SEEK_END); + const auto file_size = static_cast<size_t>(ftell(file)); + if (0 != (file_size % sizeof(T))) { + std::stringstream err; + err << "File " << input_file + << " does not contain an integral number of objects: " << file_size + << " bytes in the file, require " << sizeof(T) << " bytes per object"; + fclose(file); + return Error{err.str()}; + } + fseek(file, 0, SEEK_SET); + + std::vector<T> buffer; + buffer.resize(file_size / sizeof(T)); + + size_t bytes_read = fread(buffer.data(), 1, file_size, file); + fclose(file); + if (bytes_read != file_size) { + return Error{"Failed to read " + input_file}; + } + + return buffer; +} + +bool FindBenchmarkInputDir() { + // Attempt to find the benchmark input files by searching up from the current + // working directory. + auto path = std::filesystem::current_path(); + while (std::filesystem::is_directory(path)) { + auto test = path / "test" / "tint" / "benchmark"; + if (std::filesystem::is_directory(test)) { + kInputFileDir = test; + return true; + } + auto parent = path.parent_path(); + if (path == parent) { + break; + } + path = parent; + } + return false; +} + +} // namespace + +std::variant<tint::Source::File, Error> LoadInputFile(std::string name) { + auto path = (kInputFileDir / name).string(); + auto data = ReadFile<uint8_t>(path); + if (auto* buf = std::get_if<std::vector<uint8_t>>(&data)) { + return tint::Source::File(path, std::string(buf->begin(), buf->end())); + } + return std::get<Error>(data); +} + +std::variant<ProgramAndFile, Error> LoadProgram(std::string name) { + auto res = bench::LoadInputFile(name); + if (auto err = std::get_if<bench::Error>(&res)) { + return *err; + } + auto& file = std::get<Source::File>(res); + auto program = reader::wgsl::Parse(&file); + if (program.Diagnostics().contains_errors()) { + return Error{program.Diagnostics().str()}; + } + return ProgramAndFile{std::move(program), std::move(file)}; +} + +} // namespace tint::bench + +int main(int argc, char** argv) { + benchmark::Initialize(&argc, argv); + if (benchmark::ReportUnrecognizedArguments(argc, argv)) { + return 1; + } + if (!tint::bench::FindBenchmarkInputDir()) { + std::cerr << "failed to locate benchmark input files" << std::endl; + return 1; + } + benchmark::RunSpecifiedBenchmarks(); +}
diff --git a/src/tint/bench/benchmark.h b/src/tint/bench/benchmark.h new file mode 100644 index 0000000..96a935d --- /dev/null +++ b/src/tint/bench/benchmark.h
@@ -0,0 +1,76 @@ +// Copyright 2022 The Tint Authors. +// +// 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 SRC_TINT_BENCH_BENCHMARK_H_ +#define SRC_TINT_BENCH_BENCHMARK_H_ + +#include <memory> +#include <string> +#include <variant> // NOLINT: Found C system header after C++ system header. + +#include "benchmark/benchmark.h" +#include "src/tint/utils/concat.h" +#include "tint/tint.h" + +namespace tint::bench { + +/// Error indicates an operation did not complete successfully. +struct Error { + /// The error message. + std::string msg; +}; + +/// ProgramAndFile holds a Program and a Source::File. +struct ProgramAndFile { + /// The tint program parsed from file. + Program program; + /// The source file + Source::File file; +}; + +/// LoadInputFile attempts to load a benchmark input file with the given file +/// name. +/// @param name the file name +/// @returns either the loaded Source::File or an Error +std::variant<Source::File, Error> LoadInputFile(std::string name); + +/// LoadInputFile attempts to load a benchmark input program with the given file +/// name. +/// @param name the file name +/// @returns either the loaded Program or an Error +std::variant<ProgramAndFile, Error> LoadProgram(std::string name); + +/// Declares a benchmark with the given function and WGSL file name +#define TINT_BENCHMARK_WGSL_PROGRAM(FUNC, WGSL_NAME) \ + BENCHMARK_CAPTURE(FUNC, WGSL_NAME, WGSL_NAME); + +/// Declares a set of benchmarks for the given function using a list of WGSL +/// files in `<tint>/test/benchmark`. +#define TINT_BENCHMARK_WGSL_PROGRAMS(FUNC) \ + TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "animometer.wgsl"); \ + TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "bloom-vertical-blur.wgsl"); \ + TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "cluster-lights.wgsl"); \ + TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "empty.wgsl"); \ + TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "metaball-isosurface.wgsl"); \ + TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "particles.wgsl"); \ + TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "shadow-fragment.wgsl"); \ + TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "simple-compute.wgsl"); \ + TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "simple-fragment.wgsl"); \ + TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "simple-vertex.wgsl"); \ + TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "skinned-shadowed-pbr-fragment.wgsl"); \ + TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "skinned-shadowed-pbr-vertex.wgsl"); + +} // namespace tint::bench + +#endif // SRC_TINT_BENCH_BENCHMARK_H_
diff --git a/src/tint/builtin_table.cc b/src/tint/builtin_table.cc new file mode 100644 index 0000000..13e7c5f --- /dev/null +++ b/src/tint/builtin_table.cc
@@ -0,0 +1,1169 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/builtin_table.h" + +#include <algorithm> +#include <limits> +#include <unordered_map> +#include <utility> + +#include "src/tint/program_builder.h" +#include "src/tint/sem/atomic_type.h" +#include "src/tint/sem/depth_multisampled_texture_type.h" +#include "src/tint/sem/depth_texture_type.h" +#include "src/tint/sem/external_texture_type.h" +#include "src/tint/sem/multisampled_texture_type.h" +#include "src/tint/sem/pipeline_stage_set.h" +#include "src/tint/sem/sampled_texture_type.h" +#include "src/tint/sem/storage_texture_type.h" +#include "src/tint/utils/hash.h" +#include "src/tint/utils/map.h" +#include "src/tint/utils/math.h" +#include "src/tint/utils/scoped_assignment.h" + +namespace tint { +namespace { + +// Forward declarations +struct OverloadInfo; +class Matchers; +class NumberMatcher; +class TypeMatcher; + +/// A special type that matches all TypeMatchers +class Any final : public Castable<Any, sem::Type> { + public: + Any() = default; + ~Any() override = default; + + // Stub implementations for sem::Type conformance. + size_t Hash() const override { return 0; } + bool Equals(const sem::Type&) const override { return false; } + std::string FriendlyName(const SymbolTable&) const override { + return "<any>"; + } +}; + +/// Number is an 32 bit unsigned integer, which can be in one of three states: +/// * Invalid - Number has not been assigned a value +/// * Valid - a fixed integer value +/// * Any - matches any other non-invalid number +struct Number { + static const Number any; + static const Number invalid; + + /// Constructed as a valid number with the value v + explicit Number(uint32_t v) : value_(v), state_(kValid) {} + + /// @returns the value of the number + inline uint32_t Value() const { return value_; } + + /// @returns the true if the number is valid + inline bool IsValid() const { return state_ == kValid; } + + /// @returns the true if the number is any + inline bool IsAny() const { return state_ == kAny; } + + /// Assignment operator. + /// The number becomes valid, with the value n + inline Number& operator=(uint32_t n) { + value_ = n; + state_ = kValid; + return *this; + } + + private: + enum State { + kInvalid, + kValid, + kAny, + }; + + constexpr explicit Number(State state) : state_(state) {} + + uint32_t value_ = 0; + State state_ = kInvalid; +}; + +const Number Number::any{Number::kAny}; +const Number Number::invalid{Number::kInvalid}; + +/// ClosedState holds the state of the open / closed numbers and types. +/// Used by the MatchState. +class ClosedState { + public: + explicit ClosedState(ProgramBuilder& b) : builder(b) {} + + /// If the type with index `idx` is open, then it is closed with type `ty` and + /// Type() returns true. If the type is closed, then `Type()` returns true iff + /// it is equal to `ty`. + bool Type(uint32_t idx, const sem::Type* ty) { + auto res = types_.emplace(idx, ty); + return res.second || res.first->second == ty; + } + + /// If the number with index `idx` is open, then it is closed with number + /// `number` and Num() returns true. If the number is closed, then `Num()` + /// returns true iff it is equal to `ty`. + bool Num(uint32_t idx, Number number) { + auto res = numbers_.emplace(idx, number.Value()); + return res.second || res.first->second == number.Value(); + } + + /// Type returns the closed type with index `idx`. + /// An ICE is raised if the type is not closed. + const sem::Type* Type(uint32_t idx) const { + auto it = types_.find(idx); + if (it == types_.end()) { + TINT_ICE(Resolver, builder.Diagnostics()) + << "type with index " << idx << " is not closed"; + return nullptr; + } + TINT_ASSERT(Resolver, it != types_.end()); + return it->second; + } + + /// Type returns the number type with index `idx`. + /// An ICE is raised if the number is not closed. + Number Num(uint32_t idx) const { + auto it = numbers_.find(idx); + if (it == numbers_.end()) { + TINT_ICE(Resolver, builder.Diagnostics()) + << "number with index " << idx << " is not closed"; + return Number::invalid; + } + return Number(it->second); + } + + private: + ProgramBuilder& builder; + std::unordered_map<uint32_t, const sem::Type*> types_; + std::unordered_map<uint32_t, uint32_t> numbers_; +}; + +/// Index type used for matcher indices +using MatcherIndex = uint8_t; + +/// Index value used for open types / numbers that do not have a constraint +constexpr MatcherIndex kNoMatcher = std::numeric_limits<MatcherIndex>::max(); + +/// MatchState holds the state used to match an overload. +class MatchState { + public: + MatchState(ProgramBuilder& b, + ClosedState& c, + const Matchers& m, + const OverloadInfo& o, + MatcherIndex const* matcher_indices) + : builder(b), + closed(c), + matchers(m), + overload(o), + matcher_indices_(matcher_indices) {} + + /// The program builder + ProgramBuilder& builder; + /// The open / closed types and numbers + ClosedState& closed; + /// The type and number matchers + Matchers const& matchers; + /// The current overload being evaluated + OverloadInfo const& overload; + + /// Type uses the next TypeMatcher from the matcher indices to match the type + /// `ty`. If the type matches, the canonical expected type is returned. If the + /// type `ty` does not match, then nullptr is returned. + /// @note: The matcher indices are progressed on calling. + const sem::Type* Type(const sem::Type* ty); + + /// Num uses the next NumMatcher from the matcher indices to match the number + /// `num`. If the number matches, the canonical expected number is returned. + /// If the number `num` does not match, then an invalid number is returned. + /// @note: The matcher indices are progressed on calling. + Number Num(Number num); + + /// @returns a string representation of the next TypeMatcher from the matcher + /// indices. + /// @note: The matcher indices are progressed on calling. + std::string TypeName(); + + /// @returns a string representation of the next NumberMatcher from the + /// matcher indices. + /// @note: The matcher indices are progressed on calling. + std::string NumName(); + + private: + MatcherIndex const* matcher_indices_ = nullptr; +}; + +/// A TypeMatcher is the interface used to match an type used as part of an +/// overload's parameter or return type. +class TypeMatcher { + public: + /// Destructor + virtual ~TypeMatcher() = default; + + /// Checks whether the given type matches the matcher rules, and returns the + /// expected, canonicalized type on success. + /// Match may close open types and numbers in state. + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + virtual const sem::Type* Match(MatchState& state, + const sem::Type* type) const = 0; + + /// @return a string representation of the matcher. Used for printing error + /// messages when no overload is found. + virtual std::string String(MatchState& state) const = 0; +}; + +/// A NumberMatcher is the interface used to match a number or enumerator used +/// as part of an overload's parameter or return type. +class NumberMatcher { + public: + /// Destructor + virtual ~NumberMatcher() = default; + + /// Checks whether the given number matches the matcher rules. + /// Match may close open numbers in state. + /// @param number the number to match + /// @returns true if the argument type is as expected. + virtual Number Match(MatchState& state, Number number) const = 0; + + /// @return a string representation of the matcher. Used for printing error + /// messages when no overload is found. + virtual std::string String(MatchState& state) const = 0; +}; + +/// OpenTypeMatcher is a Matcher for an open type. +/// The OpenTypeMatcher will match against any type (so long as it is consistent +/// across all uses in the overload) +class OpenTypeMatcher : public TypeMatcher { + public: + /// Constructor + explicit OpenTypeMatcher(uint32_t index) : index_(index) {} + + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override { + if (type->Is<Any>()) { + return state.closed.Type(index_); + } + return state.closed.Type(index_, type) ? type : nullptr; + } + + std::string String(MatchState& state) const override; + + private: + uint32_t index_; +}; + +/// OpenNumberMatcher is a Matcher for an open number. +/// The OpenNumberMatcher will match against any number (so long as it is +/// consistent for the overload) +class OpenNumberMatcher : public NumberMatcher { + public: + explicit OpenNumberMatcher(uint32_t index) : index_(index) {} + + Number Match(MatchState& state, Number number) const override { + if (number.IsAny()) { + return state.closed.Num(index_); + } + return state.closed.Num(index_, number) ? number : Number::invalid; + } + + std::string String(MatchState& state) const override; + + private: + uint32_t index_; +}; + +//////////////////////////////////////////////////////////////////////////////// +// Binding functions for use in the generated builtin_table.inl +// TODO(bclayton): See if we can move more of this hand-rolled code to the +// template +//////////////////////////////////////////////////////////////////////////////// +using TexelFormat = ast::TexelFormat; +using Access = ast::Access; +using StorageClass = ast::StorageClass; +using ParameterUsage = sem::ParameterUsage; +using PipelineStageSet = sem::PipelineStageSet; +using PipelineStage = ast::PipelineStage; + +bool match_bool(const sem::Type* ty) { + return ty->IsAnyOf<Any, sem::Bool>(); +} + +const sem::Bool* build_bool(MatchState& state) { + return state.builder.create<sem::Bool>(); +} + +bool match_f32(const sem::Type* ty) { + return ty->IsAnyOf<Any, sem::F32>(); +} + +const sem::I32* build_i32(MatchState& state) { + return state.builder.create<sem::I32>(); +} + +bool match_i32(const sem::Type* ty) { + return ty->IsAnyOf<Any, sem::I32>(); +} + +const sem::U32* build_u32(MatchState& state) { + return state.builder.create<sem::U32>(); +} + +bool match_u32(const sem::Type* ty) { + return ty->IsAnyOf<Any, sem::U32>(); +} + +const sem::F32* build_f32(MatchState& state) { + return state.builder.create<sem::F32>(); +} + +bool match_vec(const sem::Type* ty, Number& N, const sem::Type*& T) { + if (ty->Is<Any>()) { + N = Number::any; + T = ty; + return true; + } + + if (auto* v = ty->As<sem::Vector>()) { + N = v->Width(); + T = v->type(); + return true; + } + return false; +} + +const sem::Vector* build_vec(MatchState& state, Number N, const sem::Type* el) { + return state.builder.create<sem::Vector>(el, N.Value()); +} + +template <int N> +bool match_vec(const sem::Type* ty, const sem::Type*& T) { + if (ty->Is<Any>()) { + T = ty; + return true; + } + + if (auto* v = ty->As<sem::Vector>()) { + if (v->Width() == N) { + T = v->type(); + return true; + } + } + return false; +} + +bool match_vec2(const sem::Type* ty, const sem::Type*& T) { + return match_vec<2>(ty, T); +} + +const sem::Vector* build_vec2(MatchState& state, const sem::Type* T) { + return build_vec(state, Number(2), T); +} + +bool match_vec3(const sem::Type* ty, const sem::Type*& T) { + return match_vec<3>(ty, T); +} + +const sem::Vector* build_vec3(MatchState& state, const sem::Type* T) { + return build_vec(state, Number(3), T); +} + +bool match_vec4(const sem::Type* ty, const sem::Type*& T) { + return match_vec<4>(ty, T); +} + +const sem::Vector* build_vec4(MatchState& state, const sem::Type* T) { + return build_vec(state, Number(4), T); +} + +bool match_mat(const sem::Type* ty, Number& M, Number& N, const sem::Type*& T) { + if (ty->Is<Any>()) { + M = Number::any; + N = Number::any; + T = ty; + return true; + } + if (auto* m = ty->As<sem::Matrix>()) { + M = m->columns(); + N = m->ColumnType()->Width(); + T = m->type(); + return true; + } + return false; +} + +const sem::Matrix* build_mat(MatchState& state, + Number N, + Number M, + const sem::Type* T) { + auto* column_type = state.builder.create<sem::Vector>(T, M.Value()); + return state.builder.create<sem::Matrix>(column_type, N.Value()); +} + +bool match_array(const sem::Type* ty, const sem::Type*& T) { + if (ty->Is<Any>()) { + T = ty; + return true; + } + + if (auto* a = ty->As<sem::Array>()) { + if (a->Count() == 0) { + T = a->ElemType(); + return true; + } + } + return false; +} + +const sem::Array* build_array(MatchState& state, const sem::Type* el) { + return state.builder.create<sem::Array>(el, + /* count */ 0u, + /* align */ 0u, + /* size */ 0u, + /* stride */ 0u, + /* stride_implicit */ 0u); +} + +bool match_ptr(const sem::Type* ty, Number& S, const sem::Type*& T, Number& A) { + if (ty->Is<Any>()) { + S = Number::any; + T = ty; + A = Number::any; + return true; + } + + if (auto* p = ty->As<sem::Pointer>()) { + S = Number(static_cast<uint32_t>(p->StorageClass())); + T = p->StoreType(); + A = Number(static_cast<uint32_t>(p->Access())); + return true; + } + return false; +} + +const sem::Pointer* build_ptr(MatchState& state, + Number S, + const sem::Type* T, + Number& A) { + return state.builder.create<sem::Pointer>( + T, static_cast<ast::StorageClass>(S.Value()), + static_cast<ast::Access>(A.Value())); +} + +bool match_atomic(const sem::Type* ty, const sem::Type*& T) { + if (ty->Is<Any>()) { + T = ty; + return true; + } + + if (auto* a = ty->As<sem::Atomic>()) { + T = a->Type(); + return true; + } + return false; +} + +const sem::Atomic* build_atomic(MatchState& state, const sem::Type* T) { + return state.builder.create<sem::Atomic>(T); +} + +bool match_sampler(const sem::Type* ty) { + if (ty->Is<Any>()) { + return true; + } + return ty->Is([](const sem::Sampler* s) { + return s->kind() == ast::SamplerKind::kSampler; + }); +} + +const sem::Sampler* build_sampler(MatchState& state) { + return state.builder.create<sem::Sampler>(ast::SamplerKind::kSampler); +} + +bool match_sampler_comparison(const sem::Type* ty) { + if (ty->Is<Any>()) { + return true; + } + return ty->Is([](const sem::Sampler* s) { + return s->kind() == ast::SamplerKind::kComparisonSampler; + }); +} + +const sem::Sampler* build_sampler_comparison(MatchState& state) { + return state.builder.create<sem::Sampler>( + ast::SamplerKind::kComparisonSampler); +} + +bool match_texture(const sem::Type* ty, + ast::TextureDimension dim, + const sem::Type*& T) { + if (ty->Is<Any>()) { + T = ty; + return true; + } + if (auto* v = ty->As<sem::SampledTexture>()) { + if (v->dim() == dim) { + T = v->type(); + return true; + } + } + return false; +} + +#define JOIN(a, b) a##b + +#define DECLARE_SAMPLED_TEXTURE(suffix, dim) \ + bool JOIN(match_texture_, suffix)(const sem::Type* ty, \ + const sem::Type*& T) { \ + return match_texture(ty, dim, T); \ + } \ + const sem::SampledTexture* JOIN(build_texture_, suffix)( \ + MatchState & state, const sem::Type* T) { \ + return state.builder.create<sem::SampledTexture>(dim, T); \ + } + +DECLARE_SAMPLED_TEXTURE(1d, ast::TextureDimension::k1d) +DECLARE_SAMPLED_TEXTURE(2d, ast::TextureDimension::k2d) +DECLARE_SAMPLED_TEXTURE(2d_array, ast::TextureDimension::k2dArray) +DECLARE_SAMPLED_TEXTURE(3d, ast::TextureDimension::k3d) +DECLARE_SAMPLED_TEXTURE(cube, ast::TextureDimension::kCube) +DECLARE_SAMPLED_TEXTURE(cube_array, ast::TextureDimension::kCubeArray) +#undef DECLARE_SAMPLED_TEXTURE + +bool match_texture_multisampled(const sem::Type* ty, + ast::TextureDimension dim, + const sem::Type*& T) { + if (ty->Is<Any>()) { + T = ty; + return true; + } + if (auto* v = ty->As<sem::MultisampledTexture>()) { + if (v->dim() == dim) { + T = v->type(); + return true; + } + } + return false; +} + +#define DECLARE_MULTISAMPLED_TEXTURE(suffix, dim) \ + bool JOIN(match_texture_multisampled_, suffix)(const sem::Type* ty, \ + const sem::Type*& T) { \ + return match_texture_multisampled(ty, dim, T); \ + } \ + const sem::MultisampledTexture* JOIN(build_texture_multisampled_, suffix)( \ + MatchState & state, const sem::Type* T) { \ + return state.builder.create<sem::MultisampledTexture>(dim, T); \ + } + +DECLARE_MULTISAMPLED_TEXTURE(2d, ast::TextureDimension::k2d) +#undef DECLARE_MULTISAMPLED_TEXTURE + +bool match_texture_depth(const sem::Type* ty, ast::TextureDimension dim) { + if (ty->Is<Any>()) { + return true; + } + return ty->Is([&](const sem::DepthTexture* t) { return t->dim() == dim; }); +} + +#define DECLARE_DEPTH_TEXTURE(suffix, dim) \ + bool JOIN(match_texture_depth_, suffix)(const sem::Type* ty) { \ + return match_texture_depth(ty, dim); \ + } \ + const sem::DepthTexture* JOIN(build_texture_depth_, \ + suffix)(MatchState & state) { \ + return state.builder.create<sem::DepthTexture>(dim); \ + } + +DECLARE_DEPTH_TEXTURE(2d, ast::TextureDimension::k2d) +DECLARE_DEPTH_TEXTURE(2d_array, ast::TextureDimension::k2dArray) +DECLARE_DEPTH_TEXTURE(cube, ast::TextureDimension::kCube) +DECLARE_DEPTH_TEXTURE(cube_array, ast::TextureDimension::kCubeArray) +#undef DECLARE_DEPTH_TEXTURE + +bool match_texture_depth_multisampled_2d(const sem::Type* ty) { + if (ty->Is<Any>()) { + return true; + } + return ty->Is([&](const sem::DepthMultisampledTexture* t) { + return t->dim() == ast::TextureDimension::k2d; + }); +} + +sem::DepthMultisampledTexture* build_texture_depth_multisampled_2d( + MatchState& state) { + return state.builder.create<sem::DepthMultisampledTexture>( + ast::TextureDimension::k2d); +} + +bool match_texture_storage(const sem::Type* ty, + ast::TextureDimension dim, + Number& F, + Number& A) { + if (ty->Is<Any>()) { + F = Number::any; + A = Number::any; + return true; + } + if (auto* v = ty->As<sem::StorageTexture>()) { + if (v->dim() == dim) { + F = Number(static_cast<uint32_t>(v->texel_format())); + A = Number(static_cast<uint32_t>(v->access())); + return true; + } + } + return false; +} + +#define DECLARE_STORAGE_TEXTURE(suffix, dim) \ + bool JOIN(match_texture_storage_, suffix)(const sem::Type* ty, Number& F, \ + Number& A) { \ + return match_texture_storage(ty, dim, F, A); \ + } \ + const sem::StorageTexture* JOIN(build_texture_storage_, suffix)( \ + MatchState & state, Number F, Number A) { \ + auto format = static_cast<TexelFormat>(F.Value()); \ + auto access = static_cast<Access>(A.Value()); \ + auto* T = sem::StorageTexture::SubtypeFor(format, state.builder.Types()); \ + return state.builder.create<sem::StorageTexture>(dim, format, access, T); \ + } + +DECLARE_STORAGE_TEXTURE(1d, ast::TextureDimension::k1d) +DECLARE_STORAGE_TEXTURE(2d, ast::TextureDimension::k2d) +DECLARE_STORAGE_TEXTURE(2d_array, ast::TextureDimension::k2dArray) +DECLARE_STORAGE_TEXTURE(3d, ast::TextureDimension::k3d) +#undef DECLARE_STORAGE_TEXTURE + +bool match_texture_external(const sem::Type* ty) { + return ty->IsAnyOf<Any, sem::ExternalTexture>(); +} + +const sem::ExternalTexture* build_texture_external(MatchState& state) { + return state.builder.create<sem::ExternalTexture>(); +} + +// Builtin types starting with a _ prefix cannot be declared in WGSL, so they +// can only be used as return types. Because of this, they must only match Any, +// which is used as the return type matcher. +bool match_modf_result(const sem::Type* ty) { + return ty->Is<Any>(); +} +bool match_modf_result_vec(const sem::Type* ty, Number& N) { + if (!ty->Is<Any>()) { + return false; + } + N = Number::any; + return true; +} +bool match_frexp_result(const sem::Type* ty) { + return ty->Is<Any>(); +} +bool match_frexp_result_vec(const sem::Type* ty, Number& N) { + if (!ty->Is<Any>()) { + return false; + } + N = Number::any; + return true; +} + +struct NameAndType { + std::string name; + sem::Type* type; +}; +const sem::Struct* build_struct( + MatchState& state, + std::string name, + std::initializer_list<NameAndType> member_names_and_types) { + uint32_t offset = 0; + uint32_t max_align = 0; + sem::StructMemberList members; + for (auto& m : member_names_and_types) { + uint32_t align = m.type->Align(); + uint32_t size = m.type->Size(); + offset = utils::RoundUp(align, offset); + max_align = std::max(max_align, align); + members.emplace_back(state.builder.create<sem::StructMember>( + /* declaration */ nullptr, + /* name */ state.builder.Sym(m.name), + /* type */ m.type, + /* index */ static_cast<uint32_t>(members.size()), + /* offset */ offset, + /* align */ align, + /* size */ size)); + offset += size; + } + uint32_t size_without_padding = offset; + uint32_t size_with_padding = utils::RoundUp(max_align, offset); + return state.builder.create<sem::Struct>( + /* declaration */ nullptr, + /* name */ state.builder.Sym(name), + /* members */ members, + /* align */ max_align, + /* size */ size_with_padding, + /* size_no_padding */ size_without_padding); +} + +const sem::Struct* build_modf_result(MatchState& state) { + auto* f32 = state.builder.create<sem::F32>(); + return build_struct(state, "__modf_result", {{"fract", f32}, {"whole", f32}}); +} +const sem::Struct* build_modf_result_vec(MatchState& state, Number& n) { + auto* vec_f32 = state.builder.create<sem::Vector>( + state.builder.create<sem::F32>(), n.Value()); + return build_struct(state, "__modf_result_vec" + std::to_string(n.Value()), + {{"fract", vec_f32}, {"whole", vec_f32}}); +} +const sem::Struct* build_frexp_result(MatchState& state) { + auto* f32 = state.builder.create<sem::F32>(); + auto* i32 = state.builder.create<sem::I32>(); + return build_struct(state, "__frexp_result", {{"sig", f32}, {"exp", i32}}); +} +const sem::Struct* build_frexp_result_vec(MatchState& state, Number& n) { + auto* vec_f32 = state.builder.create<sem::Vector>( + state.builder.create<sem::F32>(), n.Value()); + auto* vec_i32 = state.builder.create<sem::Vector>( + state.builder.create<sem::I32>(), n.Value()); + return build_struct(state, "__frexp_result_vec" + std::to_string(n.Value()), + {{"sig", vec_f32}, {"exp", vec_i32}}); +} + +/// ParameterInfo describes a parameter +struct ParameterInfo { + /// The parameter usage (parameter name in definition file) + const ParameterUsage usage; + + /// Pointer to a list of indices that are used to match the parameter type. + /// The matcher indices index on Matchers::type and / or Matchers::number. + /// These indices are consumed by the matchers themselves. + /// The first index is always a TypeMatcher. + MatcherIndex const* const matcher_indices; +}; + +/// OpenTypeInfo describes an open type +struct OpenTypeInfo { + /// Name of the open type (e.g. 'T') + const char* name; + /// Optional type matcher constraint. + /// Either an index in Matchers::type, or kNoMatcher + const MatcherIndex matcher_index; +}; + +/// OpenNumberInfo describes an open number +struct OpenNumberInfo { + /// Name of the open number (e.g. 'N') + const char* name; + /// Optional number matcher constraint. + /// Either an index in Matchers::number, or kNoMatcher + const MatcherIndex matcher_index; +}; + +/// OverloadInfo describes a single function overload +struct OverloadInfo { + /// Total number of parameters for the overload + const uint8_t num_parameters; + /// Total number of open types for the overload + const uint8_t num_open_types; + /// Total number of open numbers for the overload + const uint8_t num_open_numbers; + /// Pointer to the first open type + OpenTypeInfo const* const open_types; + /// Pointer to the first open number + OpenNumberInfo const* const open_numbers; + /// Pointer to the first parameter + ParameterInfo const* const parameters; + /// Pointer to a list of matcher indices that index on Matchers::type and + /// Matchers::number, used to build the return type. If the function has no + /// return type then this is null + MatcherIndex const* const return_matcher_indices; + /// The pipeline stages that this overload can be used in + PipelineStageSet supported_stages; + /// True if the overload is marked as deprecated + bool is_deprecated; +}; + +/// BuiltinInfo describes a builtin function +struct BuiltinInfo { + /// Number of overloads of the builtin function + const uint8_t num_overloads; + /// Pointer to the start of the overloads for the function + OverloadInfo const* const overloads; +}; + +#include "builtin_table.inl" + +/// BuiltinPrototype describes a fully matched builtin function, which is +/// used as a lookup for building unique sem::Builtin instances. +struct BuiltinPrototype { + /// Parameter describes a single parameter + struct Parameter { + /// Parameter type + const sem::Type* const type; + /// Parameter usage + ParameterUsage const usage = ParameterUsage::kNone; + }; + + /// Hasher provides a hash function for the BuiltinPrototype + struct Hasher { + /// @param i the BuiltinPrototype to create a hash for + /// @return the hash value + inline std::size_t operator()(const BuiltinPrototype& i) const { + size_t hash = utils::Hash(i.parameters.size()); + for (auto& p : i.parameters) { + utils::HashCombine(&hash, p.type, p.usage); + } + return utils::Hash(hash, i.type, i.return_type, i.supported_stages, + i.is_deprecated); + } + }; + + sem::BuiltinType type = sem::BuiltinType::kNone; + std::vector<Parameter> parameters; + sem::Type const* return_type = nullptr; + PipelineStageSet supported_stages; + bool is_deprecated = false; +}; + +/// Equality operator for BuiltinPrototype +bool operator==(const BuiltinPrototype& a, const BuiltinPrototype& b) { + if (a.type != b.type || a.supported_stages != b.supported_stages || + a.return_type != b.return_type || a.is_deprecated != b.is_deprecated || + a.parameters.size() != b.parameters.size()) { + return false; + } + for (size_t i = 0; i < a.parameters.size(); i++) { + auto& pa = a.parameters[i]; + auto& pb = b.parameters[i]; + if (pa.type != pb.type || pa.usage != pb.usage) { + return false; + } + } + return true; +} + +/// Impl is the private implementation of the BuiltinTable interface. +class Impl : public BuiltinTable { + public: + explicit Impl(ProgramBuilder& builder); + + const sem::Builtin* Lookup(sem::BuiltinType builtin_type, + const std::vector<const sem::Type*>& args, + const Source& source) override; + + private: + const sem::Builtin* Match(sem::BuiltinType builtin_type, + const OverloadInfo& overload, + const std::vector<const sem::Type*>& args, + int& match_score); + + MatchState Match(ClosedState& closed, + const OverloadInfo& overload, + MatcherIndex const* matcher_indices) const; + + void PrintOverload(std::ostream& ss, + const OverloadInfo& overload, + sem::BuiltinType builtin_type) const; + + ProgramBuilder& builder; + Matchers matchers; + std::unordered_map<BuiltinPrototype, sem::Builtin*, BuiltinPrototype::Hasher> + builtins; +}; + +/// @return a string representing a call to a builtin with the given argument +/// types. +std::string CallSignature(ProgramBuilder& builder, + sem::BuiltinType builtin_type, + const std::vector<const sem::Type*>& args) { + std::stringstream ss; + ss << sem::str(builtin_type) << "("; + { + bool first = true; + for (auto* arg : args) { + if (!first) { + ss << ", "; + } + first = false; + ss << arg->UnwrapRef()->FriendlyName(builder.Symbols()); + } + } + ss << ")"; + + return ss.str(); +} + +std::string OpenTypeMatcher::String(MatchState& state) const { + return state.overload.open_types[index_].name; +} + +std::string OpenNumberMatcher::String(MatchState& state) const { + return state.overload.open_numbers[index_].name; +} + +Impl::Impl(ProgramBuilder& b) : builder(b) {} + +const sem::Builtin* Impl::Lookup(sem::BuiltinType builtin_type, + const std::vector<const sem::Type*>& args, + const Source& source) { + // Candidate holds information about a mismatched overload that could be what + // the user intended to call. + struct Candidate { + const OverloadInfo* overload; + int score; + }; + + // The list of failed matches that had promise. + std::vector<Candidate> candidates; + + auto& builtin = kBuiltins[static_cast<uint32_t>(builtin_type)]; + for (uint32_t o = 0; o < builtin.num_overloads; o++) { + int match_score = 1000; + auto& overload = builtin.overloads[o]; + if (auto* match = Match(builtin_type, overload, args, match_score)) { + return match; + } + if (match_score > 0) { + candidates.emplace_back(Candidate{&overload, match_score}); + } + } + + // Sort the candidates with the most promising first + std::stable_sort( + candidates.begin(), candidates.end(), + [](const Candidate& a, const Candidate& b) { return a.score > b.score; }); + + // Generate an error message + std::stringstream ss; + ss << "no matching call to " << CallSignature(builder, builtin_type, args) + << std::endl; + if (!candidates.empty()) { + ss << std::endl; + ss << candidates.size() << " candidate function" + << (candidates.size() > 1 ? "s:" : ":") << std::endl; + for (auto& candidate : candidates) { + ss << " "; + PrintOverload(ss, *candidate.overload, builtin_type); + ss << std::endl; + } + } + builder.Diagnostics().add_error(diag::System::Resolver, ss.str(), source); + return nullptr; +} + +const sem::Builtin* Impl::Match(sem::BuiltinType builtin_type, + const OverloadInfo& overload, + const std::vector<const sem::Type*>& args, + int& match_score) { + // Score wait for argument <-> parameter count matches / mismatches + constexpr int kScorePerParamArgMismatch = -1; + constexpr int kScorePerMatchedParam = 2; + constexpr int kScorePerMatchedOpenType = 1; + constexpr int kScorePerMatchedOpenNumber = 1; + + auto num_parameters = overload.num_parameters; + auto num_arguments = static_cast<decltype(num_parameters)>(args.size()); + + bool overload_matched = true; + + if (num_parameters != num_arguments) { + match_score += + kScorePerParamArgMismatch * (std::max(num_parameters, num_arguments) - + std::min(num_parameters, num_arguments)); + overload_matched = false; + } + + ClosedState closed(builder); + + std::vector<BuiltinPrototype::Parameter> parameters; + + auto num_params = std::min(num_parameters, num_arguments); + for (uint32_t p = 0; p < num_params; p++) { + auto& parameter = overload.parameters[p]; + auto* indices = parameter.matcher_indices; + auto* type = Match(closed, overload, indices).Type(args[p]->UnwrapRef()); + if (type) { + parameters.emplace_back( + BuiltinPrototype::Parameter{type, parameter.usage}); + match_score += kScorePerMatchedParam; + } else { + overload_matched = false; + } + } + + if (overload_matched) { + // Check all constrained open types matched + for (uint32_t ot = 0; ot < overload.num_open_types; ot++) { + auto& open_type = overload.open_types[ot]; + if (open_type.matcher_index != kNoMatcher) { + auto* index = &open_type.matcher_index; + if (Match(closed, overload, index).Type(closed.Type(ot))) { + match_score += kScorePerMatchedOpenType; + } else { + overload_matched = false; + } + } + } + } + + if (overload_matched) { + // Check all constrained open numbers matched + for (uint32_t on = 0; on < overload.num_open_numbers; on++) { + auto& open_number = overload.open_numbers[on]; + if (open_number.matcher_index != kNoMatcher) { + auto* index = &open_number.matcher_index; + if (Match(closed, overload, index).Num(closed.Num(on)).IsValid()) { + match_score += kScorePerMatchedOpenNumber; + } else { + overload_matched = false; + } + } + } + } + + if (!overload_matched) { + return nullptr; + } + + // Build the return type + const sem::Type* return_type = nullptr; + if (auto* indices = overload.return_matcher_indices) { + Any any; + return_type = Match(closed, overload, indices).Type(&any); + if (!return_type) { + std::stringstream ss; + PrintOverload(ss, overload, builtin_type); + TINT_ICE(Resolver, builder.Diagnostics()) + << "MatchState.Match() returned null for " << ss.str(); + return nullptr; + } + } else { + return_type = builder.create<sem::Void>(); + } + + BuiltinPrototype builtin; + builtin.type = builtin_type; + builtin.return_type = return_type; + builtin.parameters = std::move(parameters); + builtin.supported_stages = overload.supported_stages; + builtin.is_deprecated = overload.is_deprecated; + + // De-duplicate builtins that are identical. + return utils::GetOrCreate(builtins, builtin, [&] { + std::vector<sem::Parameter*> params; + params.reserve(builtin.parameters.size()); + for (auto& p : builtin.parameters) { + params.emplace_back(builder.create<sem::Parameter>( + nullptr, static_cast<uint32_t>(params.size()), p.type, + ast::StorageClass::kNone, ast::Access::kUndefined, p.usage)); + } + return builder.create<sem::Builtin>( + builtin.type, builtin.return_type, std::move(params), + builtin.supported_stages, builtin.is_deprecated); + }); +} + +MatchState Impl::Match(ClosedState& closed, + const OverloadInfo& overload, + MatcherIndex const* matcher_indices) const { + return MatchState(builder, closed, matchers, overload, matcher_indices); +} + +void Impl::PrintOverload(std::ostream& ss, + const OverloadInfo& overload, + sem::BuiltinType builtin_type) const { + ClosedState closed(builder); + + ss << builtin_type << "("; + for (uint32_t p = 0; p < overload.num_parameters; p++) { + auto& parameter = overload.parameters[p]; + if (p > 0) { + ss << ", "; + } + if (parameter.usage != ParameterUsage::kNone) { + ss << sem::str(parameter.usage) << ": "; + } + auto* indices = parameter.matcher_indices; + ss << Match(closed, overload, indices).TypeName(); + } + ss << ")"; + if (overload.return_matcher_indices) { + ss << " -> "; + auto* indices = overload.return_matcher_indices; + ss << Match(closed, overload, indices).TypeName(); + } + + bool first = true; + auto separator = [&] { + ss << (first ? " where: " : ", "); + first = false; + }; + for (uint32_t i = 0; i < overload.num_open_types; i++) { + auto& open_type = overload.open_types[i]; + if (open_type.matcher_index != kNoMatcher) { + separator(); + ss << open_type.name; + auto* index = &open_type.matcher_index; + ss << " is " << Match(closed, overload, index).TypeName(); + } + } + for (uint32_t i = 0; i < overload.num_open_numbers; i++) { + auto& open_number = overload.open_numbers[i]; + if (open_number.matcher_index != kNoMatcher) { + separator(); + ss << open_number.name; + auto* index = &open_number.matcher_index; + ss << " is " << Match(closed, overload, index).NumName(); + } + } +} + +const sem::Type* MatchState::Type(const sem::Type* ty) { + MatcherIndex matcher_index = *matcher_indices_++; + auto* matcher = matchers.type[matcher_index]; + return matcher->Match(*this, ty); +} + +Number MatchState::Num(Number number) { + MatcherIndex matcher_index = *matcher_indices_++; + auto* matcher = matchers.number[matcher_index]; + return matcher->Match(*this, number); +} + +std::string MatchState::TypeName() { + MatcherIndex matcher_index = *matcher_indices_++; + auto* matcher = matchers.type[matcher_index]; + return matcher->String(*this); +} + +std::string MatchState::NumName() { + MatcherIndex matcher_index = *matcher_indices_++; + auto* matcher = matchers.number[matcher_index]; + return matcher->String(*this); +} + +} // namespace + +std::unique_ptr<BuiltinTable> BuiltinTable::Create(ProgramBuilder& builder) { + return std::make_unique<Impl>(builder); +} + +BuiltinTable::~BuiltinTable() = default; + +/// TypeInfo for the Any type declared in the anonymous namespace above +TINT_INSTANTIATE_TYPEINFO(Any); + +} // namespace tint
diff --git a/src/tint/builtin_table.h b/src/tint/builtin_table.h new file mode 100644 index 0000000..246eed2 --- /dev/null +++ b/src/tint/builtin_table.h
@@ -0,0 +1,52 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_BUILTIN_TABLE_H_ +#define SRC_TINT_BUILTIN_TABLE_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "src/tint/sem/builtin.h" + +namespace tint { + +// Forward declarations +class ProgramBuilder; + +/// BuiltinTable is a lookup table of all the WGSL builtin functions +class BuiltinTable { + public: + /// @param builder the program builder + /// @return a pointer to a newly created BuiltinTable + static std::unique_ptr<BuiltinTable> Create(ProgramBuilder& builder); + + /// Destructor + virtual ~BuiltinTable(); + + /// Lookup looks for the builtin overload with the given signature, raising + /// an error diagnostic if the builtin was not found. + /// @param type the builtin type + /// @param args the argument types passed to the builtin function + /// @param source the source of the builtin call + /// @return the semantic builtin if found, otherwise nullptr + virtual const sem::Builtin* Lookup(sem::BuiltinType type, + const std::vector<const sem::Type*>& args, + const Source& source) = 0; +}; + +} // namespace tint + +#endif // SRC_TINT_BUILTIN_TABLE_H_
diff --git a/src/tint/builtin_table.inl b/src/tint/builtin_table.inl new file mode 100644 index 0000000..fa48359 --- /dev/null +++ b/src/tint/builtin_table.inl
@@ -0,0 +1,9638 @@ +// Copyright 2021 The Tint Authors. +// +// 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. + +//////////////////////////////////////////////////////////////////////////////// +// File generated by tools/builtin-gen +// using the template: +// src/tint/builtin_table.inl.tmpl +// and the builtin defintion file: +// src/tint/builtins.def +// +// Do not modify this file directly +//////////////////////////////////////////////////////////////////////////////// + +// clang-format off + +/// TypeMatcher for 'type bool' +/// @see src/tint/builtins.def:68:6 +class Bool : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* Bool::Match(MatchState& state, const sem::Type* ty) const { + if (!match_bool(ty)) { + return nullptr; + } + return build_bool(state); +} + +std::string Bool::String(MatchState&) const { + return "bool"; +} + +/// TypeMatcher for 'type f32' +/// @see src/tint/builtins.def:69:6 +class F32 : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* F32::Match(MatchState& state, const sem::Type* ty) const { + if (!match_f32(ty)) { + return nullptr; + } + return build_f32(state); +} + +std::string F32::String(MatchState&) const { + return "f32"; +} + +/// TypeMatcher for 'type i32' +/// @see src/tint/builtins.def:70:6 +class I32 : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* I32::Match(MatchState& state, const sem::Type* ty) const { + if (!match_i32(ty)) { + return nullptr; + } + return build_i32(state); +} + +std::string I32::String(MatchState&) const { + return "i32"; +} + +/// TypeMatcher for 'type u32' +/// @see src/tint/builtins.def:71:6 +class U32 : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* U32::Match(MatchState& state, const sem::Type* ty) const { + if (!match_u32(ty)) { + return nullptr; + } + return build_u32(state); +} + +std::string U32::String(MatchState&) const { + return "u32"; +} + +/// TypeMatcher for 'type vec2' +/// @see src/tint/builtins.def:72:6 +class Vec2 : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* Vec2::Match(MatchState& state, const sem::Type* ty) const { + const sem::Type* T = nullptr; + if (!match_vec2(ty, T)) { + return nullptr; + } + T = state.Type(T); + if (T == nullptr) { + return nullptr; + } + return build_vec2(state, T); +} + +std::string Vec2::String(MatchState& state) const { + const std::string T = state.TypeName(); + return "vec2<" + T + ">"; +} + +/// TypeMatcher for 'type vec3' +/// @see src/tint/builtins.def:73:6 +class Vec3 : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* Vec3::Match(MatchState& state, const sem::Type* ty) const { + const sem::Type* T = nullptr; + if (!match_vec3(ty, T)) { + return nullptr; + } + T = state.Type(T); + if (T == nullptr) { + return nullptr; + } + return build_vec3(state, T); +} + +std::string Vec3::String(MatchState& state) const { + const std::string T = state.TypeName(); + return "vec3<" + T + ">"; +} + +/// TypeMatcher for 'type vec4' +/// @see src/tint/builtins.def:74:6 +class Vec4 : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* Vec4::Match(MatchState& state, const sem::Type* ty) const { + const sem::Type* T = nullptr; + if (!match_vec4(ty, T)) { + return nullptr; + } + T = state.Type(T); + if (T == nullptr) { + return nullptr; + } + return build_vec4(state, T); +} + +std::string Vec4::String(MatchState& state) const { + const std::string T = state.TypeName(); + return "vec4<" + T + ">"; +} + +/// TypeMatcher for 'type vec' +/// @see src/tint/builtins.def:75:37 +class Vec : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* Vec::Match(MatchState& state, const sem::Type* ty) const { + Number N = Number::invalid; + const sem::Type* T = nullptr; + if (!match_vec(ty, N, T)) { + return nullptr; + } + N = state.Num(N); + if (!N.IsValid()) { + return nullptr; + } + T = state.Type(T); + if (T == nullptr) { + return nullptr; + } + return build_vec(state, N, T); +} + +std::string Vec::String(MatchState& state) const { + const std::string N = state.NumName(); + const std::string T = state.TypeName(); + std::stringstream ss; + ss << "vec" << N << "<" << T << ">"; + return ss.str(); +} + +/// TypeMatcher for 'type mat' +/// @see src/tint/builtins.def:76:37 +class Mat : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* Mat::Match(MatchState& state, const sem::Type* ty) const { + Number N = Number::invalid; + Number M = Number::invalid; + const sem::Type* T = nullptr; + if (!match_mat(ty, N, M, T)) { + return nullptr; + } + N = state.Num(N); + if (!N.IsValid()) { + return nullptr; + } + M = state.Num(M); + if (!M.IsValid()) { + return nullptr; + } + T = state.Type(T); + if (T == nullptr) { + return nullptr; + } + return build_mat(state, N, M, T); +} + +std::string Mat::String(MatchState& state) const { + const std::string N = state.NumName(); + const std::string M = state.NumName(); + const std::string T = state.TypeName(); + std::stringstream ss; + ss << "mat" << N << "x" << M << "<" << T << ">"; + return ss.str(); +} + +/// TypeMatcher for 'type ptr' +/// @see src/tint/builtins.def:77:6 +class Ptr : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* Ptr::Match(MatchState& state, const sem::Type* ty) const { + Number S = Number::invalid; + const sem::Type* T = nullptr; + Number A = Number::invalid; + if (!match_ptr(ty, S, T, A)) { + return nullptr; + } + S = state.Num(S); + if (!S.IsValid()) { + return nullptr; + } + T = state.Type(T); + if (T == nullptr) { + return nullptr; + } + A = state.Num(A); + if (!A.IsValid()) { + return nullptr; + } + return build_ptr(state, S, T, A); +} + +std::string Ptr::String(MatchState& state) const { + const std::string S = state.NumName(); + const std::string T = state.TypeName(); + const std::string A = state.NumName(); + return "ptr<" + S + ", " + T + ", " + A + ">"; +} + +/// TypeMatcher for 'type atomic' +/// @see src/tint/builtins.def:78:6 +class Atomic : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* Atomic::Match(MatchState& state, const sem::Type* ty) const { + const sem::Type* T = nullptr; + if (!match_atomic(ty, T)) { + return nullptr; + } + T = state.Type(T); + if (T == nullptr) { + return nullptr; + } + return build_atomic(state, T); +} + +std::string Atomic::String(MatchState& state) const { + const std::string T = state.TypeName(); + return "atomic<" + T + ">"; +} + +/// TypeMatcher for 'type array' +/// @see src/tint/builtins.def:79:6 +class Array : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* Array::Match(MatchState& state, const sem::Type* ty) const { + const sem::Type* T = nullptr; + if (!match_array(ty, T)) { + return nullptr; + } + T = state.Type(T); + if (T == nullptr) { + return nullptr; + } + return build_array(state, T); +} + +std::string Array::String(MatchState& state) const { + const std::string T = state.TypeName(); + return "array<" + T + ">"; +} + +/// TypeMatcher for 'type sampler' +/// @see src/tint/builtins.def:80:6 +class Sampler : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* Sampler::Match(MatchState& state, const sem::Type* ty) const { + if (!match_sampler(ty)) { + return nullptr; + } + return build_sampler(state); +} + +std::string Sampler::String(MatchState&) const { + return "sampler"; +} + +/// TypeMatcher for 'type sampler_comparison' +/// @see src/tint/builtins.def:81:6 +class SamplerComparison : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* SamplerComparison::Match(MatchState& state, const sem::Type* ty) const { + if (!match_sampler_comparison(ty)) { + return nullptr; + } + return build_sampler_comparison(state); +} + +std::string SamplerComparison::String(MatchState&) const { + return "sampler_comparison"; +} + +/// TypeMatcher for 'type texture_1d' +/// @see src/tint/builtins.def:82:6 +class Texture1D : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* Texture1D::Match(MatchState& state, const sem::Type* ty) const { + const sem::Type* T = nullptr; + if (!match_texture_1d(ty, T)) { + return nullptr; + } + T = state.Type(T); + if (T == nullptr) { + return nullptr; + } + return build_texture_1d(state, T); +} + +std::string Texture1D::String(MatchState& state) const { + const std::string T = state.TypeName(); + return "texture_1d<" + T + ">"; +} + +/// TypeMatcher for 'type texture_2d' +/// @see src/tint/builtins.def:83:6 +class Texture2D : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* Texture2D::Match(MatchState& state, const sem::Type* ty) const { + const sem::Type* T = nullptr; + if (!match_texture_2d(ty, T)) { + return nullptr; + } + T = state.Type(T); + if (T == nullptr) { + return nullptr; + } + return build_texture_2d(state, T); +} + +std::string Texture2D::String(MatchState& state) const { + const std::string T = state.TypeName(); + return "texture_2d<" + T + ">"; +} + +/// TypeMatcher for 'type texture_2d_array' +/// @see src/tint/builtins.def:84:6 +class Texture2DArray : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* Texture2DArray::Match(MatchState& state, const sem::Type* ty) const { + const sem::Type* T = nullptr; + if (!match_texture_2d_array(ty, T)) { + return nullptr; + } + T = state.Type(T); + if (T == nullptr) { + return nullptr; + } + return build_texture_2d_array(state, T); +} + +std::string Texture2DArray::String(MatchState& state) const { + const std::string T = state.TypeName(); + return "texture_2d_array<" + T + ">"; +} + +/// TypeMatcher for 'type texture_3d' +/// @see src/tint/builtins.def:85:6 +class Texture3D : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* Texture3D::Match(MatchState& state, const sem::Type* ty) const { + const sem::Type* T = nullptr; + if (!match_texture_3d(ty, T)) { + return nullptr; + } + T = state.Type(T); + if (T == nullptr) { + return nullptr; + } + return build_texture_3d(state, T); +} + +std::string Texture3D::String(MatchState& state) const { + const std::string T = state.TypeName(); + return "texture_3d<" + T + ">"; +} + +/// TypeMatcher for 'type texture_cube' +/// @see src/tint/builtins.def:86:6 +class TextureCube : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* TextureCube::Match(MatchState& state, const sem::Type* ty) const { + const sem::Type* T = nullptr; + if (!match_texture_cube(ty, T)) { + return nullptr; + } + T = state.Type(T); + if (T == nullptr) { + return nullptr; + } + return build_texture_cube(state, T); +} + +std::string TextureCube::String(MatchState& state) const { + const std::string T = state.TypeName(); + return "texture_cube<" + T + ">"; +} + +/// TypeMatcher for 'type texture_cube_array' +/// @see src/tint/builtins.def:87:6 +class TextureCubeArray : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* TextureCubeArray::Match(MatchState& state, const sem::Type* ty) const { + const sem::Type* T = nullptr; + if (!match_texture_cube_array(ty, T)) { + return nullptr; + } + T = state.Type(T); + if (T == nullptr) { + return nullptr; + } + return build_texture_cube_array(state, T); +} + +std::string TextureCubeArray::String(MatchState& state) const { + const std::string T = state.TypeName(); + return "texture_cube_array<" + T + ">"; +} + +/// TypeMatcher for 'type texture_multisampled_2d' +/// @see src/tint/builtins.def:88:6 +class TextureMultisampled2D : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* TextureMultisampled2D::Match(MatchState& state, const sem::Type* ty) const { + const sem::Type* T = nullptr; + if (!match_texture_multisampled_2d(ty, T)) { + return nullptr; + } + T = state.Type(T); + if (T == nullptr) { + return nullptr; + } + return build_texture_multisampled_2d(state, T); +} + +std::string TextureMultisampled2D::String(MatchState& state) const { + const std::string T = state.TypeName(); + return "texture_multisampled_2d<" + T + ">"; +} + +/// TypeMatcher for 'type texture_depth_2d' +/// @see src/tint/builtins.def:89:6 +class TextureDepth2D : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* TextureDepth2D::Match(MatchState& state, const sem::Type* ty) const { + if (!match_texture_depth_2d(ty)) { + return nullptr; + } + return build_texture_depth_2d(state); +} + +std::string TextureDepth2D::String(MatchState&) const { + return "texture_depth_2d"; +} + +/// TypeMatcher for 'type texture_depth_2d_array' +/// @see src/tint/builtins.def:90:6 +class TextureDepth2DArray : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* TextureDepth2DArray::Match(MatchState& state, const sem::Type* ty) const { + if (!match_texture_depth_2d_array(ty)) { + return nullptr; + } + return build_texture_depth_2d_array(state); +} + +std::string TextureDepth2DArray::String(MatchState&) const { + return "texture_depth_2d_array"; +} + +/// TypeMatcher for 'type texture_depth_cube' +/// @see src/tint/builtins.def:91:6 +class TextureDepthCube : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* TextureDepthCube::Match(MatchState& state, const sem::Type* ty) const { + if (!match_texture_depth_cube(ty)) { + return nullptr; + } + return build_texture_depth_cube(state); +} + +std::string TextureDepthCube::String(MatchState&) const { + return "texture_depth_cube"; +} + +/// TypeMatcher for 'type texture_depth_cube_array' +/// @see src/tint/builtins.def:92:6 +class TextureDepthCubeArray : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* TextureDepthCubeArray::Match(MatchState& state, const sem::Type* ty) const { + if (!match_texture_depth_cube_array(ty)) { + return nullptr; + } + return build_texture_depth_cube_array(state); +} + +std::string TextureDepthCubeArray::String(MatchState&) const { + return "texture_depth_cube_array"; +} + +/// TypeMatcher for 'type texture_depth_multisampled_2d' +/// @see src/tint/builtins.def:93:6 +class TextureDepthMultisampled2D : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* TextureDepthMultisampled2D::Match(MatchState& state, const sem::Type* ty) const { + if (!match_texture_depth_multisampled_2d(ty)) { + return nullptr; + } + return build_texture_depth_multisampled_2d(state); +} + +std::string TextureDepthMultisampled2D::String(MatchState&) const { + return "texture_depth_multisampled_2d"; +} + +/// TypeMatcher for 'type texture_storage_1d' +/// @see src/tint/builtins.def:94:6 +class TextureStorage1D : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* TextureStorage1D::Match(MatchState& state, const sem::Type* ty) const { + Number F = Number::invalid; + Number A = Number::invalid; + if (!match_texture_storage_1d(ty, F, A)) { + return nullptr; + } + F = state.Num(F); + if (!F.IsValid()) { + return nullptr; + } + A = state.Num(A); + if (!A.IsValid()) { + return nullptr; + } + return build_texture_storage_1d(state, F, A); +} + +std::string TextureStorage1D::String(MatchState& state) const { + const std::string F = state.NumName(); + const std::string A = state.NumName(); + return "texture_storage_1d<" + F + ", " + A + ">"; +} + +/// TypeMatcher for 'type texture_storage_2d' +/// @see src/tint/builtins.def:95:6 +class TextureStorage2D : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* TextureStorage2D::Match(MatchState& state, const sem::Type* ty) const { + Number F = Number::invalid; + Number A = Number::invalid; + if (!match_texture_storage_2d(ty, F, A)) { + return nullptr; + } + F = state.Num(F); + if (!F.IsValid()) { + return nullptr; + } + A = state.Num(A); + if (!A.IsValid()) { + return nullptr; + } + return build_texture_storage_2d(state, F, A); +} + +std::string TextureStorage2D::String(MatchState& state) const { + const std::string F = state.NumName(); + const std::string A = state.NumName(); + return "texture_storage_2d<" + F + ", " + A + ">"; +} + +/// TypeMatcher for 'type texture_storage_2d_array' +/// @see src/tint/builtins.def:96:6 +class TextureStorage2DArray : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* TextureStorage2DArray::Match(MatchState& state, const sem::Type* ty) const { + Number F = Number::invalid; + Number A = Number::invalid; + if (!match_texture_storage_2d_array(ty, F, A)) { + return nullptr; + } + F = state.Num(F); + if (!F.IsValid()) { + return nullptr; + } + A = state.Num(A); + if (!A.IsValid()) { + return nullptr; + } + return build_texture_storage_2d_array(state, F, A); +} + +std::string TextureStorage2DArray::String(MatchState& state) const { + const std::string F = state.NumName(); + const std::string A = state.NumName(); + return "texture_storage_2d_array<" + F + ", " + A + ">"; +} + +/// TypeMatcher for 'type texture_storage_3d' +/// @see src/tint/builtins.def:97:6 +class TextureStorage3D : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* TextureStorage3D::Match(MatchState& state, const sem::Type* ty) const { + Number F = Number::invalid; + Number A = Number::invalid; + if (!match_texture_storage_3d(ty, F, A)) { + return nullptr; + } + F = state.Num(F); + if (!F.IsValid()) { + return nullptr; + } + A = state.Num(A); + if (!A.IsValid()) { + return nullptr; + } + return build_texture_storage_3d(state, F, A); +} + +std::string TextureStorage3D::String(MatchState& state) const { + const std::string F = state.NumName(); + const std::string A = state.NumName(); + return "texture_storage_3d<" + F + ", " + A + ">"; +} + +/// TypeMatcher for 'type texture_external' +/// @see src/tint/builtins.def:98:6 +class TextureExternal : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* TextureExternal::Match(MatchState& state, const sem::Type* ty) const { + if (!match_texture_external(ty)) { + return nullptr; + } + return build_texture_external(state); +} + +std::string TextureExternal::String(MatchState&) const { + return "texture_external"; +} + +/// TypeMatcher for 'type __modf_result' +/// @see src/tint/builtins.def:100:6 +class ModfResult : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* ModfResult::Match(MatchState& state, const sem::Type* ty) const { + if (!match_modf_result(ty)) { + return nullptr; + } + return build_modf_result(state); +} + +std::string ModfResult::String(MatchState&) const { + return "__modf_result"; +} + +/// TypeMatcher for 'type __modf_result_vec' +/// @see src/tint/builtins.def:101:42 +class ModfResultVec : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* ModfResultVec::Match(MatchState& state, const sem::Type* ty) const { + Number N = Number::invalid; + if (!match_modf_result_vec(ty, N)) { + return nullptr; + } + N = state.Num(N); + if (!N.IsValid()) { + return nullptr; + } + return build_modf_result_vec(state, N); +} + +std::string ModfResultVec::String(MatchState& state) const { + const std::string N = state.NumName(); + std::stringstream ss; + ss << "__modf_result_vec" << N; + return ss.str(); +} + +/// TypeMatcher for 'type __frexp_result' +/// @see src/tint/builtins.def:102:6 +class FrexpResult : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* FrexpResult::Match(MatchState& state, const sem::Type* ty) const { + if (!match_frexp_result(ty)) { + return nullptr; + } + return build_frexp_result(state); +} + +std::string FrexpResult::String(MatchState&) const { + return "__frexp_result"; +} + +/// TypeMatcher for 'type __frexp_result_vec' +/// @see src/tint/builtins.def:103:43 +class FrexpResultVec : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* FrexpResultVec::Match(MatchState& state, const sem::Type* ty) const { + Number N = Number::invalid; + if (!match_frexp_result_vec(ty, N)) { + return nullptr; + } + N = state.Num(N); + if (!N.IsValid()) { + return nullptr; + } + return build_frexp_result_vec(state, N); +} + +std::string FrexpResultVec::String(MatchState& state) const { + const std::string N = state.NumName(); + std::stringstream ss; + ss << "__frexp_result_vec" << N; + return ss.str(); +} + +/// TypeMatcher for 'match fiu32' +/// @see src/tint/builtins.def:111:7 +class Fiu32 : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules, and returns the + /// expected, canonicalized type on success. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* Fiu32::Match(MatchState& state, const sem::Type* ty) const { + if (match_f32(ty)) { + return build_f32(state); + } + if (match_i32(ty)) { + return build_i32(state); + } + if (match_u32(ty)) { + return build_u32(state); + } + return nullptr; +} + +std::string Fiu32::String(MatchState&) const { + return "f32, i32 or u32"; +} + +/// TypeMatcher for 'match iu32' +/// @see src/tint/builtins.def:112:7 +class Iu32 : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules, and returns the + /// expected, canonicalized type on success. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* Iu32::Match(MatchState& state, const sem::Type* ty) const { + if (match_i32(ty)) { + return build_i32(state); + } + if (match_u32(ty)) { + return build_u32(state); + } + return nullptr; +} + +std::string Iu32::String(MatchState&) const { + return "i32 or u32"; +} + +/// TypeMatcher for 'match scalar' +/// @see src/tint/builtins.def:113:7 +class Scalar : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules, and returns the + /// expected, canonicalized type on success. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* Scalar::Match(MatchState& state, const sem::Type* ty) const { + if (match_f32(ty)) { + return build_f32(state); + } + if (match_i32(ty)) { + return build_i32(state); + } + if (match_u32(ty)) { + return build_u32(state); + } + if (match_bool(ty)) { + return build_bool(state); + } + return nullptr; +} + +std::string Scalar::String(MatchState&) const { + return "f32, i32, u32 or bool"; +} + +/// EnumMatcher for 'match f32_texel_format' +/// @see src/tint/builtins.def:124:7 +class F32TexelFormat : public NumberMatcher { + public: + /// Checks whether the given number matches the enum matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param number the enum value as a Number + /// @return true if the enum value matches the set + Number Match(MatchState& state, Number number) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +Number F32TexelFormat::Match(MatchState&, Number number) const { + switch (static_cast<TexelFormat>(number.Value())) { + case TexelFormat::kRgba8Unorm: + case TexelFormat::kRgba8Snorm: + case TexelFormat::kRgba16Float: + case TexelFormat::kR32Float: + case TexelFormat::kRg32Float: + case TexelFormat::kRgba32Float: + return number; + default: + return Number::invalid; + } +} + +std::string F32TexelFormat::String(MatchState&) const { + return "rgba8unorm, rgba8snorm, rgba16float, r32float, rg32float or rgba32float"; +} + +/// EnumMatcher for 'match i32_texel_format' +/// @see src/tint/builtins.def:126:7 +class I32TexelFormat : public NumberMatcher { + public: + /// Checks whether the given number matches the enum matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param number the enum value as a Number + /// @return true if the enum value matches the set + Number Match(MatchState& state, Number number) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +Number I32TexelFormat::Match(MatchState&, Number number) const { + switch (static_cast<TexelFormat>(number.Value())) { + case TexelFormat::kRgba8Sint: + case TexelFormat::kRgba16Sint: + case TexelFormat::kR32Sint: + case TexelFormat::kRg32Sint: + case TexelFormat::kRgba32Sint: + return number; + default: + return Number::invalid; + } +} + +std::string I32TexelFormat::String(MatchState&) const { + return "rgba8sint, rgba16sint, r32sint, rg32sint or rgba32sint"; +} + +/// EnumMatcher for 'match u32_texel_format' +/// @see src/tint/builtins.def:128:7 +class U32TexelFormat : public NumberMatcher { + public: + /// Checks whether the given number matches the enum matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param number the enum value as a Number + /// @return true if the enum value matches the set + Number Match(MatchState& state, Number number) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +Number U32TexelFormat::Match(MatchState&, Number number) const { + switch (static_cast<TexelFormat>(number.Value())) { + case TexelFormat::kRgba8Uint: + case TexelFormat::kRgba16Uint: + case TexelFormat::kR32Uint: + case TexelFormat::kRg32Uint: + case TexelFormat::kRgba32Uint: + return number; + default: + return Number::invalid; + } +} + +std::string U32TexelFormat::String(MatchState&) const { + return "rgba8uint, rgba16uint, r32uint, rg32uint or rgba32uint"; +} + +/// EnumMatcher for 'match write_only' +/// @see src/tint/builtins.def:131:7 +class WriteOnly : public NumberMatcher { + public: + /// Checks whether the given number matches the enum matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param number the enum value as a Number + /// @return true if the enum value matches the set + Number Match(MatchState& state, Number number) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +Number WriteOnly::Match(MatchState&, Number number) const { + if (number.IsAny() || number.Value() == static_cast<uint32_t>(Access::kWrite)) { + return Number(static_cast<uint32_t>(Access::kWrite)); + } + return Number::invalid; +} + +std::string WriteOnly::String(MatchState&) const { + return "write"; +} + +/// EnumMatcher for 'match function_private_workgroup' +/// @see src/tint/builtins.def:133:7 +class FunctionPrivateWorkgroup : public NumberMatcher { + public: + /// Checks whether the given number matches the enum matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param number the enum value as a Number + /// @return true if the enum value matches the set + Number Match(MatchState& state, Number number) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +Number FunctionPrivateWorkgroup::Match(MatchState&, Number number) const { + switch (static_cast<StorageClass>(number.Value())) { + case StorageClass::kFunction: + case StorageClass::kPrivate: + case StorageClass::kWorkgroup: + return number; + default: + return Number::invalid; + } +} + +std::string FunctionPrivateWorkgroup::String(MatchState&) const { + return "function, private or workgroup"; +} + +/// EnumMatcher for 'match workgroup_or_storage' +/// @see src/tint/builtins.def:134:7 +class WorkgroupOrStorage : public NumberMatcher { + public: + /// Checks whether the given number matches the enum matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param number the enum value as a Number + /// @return true if the enum value matches the set + Number Match(MatchState& state, Number number) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +Number WorkgroupOrStorage::Match(MatchState&, Number number) const { + switch (static_cast<StorageClass>(number.Value())) { + case StorageClass::kWorkgroup: + case StorageClass::kStorage: + return number; + default: + return Number::invalid; + } +} + +std::string WorkgroupOrStorage::String(MatchState&) const { + return "workgroup or storage"; +} + +/// EnumMatcher for 'match storage' +class Storage : public NumberMatcher { + public: + /// Checks whether the given number matches the enum matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param number the enum value as a Number + /// @return true if the enum value matches the set + Number Match(MatchState& state, Number number) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +Number Storage::Match(MatchState&, Number number) const { + if (number.IsAny() || number.Value() == static_cast<uint32_t>(StorageClass::kStorage)) { + return Number(static_cast<uint32_t>(StorageClass::kStorage)); + } + return Number::invalid; +} + +std::string Storage::String(MatchState&) const { + return "storage"; +} + +/// EnumMatcher for 'match write' +class Write : public NumberMatcher { + public: + /// Checks whether the given number matches the enum matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param number the enum value as a Number + /// @return true if the enum value matches the set + Number Match(MatchState& state, Number number) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +Number Write::Match(MatchState&, Number number) const { + if (number.IsAny() || number.Value() == static_cast<uint32_t>(Access::kWrite)) { + return Number(static_cast<uint32_t>(Access::kWrite)); + } + return Number::invalid; +} + +std::string Write::String(MatchState&) const { + return "write"; +} + +/// EnumMatcher for 'match read_write' +class ReadWrite : public NumberMatcher { + public: + /// Checks whether the given number matches the enum matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param number the enum value as a Number + /// @return true if the enum value matches the set + Number Match(MatchState& state, Number number) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +Number ReadWrite::Match(MatchState&, Number number) const { + if (number.IsAny() || number.Value() == static_cast<uint32_t>(Access::kReadWrite)) { + return Number(static_cast<uint32_t>(Access::kReadWrite)); + } + return Number::invalid; +} + +std::string ReadWrite::String(MatchState&) const { + return "read_write"; +} + +/// Matchers holds type and number matchers +class Matchers { + private: + OpenTypeMatcher open_type_0_{0}; + OpenNumberMatcher open_number_0_{0}; + OpenNumberMatcher open_number_1_{1}; + Bool Bool_; + F32 F32_; + I32 I32_; + U32 U32_; + Vec2 Vec2_; + Vec3 Vec3_; + Vec4 Vec4_; + Vec Vec_; + Mat Mat_; + Ptr Ptr_; + Atomic Atomic_; + Array Array_; + Sampler Sampler_; + SamplerComparison SamplerComparison_; + Texture1D Texture1D_; + Texture2D Texture2D_; + Texture2DArray Texture2DArray_; + Texture3D Texture3D_; + TextureCube TextureCube_; + TextureCubeArray TextureCubeArray_; + TextureMultisampled2D TextureMultisampled2D_; + TextureDepth2D TextureDepth2D_; + TextureDepth2DArray TextureDepth2DArray_; + TextureDepthCube TextureDepthCube_; + TextureDepthCubeArray TextureDepthCubeArray_; + TextureDepthMultisampled2D TextureDepthMultisampled2D_; + TextureStorage1D TextureStorage1D_; + TextureStorage2D TextureStorage2D_; + TextureStorage2DArray TextureStorage2DArray_; + TextureStorage3D TextureStorage3D_; + TextureExternal TextureExternal_; + ModfResult ModfResult_; + ModfResultVec ModfResultVec_; + FrexpResult FrexpResult_; + FrexpResultVec FrexpResultVec_; + Fiu32 Fiu32_; + Iu32 Iu32_; + Scalar Scalar_; + F32TexelFormat F32TexelFormat_; + I32TexelFormat I32TexelFormat_; + U32TexelFormat U32TexelFormat_; + WriteOnly WriteOnly_; + FunctionPrivateWorkgroup FunctionPrivateWorkgroup_; + WorkgroupOrStorage WorkgroupOrStorage_; + Storage Storage_; + Write Write_; + ReadWrite ReadWrite_; + + public: + /// Constructor + Matchers(); + /// Destructor + ~Matchers(); + + /// The open-types, types, and type matchers + TypeMatcher const* const type[39] = { + /* [0] */ &open_type_0_, + /* [1] */ &Bool_, + /* [2] */ &F32_, + /* [3] */ &I32_, + /* [4] */ &U32_, + /* [5] */ &Vec2_, + /* [6] */ &Vec3_, + /* [7] */ &Vec4_, + /* [8] */ &Vec_, + /* [9] */ &Mat_, + /* [10] */ &Ptr_, + /* [11] */ &Atomic_, + /* [12] */ &Array_, + /* [13] */ &Sampler_, + /* [14] */ &SamplerComparison_, + /* [15] */ &Texture1D_, + /* [16] */ &Texture2D_, + /* [17] */ &Texture2DArray_, + /* [18] */ &Texture3D_, + /* [19] */ &TextureCube_, + /* [20] */ &TextureCubeArray_, + /* [21] */ &TextureMultisampled2D_, + /* [22] */ &TextureDepth2D_, + /* [23] */ &TextureDepth2DArray_, + /* [24] */ &TextureDepthCube_, + /* [25] */ &TextureDepthCubeArray_, + /* [26] */ &TextureDepthMultisampled2D_, + /* [27] */ &TextureStorage1D_, + /* [28] */ &TextureStorage2D_, + /* [29] */ &TextureStorage2DArray_, + /* [30] */ &TextureStorage3D_, + /* [31] */ &TextureExternal_, + /* [32] */ &ModfResult_, + /* [33] */ &ModfResultVec_, + /* [34] */ &FrexpResult_, + /* [35] */ &FrexpResultVec_, + /* [36] */ &Fiu32_, + /* [37] */ &Iu32_, + /* [38] */ &Scalar_, + }; + + /// The open-numbers, and number matchers + NumberMatcher const* const number[11] = { + /* [0] */ &open_number_0_, + /* [1] */ &open_number_1_, + /* [2] */ &F32TexelFormat_, + /* [3] */ &I32TexelFormat_, + /* [4] */ &U32TexelFormat_, + /* [5] */ &WriteOnly_, + /* [6] */ &FunctionPrivateWorkgroup_, + /* [7] */ &WorkgroupOrStorage_, + /* [8] */ &Storage_, + /* [9] */ &Write_, + /* [10] */ &ReadWrite_, + }; +}; + +Matchers::Matchers() = default; +Matchers::~Matchers() = default; + +constexpr MatcherIndex kMatcherIndices[] = { + /* [0] */ 10, + /* [1] */ 0, + /* [2] */ 11, + /* [3] */ 0, + /* [4] */ 10, + /* [5] */ 8, + /* [6] */ 12, + /* [7] */ 0, + /* [8] */ 0, + /* [9] */ 9, + /* [10] */ 1, + /* [11] */ 0, + /* [12] */ 2, + /* [13] */ 9, + /* [14] */ 0, + /* [15] */ 1, + /* [16] */ 2, + /* [17] */ 9, + /* [18] */ 0, + /* [19] */ 0, + /* [20] */ 2, + /* [21] */ 8, + /* [22] */ 0, + /* [23] */ 2, + /* [24] */ 8, + /* [25] */ 0, + /* [26] */ 1, + /* [27] */ 29, + /* [28] */ 0, + /* [29] */ 1, + /* [30] */ 30, + /* [31] */ 0, + /* [32] */ 1, + /* [33] */ 28, + /* [34] */ 0, + /* [35] */ 1, + /* [36] */ 27, + /* [37] */ 0, + /* [38] */ 1, + /* [39] */ 8, + /* [40] */ 0, + /* [41] */ 0, + /* [42] */ 30, + /* [43] */ 4, + /* [44] */ 9, + /* [45] */ 29, + /* [46] */ 4, + /* [47] */ 9, + /* [48] */ 28, + /* [49] */ 4, + /* [50] */ 9, + /* [51] */ 27, + /* [52] */ 4, + /* [53] */ 9, + /* [54] */ 30, + /* [55] */ 3, + /* [56] */ 9, + /* [57] */ 29, + /* [58] */ 3, + /* [59] */ 9, + /* [60] */ 28, + /* [61] */ 3, + /* [62] */ 9, + /* [63] */ 27, + /* [64] */ 3, + /* [65] */ 9, + /* [66] */ 30, + /* [67] */ 2, + /* [68] */ 9, + /* [69] */ 29, + /* [70] */ 2, + /* [71] */ 9, + /* [72] */ 28, + /* [73] */ 2, + /* [74] */ 9, + /* [75] */ 27, + /* [76] */ 2, + /* [77] */ 9, + /* [78] */ 8, + /* [79] */ 0, + /* [80] */ 3, + /* [81] */ 7, + /* [82] */ 2, + /* [83] */ 17, + /* [84] */ 2, + /* [85] */ 5, + /* [86] */ 3, + /* [87] */ 5, + /* [88] */ 2, + /* [89] */ 16, + /* [90] */ 2, + /* [91] */ 6, + /* [92] */ 2, + /* [93] */ 18, + /* [94] */ 2, + /* [95] */ 20, + /* [96] */ 2, + /* [97] */ 19, + /* [98] */ 2, + /* [99] */ 6, + /* [100] */ 3, + /* [101] */ 35, + /* [102] */ 0, + /* [103] */ 33, + /* [104] */ 0, + /* [105] */ 5, + /* [106] */ 0, + /* [107] */ 7, + /* [108] */ 3, + /* [109] */ 7, + /* [110] */ 4, + /* [111] */ 15, + /* [112] */ 0, + /* [113] */ 7, + /* [114] */ 0, + /* [115] */ 16, + /* [116] */ 0, + /* [117] */ 17, + /* [118] */ 0, + /* [119] */ 18, + /* [120] */ 0, + /* [121] */ 21, + /* [122] */ 0, + /* [123] */ 19, + /* [124] */ 0, + /* [125] */ 20, + /* [126] */ 0, + /* [127] */ 15, + /* [128] */ 2, + /* [129] */ 14, + /* [130] */ 24, + /* [131] */ 23, + /* [132] */ 25, + /* [133] */ 22, + /* [134] */ 26, + /* [135] */ 13, + /* [136] */ 31, + /* [137] */ 32, + /* [138] */ 34, +}; + +// Assert that the MatcherIndex is big enough to index all the matchers, plus +// kNoMatcher. +static_assert(static_cast<int>(sizeof(kMatcherIndices) / sizeof(kMatcherIndices[0])) < + static_cast<int>(std::numeric_limits<MatcherIndex>::max() - 1), + "MatcherIndex is not large enough to index kMatcherIndices"); + +constexpr ParameterInfo kParameters[] = { + { + /* [0] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[83], + }, + { + /* [1] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [2] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [3] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [4] */ + /* usage */ ParameterUsage::kDdx, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [5] */ + /* usage */ ParameterUsage::kDdy, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [6] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [7] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[131], + }, + { + /* [8] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[129], + }, + { + /* [9] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [10] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [11] */ + /* usage */ ParameterUsage::kDepthRef, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [12] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [13] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[95], + }, + { + /* [14] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [15] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [16] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [17] */ + /* usage */ ParameterUsage::kDdx, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [18] */ + /* usage */ ParameterUsage::kDdy, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [19] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[93], + }, + { + /* [20] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [21] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [22] */ + /* usage */ ParameterUsage::kDdx, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [23] */ + /* usage */ ParameterUsage::kDdy, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [24] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[99], + }, + { + /* [25] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[83], + }, + { + /* [26] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [27] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [28] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [29] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [30] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [31] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[83], + }, + { + /* [32] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [33] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [34] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [35] */ + /* usage */ ParameterUsage::kDdx, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [36] */ + /* usage */ ParameterUsage::kDdy, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [37] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[83], + }, + { + /* [38] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [39] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [40] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [41] */ + /* usage */ ParameterUsage::kBias, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [42] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [43] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[89], + }, + { + /* [44] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [45] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [46] */ + /* usage */ ParameterUsage::kDdx, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [47] */ + /* usage */ ParameterUsage::kDdy, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [48] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [49] */ + /* usage */ ParameterUsage::kComponent, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [50] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[117], + }, + { + /* [51] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [52] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [53] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [54] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [55] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[131], + }, + { + /* [56] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [57] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [58] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [59] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [60] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [61] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[131], + }, + { + /* [62] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[129], + }, + { + /* [63] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [64] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [65] */ + /* usage */ ParameterUsage::kDepthRef, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [66] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [67] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[131], + }, + { + /* [68] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[129], + }, + { + /* [69] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [70] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [71] */ + /* usage */ ParameterUsage::kDepthRef, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [72] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [73] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[93], + }, + { + /* [74] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [75] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [76] */ + /* usage */ ParameterUsage::kDdx, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [77] */ + /* usage */ ParameterUsage::kDdy, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [78] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[133], + }, + { + /* [79] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[129], + }, + { + /* [80] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [81] */ + /* usage */ ParameterUsage::kDepthRef, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [82] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [83] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[133], + }, + { + /* [84] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [85] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [86] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [87] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [88] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[95], + }, + { + /* [89] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [90] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [91] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [92] */ + /* usage */ ParameterUsage::kBias, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [93] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[131], + }, + { + /* [94] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[129], + }, + { + /* [95] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [96] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [97] */ + /* usage */ ParameterUsage::kDepthRef, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [98] */ + /* usage */ ParameterUsage::kComponent, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [99] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[125], + }, + { + /* [100] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [101] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [102] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [103] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[132], + }, + { + /* [104] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[129], + }, + { + /* [105] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [106] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [107] */ + /* usage */ ParameterUsage::kDepthRef, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [108] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[93], + }, + { + /* [109] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [110] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [111] */ + /* usage */ ParameterUsage::kBias, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [112] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[99], + }, + { + /* [113] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[131], + }, + { + /* [114] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [115] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [116] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [117] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [118] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[133], + }, + { + /* [119] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[129], + }, + { + /* [120] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [121] */ + /* usage */ ParameterUsage::kDepthRef, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [122] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [123] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[132], + }, + { + /* [124] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[129], + }, + { + /* [125] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [126] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [127] */ + /* usage */ ParameterUsage::kDepthRef, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [128] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[131], + }, + { + /* [129] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[129], + }, + { + /* [130] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [131] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [132] */ + /* usage */ ParameterUsage::kDepthRef, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [133] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[132], + }, + { + /* [134] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [135] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [136] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [137] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [138] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[83], + }, + { + /* [139] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [140] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [141] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [142] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [143] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[133], + }, + { + /* [144] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[129], + }, + { + /* [145] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [146] */ + /* usage */ ParameterUsage::kDepthRef, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [147] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [148] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[131], + }, + { + /* [149] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [150] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [151] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [152] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [153] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[132], + }, + { + /* [154] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[129], + }, + { + /* [155] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [156] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [157] */ + /* usage */ ParameterUsage::kDepthRef, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [158] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[89], + }, + { + /* [159] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [160] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [161] */ + /* usage */ ParameterUsage::kDdx, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [162] */ + /* usage */ ParameterUsage::kDdy, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [163] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[95], + }, + { + /* [164] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [165] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [166] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [167] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [168] */ + /* usage */ ParameterUsage::kComponent, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [169] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[117], + }, + { + /* [170] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [171] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [172] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [173] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[93], + }, + { + /* [174] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [175] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [176] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [177] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[99], + }, + { + /* [178] */ + /* usage */ ParameterUsage::kComponent, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [179] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[115], + }, + { + /* [180] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [181] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [182] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [183] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[131], + }, + { + /* [184] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [185] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [186] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [187] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [188] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[83], + }, + { + /* [189] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [190] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [191] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [192] */ + /* usage */ ParameterUsage::kBias, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [193] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[89], + }, + { + /* [194] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [195] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [196] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [197] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [198] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[89], + }, + { + /* [199] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [200] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [201] */ + /* usage */ ParameterUsage::kBias, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [202] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [203] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[131], + }, + { + /* [204] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[129], + }, + { + /* [205] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [206] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [207] */ + /* usage */ ParameterUsage::kDepthRef, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [208] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[97], + }, + { + /* [209] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [210] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [211] */ + /* usage */ ParameterUsage::kDdx, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [212] */ + /* usage */ ParameterUsage::kDdy, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [213] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[83], + }, + { + /* [214] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [215] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [216] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [217] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [218] */ + /* usage */ ParameterUsage::kComponent, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [219] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[123], + }, + { + /* [220] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [221] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [222] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[89], + }, + { + /* [223] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [224] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [225] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [226] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[93], + }, + { + /* [227] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [228] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [229] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [230] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[97], + }, + { + /* [231] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [232] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [233] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [234] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[133], + }, + { + /* [235] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [236] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [237] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [238] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[83], + }, + { + /* [239] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [240] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [241] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [242] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[130], + }, + { + /* [243] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[129], + }, + { + /* [244] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [245] */ + /* usage */ ParameterUsage::kDepthRef, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [246] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[130], + }, + { + /* [247] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [248] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [249] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [250] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[89], + }, + { + /* [251] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [252] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [253] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [254] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[130], + }, + { + /* [255] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[129], + }, + { + /* [256] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [257] */ + /* usage */ ParameterUsage::kDepthRef, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [258] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[133], + }, + { + /* [259] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[129], + }, + { + /* [260] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [261] */ + /* usage */ ParameterUsage::kDepthRef, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [262] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[69], + }, + { + /* [263] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [264] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [265] */ + /* usage */ ParameterUsage::kValue, + /* matcher indices */ &kMatcherIndices[81], + }, + { + /* [266] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[93], + }, + { + /* [267] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [268] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [269] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[99], + }, + { + /* [270] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[95], + }, + { + /* [271] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [272] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [273] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [274] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [275] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [276] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[43], + }, + { + /* [277] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[43], + }, + { + /* [278] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[130], + }, + { + /* [279] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[129], + }, + { + /* [280] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [281] */ + /* usage */ ParameterUsage::kDepthRef, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [282] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[133], + }, + { + /* [283] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[129], + }, + { + /* [284] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [285] */ + /* usage */ ParameterUsage::kDepthRef, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [286] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[132], + }, + { + /* [287] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [288] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [289] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [290] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[131], + }, + { + /* [291] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [292] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [293] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [294] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[133], + }, + { + /* [295] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[129], + }, + { + /* [296] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [297] */ + /* usage */ ParameterUsage::kDepthRef, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [298] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[133], + }, + { + /* [299] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [300] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [301] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [302] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[131], + }, + { + /* [303] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [304] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [305] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [306] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[133], + }, + { + /* [307] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [308] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [309] */ + /* usage */ ParameterUsage::kOffset, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [310] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[97], + }, + { + /* [311] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [312] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [313] */ + /* usage */ ParameterUsage::kBias, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [314] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[45], + }, + { + /* [315] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [316] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [317] */ + /* usage */ ParameterUsage::kValue, + /* matcher indices */ &kMatcherIndices[109], + }, + { + /* [318] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[93], + }, + { + /* [319] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [320] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [321] */ + /* usage */ ParameterUsage::kBias, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [322] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[117], + }, + { + /* [323] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [324] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [325] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [326] */ + /* usage */ ParameterUsage::kComponent, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [327] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[115], + }, + { + /* [328] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [329] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [330] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[131], + }, + { + /* [331] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [332] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [333] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [334] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[89], + }, + { + /* [335] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [336] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [337] */ + /* usage */ ParameterUsage::kBias, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [338] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[132], + }, + { + /* [339] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [340] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [341] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [342] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [343] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [344] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[43], + }, + { + /* [345] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[43], + }, + { + /* [346] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[57], + }, + { + /* [347] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [348] */ + /* usage */ ParameterUsage::kArrayIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [349] */ + /* usage */ ParameterUsage::kValue, + /* matcher indices */ &kMatcherIndices[107], + }, + { + /* [350] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[130], + }, + { + /* [351] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [352] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [353] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[0], + }, + { + /* [354] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [355] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [356] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [357] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [358] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [359] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [360] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [361] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [362] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[133], + }, + { + /* [363] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [364] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [365] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [366] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [367] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [368] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[97], + }, + { + /* [369] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [370] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [371] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [372] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[43], + }, + { + /* [373] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[43], + }, + { + /* [374] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[93], + }, + { + /* [375] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [376] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [377] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [378] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[43], + }, + { + /* [379] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[43], + }, + { + /* [380] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [381] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [382] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [383] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[136], + }, + { + /* [384] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [385] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [386] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[89], + }, + { + /* [387] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [388] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [389] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[127], + }, + { + /* [390] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [391] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [392] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[75], + }, + { + /* [393] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [394] */ + /* usage */ ParameterUsage::kValue, + /* matcher indices */ &kMatcherIndices[81], + }, + { + /* [395] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [396] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [397] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [398] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [399] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [400] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [401] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [402] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [403] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [404] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[72], + }, + { + /* [405] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [406] */ + /* usage */ ParameterUsage::kValue, + /* matcher indices */ &kMatcherIndices[81], + }, + { + /* [407] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[66], + }, + { + /* [408] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[99], + }, + { + /* [409] */ + /* usage */ ParameterUsage::kValue, + /* matcher indices */ &kMatcherIndices[81], + }, + { + /* [410] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [411] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [412] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [413] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [414] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [415] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [416] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[63], + }, + { + /* [417] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [418] */ + /* usage */ ParameterUsage::kValue, + /* matcher indices */ &kMatcherIndices[107], + }, + { + /* [419] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[130], + }, + { + /* [420] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [421] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [422] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[60], + }, + { + /* [423] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [424] */ + /* usage */ ParameterUsage::kValue, + /* matcher indices */ &kMatcherIndices[107], + }, + { + /* [425] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[54], + }, + { + /* [426] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[99], + }, + { + /* [427] */ + /* usage */ ParameterUsage::kValue, + /* matcher indices */ &kMatcherIndices[107], + }, + { + /* [428] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[51], + }, + { + /* [429] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [430] */ + /* usage */ ParameterUsage::kValue, + /* matcher indices */ &kMatcherIndices[109], + }, + { + /* [431] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[133], + }, + { + /* [432] */ + /* usage */ ParameterUsage::kSampler, + /* matcher indices */ &kMatcherIndices[135], + }, + { + /* [433] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [434] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[48], + }, + { + /* [435] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [436] */ + /* usage */ ParameterUsage::kValue, + /* matcher indices */ &kMatcherIndices[109], + }, + { + /* [437] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[42], + }, + { + /* [438] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[99], + }, + { + /* [439] */ + /* usage */ ParameterUsage::kValue, + /* matcher indices */ &kMatcherIndices[109], + }, + { + /* [440] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[111], + }, + { + /* [441] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [442] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [443] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [444] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [445] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [446] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[115], + }, + { + /* [447] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [448] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [449] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[119], + }, + { + /* [450] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[99], + }, + { + /* [451] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [452] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[121], + }, + { + /* [453] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [454] */ + /* usage */ ParameterUsage::kSampleIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [455] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[133], + }, + { + /* [456] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [457] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [458] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[134], + }, + { + /* [459] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [460] */ + /* usage */ ParameterUsage::kSampleIndex, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [461] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [462] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [463] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[10], + }, + { + /* [464] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [465] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [466] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[24], + }, + { + /* [467] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [468] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [469] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [470] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [471] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [472] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [473] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [474] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [475] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [476] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [477] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [478] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[10], + }, + { + /* [479] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[133], + }, + { + /* [480] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [481] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[0], + }, + { + /* [482] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [483] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[0], + }, + { + /* [484] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [485] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[0], + }, + { + /* [486] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [487] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[0], + }, + { + /* [488] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [489] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[0], + }, + { + /* [490] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [491] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[0], + }, + { + /* [492] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [493] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[0], + }, + { + /* [494] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [495] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [496] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [497] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [498] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [499] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[0], + }, + { + /* [500] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [501] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[136], + }, + { + /* [502] */ + /* usage */ ParameterUsage::kCoords, + /* matcher indices */ &kMatcherIndices[85], + }, + { + /* [503] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [504] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [505] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [506] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [507] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [508] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [509] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [510] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[91], + }, + { + /* [511] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [512] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [513] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [514] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [515] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [516] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [517] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [518] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[78], + }, + { + /* [519] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [520] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [521] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [522] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [523] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[132], + }, + { + /* [524] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [525] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[0], + }, + { + /* [526] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [527] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [528] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [529] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[111], + }, + { + /* [530] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [531] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [532] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [533] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[115], + }, + { + /* [534] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [535] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[117], + }, + { + /* [536] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [537] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [538] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [539] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [540] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [541] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[119], + }, + { + /* [542] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [543] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [544] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [545] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[123], + }, + { + /* [546] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [547] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[130], + }, + { + /* [548] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [549] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[125], + }, + { + /* [550] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [551] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[131], + }, + { + /* [552] */ + /* usage */ ParameterUsage::kLevel, + /* matcher indices */ &kMatcherIndices[55], + }, + { + /* [553] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[43], + }, + { + /* [554] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[133], + }, + { + /* [555] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[131], + }, + { + /* [556] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[121], + }, + { + /* [557] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[130], + }, + { + /* [558] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[125], + }, + { + /* [559] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[132], + }, + { + /* [560] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [561] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[134], + }, + { + /* [562] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[36], + }, + { + /* [563] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[33], + }, + { + /* [564] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[27], + }, + { + /* [565] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[30], + }, + { + /* [566] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[136], + }, + { + /* [567] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [568] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [569] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [570] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[123], + }, + { + /* [571] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [572] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [573] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[119], + }, + { + /* [574] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [575] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[81], + }, + { + /* [576] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[81], + }, + { + /* [577] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [578] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [579] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[87], + }, + { + /* [580] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [581] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[117], + }, + { + /* [582] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[115], + }, + { + /* [583] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [584] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[111], + }, + { + /* [585] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[117], + }, + { + /* [586] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[125], + }, + { + /* [587] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[131], + }, + { + /* [588] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[132], + }, + { + /* [589] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[27], + }, + { + /* [590] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[111], + }, + { + /* [591] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[115], + }, + { + /* [592] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[117], + }, + { + /* [593] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[119], + }, + { + /* [594] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[123], + }, + { + /* [595] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[125], + }, + { + /* [596] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[133], + }, + { + /* [597] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[131], + }, + { + /* [598] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[130], + }, + { + /* [599] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[132], + }, + { + /* [600] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[121], + }, + { + /* [601] */ + /* usage */ ParameterUsage::kTexture, + /* matcher indices */ &kMatcherIndices[134], + }, + { + /* [602] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[43], + }, + { + /* [603] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[43], + }, + { + /* [604] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [605] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [606] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [607] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [608] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [609] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [610] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [611] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[43], + }, + { + /* [612] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[43], + }, + { + /* [613] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [614] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [615] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [616] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [617] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [618] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [619] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [620] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [621] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [622] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [623] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [624] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [625] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [626] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [627] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [628] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [629] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [630] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [631] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [632] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [633] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [634] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [635] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [636] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [637] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [638] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [639] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [640] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [641] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [642] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [643] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [644] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [645] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [646] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [647] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [648] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [649] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [650] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[17], + }, + { + /* [651] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [652] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [653] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[13], + }, + { + /* [654] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [655] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [656] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [657] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [658] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [659] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[1], + }, + { + /* [660] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [661] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [662] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [663] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [664] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [665] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [666] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [667] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [668] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [669] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [670] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [671] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [672] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[4], + }, + { + /* [673] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[24], + }, + { + /* [674] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[10], + }, + { + /* [675] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[24], + }, + { + /* [676] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[10], + }, + { + /* [677] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [678] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [679] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[39], + }, + { + /* [680] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [681] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[0], + }, + { + /* [682] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [683] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [684] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [685] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [686] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, + { + /* [687] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [688] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[21], + }, + { + /* [689] */ + /* usage */ ParameterUsage::kNone, + /* matcher indices */ &kMatcherIndices[12], + }, +}; + +constexpr OpenTypeInfo kOpenTypes[] = { + { + /* [0] */ + /* name */ "T", + /* matcher index */ 37, + }, + { + /* [1] */ + /* name */ "T", + /* matcher index */ 36, + }, + { + /* [2] */ + /* name */ "T", + /* matcher index */ kNoMatcher, + }, + { + /* [3] */ + /* name */ "T", + /* matcher index */ 38, + }, +}; + +constexpr OpenNumberInfo kOpenNumbers[] = { + { + /* [0] */ + /* name */ "F", + /* matcher index */ kNoMatcher, + }, + { + /* [1] */ + /* name */ "A", + /* matcher index */ 5, + }, + { + /* [2] */ + /* name */ "M", + /* matcher index */ kNoMatcher, + }, + { + /* [3] */ + /* name */ "N", + /* matcher index */ kNoMatcher, + }, + { + /* [4] */ + /* name */ "A", + /* matcher index */ kNoMatcher, + }, + { + /* [5] */ + /* name */ "S", + /* matcher index */ 7, + }, +}; + +constexpr OverloadInfo kOverloads[] = { + { + /* [0] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[584], + /* return matcher indices */ &kMatcherIndices[55], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [1] */ + /* num parameters */ 2, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[529], + /* return matcher indices */ &kMatcherIndices[55], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [2] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[582], + /* return matcher indices */ &kMatcherIndices[85], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [3] */ + /* num parameters */ 2, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[533], + /* return matcher indices */ &kMatcherIndices[85], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [4] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[581], + /* return matcher indices */ &kMatcherIndices[85], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [5] */ + /* num parameters */ 2, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[535], + /* return matcher indices */ &kMatcherIndices[85], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [6] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[573], + /* return matcher indices */ &kMatcherIndices[99], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [7] */ + /* num parameters */ 2, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[541], + /* return matcher indices */ &kMatcherIndices[99], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [8] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[570], + /* return matcher indices */ &kMatcherIndices[85], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [9] */ + /* num parameters */ 2, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[545], + /* return matcher indices */ &kMatcherIndices[85], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [10] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[558], + /* return matcher indices */ &kMatcherIndices[85], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [11] */ + /* num parameters */ 2, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[549], + /* return matcher indices */ &kMatcherIndices[85], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [12] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[556], + /* return matcher indices */ &kMatcherIndices[85], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [13] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[554], + /* return matcher indices */ &kMatcherIndices[85], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [14] */ + /* num parameters */ 2, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[479], + /* return matcher indices */ &kMatcherIndices[85], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [15] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[555], + /* return matcher indices */ &kMatcherIndices[85], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [16] */ + /* num parameters */ 2, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[551], + /* return matcher indices */ &kMatcherIndices[85], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [17] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[557], + /* return matcher indices */ &kMatcherIndices[85], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [18] */ + /* num parameters */ 2, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[547], + /* return matcher indices */ &kMatcherIndices[85], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [19] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[559], + /* return matcher indices */ &kMatcherIndices[85], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [20] */ + /* num parameters */ 2, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[523], + /* return matcher indices */ &kMatcherIndices[85], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [21] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[561], + /* return matcher indices */ &kMatcherIndices[85], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [22] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 2, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[0], + /* parameters */ &kParameters[562], + /* return matcher indices */ &kMatcherIndices[55], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [23] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 2, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[0], + /* parameters */ &kParameters[563], + /* return matcher indices */ &kMatcherIndices[85], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [24] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 2, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[0], + /* parameters */ &kParameters[564], + /* return matcher indices */ &kMatcherIndices[85], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [25] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 2, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[0], + /* parameters */ &kParameters[565], + /* return matcher indices */ &kMatcherIndices[99], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [26] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[566], + /* return matcher indices */ &kMatcherIndices[85], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [27] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[222], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [28] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[193], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [29] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[213], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [30] */ + /* num parameters */ 6, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[25], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [31] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[226], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [32] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[173], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [33] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[230], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [34] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[163], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [35] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[234], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [36] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[83], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [37] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[148], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [38] */ + /* num parameters */ 6, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[55], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [39] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[246], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [40] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[133], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [41] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[383], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [42] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[389], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [43] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[386], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [44] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[250], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [45] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[238], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [46] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[138], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [47] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[374], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [48] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[266], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [49] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[368], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [50] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[270], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [51] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[362], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [52] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[298], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [53] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[302], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [54] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[183], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [55] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[350], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [56] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[338], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [57] */ + /* num parameters */ 4, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[326], + /* return matcher indices */ &kMatcherIndices[113], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [58] */ + /* num parameters */ 5, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[178], + /* return matcher indices */ &kMatcherIndices[113], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [59] */ + /* num parameters */ 5, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[168], + /* return matcher indices */ &kMatcherIndices[113], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [60] */ + /* num parameters */ 6, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[49], + /* return matcher indices */ &kMatcherIndices[113], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [61] */ + /* num parameters */ 4, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[218], + /* return matcher indices */ &kMatcherIndices[113], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [62] */ + /* num parameters */ 5, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[98], + /* return matcher indices */ &kMatcherIndices[113], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [63] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[431], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [64] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[306], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [65] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[290], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [66] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[113], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [67] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[419], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [68] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[286], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [69] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[392], + /* return matcher indices */ nullptr, + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [70] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[404], + /* return matcher indices */ nullptr, + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [71] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[262], + /* return matcher indices */ nullptr, + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [72] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[407], + /* return matcher indices */ nullptr, + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [73] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[416], + /* return matcher indices */ nullptr, + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [74] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[422], + /* return matcher indices */ nullptr, + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [75] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[346], + /* return matcher indices */ nullptr, + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [76] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[425], + /* return matcher indices */ nullptr, + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [77] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[428], + /* return matcher indices */ nullptr, + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [78] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[434], + /* return matcher indices */ nullptr, + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [79] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[314], + /* return matcher indices */ nullptr, + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [80] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[437], + /* return matcher indices */ nullptr, + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [81] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[590], + /* return matcher indices */ &kMatcherIndices[55], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [82] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[591], + /* return matcher indices */ &kMatcherIndices[55], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [83] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[592], + /* return matcher indices */ &kMatcherIndices[55], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [84] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[593], + /* return matcher indices */ &kMatcherIndices[55], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [85] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[594], + /* return matcher indices */ &kMatcherIndices[55], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [86] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[595], + /* return matcher indices */ &kMatcherIndices[55], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [87] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[596], + /* return matcher indices */ &kMatcherIndices[55], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [88] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[597], + /* return matcher indices */ &kMatcherIndices[55], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [89] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[598], + /* return matcher indices */ &kMatcherIndices[55], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [90] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[599], + /* return matcher indices */ &kMatcherIndices[55], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [91] */ + /* num parameters */ 3, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[440], + /* return matcher indices */ &kMatcherIndices[113], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [92] */ + /* num parameters */ 3, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[446], + /* return matcher indices */ &kMatcherIndices[113], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [93] */ + /* num parameters */ 4, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[322], + /* return matcher indices */ &kMatcherIndices[113], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [94] */ + /* num parameters */ 3, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[449], + /* return matcher indices */ &kMatcherIndices[113], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [95] */ + /* num parameters */ 3, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[452], + /* return matcher indices */ &kMatcherIndices[113], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [96] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[455], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [97] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[330], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [98] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[458], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [99] */ + /* num parameters */ 2, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[501], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [100] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[334], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [101] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[198], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [102] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[188], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [103] */ + /* num parameters */ 6, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[37], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [104] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[318], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [105] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[108], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [106] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[310], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [107] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[88], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [108] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[158], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [109] */ + /* num parameters */ 6, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[43], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [110] */ + /* num parameters */ 6, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[31], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [111] */ + /* num parameters */ 7, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[0], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [112] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[73], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [113] */ + /* num parameters */ 6, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[19], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [114] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[208], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [115] */ + /* num parameters */ 6, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[13], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [116] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[282], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [117] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[143], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [118] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[203], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [119] */ + /* num parameters */ 6, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[61], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [120] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[254], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [121] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[123], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [122] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[258], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [123] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[118], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [124] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[128], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [125] */ + /* num parameters */ 6, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[7], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [126] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[242], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [127] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[153], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [128] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[294], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [129] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[78], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [130] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[93], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [131] */ + /* num parameters */ 6, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[67], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [132] */ + /* num parameters */ 4, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[278], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [133] */ + /* num parameters */ 5, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[103], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [134] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[585], + /* return matcher indices */ &kMatcherIndices[55], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [135] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[586], + /* return matcher indices */ &kMatcherIndices[55], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [136] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[587], + /* return matcher indices */ &kMatcherIndices[55], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [137] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[588], + /* return matcher indices */ &kMatcherIndices[55], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [138] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 2, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[0], + /* parameters */ &kParameters[589], + /* return matcher indices */ &kMatcherIndices[55], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [139] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[395], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [140] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[398], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [141] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[401], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [142] */ + /* num parameters */ 3, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[3], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[476], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [143] */ + /* num parameters */ 3, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[3], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[461], + /* return matcher indices */ &kMatcherIndices[39], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [144] */ + /* num parameters */ 3, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[3], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[464], + /* return matcher indices */ &kMatcherIndices[39], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [145] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[649], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [146] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[648], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [147] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[652], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [148] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[651], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [149] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[655], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [150] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[654], + /* return matcher indices */ &kMatcherIndices[39], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [151] */ + /* num parameters */ 2, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[511], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [152] */ + /* num parameters */ 2, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[513], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [153] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[657], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [154] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[656], + /* return matcher indices */ &kMatcherIndices[39], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [155] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[646], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [156] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[645], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [157] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[644], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [158] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[643], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [159] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[642], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [160] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[641], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [161] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[640], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [162] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[639], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [163] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[638], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [164] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[637], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [165] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[636], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [166] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[635], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [167] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[634], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [168] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[633], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [169] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[632], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [170] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[631], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [171] */ + /* num parameters */ 3, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[377], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [172] */ + /* num parameters */ 3, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[371], + /* return matcher indices */ &kMatcherIndices[39], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [173] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[659], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [174] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[658], + /* return matcher indices */ &kMatcherIndices[39], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [175] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[630], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [176] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[629], + /* return matcher indices */ &kMatcherIndices[39], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [177] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[628], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [178] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[627], + /* return matcher indices */ &kMatcherIndices[39], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [179] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[626], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [180] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[625], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [181] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[359], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [182] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[356], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [183] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[624], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [184] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[623], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [185] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[622], + /* return matcher indices */ &kMatcherIndices[138], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [186] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[621], + /* return matcher indices */ &kMatcherIndices[101], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [187] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[620], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [188] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[619], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [189] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[618], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [190] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[617], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [191] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[616], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [192] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[615], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment), + /* is_deprecated */ false, + }, + { + /* [193] */ + /* num parameters */ 4, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[274], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [194] */ + /* num parameters */ 4, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[342], + /* return matcher indices */ &kMatcherIndices[39], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [195] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[614], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [196] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[613], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [197] */ + /* num parameters */ 2, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[515], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [198] */ + /* num parameters */ 2, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[517], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [199] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[610], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [200] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[609], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [201] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[608], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [202] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[607], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [203] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[606], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [204] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[605], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [205] */ + /* num parameters */ 2, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[531], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [206] */ + /* num parameters */ 2, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[519], + /* return matcher indices */ &kMatcherIndices[39], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [207] */ + /* num parameters */ 2, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[521], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [208] */ + /* num parameters */ 2, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[527], + /* return matcher indices */ &kMatcherIndices[39], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [209] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[661], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [210] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[660], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [211] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[583], + /* return matcher indices */ &kMatcherIndices[137], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [212] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[604], + /* return matcher indices */ &kMatcherIndices[103], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [213] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[663], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [214] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[662], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [215] */ + /* num parameters */ 3, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[413], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [216] */ + /* num parameters */ 3, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[410], + /* return matcher indices */ &kMatcherIndices[39], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [217] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[665], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [218] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[664], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [219] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[380], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [220] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[473], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [221] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[600], + /* return matcher indices */ &kMatcherIndices[55], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [222] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[601], + /* return matcher indices */ &kMatcherIndices[55], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [223] */ + /* num parameters */ 2, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[505], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [224] */ + /* num parameters */ 2, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[507], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [225] */ + /* num parameters */ 2, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[537], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [226] */ + /* num parameters */ 2, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[539], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [227] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[572], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [228] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[571], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [229] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[689], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [230] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[668], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [231] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[671], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [232] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[670], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [233] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[569], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [234] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[568], + /* return matcher indices */ &kMatcherIndices[39], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [235] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[567], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [236] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[574], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [237] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[674], + /* return matcher indices */ &kMatcherIndices[10], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [238] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[673], + /* return matcher indices */ &kMatcherIndices[10], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [239] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[684], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [240] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[685], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [241] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[686], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [242] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[687], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [243] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[560], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [244] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[688], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [245] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[676], + /* return matcher indices */ &kMatcherIndices[10], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [246] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[675], + /* return matcher indices */ &kMatcherIndices[10], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [247] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[470], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ true, + }, + { + /* [248] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[467], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ true, + }, + { + /* [249] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[683], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [250] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[682], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [251] */ + /* num parameters */ 2, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[495], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [252] */ + /* num parameters */ 2, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[497], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [253] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[678], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [254] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[677], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [255] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[680], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [256] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[669], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [257] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[667], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [258] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[666], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [259] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[647], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [260] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[679], + /* return matcher indices */ &kMatcherIndices[39], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [261] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 2, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[2], + /* parameters */ &kParameters[653], + /* return matcher indices */ &kMatcherIndices[9], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [262] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[612], + /* return matcher indices */ &kMatcherIndices[87], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [263] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[611], + /* return matcher indices */ &kMatcherIndices[87], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [264] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[603], + /* return matcher indices */ &kMatcherIndices[87], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [265] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[602], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [266] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[553], + /* return matcher indices */ &kMatcherIndices[81], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [267] */ + /* num parameters */ 0, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[690], + /* return matcher indices */ nullptr, + /* supported_stages */ PipelineStageSet(PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [268] */ + /* num parameters */ 0, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[690], + /* return matcher indices */ nullptr, + /* supported_stages */ PipelineStageSet(PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [269] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[577], + /* return matcher indices */ &kMatcherIndices[43], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [270] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[443], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [271] */ + /* num parameters */ 2, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[543], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [272] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[575], + /* return matcher indices */ &kMatcherIndices[43], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [273] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[576], + /* return matcher indices */ &kMatcherIndices[43], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [274] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[578], + /* return matcher indices */ &kMatcherIndices[43], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [275] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[579], + /* return matcher indices */ &kMatcherIndices[43], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [276] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[580], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [277] */ + /* num parameters */ 3, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[365], + /* return matcher indices */ &kMatcherIndices[21], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [278] */ + /* num parameters */ 2, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[1], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[503], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [279] */ + /* num parameters */ 1, + /* num open types */ 0, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[3], + /* parameters */ &kParameters[650], + /* return matcher indices */ &kMatcherIndices[12], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [280] */ + /* num parameters */ 2, + /* num open types */ 0, + /* num open numbers */ 0, + /* open types */ &kOpenTypes[4], + /* open numbers */ &kOpenNumbers[6], + /* parameters */ &kParameters[509], + /* return matcher indices */ &kMatcherIndices[91], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [281] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[2], + /* open numbers */ &kOpenNumbers[4], + /* parameters */ &kParameters[672], + /* return matcher indices */ &kMatcherIndices[43], + /* supported_stages */ PipelineStageSet(PipelineStage::kVertex, PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [282] */ + /* num parameters */ 1, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[5], + /* parameters */ &kParameters[681], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [283] */ + /* num parameters */ 2, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[5], + /* parameters */ &kParameters[493], + /* return matcher indices */ nullptr, + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [284] */ + /* num parameters */ 2, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[5], + /* parameters */ &kParameters[491], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [285] */ + /* num parameters */ 2, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[5], + /* parameters */ &kParameters[489], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [286] */ + /* num parameters */ 2, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[5], + /* parameters */ &kParameters[487], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [287] */ + /* num parameters */ 2, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[5], + /* parameters */ &kParameters[485], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [288] */ + /* num parameters */ 2, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[5], + /* parameters */ &kParameters[483], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [289] */ + /* num parameters */ 2, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[5], + /* parameters */ &kParameters[481], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [290] */ + /* num parameters */ 2, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[5], + /* parameters */ &kParameters[525], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [291] */ + /* num parameters */ 2, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[5], + /* parameters */ &kParameters[499], + /* return matcher indices */ &kMatcherIndices[1], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, + { + /* [292] */ + /* num parameters */ 3, + /* num open types */ 1, + /* num open numbers */ 1, + /* open types */ &kOpenTypes[0], + /* open numbers */ &kOpenNumbers[5], + /* parameters */ &kParameters[353], + /* return matcher indices */ &kMatcherIndices[105], + /* supported_stages */ PipelineStageSet(PipelineStage::kFragment, PipelineStage::kCompute), + /* is_deprecated */ false, + }, +}; + +constexpr BuiltinInfo kBuiltins[] = { + { + /* [0] */ + /* fn abs<T : fiu32>(T) -> T */ + /* fn abs<N : num, T : fiu32>(vec<N, T>) -> vec<N, T> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[259], + }, + { + /* [1] */ + /* fn acos(f32) -> f32 */ + /* fn acos<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[253], + }, + { + /* [2] */ + /* fn all(bool) -> bool */ + /* fn all<N : num>(vec<N, bool>) -> bool */ + /* num overloads */ 2, + /* overloads */ &kOverloads[245], + }, + { + /* [3] */ + /* fn any(bool) -> bool */ + /* fn any<N : num>(vec<N, bool>) -> bool */ + /* num overloads */ 2, + /* overloads */ &kOverloads[237], + }, + { + /* [4] */ + /* fn arrayLength<T, A : access>(ptr<storage, array<T>, A>) -> u32 */ + /* num overloads */ 1, + /* overloads */ &kOverloads[281], + }, + { + /* [5] */ + /* fn asin(f32) -> f32 */ + /* fn asin<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[231], + }, + { + /* [6] */ + /* fn atan(f32) -> f32 */ + /* fn atan<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[229], + }, + { + /* [7] */ + /* fn atan2(f32, f32) -> f32 */ + /* fn atan2<N : num>(vec<N, f32>, vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[223], + }, + { + /* [8] */ + /* fn ceil(f32) -> f32 */ + /* fn ceil<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[217], + }, + { + /* [9] */ + /* fn clamp<T : fiu32>(T, T, T) -> T */ + /* fn clamp<N : num, T : fiu32>(vec<N, T>, vec<N, T>, vec<N, T>) -> vec<N, T> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[215], + }, + { + /* [10] */ + /* fn cos(f32) -> f32 */ + /* fn cos<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[213], + }, + { + /* [11] */ + /* fn cosh(f32) -> f32 */ + /* fn cosh<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[209], + }, + { + /* [12] */ + /* fn countLeadingZeros<T : iu32>(T) -> T */ + /* fn countLeadingZeros<N : num, T : iu32>(vec<N, T>) -> vec<N, T> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[173], + }, + { + /* [13] */ + /* fn countOneBits<T : iu32>(T) -> T */ + /* fn countOneBits<N : num, T : iu32>(vec<N, T>) -> vec<N, T> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[153], + }, + { + /* [14] */ + /* fn countTrailingZeros<T : iu32>(T) -> T */ + /* fn countTrailingZeros<N : num, T : iu32>(vec<N, T>) -> vec<N, T> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[149], + }, + { + /* [15] */ + /* fn cross(vec3<f32>, vec3<f32>) -> vec3<f32> */ + /* num overloads */ 1, + /* overloads */ &kOverloads[280], + }, + { + /* [16] */ + /* fn degrees(f32) -> f32 */ + /* fn degrees<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[147], + }, + { + /* [17] */ + /* fn determinant<N : num>(mat<N, N, f32>) -> f32 */ + /* num overloads */ 1, + /* overloads */ &kOverloads[279], + }, + { + /* [18] */ + /* fn distance(f32, f32) -> f32 */ + /* fn distance<N : num>(vec<N, f32>, vec<N, f32>) -> f32 */ + /* num overloads */ 2, + /* overloads */ &kOverloads[151], + }, + { + /* [19] */ + /* fn dot<N : num, T : fiu32>(vec<N, T>, vec<N, T>) -> T */ + /* num overloads */ 1, + /* overloads */ &kOverloads[278], + }, + { + /* [20] */ + /* fn dpdx(f32) -> f32 */ + /* fn dpdx<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[155], + }, + { + /* [21] */ + /* fn dpdxCoarse(f32) -> f32 */ + /* fn dpdxCoarse<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[157], + }, + { + /* [22] */ + /* fn dpdxFine(f32) -> f32 */ + /* fn dpdxFine<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[159], + }, + { + /* [23] */ + /* fn dpdy(f32) -> f32 */ + /* fn dpdy<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[161], + }, + { + /* [24] */ + /* fn dpdyCoarse(f32) -> f32 */ + /* fn dpdyCoarse<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[163], + }, + { + /* [25] */ + /* fn dpdyFine(f32) -> f32 */ + /* fn dpdyFine<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[165], + }, + { + /* [26] */ + /* fn exp(f32) -> f32 */ + /* fn exp<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[167], + }, + { + /* [27] */ + /* fn exp2(f32) -> f32 */ + /* fn exp2<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[169], + }, + { + /* [28] */ + /* fn extractBits<T : iu32>(T, u32, u32) -> T */ + /* fn extractBits<N : num, T : iu32>(vec<N, T>, u32, u32) -> vec<N, T> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[171], + }, + { + /* [29] */ + /* fn faceForward<N : num>(vec<N, f32>, vec<N, f32>, vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 1, + /* overloads */ &kOverloads[277], + }, + { + /* [30] */ + /* fn firstLeadingBit<T : iu32>(T) -> T */ + /* fn firstLeadingBit<N : num, T : iu32>(vec<N, T>) -> vec<N, T> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[175], + }, + { + /* [31] */ + /* fn firstTrailingBit<T : iu32>(T) -> T */ + /* fn firstTrailingBit<N : num, T : iu32>(vec<N, T>) -> vec<N, T> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[177], + }, + { + /* [32] */ + /* fn floor(f32) -> f32 */ + /* fn floor<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[179], + }, + { + /* [33] */ + /* fn fma(f32, f32, f32) -> f32 */ + /* fn fma<N : num>(vec<N, f32>, vec<N, f32>, vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[181], + }, + { + /* [34] */ + /* fn fract(f32) -> f32 */ + /* fn fract<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[183], + }, + { + /* [35] */ + /* fn frexp(f32) -> __frexp_result */ + /* fn frexp<N : num>(vec<N, f32>) -> __frexp_result_vec<N> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[185], + }, + { + /* [36] */ + /* fn fwidth(f32) -> f32 */ + /* fn fwidth<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[187], + }, + { + /* [37] */ + /* fn fwidthCoarse(f32) -> f32 */ + /* fn fwidthCoarse<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[189], + }, + { + /* [38] */ + /* fn fwidthFine(f32) -> f32 */ + /* fn fwidthFine<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[191], + }, + { + /* [39] */ + /* fn insertBits<T : iu32>(T, T, u32, u32) -> T */ + /* fn insertBits<N : num, T : iu32>(vec<N, T>, vec<N, T>, u32, u32) -> vec<N, T> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[193], + }, + { + /* [40] */ + /* fn inverseSqrt(f32) -> f32 */ + /* fn inverseSqrt<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[195], + }, + { + /* [41] */ + /* fn ldexp(f32, i32) -> f32 */ + /* fn ldexp<N : num>(vec<N, f32>, vec<N, i32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[197], + }, + { + /* [42] */ + /* fn length(f32) -> f32 */ + /* fn length<N : num>(vec<N, f32>) -> f32 */ + /* num overloads */ 2, + /* overloads */ &kOverloads[199], + }, + { + /* [43] */ + /* fn log(f32) -> f32 */ + /* fn log<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[201], + }, + { + /* [44] */ + /* fn log2(f32) -> f32 */ + /* fn log2<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[203], + }, + { + /* [45] */ + /* fn max<T : fiu32>(T, T) -> T */ + /* fn max<N : num, T : fiu32>(vec<N, T>, vec<N, T>) -> vec<N, T> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[205], + }, + { + /* [46] */ + /* fn min<T : fiu32>(T, T) -> T */ + /* fn min<N : num, T : fiu32>(vec<N, T>, vec<N, T>) -> vec<N, T> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[207], + }, + { + /* [47] */ + /* fn mix(f32, f32, f32) -> f32 */ + /* fn mix<N : num>(vec<N, f32>, vec<N, f32>, vec<N, f32>) -> vec<N, f32> */ + /* fn mix<N : num>(vec<N, f32>, vec<N, f32>, f32) -> vec<N, f32> */ + /* num overloads */ 3, + /* overloads */ &kOverloads[139], + }, + { + /* [48] */ + /* fn modf(f32) -> __modf_result */ + /* fn modf<N : num>(vec<N, f32>) -> __modf_result_vec<N> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[211], + }, + { + /* [49] */ + /* fn normalize<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 1, + /* overloads */ &kOverloads[276], + }, + { + /* [50] */ + /* fn pack2x16float(vec2<f32>) -> u32 */ + /* num overloads */ 1, + /* overloads */ &kOverloads[275], + }, + { + /* [51] */ + /* fn pack2x16snorm(vec2<f32>) -> u32 */ + /* num overloads */ 1, + /* overloads */ &kOverloads[274], + }, + { + /* [52] */ + /* fn pack2x16unorm(vec2<f32>) -> u32 */ + /* num overloads */ 1, + /* overloads */ &kOverloads[269], + }, + { + /* [53] */ + /* fn pack4x8snorm(vec4<f32>) -> u32 */ + /* num overloads */ 1, + /* overloads */ &kOverloads[273], + }, + { + /* [54] */ + /* fn pack4x8unorm(vec4<f32>) -> u32 */ + /* num overloads */ 1, + /* overloads */ &kOverloads[272], + }, + { + /* [55] */ + /* fn pow(f32, f32) -> f32 */ + /* fn pow<N : num>(vec<N, f32>, vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[225], + }, + { + /* [56] */ + /* fn radians(f32) -> f32 */ + /* fn radians<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[227], + }, + { + /* [57] */ + /* fn reflect<N : num>(vec<N, f32>, vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 1, + /* overloads */ &kOverloads[271], + }, + { + /* [58] */ + /* fn refract<N : num>(vec<N, f32>, vec<N, f32>, f32) -> vec<N, f32> */ + /* num overloads */ 1, + /* overloads */ &kOverloads[270], + }, + { + /* [59] */ + /* fn reverseBits<T : iu32>(T) -> T */ + /* fn reverseBits<N : num, T : iu32>(vec<N, T>) -> vec<N, T> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[233], + }, + { + /* [60] */ + /* fn round(f32) -> f32 */ + /* fn round<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[235], + }, + { + /* [61] */ + /* fn select<T : scalar>(T, T, bool) -> T */ + /* fn select<T : scalar, N : num>(vec<N, T>, vec<N, T>, bool) -> vec<N, T> */ + /* fn select<N : num, T : scalar>(vec<N, T>, vec<N, T>, vec<N, bool>) -> vec<N, T> */ + /* num overloads */ 3, + /* overloads */ &kOverloads[142], + }, + { + /* [62] */ + /* fn sign(f32) -> f32 */ + /* fn sign<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[239], + }, + { + /* [63] */ + /* fn sin(f32) -> f32 */ + /* fn sin<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[241], + }, + { + /* [64] */ + /* fn sinh(f32) -> f32 */ + /* fn sinh<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[243], + }, + { + /* [65] */ + /* fn smoothstep(f32, f32, f32) -> f32 */ + /* fn smoothstep<N : num>(vec<N, f32>, vec<N, f32>, vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[219], + }, + { + /* [66] */ + /* fn smoothStep(f32, f32, f32) -> f32 */ + /* fn smoothStep<N : num>(vec<N, f32>, vec<N, f32>, vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[247], + }, + { + /* [67] */ + /* fn sqrt(f32) -> f32 */ + /* fn sqrt<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[249], + }, + { + /* [68] */ + /* fn step(f32, f32) -> f32 */ + /* fn step<N : num>(vec<N, f32>, vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[251], + }, + { + /* [69] */ + /* fn storageBarrier() */ + /* num overloads */ 1, + /* overloads */ &kOverloads[268], + }, + { + /* [70] */ + /* fn tan(f32) -> f32 */ + /* fn tan<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[255], + }, + { + /* [71] */ + /* fn tanh(f32) -> f32 */ + /* fn tanh<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[257], + }, + { + /* [72] */ + /* fn transpose<M : num, N : num>(mat<M, N, f32>) -> mat<N, M, f32> */ + /* num overloads */ 1, + /* overloads */ &kOverloads[261], + }, + { + /* [73] */ + /* fn trunc(f32) -> f32 */ + /* fn trunc<N : num>(vec<N, f32>) -> vec<N, f32> */ + /* num overloads */ 2, + /* overloads */ &kOverloads[145], + }, + { + /* [74] */ + /* fn unpack2x16float(u32) -> vec2<f32> */ + /* num overloads */ 1, + /* overloads */ &kOverloads[262], + }, + { + /* [75] */ + /* fn unpack2x16snorm(u32) -> vec2<f32> */ + /* num overloads */ 1, + /* overloads */ &kOverloads[263], + }, + { + /* [76] */ + /* fn unpack2x16unorm(u32) -> vec2<f32> */ + /* num overloads */ 1, + /* overloads */ &kOverloads[264], + }, + { + /* [77] */ + /* fn unpack4x8snorm(u32) -> vec4<f32> */ + /* num overloads */ 1, + /* overloads */ &kOverloads[265], + }, + { + /* [78] */ + /* fn unpack4x8unorm(u32) -> vec4<f32> */ + /* num overloads */ 1, + /* overloads */ &kOverloads[266], + }, + { + /* [79] */ + /* fn workgroupBarrier() */ + /* num overloads */ 1, + /* overloads */ &kOverloads[267], + }, + { + /* [80] */ + /* fn textureDimensions<T : fiu32>(texture: texture_1d<T>) -> i32 */ + /* fn textureDimensions<T : fiu32>(texture: texture_1d<T>, level: i32) -> i32 */ + /* fn textureDimensions<T : fiu32>(texture: texture_2d<T>) -> vec2<i32> */ + /* fn textureDimensions<T : fiu32>(texture: texture_2d<T>, level: i32) -> vec2<i32> */ + /* fn textureDimensions<T : fiu32>(texture: texture_2d_array<T>) -> vec2<i32> */ + /* fn textureDimensions<T : fiu32>(texture: texture_2d_array<T>, level: i32) -> vec2<i32> */ + /* fn textureDimensions<T : fiu32>(texture: texture_3d<T>) -> vec3<i32> */ + /* fn textureDimensions<T : fiu32>(texture: texture_3d<T>, level: i32) -> vec3<i32> */ + /* fn textureDimensions<T : fiu32>(texture: texture_cube<T>) -> vec2<i32> */ + /* fn textureDimensions<T : fiu32>(texture: texture_cube<T>, level: i32) -> vec2<i32> */ + /* fn textureDimensions<T : fiu32>(texture: texture_cube_array<T>) -> vec2<i32> */ + /* fn textureDimensions<T : fiu32>(texture: texture_cube_array<T>, level: i32) -> vec2<i32> */ + /* fn textureDimensions<T : fiu32>(texture: texture_multisampled_2d<T>) -> vec2<i32> */ + /* fn textureDimensions(texture: texture_depth_2d) -> vec2<i32> */ + /* fn textureDimensions(texture: texture_depth_2d, level: i32) -> vec2<i32> */ + /* fn textureDimensions(texture: texture_depth_2d_array) -> vec2<i32> */ + /* fn textureDimensions(texture: texture_depth_2d_array, level: i32) -> vec2<i32> */ + /* fn textureDimensions(texture: texture_depth_cube) -> vec2<i32> */ + /* fn textureDimensions(texture: texture_depth_cube, level: i32) -> vec2<i32> */ + /* fn textureDimensions(texture: texture_depth_cube_array) -> vec2<i32> */ + /* fn textureDimensions(texture: texture_depth_cube_array, level: i32) -> vec2<i32> */ + /* fn textureDimensions(texture: texture_depth_multisampled_2d) -> vec2<i32> */ + /* fn textureDimensions<F : texel_format, A : write_only>(texture: texture_storage_1d<F, A>) -> i32 */ + /* fn textureDimensions<F : texel_format, A : write_only>(texture: texture_storage_2d<F, A>) -> vec2<i32> */ + /* fn textureDimensions<F : texel_format, A : write_only>(texture: texture_storage_2d_array<F, A>) -> vec2<i32> */ + /* fn textureDimensions<F : texel_format, A : write_only>(texture: texture_storage_3d<F, A>) -> vec3<i32> */ + /* fn textureDimensions(texture: texture_external) -> vec2<i32> */ + /* num overloads */ 27, + /* overloads */ &kOverloads[0], + }, + { + /* [81] */ + /* fn textureGather<T : fiu32>(component: i32, texture: texture_2d<T>, sampler: sampler, coords: vec2<f32>) -> vec4<T> */ + /* fn textureGather<T : fiu32>(component: i32, texture: texture_2d<T>, sampler: sampler, coords: vec2<f32>, offset: vec2<i32>) -> vec4<T> */ + /* fn textureGather<T : fiu32>(component: i32, texture: texture_2d_array<T>, sampler: sampler, coords: vec2<f32>, array_index: i32) -> vec4<T> */ + /* fn textureGather<T : fiu32>(component: i32, texture: texture_2d_array<T>, sampler: sampler, coords: vec2<f32>, array_index: i32, offset: vec2<i32>) -> vec4<T> */ + /* fn textureGather<T : fiu32>(component: i32, texture: texture_cube<T>, sampler: sampler, coords: vec3<f32>) -> vec4<T> */ + /* fn textureGather<T : fiu32>(component: i32, texture: texture_cube_array<T>, sampler: sampler, coords: vec3<f32>, array_index: i32) -> vec4<T> */ + /* fn textureGather(texture: texture_depth_2d, sampler: sampler, coords: vec2<f32>) -> vec4<f32> */ + /* fn textureGather(texture: texture_depth_2d, sampler: sampler, coords: vec2<f32>, offset: vec2<i32>) -> vec4<f32> */ + /* fn textureGather(texture: texture_depth_2d_array, sampler: sampler, coords: vec2<f32>, array_index: i32) -> vec4<f32> */ + /* fn textureGather(texture: texture_depth_2d_array, sampler: sampler, coords: vec2<f32>, array_index: i32, offset: vec2<i32>) -> vec4<f32> */ + /* fn textureGather(texture: texture_depth_cube, sampler: sampler, coords: vec3<f32>) -> vec4<f32> */ + /* fn textureGather(texture: texture_depth_cube_array, sampler: sampler, coords: vec3<f32>, array_index: i32) -> vec4<f32> */ + /* num overloads */ 12, + /* overloads */ &kOverloads[57], + }, + { + /* [82] */ + /* fn textureGatherCompare(texture: texture_depth_2d, sampler: sampler_comparison, coords: vec2<f32>, depth_ref: f32) -> vec4<f32> */ + /* fn textureGatherCompare(texture: texture_depth_2d, sampler: sampler_comparison, coords: vec2<f32>, depth_ref: f32, offset: vec2<i32>) -> vec4<f32> */ + /* fn textureGatherCompare(texture: texture_depth_2d_array, sampler: sampler_comparison, coords: vec2<f32>, array_index: i32, depth_ref: f32) -> vec4<f32> */ + /* fn textureGatherCompare(texture: texture_depth_2d_array, sampler: sampler_comparison, coords: vec2<f32>, array_index: i32, depth_ref: f32, offset: vec2<i32>) -> vec4<f32> */ + /* fn textureGatherCompare(texture: texture_depth_cube, sampler: sampler_comparison, coords: vec3<f32>, depth_ref: f32) -> vec4<f32> */ + /* fn textureGatherCompare(texture: texture_depth_cube_array, sampler: sampler_comparison, coords: vec3<f32>, array_index: i32, depth_ref: f32) -> vec4<f32> */ + /* num overloads */ 6, + /* overloads */ &kOverloads[116], + }, + { + /* [83] */ + /* fn textureNumLayers<T : fiu32>(texture: texture_2d_array<T>) -> i32 */ + /* fn textureNumLayers<T : fiu32>(texture: texture_cube_array<T>) -> i32 */ + /* fn textureNumLayers(texture: texture_depth_2d_array) -> i32 */ + /* fn textureNumLayers(texture: texture_depth_cube_array) -> i32 */ + /* fn textureNumLayers<F : texel_format, A : write_only>(texture: texture_storage_2d_array<F, A>) -> i32 */ + /* num overloads */ 5, + /* overloads */ &kOverloads[134], + }, + { + /* [84] */ + /* fn textureNumLevels<T : fiu32>(texture: texture_1d<T>) -> i32 */ + /* fn textureNumLevels<T : fiu32>(texture: texture_2d<T>) -> i32 */ + /* fn textureNumLevels<T : fiu32>(texture: texture_2d_array<T>) -> i32 */ + /* fn textureNumLevels<T : fiu32>(texture: texture_3d<T>) -> i32 */ + /* fn textureNumLevels<T : fiu32>(texture: texture_cube<T>) -> i32 */ + /* fn textureNumLevels<T : fiu32>(texture: texture_cube_array<T>) -> i32 */ + /* fn textureNumLevels(texture: texture_depth_2d) -> i32 */ + /* fn textureNumLevels(texture: texture_depth_2d_array) -> i32 */ + /* fn textureNumLevels(texture: texture_depth_cube) -> i32 */ + /* fn textureNumLevels(texture: texture_depth_cube_array) -> i32 */ + /* num overloads */ 10, + /* overloads */ &kOverloads[81], + }, + { + /* [85] */ + /* fn textureNumSamples<T : fiu32>(texture: texture_multisampled_2d<T>) -> i32 */ + /* fn textureNumSamples(texture: texture_depth_multisampled_2d) -> i32 */ + /* num overloads */ 2, + /* overloads */ &kOverloads[221], + }, + { + /* [86] */ + /* fn textureSample(texture: texture_1d<f32>, sampler: sampler, coords: f32) -> vec4<f32> */ + /* fn textureSample(texture: texture_2d<f32>, sampler: sampler, coords: vec2<f32>) -> vec4<f32> */ + /* fn textureSample(texture: texture_2d<f32>, sampler: sampler, coords: vec2<f32>, offset: vec2<i32>) -> vec4<f32> */ + /* fn textureSample(texture: texture_2d_array<f32>, sampler: sampler, coords: vec2<f32>, array_index: i32) -> vec4<f32> */ + /* fn textureSample(texture: texture_2d_array<f32>, sampler: sampler, coords: vec2<f32>, array_index: i32, offset: vec2<i32>) -> vec4<f32> */ + /* fn textureSample(texture: texture_3d<f32>, sampler: sampler, coords: vec3<f32>) -> vec4<f32> */ + /* fn textureSample(texture: texture_3d<f32>, sampler: sampler, coords: vec3<f32>, offset: vec3<i32>) -> vec4<f32> */ + /* fn textureSample(texture: texture_cube<f32>, sampler: sampler, coords: vec3<f32>) -> vec4<f32> */ + /* fn textureSample(texture: texture_cube_array<f32>, sampler: sampler, coords: vec3<f32>, array_index: i32) -> vec4<f32> */ + /* fn textureSample(texture: texture_depth_2d, sampler: sampler, coords: vec2<f32>) -> f32 */ + /* fn textureSample(texture: texture_depth_2d, sampler: sampler, coords: vec2<f32>, offset: vec2<i32>) -> f32 */ + /* fn textureSample(texture: texture_depth_2d_array, sampler: sampler, coords: vec2<f32>, array_index: i32) -> f32 */ + /* fn textureSample(texture: texture_depth_2d_array, sampler: sampler, coords: vec2<f32>, array_index: i32, offset: vec2<i32>) -> f32 */ + /* fn textureSample(texture: texture_depth_cube, sampler: sampler, coords: vec3<f32>) -> f32 */ + /* fn textureSample(texture: texture_depth_cube_array, sampler: sampler, coords: vec3<f32>, array_index: i32) -> f32 */ + /* num overloads */ 15, + /* overloads */ &kOverloads[42], + }, + { + /* [87] */ + /* fn textureSampleBias(texture: texture_2d<f32>, sampler: sampler, coords: vec2<f32>, bias: f32) -> vec4<f32> */ + /* fn textureSampleBias(texture: texture_2d<f32>, sampler: sampler, coords: vec2<f32>, bias: f32, offset: vec2<i32>) -> vec4<f32> */ + /* fn textureSampleBias(texture: texture_2d_array<f32>, sampler: sampler, coords: vec2<f32>, array_index: i32, bias: f32) -> vec4<f32> */ + /* fn textureSampleBias(texture: texture_2d_array<f32>, sampler: sampler, coords: vec2<f32>, array_index: i32, bias: f32, offset: vec2<i32>) -> vec4<f32> */ + /* fn textureSampleBias(texture: texture_3d<f32>, sampler: sampler, coords: vec3<f32>, bias: f32) -> vec4<f32> */ + /* fn textureSampleBias(texture: texture_3d<f32>, sampler: sampler, coords: vec3<f32>, bias: f32, offset: vec3<i32>) -> vec4<f32> */ + /* fn textureSampleBias(texture: texture_cube<f32>, sampler: sampler, coords: vec3<f32>, bias: f32) -> vec4<f32> */ + /* fn textureSampleBias(texture: texture_cube_array<f32>, sampler: sampler, coords: vec3<f32>, array_index: i32, bias: f32) -> vec4<f32> */ + /* num overloads */ 8, + /* overloads */ &kOverloads[100], + }, + { + /* [88] */ + /* fn textureSampleCompare(texture: texture_depth_2d, sampler: sampler_comparison, coords: vec2<f32>, depth_ref: f32) -> f32 */ + /* fn textureSampleCompare(texture: texture_depth_2d, sampler: sampler_comparison, coords: vec2<f32>, depth_ref: f32, offset: vec2<i32>) -> f32 */ + /* fn textureSampleCompare(texture: texture_depth_2d_array, sampler: sampler_comparison, coords: vec2<f32>, array_index: i32, depth_ref: f32) -> f32 */ + /* fn textureSampleCompare(texture: texture_depth_2d_array, sampler: sampler_comparison, coords: vec2<f32>, array_index: i32, depth_ref: f32, offset: vec2<i32>) -> f32 */ + /* fn textureSampleCompare(texture: texture_depth_cube, sampler: sampler_comparison, coords: vec3<f32>, depth_ref: f32) -> f32 */ + /* fn textureSampleCompare(texture: texture_depth_cube_array, sampler: sampler_comparison, coords: vec3<f32>, array_index: i32, depth_ref: f32) -> f32 */ + /* num overloads */ 6, + /* overloads */ &kOverloads[128], + }, + { + /* [89] */ + /* fn textureSampleCompareLevel(texture: texture_depth_2d, sampler: sampler_comparison, coords: vec2<f32>, depth_ref: f32) -> f32 */ + /* fn textureSampleCompareLevel(texture: texture_depth_2d, sampler: sampler_comparison, coords: vec2<f32>, depth_ref: f32, offset: vec2<i32>) -> f32 */ + /* fn textureSampleCompareLevel(texture: texture_depth_2d_array, sampler: sampler_comparison, coords: vec2<f32>, array_index: i32, depth_ref: f32) -> f32 */ + /* fn textureSampleCompareLevel(texture: texture_depth_2d_array, sampler: sampler_comparison, coords: vec2<f32>, array_index: i32, depth_ref: f32, offset: vec2<i32>) -> f32 */ + /* fn textureSampleCompareLevel(texture: texture_depth_cube, sampler: sampler_comparison, coords: vec3<f32>, depth_ref: f32) -> f32 */ + /* fn textureSampleCompareLevel(texture: texture_depth_cube_array, sampler: sampler_comparison, coords: vec3<f32>, array_index: i32, depth_ref: f32) -> f32 */ + /* num overloads */ 6, + /* overloads */ &kOverloads[122], + }, + { + /* [90] */ + /* fn textureSampleGrad(texture: texture_2d<f32>, sampler: sampler, coords: vec2<f32>, ddx: vec2<f32>, ddy: vec2<f32>) -> vec4<f32> */ + /* fn textureSampleGrad(texture: texture_2d<f32>, sampler: sampler, coords: vec2<f32>, ddx: vec2<f32>, ddy: vec2<f32>, offset: vec2<i32>) -> vec4<f32> */ + /* fn textureSampleGrad(texture: texture_2d_array<f32>, sampler: sampler, coords: vec2<f32>, array_index: i32, ddx: vec2<f32>, ddy: vec2<f32>) -> vec4<f32> */ + /* fn textureSampleGrad(texture: texture_2d_array<f32>, sampler: sampler, coords: vec2<f32>, array_index: i32, ddx: vec2<f32>, ddy: vec2<f32>, offset: vec2<i32>) -> vec4<f32> */ + /* fn textureSampleGrad(texture: texture_3d<f32>, sampler: sampler, coords: vec3<f32>, ddx: vec3<f32>, ddy: vec3<f32>) -> vec4<f32> */ + /* fn textureSampleGrad(texture: texture_3d<f32>, sampler: sampler, coords: vec3<f32>, ddx: vec3<f32>, ddy: vec3<f32>, offset: vec3<i32>) -> vec4<f32> */ + /* fn textureSampleGrad(texture: texture_cube<f32>, sampler: sampler, coords: vec3<f32>, ddx: vec3<f32>, ddy: vec3<f32>) -> vec4<f32> */ + /* fn textureSampleGrad(texture: texture_cube_array<f32>, sampler: sampler, coords: vec3<f32>, array_index: i32, ddx: vec3<f32>, ddy: vec3<f32>) -> vec4<f32> */ + /* num overloads */ 8, + /* overloads */ &kOverloads[108], + }, + { + /* [91] */ + /* fn textureSampleLevel(texture: texture_2d<f32>, sampler: sampler, coords: vec2<f32>, level: f32) -> vec4<f32> */ + /* fn textureSampleLevel(texture: texture_2d<f32>, sampler: sampler, coords: vec2<f32>, level: f32, offset: vec2<i32>) -> vec4<f32> */ + /* fn textureSampleLevel(texture: texture_2d_array<f32>, sampler: sampler, coords: vec2<f32>, array_index: i32, level: f32) -> vec4<f32> */ + /* fn textureSampleLevel(texture: texture_2d_array<f32>, sampler: sampler, coords: vec2<f32>, array_index: i32, level: f32, offset: vec2<i32>) -> vec4<f32> */ + /* fn textureSampleLevel(texture: texture_3d<f32>, sampler: sampler, coords: vec3<f32>, level: f32) -> vec4<f32> */ + /* fn textureSampleLevel(texture: texture_3d<f32>, sampler: sampler, coords: vec3<f32>, level: f32, offset: vec3<i32>) -> vec4<f32> */ + /* fn textureSampleLevel(texture: texture_cube<f32>, sampler: sampler, coords: vec3<f32>, level: f32) -> vec4<f32> */ + /* fn textureSampleLevel(texture: texture_cube_array<f32>, sampler: sampler, coords: vec3<f32>, array_index: i32, level: f32) -> vec4<f32> */ + /* fn textureSampleLevel(texture: texture_depth_2d, sampler: sampler, coords: vec2<f32>, level: i32) -> f32 */ + /* fn textureSampleLevel(texture: texture_depth_2d, sampler: sampler, coords: vec2<f32>, level: i32, offset: vec2<i32>) -> f32 */ + /* fn textureSampleLevel(texture: texture_depth_2d_array, sampler: sampler, coords: vec2<f32>, array_index: i32, level: i32) -> f32 */ + /* fn textureSampleLevel(texture: texture_depth_2d_array, sampler: sampler, coords: vec2<f32>, array_index: i32, level: i32, offset: vec2<i32>) -> f32 */ + /* fn textureSampleLevel(texture: texture_depth_cube, sampler: sampler, coords: vec3<f32>, level: i32) -> f32 */ + /* fn textureSampleLevel(texture: texture_depth_cube_array, sampler: sampler, coords: vec3<f32>, array_index: i32, level: i32) -> f32 */ + /* fn textureSampleLevel(texture: texture_external, sampler: sampler, coords: vec2<f32>) -> vec4<f32> */ + /* num overloads */ 15, + /* overloads */ &kOverloads[27], + }, + { + /* [92] */ + /* fn textureStore(texture: texture_storage_1d<f32_texel_format, write>, coords: i32, value: vec4<f32>) */ + /* fn textureStore(texture: texture_storage_2d<f32_texel_format, write>, coords: vec2<i32>, value: vec4<f32>) */ + /* fn textureStore(texture: texture_storage_2d_array<f32_texel_format, write>, coords: vec2<i32>, array_index: i32, value: vec4<f32>) */ + /* fn textureStore(texture: texture_storage_3d<f32_texel_format, write>, coords: vec3<i32>, value: vec4<f32>) */ + /* fn textureStore(texture: texture_storage_1d<i32_texel_format, write>, coords: i32, value: vec4<i32>) */ + /* fn textureStore(texture: texture_storage_2d<i32_texel_format, write>, coords: vec2<i32>, value: vec4<i32>) */ + /* fn textureStore(texture: texture_storage_2d_array<i32_texel_format, write>, coords: vec2<i32>, array_index: i32, value: vec4<i32>) */ + /* fn textureStore(texture: texture_storage_3d<i32_texel_format, write>, coords: vec3<i32>, value: vec4<i32>) */ + /* fn textureStore(texture: texture_storage_1d<u32_texel_format, write>, coords: i32, value: vec4<u32>) */ + /* fn textureStore(texture: texture_storage_2d<u32_texel_format, write>, coords: vec2<i32>, value: vec4<u32>) */ + /* fn textureStore(texture: texture_storage_2d_array<u32_texel_format, write>, coords: vec2<i32>, array_index: i32, value: vec4<u32>) */ + /* fn textureStore(texture: texture_storage_3d<u32_texel_format, write>, coords: vec3<i32>, value: vec4<u32>) */ + /* num overloads */ 12, + /* overloads */ &kOverloads[69], + }, + { + /* [93] */ + /* fn textureLoad<T : fiu32>(texture: texture_1d<T>, coords: i32, level: i32) -> vec4<T> */ + /* fn textureLoad<T : fiu32>(texture: texture_2d<T>, coords: vec2<i32>, level: i32) -> vec4<T> */ + /* fn textureLoad<T : fiu32>(texture: texture_2d_array<T>, coords: vec2<i32>, array_index: i32, level: i32) -> vec4<T> */ + /* fn textureLoad<T : fiu32>(texture: texture_3d<T>, coords: vec3<i32>, level: i32) -> vec4<T> */ + /* fn textureLoad<T : fiu32>(texture: texture_multisampled_2d<T>, coords: vec2<i32>, sample_index: i32) -> vec4<T> */ + /* fn textureLoad(texture: texture_depth_2d, coords: vec2<i32>, level: i32) -> f32 */ + /* fn textureLoad(texture: texture_depth_2d_array, coords: vec2<i32>, array_index: i32, level: i32) -> f32 */ + /* fn textureLoad(texture: texture_depth_multisampled_2d, coords: vec2<i32>, sample_index: i32) -> f32 */ + /* fn textureLoad(texture: texture_external, coords: vec2<i32>) -> vec4<f32> */ + /* num overloads */ 9, + /* overloads */ &kOverloads[91], + }, + { + /* [94] */ + /* fn atomicLoad<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>) -> T */ + /* num overloads */ 1, + /* overloads */ &kOverloads[282], + }, + { + /* [95] */ + /* fn atomicStore<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) */ + /* num overloads */ 1, + /* overloads */ &kOverloads[283], + }, + { + /* [96] */ + /* fn atomicAdd<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */ + /* num overloads */ 1, + /* overloads */ &kOverloads[284], + }, + { + /* [97] */ + /* fn atomicSub<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */ + /* num overloads */ 1, + /* overloads */ &kOverloads[285], + }, + { + /* [98] */ + /* fn atomicMax<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */ + /* num overloads */ 1, + /* overloads */ &kOverloads[286], + }, + { + /* [99] */ + /* fn atomicMin<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */ + /* num overloads */ 1, + /* overloads */ &kOverloads[287], + }, + { + /* [100] */ + /* fn atomicAnd<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */ + /* num overloads */ 1, + /* overloads */ &kOverloads[288], + }, + { + /* [101] */ + /* fn atomicOr<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */ + /* num overloads */ 1, + /* overloads */ &kOverloads[289], + }, + { + /* [102] */ + /* fn atomicXor<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */ + /* num overloads */ 1, + /* overloads */ &kOverloads[290], + }, + { + /* [103] */ + /* fn atomicExchange<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T */ + /* num overloads */ 1, + /* overloads */ &kOverloads[291], + }, + { + /* [104] */ + /* fn atomicCompareExchangeWeak<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T, T) -> vec2<T> */ + /* num overloads */ 1, + /* overloads */ &kOverloads[292], + }, +}; + +// clang-format on
diff --git a/src/tint/builtin_table.inl.tmpl b/src/tint/builtin_table.inl.tmpl new file mode 100644 index 0000000..b7604b8 --- /dev/null +++ b/src/tint/builtin_table.inl.tmpl
@@ -0,0 +1,401 @@ +{{- /* +-------------------------------------------------------------------------------- +Template file for use with tools/builtin-gen to generate builtin_table.inl +Used by BuiltinTable.cc for builtin overload resolution. + +See: +* tools/cmd/builtin-gen/gen for structures used by this template +* https://golang.org/pkg/text/template/ for documentation on the template syntax +-------------------------------------------------------------------------------- +*/ -}} + +// clang-format off + +{{ with .Sem -}} +{{ range .Types -}} +{{ template "Type" . }} +{{ end -}} +{{ range .TypeMatchers -}} +{{ template "TypeMatcher" . }} +{{ end -}} +{{ range .EnumMatchers -}} +{{ template "EnumMatcher" . }} +{{ end -}} +{{- end -}} + +{{- with BuiltinTable -}} +{{- template "Matchers" . }} + +constexpr MatcherIndex kMatcherIndices[] = { +{{- range $i, $idx := .MatcherIndices }} + /* [{{$i}}] */ {{$idx}}, +{{- end }} +}; + +// Assert that the MatcherIndex is big enough to index all the matchers, plus +// kNoMatcher. +static_assert(static_cast<int>(sizeof(kMatcherIndices) / sizeof(kMatcherIndices[0])) < + static_cast<int>(std::numeric_limits<MatcherIndex>::max() - 1), + "MatcherIndex is not large enough to index kMatcherIndices"); + +constexpr ParameterInfo kParameters[] = { +{{- range $i, $p := .Parameters }} + { + /* [{{$i}}] */ + /* usage */ ParameterUsage:: +{{- if $p.Usage }}k{{PascalCase $p.Usage}} +{{- else }}kNone +{{- end }}, + /* matcher indices */ &kMatcherIndices[{{$p.MatcherIndicesOffset}}], + }, +{{- end }} +}; + +constexpr OpenTypeInfo kOpenTypes[] = { +{{- range $i, $o := .OpenTypes }} + { + /* [{{$i}}] */ + /* name */ "{{$o.Name}}", + /* matcher index */ +{{- if ge $o.MatcherIndex 0 }} {{$o.MatcherIndex}} +{{- else }} kNoMatcher +{{- end }}, + }, +{{- end }} +}; + +constexpr OpenNumberInfo kOpenNumbers[] = { +{{- range $i, $o := .OpenNumbers }} + { + /* [{{$i}}] */ + /* name */ "{{$o.Name}}", + /* matcher index */ +{{- if ge $o.MatcherIndex 0 }} {{$o.MatcherIndex}} +{{- else }} kNoMatcher +{{- end }}, + }, +{{- end }} +}; + +constexpr OverloadInfo kOverloads[] = { +{{- range $i, $o := .Overloads }} + { + /* [{{$i}}] */ + /* num parameters */ {{$o.NumParameters}}, + /* num open types */ {{$o.NumOpenTypes}}, + /* num open numbers */ {{$o.NumOpenNumbers}}, + /* open types */ +{{- if $o.OpenTypesOffset }} &kOpenTypes[{{$o.OpenTypesOffset}}], +{{- else }} nullptr, +{{- end }} + /* open numbers */ +{{- if $o.OpenNumbersOffset }} &kOpenNumbers[{{$o.OpenNumbersOffset}}] +{{- else }} nullptr +{{- end }}, + /* parameters */ &kParameters[{{$o.ParametersOffset}}], + /* return matcher indices */ +{{- if $o.ReturnMatcherIndicesOffset }} &kMatcherIndices[{{$o.ReturnMatcherIndicesOffset}}] +{{- else }} nullptr +{{- end }}, + /* supported_stages */ PipelineStageSet( +{{- range $i, $u := $o.CanBeUsedInStage.List -}} +{{- if $i -}}, {{end}}PipelineStage::k{{Title $u}} +{{- end }}), + /* is_deprecated */ {{$o.IsDeprecated}}, + }, +{{- end }} +}; + +constexpr BuiltinInfo kBuiltins[] = { +{{- range $i, $f := .Functions }} + { + /* [{{$i}}] */ +{{- range $f.OverloadDescriptions }} + /* {{.}} */ +{{- end }} + /* num overloads */ {{$f.NumOverloads}}, + /* overloads */ &kOverloads[{{$f.OverloadsOffset}}], + }, +{{- end }} +}; + +// clang-format on +{{ end -}} + +{{- /* ------------------------------------------------------------------ */ -}} +{{- define "Type" -}} +{{- /* ------------------------------------------------------------------ */ -}} +{{- $class := PascalCase .Name -}} +/// TypeMatcher for 'type {{.Name}}' +{{- if .Decl.Source.S.Filepath }} +/// @see {{.Decl.Source}} +{{- end }} +class {{$class}} : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* {{$class}}::Match(MatchState& state, const sem::Type* ty) const { +{{- range .TemplateParams }} +{{- template "DeclareLocalTemplateParam" . }} +{{- end }} + if (!match_{{TrimLeft .Name "_"}}(ty{{range .TemplateParams}}, {{.GetName}}{{end}})) { + return nullptr; + } +{{- range .TemplateParams }} + {{.Name}} = {{ template "MatchTemplateParam" .}}({{.Name}}); + if ({{ template "IsTemplateParamInvalid" .}}) { + return nullptr; + } +{{- end }} + return build_{{TrimLeft .Name "_"}}(state{{range .TemplateParams}}, {{.GetName}}{{end}}); +} + +std::string {{$class}}::String(MatchState&{{if .TemplateParams}} state{{end}}) const { +{{- range .TemplateParams }} +{{- template "DeclareLocalTemplateParamName" . }} +{{- end }} + +{{- if .DisplayName }} + std::stringstream ss; + ss{{range SplitDisplayName .DisplayName}} << {{.}}{{end}}; + return ss.str(); +{{- else if .TemplateParams }} + return "{{.Name}}<"{{template "AppendTemplateParamNames" .TemplateParams}} + ">"; +{{- else }} + return "{{.Name}}"; +{{- end }} +} +{{ end -}} + +{{- /* ------------------------------------------------------------------ */ -}} +{{- define "TypeMatcher" -}} +{{- /* ------------------------------------------------------------------ */ -}} +{{- $class := PascalCase .Name -}} +/// TypeMatcher for 'match {{.Name}}' +{{- if .Decl.Source.S.Filepath }} +/// @see {{.Decl.Source}} +{{- end }} +class {{$class}} : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules, and returns the + /// expected, canonicalized type on success. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +const sem::Type* {{$class}}::Match(MatchState& state, const sem::Type* ty) const { +{{- range .Types }} + if (match_{{.Name}}(ty)) { + return build_{{.Name}}(state); + } +{{- end }} + return nullptr; +} + +std::string {{$class}}::String(MatchState&) const { + return " +{{- range .Types -}} +{{- if IsFirstIn . $.Types }}{{.Name}} +{{- else if IsLastIn . $.Types }} or {{.Name}} +{{- else }}, {{.Name}} +{{- end -}} +{{- end -}} + "; +} +{{ end -}} + +{{- /* ------------------------------------------------------------------ */ -}} +{{- define "EnumMatcher" -}} +{{- /* ------------------------------------------------------------------ */ -}} +{{- $class := PascalCase .Name -}} +{{- $enum := PascalCase .Enum.Name -}} +/// EnumMatcher for 'match {{.Name}}' +{{- if .Decl.Source.S.Filepath }} +/// @see {{.Decl.Source}} +{{- end }} +class {{$class}} : public NumberMatcher { + public: + /// Checks whether the given number matches the enum matcher rules. + /// Match may close open types and numbers in state. + /// @param state the MatchState + /// @param number the enum value as a Number + /// @return true if the enum value matches the set + Number Match(MatchState& state, Number number) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState& state) const override; +}; + +{{ if eq 1 (len .Options) -}} +{{- $option := index .Options 0 }} +{{- $entry := printf "k%v" (PascalCase $option.Name) -}} +Number {{$class}}::Match(MatchState&, Number number) const { + if (number.IsAny() || number.Value() == static_cast<uint32_t>({{$enum}}::{{$entry}})) { + return Number(static_cast<uint32_t>({{$enum}}::{{$entry}})); + } + return Number::invalid; +} +{{- else -}} +Number {{$class}}::Match(MatchState&, Number number) const { + switch (static_cast<{{$enum}}>(number.Value())) { +{{- range .Options }} + case {{$enum}}::k{{PascalCase .Name}}: +{{- end }} + return number; + default: + return Number::invalid; + } +} +{{- end }} + +std::string {{$class}}::String(MatchState&) const { + return " +{{- range .Options -}} +{{- if IsFirstIn . $.Options }}{{.Name}} +{{- else if IsLastIn . $.Options }} or {{.Name}} +{{- else }}, {{.Name}} +{{- end -}} +{{- end -}} +"; +} +{{ end -}} + +{{- /* ------------------------------------------------------------------ */ -}} +{{- define "Matchers" -}} +{{- /* ------------------------------------------------------------------ */ -}} +/// Matchers holds type and number matchers +class Matchers { + private: +{{- $t_names := Map -}} +{{- $n_names := Map -}} +{{- range Iterate .Sem.MaxOpenTypes -}} +{{- $name := printf "open_type_%v" . -}} +{{- $t_names.Put . $name }} + OpenTypeMatcher {{$name}}_{ {{- . -}} }; +{{- end }} +{{- range Iterate .Sem.MaxOpenNumbers -}} +{{- $name := printf "open_number_%v" . -}} +{{- $n_names.Put . $name }} + OpenNumberMatcher {{$name}}_{ {{- . -}} }; +{{- end }} +{{- range .Sem.Types -}} +{{- $name := PascalCase .Name -}} +{{- $t_names.Put . $name }} + {{$name}} {{$name}}_; +{{- end }} +{{- range .Sem.TypeMatchers -}} +{{- $name := PascalCase .Name -}} +{{- $t_names.Put . $name }} + {{$name}} {{$name}}_; +{{- end }} +{{- range .Sem.EnumMatchers -}} +{{- $name := PascalCase .Name -}} +{{- $n_names.Put . $name }} + {{$name}} {{$name}}_; +{{- end }} + + public: + /// Constructor + Matchers(); + /// Destructor + ~Matchers(); + + /// The open-types, types, and type matchers + TypeMatcher const* const type[{{len .TMatchers}}] = { +{{- range $i, $m := .TMatchers }} + /* [{{$i}}] */ +{{- if $m }} &{{$t_names.Get $m}}_, +{{- else }} &{{$t_names.Get $i}}_, +{{- end }} +{{- end }} + }; + + /// The open-numbers, and number matchers + NumberMatcher const* const number[{{len .NMatchers}}] = { +{{- range $i, $m := .NMatchers }} + /* [{{$i}}] */ +{{- if $m }} &{{$n_names.Get $m}}_, +{{- else }} &{{$n_names.Get $i}}_, +{{- end }} +{{- end }} + }; +}; + +Matchers::Matchers() = default; +Matchers::~Matchers() = default; +{{- end -}} + +{{- /* ------------------------------------------------------------------ */ -}} +{{- define "DeclareLocalTemplateParam" -}} +{{- /* ------------------------------------------------------------------ */ -}} +{{- if IsTemplateTypeParam . }} + const sem::Type* {{.Name}} = nullptr; +{{- else if IsTemplateNumberParam . }} + Number {{.Name}} = Number::invalid; +{{- else if IsTemplateEnumParam . }} + Number {{.Name}} = Number::invalid; +{{- end -}} +{{- end -}} + +{{- /* ------------------------------------------------------------------ */ -}} +{{- define "DeclareLocalTemplateParamName" -}} +{{- /* ------------------------------------------------------------------ */ -}} +{{- if IsTemplateTypeParam . }} + const std::string {{.Name}} = state.TypeName(); +{{- else if IsTemplateNumberParam . }} + const std::string {{.Name}} = state.NumName(); +{{- else if IsTemplateEnumParam . }} + const std::string {{.Name}} = state.NumName(); +{{- end -}} +{{- end -}} + +{{- /* ------------------------------------------------------------------ */ -}} +{{- define "MatchTemplateParam" -}} +{{- /* ------------------------------------------------------------------ */ -}} +{{- if IsTemplateTypeParam . -}} + state.Type +{{- else if IsTemplateNumberParam . -}} + state.Num +{{- else if IsTemplateEnumParam . -}} + state.Num +{{- end -}} +{{- end -}} + +{{- /* ------------------------------------------------------------------ */ -}} +{{- define "IsTemplateParamInvalid" -}} +{{- /* ------------------------------------------------------------------ */ -}} +{{- if IsTemplateTypeParam . -}} + {{.Name}} == nullptr +{{- else if IsTemplateNumberParam . -}} + !{{.Name}}.IsValid() +{{- else if IsTemplateEnumParam . -}} + !{{.Name}}.IsValid() +{{- end -}} +{{- end -}} + +{{- /* ------------------------------------------------------------------ */ -}} +{{- define "AppendTemplateParamNames" -}} +{{- /* ------------------------------------------------------------------ */ -}} +{{- range $i, $ := . -}} +{{- if $i }} + ", " + {{.Name}} +{{- else }} + {{.Name}} +{{- end -}} +{{- end -}} +{{- end -}}
diff --git a/src/tint/builtin_table_test.cc b/src/tint/builtin_table_test.cc new file mode 100644 index 0000000..b8454d1 --- /dev/null +++ b/src/tint/builtin_table_test.cc
@@ -0,0 +1,601 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/builtin_table.h" + +#include "gmock/gmock.h" +#include "src/tint/program_builder.h" +#include "src/tint/sem/atomic_type.h" +#include "src/tint/sem/depth_multisampled_texture_type.h" +#include "src/tint/sem/depth_texture_type.h" +#include "src/tint/sem/external_texture_type.h" +#include "src/tint/sem/multisampled_texture_type.h" +#include "src/tint/sem/reference_type.h" +#include "src/tint/sem/sampled_texture_type.h" +#include "src/tint/sem/storage_texture_type.h" + +namespace tint { +namespace { + +using ::testing::HasSubstr; + +using BuiltinType = sem::BuiltinType; +using Parameter = sem::Parameter; +using ParameterUsage = sem::ParameterUsage; + +class BuiltinTableTest : public testing::Test, public ProgramBuilder { + public: + std::unique_ptr<BuiltinTable> table = BuiltinTable::Create(*this); +}; + +TEST_F(BuiltinTableTest, MatchF32) { + auto* f32 = create<sem::F32>(); + auto* result = table->Lookup(BuiltinType::kCos, {f32}, Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kCos); + EXPECT_EQ(result->ReturnType(), f32); + ASSERT_EQ(result->Parameters().size(), 1u); + EXPECT_EQ(result->Parameters()[0]->Type(), f32); +} + +TEST_F(BuiltinTableTest, MismatchF32) { + auto* i32 = create<sem::I32>(); + auto* result = table->Lookup(BuiltinType::kCos, {i32}, Source{}); + ASSERT_EQ(result, nullptr); + ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call")); +} + +TEST_F(BuiltinTableTest, MatchU32) { + auto* f32 = create<sem::F32>(); + auto* u32 = create<sem::U32>(); + auto* vec2_f32 = create<sem::Vector>(f32, 2u); + auto* result = table->Lookup(BuiltinType::kUnpack2x16float, {u32}, Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kUnpack2x16float); + EXPECT_EQ(result->ReturnType(), vec2_f32); + ASSERT_EQ(result->Parameters().size(), 1u); + EXPECT_EQ(result->Parameters()[0]->Type(), u32); +} + +TEST_F(BuiltinTableTest, MismatchU32) { + auto* f32 = create<sem::F32>(); + auto* result = table->Lookup(BuiltinType::kUnpack2x16float, {f32}, Source{}); + ASSERT_EQ(result, nullptr); + ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call")); +} + +TEST_F(BuiltinTableTest, MatchI32) { + auto* f32 = create<sem::F32>(); + auto* i32 = create<sem::I32>(); + auto* vec4_f32 = create<sem::Vector>(f32, 4u); + auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k1d, f32); + auto* result = + table->Lookup(BuiltinType::kTextureLoad, {tex, i32, i32}, Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kTextureLoad); + EXPECT_EQ(result->ReturnType(), vec4_f32); + ASSERT_EQ(result->Parameters().size(), 3u); + EXPECT_EQ(result->Parameters()[0]->Type(), tex); + EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture); + EXPECT_EQ(result->Parameters()[1]->Type(), i32); + EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kCoords); + EXPECT_EQ(result->Parameters()[2]->Type(), i32); + EXPECT_EQ(result->Parameters()[2]->Usage(), ParameterUsage::kLevel); +} + +TEST_F(BuiltinTableTest, MismatchI32) { + auto* f32 = create<sem::F32>(); + auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k1d, f32); + auto* result = table->Lookup(BuiltinType::kTextureLoad, {tex, f32}, Source{}); + ASSERT_EQ(result, nullptr); + ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call")); +} + +TEST_F(BuiltinTableTest, MatchIU32AsI32) { + auto* i32 = create<sem::I32>(); + auto* result = table->Lookup(BuiltinType::kCountOneBits, {i32}, Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kCountOneBits); + EXPECT_EQ(result->ReturnType(), i32); + ASSERT_EQ(result->Parameters().size(), 1u); + EXPECT_EQ(result->Parameters()[0]->Type(), i32); +} + +TEST_F(BuiltinTableTest, MatchIU32AsU32) { + auto* u32 = create<sem::U32>(); + auto* result = table->Lookup(BuiltinType::kCountOneBits, {u32}, Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kCountOneBits); + EXPECT_EQ(result->ReturnType(), u32); + ASSERT_EQ(result->Parameters().size(), 1u); + EXPECT_EQ(result->Parameters()[0]->Type(), u32); +} + +TEST_F(BuiltinTableTest, MismatchIU32) { + auto* f32 = create<sem::F32>(); + auto* result = table->Lookup(BuiltinType::kCountOneBits, {f32}, Source{}); + ASSERT_EQ(result, nullptr); + ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call")); +} + +TEST_F(BuiltinTableTest, MatchFIU32AsI32) { + auto* i32 = create<sem::I32>(); + auto* result = table->Lookup(BuiltinType::kClamp, {i32, i32, i32}, Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kClamp); + EXPECT_EQ(result->ReturnType(), i32); + ASSERT_EQ(result->Parameters().size(), 3u); + EXPECT_EQ(result->Parameters()[0]->Type(), i32); + EXPECT_EQ(result->Parameters()[1]->Type(), i32); + EXPECT_EQ(result->Parameters()[2]->Type(), i32); +} + +TEST_F(BuiltinTableTest, MatchFIU32AsU32) { + auto* u32 = create<sem::U32>(); + auto* result = table->Lookup(BuiltinType::kClamp, {u32, u32, u32}, Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kClamp); + EXPECT_EQ(result->ReturnType(), u32); + ASSERT_EQ(result->Parameters().size(), 3u); + EXPECT_EQ(result->Parameters()[0]->Type(), u32); + EXPECT_EQ(result->Parameters()[1]->Type(), u32); + EXPECT_EQ(result->Parameters()[2]->Type(), u32); +} + +TEST_F(BuiltinTableTest, MatchFIU32AsF32) { + auto* f32 = create<sem::F32>(); + auto* result = table->Lookup(BuiltinType::kClamp, {f32, f32, f32}, Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kClamp); + EXPECT_EQ(result->ReturnType(), f32); + ASSERT_EQ(result->Parameters().size(), 3u); + EXPECT_EQ(result->Parameters()[0]->Type(), f32); + EXPECT_EQ(result->Parameters()[1]->Type(), f32); + EXPECT_EQ(result->Parameters()[2]->Type(), f32); +} + +TEST_F(BuiltinTableTest, MismatchFIU32) { + auto* bool_ = create<sem::Bool>(); + auto* result = + table->Lookup(BuiltinType::kClamp, {bool_, bool_, bool_}, Source{}); + ASSERT_EQ(result, nullptr); + ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call")); +} + +TEST_F(BuiltinTableTest, MatchBool) { + auto* f32 = create<sem::F32>(); + auto* bool_ = create<sem::Bool>(); + auto* result = + table->Lookup(BuiltinType::kSelect, {f32, f32, bool_}, Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kSelect); + EXPECT_EQ(result->ReturnType(), f32); + ASSERT_EQ(result->Parameters().size(), 3u); + EXPECT_EQ(result->Parameters()[0]->Type(), f32); + EXPECT_EQ(result->Parameters()[1]->Type(), f32); + EXPECT_EQ(result->Parameters()[2]->Type(), bool_); +} + +TEST_F(BuiltinTableTest, MismatchBool) { + auto* f32 = create<sem::F32>(); + auto* result = table->Lookup(BuiltinType::kSelect, {f32, f32, f32}, Source{}); + ASSERT_EQ(result, nullptr); + ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call")); +} + +TEST_F(BuiltinTableTest, MatchPointer) { + auto* i32 = create<sem::I32>(); + auto* atomicI32 = create<sem::Atomic>(i32); + auto* ptr = create<sem::Pointer>(atomicI32, ast::StorageClass::kWorkgroup, + ast::Access::kReadWrite); + auto* result = table->Lookup(BuiltinType::kAtomicLoad, {ptr}, Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kAtomicLoad); + EXPECT_EQ(result->ReturnType(), i32); + ASSERT_EQ(result->Parameters().size(), 1u); + EXPECT_EQ(result->Parameters()[0]->Type(), ptr); +} + +TEST_F(BuiltinTableTest, MismatchPointer) { + auto* i32 = create<sem::I32>(); + auto* atomicI32 = create<sem::Atomic>(i32); + auto* result = table->Lookup(BuiltinType::kAtomicLoad, {atomicI32}, Source{}); + ASSERT_EQ(result, nullptr); + ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call")); +} + +TEST_F(BuiltinTableTest, MatchArray) { + auto* arr = create<sem::Array>(create<sem::U32>(), 0u, 4u, 4u, 4u, 4u); + auto* arr_ptr = create<sem::Pointer>(arr, ast::StorageClass::kStorage, + ast::Access::kReadWrite); + auto* result = table->Lookup(BuiltinType::kArrayLength, {arr_ptr}, Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kArrayLength); + EXPECT_TRUE(result->ReturnType()->Is<sem::U32>()); + ASSERT_EQ(result->Parameters().size(), 1u); + auto* param_type = result->Parameters()[0]->Type(); + ASSERT_TRUE(param_type->Is<sem::Pointer>()); + EXPECT_TRUE(param_type->As<sem::Pointer>()->StoreType()->Is<sem::Array>()); +} + +TEST_F(BuiltinTableTest, MismatchArray) { + auto* f32 = create<sem::F32>(); + auto* result = table->Lookup(BuiltinType::kArrayLength, {f32}, Source{}); + ASSERT_EQ(result, nullptr); + ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call")); +} + +TEST_F(BuiltinTableTest, MatchSampler) { + auto* f32 = create<sem::F32>(); + auto* vec2_f32 = create<sem::Vector>(f32, 2u); + auto* vec4_f32 = create<sem::Vector>(f32, 4u); + auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k2d, f32); + auto* sampler = create<sem::Sampler>(ast::SamplerKind::kSampler); + auto* result = table->Lookup(BuiltinType::kTextureSample, + {tex, sampler, vec2_f32}, Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kTextureSample); + EXPECT_EQ(result->ReturnType(), vec4_f32); + ASSERT_EQ(result->Parameters().size(), 3u); + EXPECT_EQ(result->Parameters()[0]->Type(), tex); + EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture); + EXPECT_EQ(result->Parameters()[1]->Type(), sampler); + EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kSampler); + EXPECT_EQ(result->Parameters()[2]->Type(), vec2_f32); + EXPECT_EQ(result->Parameters()[2]->Usage(), ParameterUsage::kCoords); +} + +TEST_F(BuiltinTableTest, MismatchSampler) { + auto* f32 = create<sem::F32>(); + auto* vec2_f32 = create<sem::Vector>(f32, 2u); + auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k2d, f32); + auto* result = table->Lookup(BuiltinType::kTextureSample, + {tex, f32, vec2_f32}, Source{}); + ASSERT_EQ(result, nullptr); + ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call")); +} + +TEST_F(BuiltinTableTest, MatchSampledTexture) { + auto* i32 = create<sem::I32>(); + auto* f32 = create<sem::F32>(); + auto* vec2_i32 = create<sem::Vector>(i32, 2u); + auto* vec4_f32 = create<sem::Vector>(f32, 4u); + auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k2d, f32); + auto* result = + table->Lookup(BuiltinType::kTextureLoad, {tex, vec2_i32, i32}, Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kTextureLoad); + EXPECT_EQ(result->ReturnType(), vec4_f32); + ASSERT_EQ(result->Parameters().size(), 3u); + EXPECT_EQ(result->Parameters()[0]->Type(), tex); + EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture); + EXPECT_EQ(result->Parameters()[1]->Type(), vec2_i32); + EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kCoords); + EXPECT_EQ(result->Parameters()[2]->Type(), i32); + EXPECT_EQ(result->Parameters()[2]->Usage(), ParameterUsage::kLevel); +} + +TEST_F(BuiltinTableTest, MatchMultisampledTexture) { + auto* i32 = create<sem::I32>(); + auto* f32 = create<sem::F32>(); + auto* vec2_i32 = create<sem::Vector>(i32, 2u); + auto* vec4_f32 = create<sem::Vector>(f32, 4u); + auto* tex = create<sem::MultisampledTexture>(ast::TextureDimension::k2d, f32); + auto* result = + table->Lookup(BuiltinType::kTextureLoad, {tex, vec2_i32, i32}, Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kTextureLoad); + EXPECT_EQ(result->ReturnType(), vec4_f32); + ASSERT_EQ(result->Parameters().size(), 3u); + EXPECT_EQ(result->Parameters()[0]->Type(), tex); + EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture); + EXPECT_EQ(result->Parameters()[1]->Type(), vec2_i32); + EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kCoords); + EXPECT_EQ(result->Parameters()[2]->Type(), i32); + EXPECT_EQ(result->Parameters()[2]->Usage(), ParameterUsage::kSampleIndex); +} + +TEST_F(BuiltinTableTest, MatchDepthTexture) { + auto* f32 = create<sem::F32>(); + auto* i32 = create<sem::I32>(); + auto* vec2_i32 = create<sem::Vector>(i32, 2u); + auto* tex = create<sem::DepthTexture>(ast::TextureDimension::k2d); + auto* result = + table->Lookup(BuiltinType::kTextureLoad, {tex, vec2_i32, i32}, Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kTextureLoad); + EXPECT_EQ(result->ReturnType(), f32); + ASSERT_EQ(result->Parameters().size(), 3u); + EXPECT_EQ(result->Parameters()[0]->Type(), tex); + EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture); + EXPECT_EQ(result->Parameters()[1]->Type(), vec2_i32); + EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kCoords); + EXPECT_EQ(result->Parameters()[2]->Type(), i32); + EXPECT_EQ(result->Parameters()[2]->Usage(), ParameterUsage::kLevel); +} + +TEST_F(BuiltinTableTest, MatchDepthMultisampledTexture) { + auto* f32 = create<sem::F32>(); + auto* i32 = create<sem::I32>(); + auto* vec2_i32 = create<sem::Vector>(i32, 2u); + auto* tex = create<sem::DepthMultisampledTexture>(ast::TextureDimension::k2d); + auto* result = + table->Lookup(BuiltinType::kTextureLoad, {tex, vec2_i32, i32}, Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kTextureLoad); + EXPECT_EQ(result->ReturnType(), f32); + ASSERT_EQ(result->Parameters().size(), 3u); + EXPECT_EQ(result->Parameters()[0]->Type(), tex); + EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture); + EXPECT_EQ(result->Parameters()[1]->Type(), vec2_i32); + EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kCoords); + EXPECT_EQ(result->Parameters()[2]->Type(), i32); + EXPECT_EQ(result->Parameters()[2]->Usage(), ParameterUsage::kSampleIndex); +} + +TEST_F(BuiltinTableTest, MatchExternalTexture) { + auto* f32 = create<sem::F32>(); + auto* i32 = create<sem::I32>(); + auto* vec2_i32 = create<sem::Vector>(i32, 2u); + auto* vec4_f32 = create<sem::Vector>(f32, 4u); + auto* tex = create<sem::ExternalTexture>(); + auto* result = + table->Lookup(BuiltinType::kTextureLoad, {tex, vec2_i32}, Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kTextureLoad); + EXPECT_EQ(result->ReturnType(), vec4_f32); + ASSERT_EQ(result->Parameters().size(), 2u); + EXPECT_EQ(result->Parameters()[0]->Type(), tex); + EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture); + EXPECT_EQ(result->Parameters()[1]->Type(), vec2_i32); + EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kCoords); +} + +TEST_F(BuiltinTableTest, MatchWOStorageTexture) { + auto* f32 = create<sem::F32>(); + auto* i32 = create<sem::I32>(); + auto* vec2_i32 = create<sem::Vector>(i32, 2u); + auto* vec4_f32 = create<sem::Vector>(f32, 4u); + auto* subtype = + sem::StorageTexture::SubtypeFor(ast::TexelFormat::kR32Float, Types()); + auto* tex = create<sem::StorageTexture>(ast::TextureDimension::k2d, + ast::TexelFormat::kR32Float, + ast::Access::kWrite, subtype); + + auto* result = table->Lookup(BuiltinType::kTextureStore, + {tex, vec2_i32, vec4_f32}, Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kTextureStore); + EXPECT_TRUE(result->ReturnType()->Is<sem::Void>()); + ASSERT_EQ(result->Parameters().size(), 3u); + EXPECT_EQ(result->Parameters()[0]->Type(), tex); + EXPECT_EQ(result->Parameters()[0]->Usage(), ParameterUsage::kTexture); + EXPECT_EQ(result->Parameters()[1]->Type(), vec2_i32); + EXPECT_EQ(result->Parameters()[1]->Usage(), ParameterUsage::kCoords); + EXPECT_EQ(result->Parameters()[2]->Type(), vec4_f32); + EXPECT_EQ(result->Parameters()[2]->Usage(), ParameterUsage::kValue); +} + +TEST_F(BuiltinTableTest, MismatchTexture) { + auto* f32 = create<sem::F32>(); + auto* i32 = create<sem::I32>(); + auto* vec2_i32 = create<sem::Vector>(i32, 2u); + auto* result = + table->Lookup(BuiltinType::kTextureLoad, {f32, vec2_i32}, Source{}); + ASSERT_EQ(result, nullptr); + ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call")); +} + +TEST_F(BuiltinTableTest, ImplicitLoadOnReference) { + auto* f32 = create<sem::F32>(); + auto* result = + table->Lookup(BuiltinType::kCos, + {create<sem::Reference>(f32, ast::StorageClass::kFunction, + ast::Access::kReadWrite)}, + Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kCos); + EXPECT_EQ(result->ReturnType(), f32); + ASSERT_EQ(result->Parameters().size(), 1u); + EXPECT_EQ(result->Parameters()[0]->Type(), f32); +} + +TEST_F(BuiltinTableTest, MatchOpenType) { + auto* f32 = create<sem::F32>(); + auto* result = table->Lookup(BuiltinType::kClamp, {f32, f32, f32}, Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kClamp); + EXPECT_EQ(result->ReturnType(), f32); + EXPECT_EQ(result->Parameters()[0]->Type(), f32); + EXPECT_EQ(result->Parameters()[1]->Type(), f32); + EXPECT_EQ(result->Parameters()[2]->Type(), f32); +} + +TEST_F(BuiltinTableTest, MismatchOpenType) { + auto* f32 = create<sem::F32>(); + auto* u32 = create<sem::U32>(); + auto* result = table->Lookup(BuiltinType::kClamp, {f32, u32, f32}, Source{}); + ASSERT_EQ(result, nullptr); + ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call")); +} + +TEST_F(BuiltinTableTest, MatchOpenSizeVector) { + auto* f32 = create<sem::F32>(); + auto* vec2_f32 = create<sem::Vector>(f32, 2u); + auto* result = table->Lookup(BuiltinType::kClamp, + {vec2_f32, vec2_f32, vec2_f32}, Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kClamp); + EXPECT_EQ(result->ReturnType(), vec2_f32); + ASSERT_EQ(result->Parameters().size(), 3u); + EXPECT_EQ(result->Parameters()[0]->Type(), vec2_f32); + EXPECT_EQ(result->Parameters()[1]->Type(), vec2_f32); + EXPECT_EQ(result->Parameters()[2]->Type(), vec2_f32); +} + +TEST_F(BuiltinTableTest, MismatchOpenSizeVector) { + auto* f32 = create<sem::F32>(); + auto* u32 = create<sem::U32>(); + auto* vec2_f32 = create<sem::Vector>(f32, 2u); + auto* result = + table->Lookup(BuiltinType::kClamp, {vec2_f32, u32, vec2_f32}, Source{}); + ASSERT_EQ(result, nullptr); + ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call")); +} + +TEST_F(BuiltinTableTest, MatchOpenSizeMatrix) { + auto* f32 = create<sem::F32>(); + auto* vec3_f32 = create<sem::Vector>(f32, 3u); + auto* mat3_f32 = create<sem::Matrix>(vec3_f32, 3u); + auto* result = table->Lookup(BuiltinType::kDeterminant, {mat3_f32}, Source{}); + ASSERT_NE(result, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + EXPECT_EQ(result->Type(), BuiltinType::kDeterminant); + EXPECT_EQ(result->ReturnType(), f32); + ASSERT_EQ(result->Parameters().size(), 1u); + EXPECT_EQ(result->Parameters()[0]->Type(), mat3_f32); +} + +TEST_F(BuiltinTableTest, MismatchOpenSizeMatrix) { + auto* f32 = create<sem::F32>(); + auto* vec2_f32 = create<sem::Vector>(f32, 2u); + auto* mat3x2_f32 = create<sem::Matrix>(vec2_f32, 3u); + auto* result = + table->Lookup(BuiltinType::kDeterminant, {mat3x2_f32}, Source{}); + ASSERT_EQ(result, nullptr); + ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call")); +} + +TEST_F(BuiltinTableTest, OverloadOrderByNumberOfParameters) { + // None of the arguments match, so expect the overloads with 2 parameters to + // come first + auto* bool_ = create<sem::Bool>(); + table->Lookup(BuiltinType::kTextureDimensions, {bool_, bool_}, Source{}); + ASSERT_EQ(Diagnostics().str(), + R"(error: no matching call to textureDimensions(bool, bool) + +27 candidate functions: + textureDimensions(texture: texture_1d<T>, level: i32) -> i32 where: T is f32, i32 or u32 + textureDimensions(texture: texture_2d<T>, level: i32) -> vec2<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_2d_array<T>, level: i32) -> vec2<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_3d<T>, level: i32) -> vec3<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_cube<T>, level: i32) -> vec2<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_cube_array<T>, level: i32) -> vec2<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_depth_2d, level: i32) -> vec2<i32> + textureDimensions(texture: texture_depth_2d_array, level: i32) -> vec2<i32> + textureDimensions(texture: texture_depth_cube, level: i32) -> vec2<i32> + textureDimensions(texture: texture_depth_cube_array, level: i32) -> vec2<i32> + textureDimensions(texture: texture_1d<T>) -> i32 where: T is f32, i32 or u32 + textureDimensions(texture: texture_2d<T>) -> vec2<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_2d_array<T>) -> vec2<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_3d<T>) -> vec3<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_cube<T>) -> vec2<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_cube_array<T>) -> vec2<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_multisampled_2d<T>) -> vec2<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_depth_2d) -> vec2<i32> + textureDimensions(texture: texture_depth_2d_array) -> vec2<i32> + textureDimensions(texture: texture_depth_cube) -> vec2<i32> + textureDimensions(texture: texture_depth_cube_array) -> vec2<i32> + textureDimensions(texture: texture_depth_multisampled_2d) -> vec2<i32> + textureDimensions(texture: texture_storage_1d<F, A>) -> i32 where: A is write + textureDimensions(texture: texture_storage_2d<F, A>) -> vec2<i32> where: A is write + textureDimensions(texture: texture_storage_2d_array<F, A>) -> vec2<i32> where: A is write + textureDimensions(texture: texture_storage_3d<F, A>) -> vec3<i32> where: A is write + textureDimensions(texture: texture_external) -> vec2<i32> +)"); +} + +TEST_F(BuiltinTableTest, OverloadOrderByMatchingParameter) { + auto* tex = create<sem::DepthTexture>(ast::TextureDimension::k2d); + auto* bool_ = create<sem::Bool>(); + table->Lookup(BuiltinType::kTextureDimensions, {tex, bool_}, Source{}); + ASSERT_EQ( + Diagnostics().str(), + R"(error: no matching call to textureDimensions(texture_depth_2d, bool) + +27 candidate functions: + textureDimensions(texture: texture_depth_2d, level: i32) -> vec2<i32> + textureDimensions(texture: texture_depth_2d) -> vec2<i32> + textureDimensions(texture: texture_1d<T>, level: i32) -> i32 where: T is f32, i32 or u32 + textureDimensions(texture: texture_2d<T>, level: i32) -> vec2<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_2d_array<T>, level: i32) -> vec2<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_3d<T>, level: i32) -> vec3<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_cube<T>, level: i32) -> vec2<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_cube_array<T>, level: i32) -> vec2<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_depth_2d_array, level: i32) -> vec2<i32> + textureDimensions(texture: texture_depth_cube, level: i32) -> vec2<i32> + textureDimensions(texture: texture_depth_cube_array, level: i32) -> vec2<i32> + textureDimensions(texture: texture_1d<T>) -> i32 where: T is f32, i32 or u32 + textureDimensions(texture: texture_2d<T>) -> vec2<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_2d_array<T>) -> vec2<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_3d<T>) -> vec3<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_cube<T>) -> vec2<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_cube_array<T>) -> vec2<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_multisampled_2d<T>) -> vec2<i32> where: T is f32, i32 or u32 + textureDimensions(texture: texture_depth_2d_array) -> vec2<i32> + textureDimensions(texture: texture_depth_cube) -> vec2<i32> + textureDimensions(texture: texture_depth_cube_array) -> vec2<i32> + textureDimensions(texture: texture_depth_multisampled_2d) -> vec2<i32> + textureDimensions(texture: texture_storage_1d<F, A>) -> i32 where: A is write + textureDimensions(texture: texture_storage_2d<F, A>) -> vec2<i32> where: A is write + textureDimensions(texture: texture_storage_2d_array<F, A>) -> vec2<i32> where: A is write + textureDimensions(texture: texture_storage_3d<F, A>) -> vec3<i32> where: A is write + textureDimensions(texture: texture_external) -> vec2<i32> +)"); +} + +TEST_F(BuiltinTableTest, SameOverloadReturnsSameBuiltinPointer) { + auto* f32 = create<sem::F32>(); + auto* vec2_f32 = create<sem::Vector>(create<sem::F32>(), 2u); + auto* bool_ = create<sem::Bool>(); + auto* a = table->Lookup(BuiltinType::kSelect, {f32, f32, bool_}, Source{}); + ASSERT_NE(a, nullptr) << Diagnostics().str(); + + auto* b = table->Lookup(BuiltinType::kSelect, {f32, f32, bool_}, Source{}); + ASSERT_NE(b, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + + auto* c = table->Lookup(BuiltinType::kSelect, {vec2_f32, vec2_f32, bool_}, + Source{}); + ASSERT_NE(c, nullptr) << Diagnostics().str(); + ASSERT_EQ(Diagnostics().str(), ""); + + EXPECT_EQ(a, b); + EXPECT_NE(a, c); + EXPECT_NE(b, c); +} + +} // namespace +} // namespace tint
diff --git a/src/tint/builtins.def b/src/tint/builtins.def new file mode 100644 index 0000000..443634d --- /dev/null +++ b/src/tint/builtins.def
@@ -0,0 +1,560 @@ +// Copyright 2021 The Tint Authors. +// +// 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. + +//////////////////////////////////////////////////////////////////////////////// +// WGSL builtin definition file // +// // +// This file is used to generate parts of the Tint BuiltinTable, various // +// enum definition files, as well as test .wgsl files. // +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Enumerators // +//////////////////////////////////////////////////////////////////////////////// + +// https://gpuweb.github.io/gpuweb/wgsl/#storage-class +enum storage_class { + function + private + workgroup + uniform + storage + [[internal]] handle +} + +// https://gpuweb.github.io/gpuweb/wgsl/#memory-access-mode +enum access { + read + write + read_write +} + +// https://gpuweb.github.io/gpuweb/wgsl/#texel-formats +enum texel_format { + rgba8unorm + rgba8snorm + rgba8uint + rgba8sint + rgba16uint + rgba16sint + rgba16float + r32uint + r32sint + r32float + rg32uint + rg32sint + rg32float + rgba32uint + rgba32sint + rgba32float +} + +//////////////////////////////////////////////////////////////////////////////// +// WGSL primitive types // +//////////////////////////////////////////////////////////////////////////////// + +// https://gpuweb.github.io/gpuweb/wgsl/#plain-types-section +type bool +type f32 +type i32 +type u32 +type vec2<T> +type vec3<T> +type vec4<T> +[[display("vec{N}<{T}>")]] type vec<N: num, T> +[[display("mat{N}x{M}<{T}>")]] type mat<N: num, M: num, T> +type ptr<S: storage_class, T, A: access> +type atomic<T> +type array<T> +type sampler +type sampler_comparison +type texture_1d<T> +type texture_2d<T> +type texture_2d_array<T> +type texture_3d<T> +type texture_cube<T> +type texture_cube_array<T> +type texture_multisampled_2d<T> +type texture_depth_2d +type texture_depth_2d_array +type texture_depth_cube +type texture_depth_cube_array +type texture_depth_multisampled_2d +type texture_storage_1d<F: texel_format, A: access> +type texture_storage_2d<F: texel_format, A: access> +type texture_storage_2d_array<F: texel_format, A: access> +type texture_storage_3d<F: texel_format, A: access> +type texture_external + +type __modf_result +[[display("__modf_result_vec{N}")]] type __modf_result_vec<N: num> +type __frexp_result +[[display("__frexp_result_vec{N}")]] type __frexp_result_vec<N: num> + +//////////////////////////////////////////////////////////////////////////////// +// Type matchers // +// // +// A type matcher that can match one or more types. // +//////////////////////////////////////////////////////////////////////////////// + +match fiu32: f32 | i32 | u32 +match iu32: i32 | u32 +match scalar: f32 | i32 | u32 | bool + +//////////////////////////////////////////////////////////////////////////////// +// Enum matchers // +// // +// A number matcher that can match one or more enumerator values. // +// All enumerator values listed in the match declaration need to be from the // +// same enum. // +//////////////////////////////////////////////////////////////////////////////// + +// https://gpuweb.github.io/gpuweb/wgsl/#texel-formats +match f32_texel_format: + rgba8unorm | rgba8snorm | rgba16float | r32float | rg32float | rgba32float +match i32_texel_format: + rgba8sint | rgba16sint | r32sint | rg32sint | rgba32sint +match u32_texel_format: + rgba8uint | rgba16uint | r32uint | rg32uint | rgba32uint + +match write_only: write + +match function_private_workgroup: function | private | workgroup +match workgroup_or_storage: workgroup | storage + +//////////////////////////////////////////////////////////////////////////////// +// Builtin Functions // +// // +// The builtin function declarations below declare all the built-in // +// functions supported by the WGSL language. This builtin definition // +// language supports simple static-type function declarations, as well as // +// single overload declarations that can match a number of different // +// argument types via the use of 'open-types' and 'open-numbers'. // +// // +// * Basic example: // +// // +// fn isInf(f32) -> bool // +// // +// Declares an overload of the function 'isInf' that accepts a single // +// parameter of type 'f32' and returns a 'bool'. // +// // +// An 'open-type' can be thought as a template type that is determined by the // +// arguments to the builtin. // +// // +// * Open-type example without constraint: // +// // +// fn arrayLength<T>(array<T>) -> u32 // +// // +// Declares an overload of the function 'arrayLength' that accepts a // +// single argument of an array type with no constraints on the array // +// element type. This overload will always return a value of the same type // +// as its single argument. // +// // +// * Open-type example with constraint: // +// // +// fn abs<T: fiu32>(T) -> T // +// // +// Declares an overload of the function 'abs' that accepts a single // +// argument of type 'f32', 'i32' or 'u32', which returns a value of the // +// same argument type. // +// // +// Similarly an 'open-number' can be thought as a template number or // +// enumerator that is determined by the arguments to the builtin. // +// // +// * Open-number example: // +// // +// fn dpdx<N: num>(vec<N, f32>) -> vec<N, f32> // +// // +// Declares an overload of the function 'dpdx' that accepts a single // +// argument of a variable-sized vector of 'f32', which returns a value of // +// the same argument type. // +// // +// // +// Matching algorithm: // +// ------------------- // +// // +// Prior to matching an overload, all open-types are undefined. // +// // +// Open-types become closed-types (pinned to a fixed type) on the first // +// attempt to match an argument to that open-type. // +// Once open-types are closed, they remain that type for the rest of the // +// overload evaluation. // +// // +// To better understand, let's consider the following hypothetical overload // +// declaration: // +// // +// fn foo<T: scalar>(T, T); // +// // +// T - is the open-type // +// scalar - is a matcher for the types 'f32', 'i32', 'u32' or 'bool' // +// (declared above) // +// <T: scalar> - declares the open-type T, with the constraint that T must // +// match one of 'f32', 'i32', 'u32' or 'bool'. // +// // +// The process for resolving this overload is as follows: // +// // +// (1) The overload resolver begins by attempting to match the argument // +// types from left to right. // +// The first parameter type is compared against the argument type. // +// As the open-type T has not been closed yet, T is closed as the type // +// of the first argument. // +// There's no verification that the T type is a scalar at this stage. // +// (2) The second parameter is then compared against the second argument. // +// As the open-type T is now closed, the argument type is compared // +// against the value of the closed-type of T. If the types match, then // +// the overload is still a candidate for matching, otherwise the // +// overload is no longer considered. // +// (3) If all the parameters matched, constraints on the open-types need // +// to be checked next. If the closed-type does not match the 'match' // +// constraint, then the overload is no longer considered. // +// // +// The algorithm for matching open-numbers is almost identical to open-types, // +// except of course, they match against integer numbers or enumerators // +// instead of types. // +// // +// // +// * More examples: // +// // +// fn F() // +// - Function called F. // +// No open types or numbers, no parameters, no return value // +// // +// fn F() -> RETURN_TYPE // +// - Function with RETURN_TYPE as the return type value // +// // +// fn F(f32, i32) // +// - Two fixed-type, anonymous parameters // +// // +// fn F(USAGE : f32) // +// - Single parameter with name USAGE. // +// Note: Parameter names are used by Tint to infer parameter order for // +// some builtin functions // +// // +// fn F<T>(T) // +// - Single parameter of unconstrained open-type T (any type) // +// // +// fn F<T: scalar>(T) // +// - Single parameter of constrained open-type T (must be a scalar) // +// // +// fn F<T: fiu32>(T) -> T // +// - Single parameter of constrained open-type T (must be a one of fiu32) // +// Return type matches parameter type // +// // +// fn F<T, N: num>(vec<N, T>) // +// - Single parameter of vector type with open-number size N and element // +// open-type T // +// // +// fn F<A: access>(texture_storage_1d<f32_texel_format, A>) // +// - Single parameter of texture_storage_1d type with open-number // +// access-control C, and of a texel format that is listed in // +// f32_texel_format // +// // +//////////////////////////////////////////////////////////////////////////////// + +// https://gpuweb.github.io/gpuweb/wgsl/#builtin-functions +fn abs<T: fiu32>(T) -> T +fn abs<N: num, T: fiu32>(vec<N, T>) -> vec<N, T> +fn acos(f32) -> f32 +fn acos<N: num>(vec<N, f32>) -> vec<N, f32> +fn all(bool) -> bool +fn all<N: num>(vec<N, bool>) -> bool +fn any(bool) -> bool +fn any<N: num>(vec<N, bool>) -> bool +fn arrayLength<T, A: access>(ptr<storage, array<T>, A>) -> u32 +fn asin(f32) -> f32 +fn asin<N: num>(vec<N, f32>) -> vec<N, f32> +fn atan(f32) -> f32 +fn atan<N: num>(vec<N, f32>) -> vec<N, f32> +fn atan2(f32, f32) -> f32 +fn atan2<N: num>(vec<N, f32>, vec<N, f32>) -> vec<N, f32> +fn ceil(f32) -> f32 +fn ceil<N: num>(vec<N, f32>) -> vec<N, f32> +fn clamp<T: fiu32>(T, T, T) -> T +fn clamp<N: num, T: fiu32>(vec<N, T>, vec<N, T>, vec<N, T>) -> vec<N, T> +fn cos(f32) -> f32 +fn cos<N: num>(vec<N, f32>) -> vec<N, f32> +fn cosh(f32) -> f32 +fn cosh<N: num>(vec<N, f32>) -> vec<N, f32> +fn countLeadingZeros<T: iu32>(T) -> T +fn countLeadingZeros<N: num, T: iu32>(vec<N, T>) -> vec<N, T> +fn countOneBits<T: iu32>(T) -> T +fn countOneBits<N: num, T: iu32>(vec<N, T>) -> vec<N, T> +fn countTrailingZeros<T: iu32>(T) -> T +fn countTrailingZeros<N: num, T: iu32>(vec<N, T>) -> vec<N, T> +fn cross(vec3<f32>, vec3<f32>) -> vec3<f32> +fn degrees(f32) -> f32 +fn degrees<N: num>(vec<N, f32>) -> vec<N, f32> +fn determinant<N: num>(mat<N, N, f32>) -> f32 +fn distance(f32, f32) -> f32 +fn distance<N: num>(vec<N, f32>, vec<N, f32>) -> f32 +fn dot<N: num, T: fiu32>(vec<N, T>, vec<N, T>) -> T +[[stage("fragment")]] fn dpdx(f32) -> f32 +[[stage("fragment")]] fn dpdx<N: num>(vec<N, f32>) -> vec<N, f32> +[[stage("fragment")]] fn dpdxCoarse(f32) -> f32 +[[stage("fragment")]] fn dpdxCoarse<N: num>(vec<N, f32>) -> vec<N, f32> +[[stage("fragment")]] fn dpdxFine(f32) -> f32 +[[stage("fragment")]] fn dpdxFine<N: num>(vec<N, f32>) -> vec<N, f32> +[[stage("fragment")]] fn dpdy(f32) -> f32 +[[stage("fragment")]] fn dpdy<N: num>(vec<N, f32>) -> vec<N, f32> +[[stage("fragment")]] fn dpdyCoarse(f32) -> f32 +[[stage("fragment")]] fn dpdyCoarse<N: num>(vec<N, f32>) -> vec<N, f32> +[[stage("fragment")]] fn dpdyFine(f32) -> f32 +[[stage("fragment")]] fn dpdyFine<N: num>(vec<N, f32>) -> vec<N, f32> +fn exp(f32) -> f32 +fn exp<N: num>(vec<N, f32>) -> vec<N, f32> +fn exp2(f32) -> f32 +fn exp2<N: num>(vec<N, f32>) -> vec<N, f32> +fn extractBits<T: iu32>(T, u32, u32) -> T +fn extractBits<N: num, T: iu32>(vec<N, T>, u32, u32) -> vec<N, T> +fn faceForward<N: num>(vec<N, f32>, vec<N, f32>, vec<N, f32>) -> vec<N, f32> +fn firstLeadingBit<T: iu32>(T) -> T +fn firstLeadingBit<N: num, T: iu32>(vec<N, T>) -> vec<N, T> +fn firstTrailingBit<T: iu32>(T) -> T +fn firstTrailingBit<N: num, T: iu32>(vec<N, T>) -> vec<N, T> +fn floor(f32) -> f32 +fn floor<N: num>(vec<N, f32>) -> vec<N, f32> +fn fma(f32, f32, f32) -> f32 +fn fma<N: num>(vec<N, f32>, vec<N, f32>, vec<N, f32>) -> vec<N, f32> +fn fract(f32) -> f32 +fn fract<N: num>(vec<N, f32>) -> vec<N, f32> +fn frexp(f32) -> __frexp_result +fn frexp<N: num>(vec<N, f32>) -> __frexp_result_vec<N> +[[stage("fragment")]] fn fwidth(f32) -> f32 +[[stage("fragment")]] fn fwidth<N: num>(vec<N, f32>) -> vec<N, f32> +[[stage("fragment")]] fn fwidthCoarse(f32) -> f32 +[[stage("fragment")]] fn fwidthCoarse<N: num>(vec<N, f32>) -> vec<N, f32> +[[stage("fragment")]] fn fwidthFine(f32) -> f32 +[[stage("fragment")]] fn fwidthFine<N: num>(vec<N, f32>) -> vec<N, f32> +fn insertBits<T: iu32>(T, T, u32, u32) -> T +fn insertBits<N: num, T: iu32>(vec<N, T>, vec<N, T>, u32, u32) -> vec<N, T> +fn inverseSqrt(f32) -> f32 +fn inverseSqrt<N: num>(vec<N, f32>) -> vec<N, f32> +fn ldexp(f32, i32) -> f32 +fn ldexp<N: num>(vec<N, f32>, vec<N, i32>) -> vec<N, f32> +fn length(f32) -> f32 +fn length<N: num>(vec<N, f32>) -> f32 +fn log(f32) -> f32 +fn log<N: num>(vec<N, f32>) -> vec<N, f32> +fn log2(f32) -> f32 +fn log2<N: num>(vec<N, f32>) -> vec<N, f32> +fn max<T: fiu32>(T, T) -> T +fn max<N: num, T: fiu32>(vec<N, T>, vec<N, T>) -> vec<N, T> +fn min<T: fiu32>(T, T) -> T +fn min<N: num, T: fiu32>(vec<N, T>, vec<N, T>) -> vec<N, T> +fn mix(f32, f32, f32) -> f32 +fn mix<N: num>(vec<N, f32>, vec<N, f32>, vec<N, f32>) -> vec<N, f32> +fn mix<N: num>(vec<N, f32>, vec<N, f32>, f32) -> vec<N, f32> +fn modf(f32) -> __modf_result +fn modf<N: num>(vec<N, f32>) -> __modf_result_vec<N> +fn normalize<N: num>(vec<N, f32>) -> vec<N, f32> +fn pack2x16float(vec2<f32>) -> u32 +fn pack2x16snorm(vec2<f32>) -> u32 +fn pack2x16unorm(vec2<f32>) -> u32 +fn pack4x8snorm(vec4<f32>) -> u32 +fn pack4x8unorm(vec4<f32>) -> u32 +fn pow(f32, f32) -> f32 +fn pow<N: num>(vec<N, f32>, vec<N, f32>) -> vec<N, f32> +fn radians(f32) -> f32 +fn radians<N: num>(vec<N, f32>) -> vec<N, f32> +fn reflect<N: num>(vec<N, f32>, vec<N, f32>) -> vec<N, f32> +fn refract<N: num>(vec<N, f32>, vec<N, f32>, f32) -> vec<N, f32> +fn reverseBits<T: iu32>(T) -> T +fn reverseBits<N: num, T: iu32>(vec<N, T>) -> vec<N, T> +fn round(f32) -> f32 +fn round<N: num>(vec<N, f32>) -> vec<N, f32> +fn select<T: scalar>(T, T, bool) -> T +fn select<T: scalar, N: num>(vec<N, T>, vec<N, T>, bool) -> vec<N, T> +fn select<N: num, T: scalar>(vec<N, T>, vec<N, T>, vec<N, bool>) -> vec<N, T> +fn sign(f32) -> f32 +fn sign<N: num>(vec<N, f32>) -> vec<N, f32> +fn sin(f32) -> f32 +fn sin<N: num>(vec<N, f32>) -> vec<N, f32> +fn sinh(f32) -> f32 +fn sinh<N: num>(vec<N, f32>) -> vec<N, f32> +fn smoothstep(f32, f32, f32) -> f32 +fn smoothstep<N: num>(vec<N, f32>, vec<N, f32>, vec<N, f32>) -> vec<N, f32> +[[deprecated]] fn smoothStep(f32, f32, f32) -> f32 +[[deprecated]] fn smoothStep<N: num>(vec<N, f32>, vec<N, f32>, vec<N, f32>) -> vec<N, f32> +fn sqrt(f32) -> f32 +fn sqrt<N: num>(vec<N, f32>) -> vec<N, f32> +fn step(f32, f32) -> f32 +fn step<N: num>(vec<N, f32>, vec<N, f32>) -> vec<N, f32> +[[stage("compute")]] fn storageBarrier() +fn tan(f32) -> f32 +fn tan<N: num>(vec<N, f32>) -> vec<N, f32> +fn tanh(f32) -> f32 +fn tanh<N: num>(vec<N, f32>) -> vec<N, f32> +fn transpose<M: num, N: num>(mat<M, N, f32>) -> mat<N, M, f32> +fn trunc(f32) -> f32 +fn trunc<N: num>(vec<N, f32>) -> vec<N, f32> +fn unpack2x16float(u32) -> vec2<f32> +fn unpack2x16snorm(u32) -> vec2<f32> +fn unpack2x16unorm(u32) -> vec2<f32> +fn unpack4x8snorm(u32) -> vec4<f32> +fn unpack4x8unorm(u32) -> vec4<f32> +[[stage("compute")]] fn workgroupBarrier() + +fn textureDimensions<T: fiu32>(texture: texture_1d<T>) -> i32 +fn textureDimensions<T: fiu32>(texture: texture_1d<T>, level: i32) -> i32 +fn textureDimensions<T: fiu32>(texture: texture_2d<T>) -> vec2<i32> +fn textureDimensions<T: fiu32>(texture: texture_2d<T>, level: i32) -> vec2<i32> +fn textureDimensions<T: fiu32>(texture: texture_2d_array<T>) -> vec2<i32> +fn textureDimensions<T: fiu32>(texture: texture_2d_array<T>, level: i32) -> vec2<i32> +fn textureDimensions<T: fiu32>(texture: texture_3d<T>) -> vec3<i32> +fn textureDimensions<T: fiu32>(texture: texture_3d<T>, level: i32) -> vec3<i32> +fn textureDimensions<T: fiu32>(texture: texture_cube<T>) -> vec2<i32> +fn textureDimensions<T: fiu32>(texture: texture_cube<T>, level: i32) -> vec2<i32> +fn textureDimensions<T: fiu32>(texture: texture_cube_array<T>) -> vec2<i32> +fn textureDimensions<T: fiu32>(texture: texture_cube_array<T>, level: i32) -> vec2<i32> +fn textureDimensions<T: fiu32>(texture: texture_multisampled_2d<T>) -> vec2<i32> +fn textureDimensions(texture: texture_depth_2d) -> vec2<i32> +fn textureDimensions(texture: texture_depth_2d, level: i32) -> vec2<i32> +fn textureDimensions(texture: texture_depth_2d_array) -> vec2<i32> +fn textureDimensions(texture: texture_depth_2d_array, level: i32) -> vec2<i32> +fn textureDimensions(texture: texture_depth_cube) -> vec2<i32> +fn textureDimensions(texture: texture_depth_cube, level: i32) -> vec2<i32> +fn textureDimensions(texture: texture_depth_cube_array) -> vec2<i32> +fn textureDimensions(texture: texture_depth_cube_array, level: i32) -> vec2<i32> +fn textureDimensions(texture: texture_depth_multisampled_2d) -> vec2<i32> +fn textureDimensions<F: texel_format, A: write_only>(texture: texture_storage_1d<F, A>) -> i32 +fn textureDimensions<F: texel_format, A: write_only>(texture: texture_storage_2d<F, A>) -> vec2<i32> +fn textureDimensions<F: texel_format, A: write_only>(texture: texture_storage_2d_array<F, A>) -> vec2<i32> +fn textureDimensions<F: texel_format, A: write_only>(texture: texture_storage_3d<F, A>) -> vec3<i32> +fn textureDimensions(texture: texture_external) -> vec2<i32> +fn textureGather<T: fiu32>(component: i32, texture: texture_2d<T>, sampler: sampler, coords: vec2<f32>) -> vec4<T> +fn textureGather<T: fiu32>(component: i32, texture: texture_2d<T>, sampler: sampler, coords: vec2<f32>, offset: vec2<i32>) -> vec4<T> +fn textureGather<T: fiu32>(component: i32, texture: texture_2d_array<T>, sampler: sampler, coords: vec2<f32>, array_index: i32) -> vec4<T> +fn textureGather<T: fiu32>(component: i32, texture: texture_2d_array<T>, sampler: sampler, coords: vec2<f32>, array_index: i32, offset: vec2<i32>) -> vec4<T> +fn textureGather<T: fiu32>(component: i32, texture: texture_cube<T>, sampler: sampler, coords: vec3<f32>) -> vec4<T> +fn textureGather<T: fiu32>(component: i32, texture: texture_cube_array<T>, sampler: sampler, coords: vec3<f32>, array_index: i32) -> vec4<T> +fn textureGather(texture: texture_depth_2d, sampler: sampler, coords: vec2<f32>) -> vec4<f32> +fn textureGather(texture: texture_depth_2d, sampler: sampler, coords: vec2<f32>, offset: vec2<i32>) -> vec4<f32> +fn textureGather(texture: texture_depth_2d_array, sampler: sampler, coords: vec2<f32>, array_index: i32) -> vec4<f32> +fn textureGather(texture: texture_depth_2d_array, sampler: sampler, coords: vec2<f32>, array_index: i32, offset: vec2<i32>) -> vec4<f32> +fn textureGather(texture: texture_depth_cube, sampler: sampler, coords: vec3<f32>) -> vec4<f32> +fn textureGather(texture: texture_depth_cube_array, sampler: sampler, coords: vec3<f32>, array_index: i32) -> vec4<f32> +fn textureGatherCompare(texture: texture_depth_2d, sampler: sampler_comparison, coords: vec2<f32>, depth_ref: f32) -> vec4<f32> +fn textureGatherCompare(texture: texture_depth_2d, sampler: sampler_comparison, coords: vec2<f32>, depth_ref: f32, offset: vec2<i32>) -> vec4<f32> +fn textureGatherCompare(texture: texture_depth_2d_array, sampler: sampler_comparison, coords: vec2<f32>, array_index: i32, depth_ref: f32) -> vec4<f32> +fn textureGatherCompare(texture: texture_depth_2d_array, sampler: sampler_comparison, coords: vec2<f32>, array_index: i32, depth_ref: f32, offset: vec2<i32>) -> vec4<f32> +fn textureGatherCompare(texture: texture_depth_cube, sampler: sampler_comparison, coords: vec3<f32>, depth_ref: f32) -> vec4<f32> +fn textureGatherCompare(texture: texture_depth_cube_array, sampler: sampler_comparison, coords: vec3<f32>, array_index: i32, depth_ref: f32) -> vec4<f32> +fn textureNumLayers<T: fiu32>(texture: texture_2d_array<T>) -> i32 +fn textureNumLayers<T: fiu32>(texture: texture_cube_array<T>) -> i32 +fn textureNumLayers(texture: texture_depth_2d_array) -> i32 +fn textureNumLayers(texture: texture_depth_cube_array) -> i32 +fn textureNumLayers<F: texel_format, A: write_only>(texture: texture_storage_2d_array<F, A>) -> i32 +fn textureNumLevels<T: fiu32>(texture: texture_1d<T>) -> i32 +fn textureNumLevels<T: fiu32>(texture: texture_2d<T>) -> i32 +fn textureNumLevels<T: fiu32>(texture: texture_2d_array<T>) -> i32 +fn textureNumLevels<T: fiu32>(texture: texture_3d<T>) -> i32 +fn textureNumLevels<T: fiu32>(texture: texture_cube<T>) -> i32 +fn textureNumLevels<T: fiu32>(texture: texture_cube_array<T>) -> i32 +fn textureNumLevels(texture: texture_depth_2d) -> i32 +fn textureNumLevels(texture: texture_depth_2d_array) -> i32 +fn textureNumLevels(texture: texture_depth_cube) -> i32 +fn textureNumLevels(texture: texture_depth_cube_array) -> i32 +fn textureNumSamples<T: fiu32>(texture: texture_multisampled_2d<T>) -> i32 +fn textureNumSamples(texture: texture_depth_multisampled_2d) -> i32 +[[stage("fragment")]] fn textureSample(texture: texture_1d<f32>, sampler: sampler, coords: f32) -> vec4<f32> +[[stage("fragment")]] fn textureSample(texture: texture_2d<f32>, sampler: sampler, coords: vec2<f32>) -> vec4<f32> +[[stage("fragment")]] fn textureSample(texture: texture_2d<f32>, sampler: sampler, coords: vec2<f32>, offset: vec2<i32>) -> vec4<f32> +[[stage("fragment")]] fn textureSample(texture: texture_2d_array<f32>, sampler: sampler, coords: vec2<f32>, array_index: i32) -> vec4<f32> +[[stage("fragment")]] fn textureSample(texture: texture_2d_array<f32>, sampler: sampler, coords: vec2<f32>, array_index: i32, offset: vec2<i32>) -> vec4<f32> +[[stage("fragment")]] fn textureSample(texture: texture_3d<f32>, sampler: sampler, coords: vec3<f32>) -> vec4<f32> +[[stage("fragment")]] fn textureSample(texture: texture_3d<f32>, sampler: sampler, coords: vec3<f32>, offset: vec3<i32>) -> vec4<f32> +[[stage("fragment")]] fn textureSample(texture: texture_cube<f32>, sampler: sampler, coords: vec3<f32>) -> vec4<f32> +[[stage("fragment")]] fn textureSample(texture: texture_cube_array<f32>, sampler: sampler, coords: vec3<f32>, array_index: i32) -> vec4<f32> +[[stage("fragment")]] fn textureSample(texture: texture_depth_2d, sampler: sampler, coords: vec2<f32>) -> f32 +[[stage("fragment")]] fn textureSample(texture: texture_depth_2d, sampler: sampler, coords: vec2<f32>, offset: vec2<i32>) -> f32 +[[stage("fragment")]] fn textureSample(texture: texture_depth_2d_array, sampler: sampler, coords: vec2<f32>, array_index: i32) -> f32 +[[stage("fragment")]] fn textureSample(texture: texture_depth_2d_array, sampler: sampler, coords: vec2<f32>, array_index: i32, offset: vec2<i32>) -> f32 +[[stage("fragment")]] fn textureSample(texture: texture_depth_cube, sampler: sampler, coords: vec3<f32>) -> f32 +[[stage("fragment")]] fn textureSample(texture: texture_depth_cube_array, sampler: sampler, coords: vec3<f32>, array_index: i32) -> f32 +[[stage("fragment")]] fn textureSampleBias(texture: texture_2d<f32>, sampler: sampler, coords: vec2<f32>, bias: f32) -> vec4<f32> +[[stage("fragment")]] fn textureSampleBias(texture: texture_2d<f32>, sampler: sampler, coords: vec2<f32>, bias: f32, offset: vec2<i32>) -> vec4<f32> +[[stage("fragment")]] fn textureSampleBias(texture: texture_2d_array<f32>, sampler: sampler, coords: vec2<f32>, array_index: i32, bias: f32) -> vec4<f32> +[[stage("fragment")]] fn textureSampleBias(texture: texture_2d_array<f32>, sampler: sampler, coords: vec2<f32>, array_index: i32, bias: f32, offset: vec2<i32>) -> vec4<f32> +[[stage("fragment")]] fn textureSampleBias(texture: texture_3d<f32>, sampler: sampler, coords: vec3<f32>, bias: f32) -> vec4<f32> +[[stage("fragment")]] fn textureSampleBias(texture: texture_3d<f32>, sampler: sampler, coords: vec3<f32>, bias: f32, offset: vec3<i32>) -> vec4<f32> +[[stage("fragment")]] fn textureSampleBias(texture: texture_cube<f32>, sampler: sampler, coords: vec3<f32>, bias: f32) -> vec4<f32> +[[stage("fragment")]] fn textureSampleBias(texture: texture_cube_array<f32>, sampler: sampler, coords: vec3<f32>, array_index: i32, bias: f32) -> vec4<f32> +[[stage("fragment")]] fn textureSampleCompare(texture: texture_depth_2d, sampler: sampler_comparison, coords: vec2<f32>, depth_ref: f32) -> f32 +[[stage("fragment")]] fn textureSampleCompare(texture: texture_depth_2d, sampler: sampler_comparison, coords: vec2<f32>, depth_ref: f32, offset: vec2<i32>) -> f32 +[[stage("fragment")]] fn textureSampleCompare(texture: texture_depth_2d_array, sampler: sampler_comparison, coords: vec2<f32>, array_index: i32, depth_ref: f32) -> f32 +[[stage("fragment")]] fn textureSampleCompare(texture: texture_depth_2d_array, sampler: sampler_comparison, coords: vec2<f32>, array_index: i32, depth_ref: f32, offset: vec2<i32>) -> f32 +[[stage("fragment")]] fn textureSampleCompare(texture: texture_depth_cube, sampler: sampler_comparison, coords: vec3<f32>, depth_ref: f32) -> f32 +[[stage("fragment")]] fn textureSampleCompare(texture: texture_depth_cube_array, sampler: sampler_comparison, coords: vec3<f32>, array_index: i32, depth_ref: f32) -> f32 +fn textureSampleCompareLevel(texture: texture_depth_2d, sampler: sampler_comparison, coords: vec2<f32>, depth_ref: f32) -> f32 +fn textureSampleCompareLevel(texture: texture_depth_2d, sampler: sampler_comparison, coords: vec2<f32>, depth_ref: f32, offset: vec2<i32>) -> f32 +fn textureSampleCompareLevel(texture: texture_depth_2d_array, sampler: sampler_comparison, coords: vec2<f32>, array_index: i32, depth_ref: f32) -> f32 +fn textureSampleCompareLevel(texture: texture_depth_2d_array, sampler: sampler_comparison, coords: vec2<f32>, array_index: i32, depth_ref: f32, offset: vec2<i32>) -> f32 +fn textureSampleCompareLevel(texture: texture_depth_cube, sampler: sampler_comparison, coords: vec3<f32>, depth_ref: f32) -> f32 +fn textureSampleCompareLevel(texture: texture_depth_cube_array, sampler: sampler_comparison, coords: vec3<f32>, array_index: i32, depth_ref: f32) -> f32 +fn textureSampleGrad(texture: texture_2d<f32>, sampler: sampler, coords: vec2<f32>, ddx: vec2<f32>, ddy: vec2<f32>) -> vec4<f32> +fn textureSampleGrad(texture: texture_2d<f32>, sampler: sampler, coords: vec2<f32>, ddx: vec2<f32>, ddy: vec2<f32>, offset: vec2<i32>) -> vec4<f32> +fn textureSampleGrad(texture: texture_2d_array<f32>, sampler: sampler, coords: vec2<f32>, array_index: i32, ddx: vec2<f32>, ddy: vec2<f32>) -> vec4<f32> +fn textureSampleGrad(texture: texture_2d_array<f32>, sampler: sampler, coords: vec2<f32>, array_index: i32, ddx: vec2<f32>, ddy: vec2<f32>, offset: vec2<i32>) -> vec4<f32> +fn textureSampleGrad(texture: texture_3d<f32>, sampler: sampler, coords: vec3<f32>, ddx: vec3<f32>, ddy: vec3<f32>) -> vec4<f32> +fn textureSampleGrad(texture: texture_3d<f32>, sampler: sampler, coords: vec3<f32>, ddx: vec3<f32>, ddy: vec3<f32>, offset: vec3<i32>) -> vec4<f32> +fn textureSampleGrad(texture: texture_cube<f32>, sampler: sampler, coords: vec3<f32>, ddx: vec3<f32>, ddy: vec3<f32>) -> vec4<f32> +fn textureSampleGrad(texture: texture_cube_array<f32>, sampler: sampler, coords: vec3<f32>, array_index: i32, ddx: vec3<f32>, ddy: vec3<f32>) -> vec4<f32> +fn textureSampleLevel(texture: texture_2d<f32>, sampler: sampler, coords: vec2<f32>, level: f32) -> vec4<f32> +fn textureSampleLevel(texture: texture_2d<f32>, sampler: sampler, coords: vec2<f32>, level: f32, offset: vec2<i32>) -> vec4<f32> +fn textureSampleLevel(texture: texture_2d_array<f32>, sampler: sampler, coords: vec2<f32>, array_index: i32, level: f32) -> vec4<f32> +fn textureSampleLevel(texture: texture_2d_array<f32>, sampler: sampler, coords: vec2<f32>, array_index: i32, level: f32, offset: vec2<i32>) -> vec4<f32> +fn textureSampleLevel(texture: texture_3d<f32>, sampler: sampler, coords: vec3<f32>, level: f32) -> vec4<f32> +fn textureSampleLevel(texture: texture_3d<f32>, sampler: sampler, coords: vec3<f32>, level: f32, offset: vec3<i32>) -> vec4<f32> +fn textureSampleLevel(texture: texture_cube<f32>, sampler: sampler, coords: vec3<f32>, level: f32) -> vec4<f32> +fn textureSampleLevel(texture: texture_cube_array<f32>, sampler: sampler, coords: vec3<f32>, array_index: i32, level: f32) -> vec4<f32> +fn textureSampleLevel(texture: texture_depth_2d, sampler: sampler, coords: vec2<f32>, level: i32) -> f32 +fn textureSampleLevel(texture: texture_depth_2d, sampler: sampler, coords: vec2<f32>, level: i32, offset: vec2<i32>) -> f32 +fn textureSampleLevel(texture: texture_depth_2d_array, sampler: sampler, coords: vec2<f32>, array_index: i32, level: i32) -> f32 +fn textureSampleLevel(texture: texture_depth_2d_array, sampler: sampler, coords: vec2<f32>, array_index: i32, level: i32, offset: vec2<i32>) -> f32 +fn textureSampleLevel(texture: texture_depth_cube, sampler: sampler, coords: vec3<f32>, level: i32) -> f32 +fn textureSampleLevel(texture: texture_depth_cube_array,sampler: sampler, coords: vec3<f32>, array_index: i32, level: i32) -> f32 +fn textureSampleLevel(texture: texture_external, sampler: sampler, coords: vec2<f32>) -> vec4<f32> +fn textureStore(texture: texture_storage_1d<f32_texel_format, write>, coords: i32, value: vec4<f32>) +fn textureStore(texture: texture_storage_2d<f32_texel_format, write>, coords: vec2<i32>, value: vec4<f32>) +fn textureStore(texture: texture_storage_2d_array<f32_texel_format, write>, coords: vec2<i32>, array_index: i32, value: vec4<f32>) +fn textureStore(texture: texture_storage_3d<f32_texel_format, write>, coords: vec3<i32>, value: vec4<f32>) +fn textureStore(texture: texture_storage_1d<i32_texel_format, write>, coords: i32, value: vec4<i32>) +fn textureStore(texture: texture_storage_2d<i32_texel_format, write>, coords: vec2<i32>, value: vec4<i32>) +fn textureStore(texture: texture_storage_2d_array<i32_texel_format, write>, coords: vec2<i32>, array_index: i32, value: vec4<i32>) +fn textureStore(texture: texture_storage_3d<i32_texel_format, write>, coords: vec3<i32>, value: vec4<i32>) +fn textureStore(texture: texture_storage_1d<u32_texel_format, write>, coords: i32, value: vec4<u32>) +fn textureStore(texture: texture_storage_2d<u32_texel_format, write>, coords: vec2<i32>, value: vec4<u32>) +fn textureStore(texture: texture_storage_2d_array<u32_texel_format, write>, coords: vec2<i32>, array_index: i32, value: vec4<u32>) +fn textureStore(texture: texture_storage_3d<u32_texel_format, write>, coords: vec3<i32>, value: vec4<u32>) +fn textureLoad<T: fiu32>(texture: texture_1d<T>, coords: i32, level: i32) -> vec4<T> +fn textureLoad<T: fiu32>(texture: texture_2d<T>, coords: vec2<i32>, level: i32) -> vec4<T> +fn textureLoad<T: fiu32>(texture: texture_2d_array<T>, coords: vec2<i32>, array_index: i32, level: i32) -> vec4<T> +fn textureLoad<T: fiu32>(texture: texture_3d<T>, coords: vec3<i32>, level: i32) -> vec4<T> +fn textureLoad<T: fiu32>(texture: texture_multisampled_2d<T>, coords: vec2<i32>, sample_index: i32) -> vec4<T> +fn textureLoad(texture: texture_depth_2d, coords: vec2<i32>, level: i32) -> f32 +fn textureLoad(texture: texture_depth_2d_array, coords: vec2<i32>, array_index: i32, level: i32) -> f32 +fn textureLoad(texture: texture_depth_multisampled_2d, coords: vec2<i32>, sample_index: i32) -> f32 +fn textureLoad(texture: texture_external, coords: vec2<i32>) -> vec4<f32> + +[[stage("fragment", "compute")]] fn atomicLoad<T: iu32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>) -> T +[[stage("fragment", "compute")]] fn atomicStore<T: iu32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) +[[stage("fragment", "compute")]] fn atomicAdd<T: iu32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T +[[stage("fragment", "compute")]] fn atomicSub<T: iu32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T +[[stage("fragment", "compute")]] fn atomicMax<T: iu32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T +[[stage("fragment", "compute")]] fn atomicMin<T: iu32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T +[[stage("fragment", "compute")]] fn atomicAnd<T: iu32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T +[[stage("fragment", "compute")]] fn atomicOr<T: iu32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T +[[stage("fragment", "compute")]] fn atomicXor<T: iu32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T +[[stage("fragment", "compute")]] fn atomicExchange<T: iu32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T +[[stage("fragment", "compute")]] fn atomicCompareExchangeWeak<T: iu32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T, T) -> vec2<T>
diff --git a/src/tint/castable.cc b/src/tint/castable.cc new file mode 100644 index 0000000..cff430e --- /dev/null +++ b/src/tint/castable.cc
@@ -0,0 +1,29 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/castable.h" + +namespace tint { + +/// The unique TypeInfo for the CastableBase type +/// @return doxygen-thinks-this-static-field-is-a-function :( +template <> +const TypeInfo detail::TypeInfoOf<CastableBase>::info{ + nullptr, + "CastableBase", + tint::TypeInfo::HashCodeOf<CastableBase>(), + tint::TypeInfo::FullHashCodeOf<CastableBase>(), +}; + +} // namespace tint
diff --git a/src/tint/castable.h b/src/tint/castable.h new file mode 100644 index 0000000..f5d2cb2 --- /dev/null +++ b/src/tint/castable.h
@@ -0,0 +1,815 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_CASTABLE_H_ +#define SRC_TINT_CASTABLE_H_ + +#include <stdint.h> +#include <functional> +#include <tuple> +#include <utility> + +#include "src/tint/traits.h" +#include "src/tint/utils/crc32.h" + +#if defined(__clang__) +/// Temporarily disable certain warnings when using Castable API +#define TINT_CASTABLE_PUSH_DISABLE_WARNINGS() \ + _Pragma("clang diagnostic push") /**/ \ + _Pragma("clang diagnostic ignored \"-Wundefined-var-template\"") /**/ \ + static_assert(true, "require extra semicolon") + +/// Restore disabled warnings +#define TINT_CASTABLE_POP_DISABLE_WARNINGS() \ + _Pragma("clang diagnostic pop") /**/ \ + static_assert(true, "require extra semicolon") +#else +#define TINT_CASTABLE_PUSH_DISABLE_WARNINGS() \ + static_assert(true, "require extra semicolon") +#define TINT_CASTABLE_POP_DISABLE_WARNINGS() \ + static_assert(true, "require extra semicolon") +#endif + +TINT_CASTABLE_PUSH_DISABLE_WARNINGS(); + +namespace tint { + +// Forward declaration +class CastableBase; + +/// Ignore is used as a special type used for skipping over types for trait +/// helper functions. +class Ignore {}; + +namespace detail { +template <typename T> +struct TypeInfoOf; + +} // namespace detail + +/// True if all template types that are not Ignore derive from CastableBase +template <typename... TYPES> +static constexpr bool IsCastable = + ((traits::IsTypeOrDerived<TYPES, CastableBase> || + std::is_same_v<TYPES, Ignore>)&&...) && + !(std::is_same_v<TYPES, Ignore> && ...); + +/// Helper macro to instantiate the TypeInfo<T> template for `CLASS`. +#define TINT_INSTANTIATE_TYPEINFO(CLASS) \ + TINT_CASTABLE_PUSH_DISABLE_WARNINGS(); \ + template <> \ + const tint::TypeInfo tint::detail::TypeInfoOf<CLASS>::info{ \ + &tint::detail::TypeInfoOf<CLASS::TrueBase>::info, \ + #CLASS, \ + tint::TypeInfo::HashCodeOf<CLASS>(), \ + tint::TypeInfo::FullHashCodeOf<CLASS>(), \ + }; \ + TINT_CASTABLE_POP_DISABLE_WARNINGS() + +/// Bit flags that can be passed to the template parameter `FLAGS` of Is() and +/// As(). +enum CastFlags { + /// Disables the static_assert() inside Is(), that compile-time-verifies that + /// the cast is possible. This flag may be useful for highly-generic template + /// code that needs to compile for template permutations that generate + /// impossible casts. + kDontErrorOnImpossibleCast = 1, +}; + +/// TypeInfo holds type information for a Castable type. +struct TypeInfo { + /// The type of a hash code + using HashCode = uint64_t; + + /// The base class of this type + const TypeInfo* base; + /// The type name + const char* name; + /// The type hash code + const HashCode hashcode; + /// The type hash code bitwise-or'd with all ancestor's hashcodes. + const HashCode full_hashcode; + + /// @param type the test type info + /// @returns true if the class with this TypeInfo is of, or derives from the + /// class with the given TypeInfo. + inline bool Is(const tint::TypeInfo* type) const { + // Optimization: Check whether the all the bits of the type's hashcode can + // be found in the full_hashcode. If a single bit is missing, then we + // can quickly tell that that this TypeInfo does not derive from `type`. + if ((full_hashcode & type->hashcode) != type->hashcode) { + return false; + } + + // Walk the base types, starting with this TypeInfo, to see if any of the + // pointers match `type`. + for (auto* ti = this; ti != nullptr; ti = ti->base) { + if (ti == type) { + return true; + } + } + return false; + } + + /// @returns true if `type` derives from the class `TO` + /// @param type the object type to test from, which must be, or derive from + /// type `FROM`. + /// @see CastFlags + template <typename TO, typename FROM, int FLAGS = 0> + static inline bool Is(const tint::TypeInfo* type) { + constexpr const bool downcast = std::is_base_of<FROM, TO>::value; + constexpr const bool upcast = std::is_base_of<TO, FROM>::value; + constexpr const bool nocast = std::is_same<FROM, TO>::value; + constexpr const bool assert_is_castable = + (FLAGS & kDontErrorOnImpossibleCast) == 0; + + static_assert(upcast || downcast || nocast || !assert_is_castable, + "impossible cast"); + + if (upcast || nocast) { + return true; + } + + return type->Is(&Of<std::remove_cv_t<TO>>()); + } + + /// @returns the static TypeInfo for the type T + template <typename T> + static const TypeInfo& Of() { + return detail::TypeInfoOf<std::remove_cv_t<T>>::info; + } + + /// @returns a compile-time hashcode for the type `T`. + /// @note the returned hashcode will have at most 2 bits set, as the hashes + /// are expected to be used in bloom-filters which will quickly saturate when + /// multiple hashcodes are bitwise-or'd together. + template <typename T> + static constexpr HashCode HashCodeOf() { + static_assert(IsCastable<T>, "T is not Castable"); + static_assert( + std::is_same_v<T, std::remove_cv_t<T>>, + "Strip const / volatile decorations before calling HashCodeOf"); + /// Use the compiler's "pretty" function name, which includes the template + /// type, to obtain a unique hash value. +#ifdef _MSC_VER + constexpr uint32_t crc = utils::CRC32(__FUNCSIG__); +#else + constexpr uint32_t crc = utils::CRC32(__PRETTY_FUNCTION__); +#endif + constexpr uint32_t bit_a = (crc & 63); + constexpr uint32_t bit_b = ((crc >> 6) & 63); + return (static_cast<HashCode>(1) << bit_a) | + (static_cast<HashCode>(1) << bit_b); + } + + /// @returns the hashcode of the given type, bitwise-or'd with the hashcodes + /// of all base classes. + template <typename T> + static constexpr HashCode FullHashCodeOf() { + if constexpr (std::is_same_v<T, CastableBase>) { + return HashCodeOf<CastableBase>(); + } else { + return HashCodeOf<T>() | FullHashCodeOf<typename T::TrueBase>(); + } + } + + /// @returns the bitwise-or'd hashcodes of all the types of the tuple `TUPLE`. + /// @see HashCodeOf + template <typename TUPLE> + static constexpr HashCode CombinedHashCodeOfTuple() { + constexpr auto kCount = std::tuple_size_v<TUPLE>; + if constexpr (kCount == 0) { + return 0; + } else if constexpr (kCount == 1) { + return HashCodeOf<std::remove_cv_t<std::tuple_element_t<0, TUPLE>>>(); + } else { + constexpr auto kMid = kCount / 2; + return CombinedHashCodeOfTuple<traits::SliceTuple<0, kMid, TUPLE>>() | + CombinedHashCodeOfTuple< + traits::SliceTuple<kMid, kCount - kMid, TUPLE>>(); + } + } + + /// @returns the bitwise-or'd hashcodes of all the template parameter types. + /// @see HashCodeOf + template <typename... TYPES> + static constexpr HashCode CombinedHashCodeOf() { + return CombinedHashCodeOfTuple<std::tuple<TYPES...>>(); + } + + /// @returns true if this TypeInfo is of, or derives from any of the types in + /// `TUPLE`. + template <typename TUPLE> + inline bool IsAnyOfTuple() const { + constexpr auto kCount = std::tuple_size_v<TUPLE>; + if constexpr (kCount == 0) { + return false; + } else if constexpr (kCount == 1) { + return Is(&Of<std::tuple_element_t<0, TUPLE>>()); + } else if constexpr (kCount == 2) { + return Is(&Of<std::tuple_element_t<0, TUPLE>>()) || + Is(&Of<std::tuple_element_t<1, TUPLE>>()); + } else if constexpr (kCount == 3) { + return Is(&Of<std::tuple_element_t<0, TUPLE>>()) || + Is(&Of<std::tuple_element_t<1, TUPLE>>()) || + Is(&Of<std::tuple_element_t<2, TUPLE>>()); + } else { + // Optimization: Compare the object's hashcode to the bitwise-or of all + // the tested type's hashcodes. If there's no intersection of bits in + // the two masks, then we can guarantee that the type is not in `TO`. + if (full_hashcode & TypeInfo::CombinedHashCodeOfTuple<TUPLE>()) { + // Possibly one of the types in `TUPLE`. + // Split the search in two, and scan each block. + static constexpr auto kMid = kCount / 2; + return IsAnyOfTuple<traits::SliceTuple<0, kMid, TUPLE>>() || + IsAnyOfTuple<traits::SliceTuple<kMid, kCount - kMid, TUPLE>>(); + } + return false; + } + } + + /// @returns true if this TypeInfo is of, or derives from any of the types in + /// `TYPES`. + template <typename... TYPES> + inline bool IsAnyOf() const { + return IsAnyOfTuple<std::tuple<TYPES...>>(); + } +}; + +namespace detail { + +/// TypeInfoOf contains a single TypeInfo field for the type T. +/// TINT_INSTANTIATE_TYPEINFO() must be defined in a .cpp file for each type +/// `T`. +template <typename T> +struct TypeInfoOf { + /// The unique TypeInfo for the type T. + static const TypeInfo info; +}; + +/// A placeholder structure used for template parameters that need a default +/// type, but can always be automatically inferred. +struct Infer; + +} // namespace detail + +/// @returns true if `obj` is a valid pointer, and is of, or derives from the +/// class `TO` +/// @param obj the object to test from +/// @see CastFlags +template <typename TO, int FLAGS = 0, typename FROM = detail::Infer> +inline bool Is(FROM* obj) { + if (obj == nullptr) { + return false; + } + return TypeInfo::Is<TO, FROM, FLAGS>(&obj->TypeInfo()); +} + +/// @returns true if `obj` is a valid pointer, and is of, or derives from the +/// type `TYPE`, and pred(const TYPE*) returns true +/// @param obj the object to test from +/// @param pred predicate function with signature `bool(const TYPE*)` called iff +/// object is of, or derives from the class `TYPE`. +/// @see CastFlags +template <typename TYPE, + int FLAGS = 0, + typename OBJ = detail::Infer, + typename Pred = detail::Infer> +inline bool Is(OBJ* obj, Pred&& pred) { + return Is<TYPE, FLAGS, OBJ>(obj) && + pred(static_cast<std::add_const_t<TYPE>*>(obj)); +} + +/// @returns true if `obj` is a valid pointer, and is of, or derives from any of +/// the types in `TYPES`.OBJ +/// @param obj the object to query. +template <typename... TYPES, typename OBJ> +inline bool IsAnyOf(OBJ* obj) { + if (!obj) { + return false; + } + return obj->TypeInfo().template IsAnyOf<TYPES...>(); +} + +/// @returns obj dynamically cast to the type `TO` or `nullptr` if +/// this object does not derive from `TO`. +/// @param obj the object to cast from +/// @see CastFlags +template <typename TO, int FLAGS = 0, typename FROM = detail::Infer> +inline TO* As(FROM* obj) { + auto* as_castable = static_cast<CastableBase*>(obj); + return Is<TO, FLAGS>(obj) ? static_cast<TO*>(as_castable) : nullptr; +} + +/// @returns obj dynamically cast to the type `TO` or `nullptr` if +/// this object does not derive from `TO`. +/// @param obj the object to cast from +/// @see CastFlags +template <typename TO, int FLAGS = 0, typename FROM = detail::Infer> +inline const TO* As(const FROM* obj) { + auto* as_castable = static_cast<const CastableBase*>(obj); + return Is<TO, FLAGS>(obj) ? static_cast<const TO*>(as_castable) : nullptr; +} + +/// CastableBase is the base class for all Castable objects. +/// It is not encouraged to directly derive from CastableBase without using the +/// Castable helper template. +/// @see Castable +class CastableBase { + public: + /// Copy constructor + CastableBase(const CastableBase&) = default; + + /// Destructor + virtual ~CastableBase() = default; + + /// Copy assignment + /// @param other the CastableBase to copy + /// @returns the new CastableBase + CastableBase& operator=(const CastableBase& other) = default; + + /// @returns the TypeInfo of the object + virtual const tint::TypeInfo& TypeInfo() const = 0; + + /// @returns true if this object is of, or derives from the class `TO` + template <typename TO> + inline bool Is() const { + return tint::Is<TO>(this); + } + + /// @returns true if this object is of, or derives from the class `TO` and + /// pred(const TO*) returns true + /// @param pred predicate function with signature `bool(const TO*)` called iff + /// object is of, or derives from the class `TO`. + template <typename TO, int FLAGS = 0, typename Pred = detail::Infer> + inline bool Is(Pred&& pred) const { + return tint::Is<TO, FLAGS>(this, std::forward<Pred>(pred)); + } + + /// @returns true if this object is of, or derives from any of the `TO` + /// classes. + template <typename... TO> + inline bool IsAnyOf() const { + return tint::IsAnyOf<TO...>(this); + } + + /// @returns this object dynamically cast to the type `TO` or `nullptr` if + /// this object does not derive from `TO`. + /// @see CastFlags + template <typename TO, int FLAGS = 0> + inline TO* As() { + return tint::As<TO, FLAGS>(this); + } + + /// @returns this object dynamically cast to the type `TO` or `nullptr` if + /// this object does not derive from `TO`. + /// @see CastFlags + template <typename TO, int FLAGS = 0> + inline const TO* As() const { + return tint::As<const TO, FLAGS>(this); + } + + protected: + CastableBase() = default; +}; + +/// Castable is a helper to derive `CLASS` from `BASE`, automatically +/// implementing the Is() and As() methods, along with a #Base type alias. +/// +/// Example usage: +/// +/// ``` +/// class Animal : public Castable<Animal> {}; +/// +/// class Sheep : public Castable<Sheep, Animal> {}; +/// +/// Sheep* cast_to_sheep(Animal* animal) { +/// // You can query whether a Castable is of the given type with Is<T>(): +/// printf("animal is a sheep? %s", animal->Is<Sheep>() ? "yes" : "no"); +/// +/// // You can always just try the cast with As<T>(). +/// // If the object is not of the correct type, As<T>() will return nullptr: +/// return animal->As<Sheep>(); +/// } +/// ``` +template <typename CLASS, typename BASE = CastableBase> +class Castable : public BASE { + public: + // Inherit the `BASE` class constructors. + using BASE::BASE; + + /// A type alias for `CLASS` to easily access the `BASE` class members. + /// Base actually aliases to the Castable instead of `BASE` so that you can + /// use Base in the `CLASS` constructor. + using Base = Castable; + + /// A type alias for `BASE`. + using TrueBase = BASE; + + /// @returns the TypeInfo of the object + const tint::TypeInfo& TypeInfo() const override { + return TypeInfo::Of<CLASS>(); + } + + /// @returns true if this object is of, or derives from the class `TO` + /// @see CastFlags + template <typename TO, int FLAGS = 0> + inline bool Is() const { + return tint::Is<TO, FLAGS>(static_cast<const CLASS*>(this)); + } + + /// @returns true if this object is of, or derives from the class `TO` and + /// pred(const TO*) returns true + /// @param pred predicate function with signature `bool(const TO*)` called iff + /// object is of, or derives from the class `TO`. + template <int FLAGS = 0, typename Pred = detail::Infer> + inline bool Is(Pred&& pred) const { + using TO = + typename std::remove_pointer<traits::ParameterType<Pred, 0>>::type; + return tint::Is<TO, FLAGS>(static_cast<const CLASS*>(this), + std::forward<Pred>(pred)); + } + + /// @returns true if this object is of, or derives from any of the `TO` + /// classes. + template <typename... TO> + inline bool IsAnyOf() const { + return tint::IsAnyOf<TO...>(static_cast<const CLASS*>(this)); + } + + /// @returns this object dynamically cast to the type `TO` or `nullptr` if + /// this object does not derive from `TO`. + /// @see CastFlags + template <typename TO, int FLAGS = 0> + inline TO* As() { + return tint::As<TO, FLAGS>(this); + } + + /// @returns this object dynamically cast to the type `TO` or `nullptr` if + /// this object does not derive from `TO`. + /// @see CastFlags + template <typename TO, int FLAGS = 0> + inline const TO* As() const { + return tint::As<const TO, FLAGS>(this); + } +}; + +namespace detail { +/// <code>typename CastableCommonBaseImpl<TYPES>::type</code> resolves to the +/// common base class for all of TYPES. +template <typename... TYPES> +struct CastableCommonBaseImpl {}; + +/// Alias to typename CastableCommonBaseImpl<TYPES>::type +template <typename... TYPES> +using CastableCommonBase = + typename detail::CastableCommonBaseImpl<TYPES...>::type; + +/// CastableCommonBaseImpl template specialization for a single type +template <typename T> +struct CastableCommonBaseImpl<T> { + /// Common base class of a single type is itself + using type = T; +}; + +/// CastableCommonBaseImpl A <-> CastableBase specialization +template <typename A> +struct CastableCommonBaseImpl<A, CastableBase> { + /// Common base class for A and CastableBase is CastableBase + using type = CastableBase; +}; + +/// CastableCommonBaseImpl T <-> Ignore specialization +template <typename T> +struct CastableCommonBaseImpl<T, Ignore> { + /// Resolves to T as the other type is ignored + using type = T; +}; + +/// CastableCommonBaseImpl Ignore <-> T specialization +template <typename T> +struct CastableCommonBaseImpl<Ignore, T> { + /// Resolves to T as the other type is ignored + using type = T; +}; + +/// CastableCommonBaseImpl A <-> B specialization +template <typename A, typename B> +struct CastableCommonBaseImpl<A, B> { + /// The common base class for A, B and OTHERS + using type = std::conditional_t<traits::IsTypeOrDerived<A, B>, + B, // A derives from B + CastableCommonBase<A, typename B::TrueBase>>; +}; + +/// CastableCommonBaseImpl 3+ types specialization +template <typename A, typename B, typename... OTHERS> +struct CastableCommonBaseImpl<A, B, OTHERS...> { + /// The common base class for A, B and OTHERS + using type = CastableCommonBase<CastableCommonBase<A, B>, OTHERS...>; +}; + +} // namespace detail + +/// Resolves to the common most derived type that each of the types in `TYPES` +/// derives from. +template <typename... TYPES> +using CastableCommonBase = detail::CastableCommonBase<TYPES...>; + +/// Default can be used as the default case for a Switch(), when all previous +/// cases failed to match. +/// +/// Example: +/// ``` +/// Switch(object, +/// [&](TypeA*) { /* ... */ }, +/// [&](TypeB*) { /* ... */ }, +/// [&](Default) { /* If not TypeA or TypeB */ }); +/// ``` +struct Default {}; + +namespace detail { + +/// Evaluates to the Switch case type being matched by the switch case function +/// `FN`. +/// @note does not handle the Default case +/// @see Switch(). +template <typename FN> +using SwitchCaseType = std::remove_pointer_t< + traits::ParameterType<std::remove_reference_t<FN>, 0>>; + +/// Evaluates to true if the function `FN` has the signature of a Default case +/// in a Switch(). +/// @see Switch(). +template <typename FN> +inline constexpr bool IsDefaultCase = + std::is_same_v<traits::ParameterType<std::remove_reference_t<FN>, 0>, + Default>; + +/// Searches the list of Switch cases for a Default case, returning the index of +/// the Default case. If the a Default case is not found in the tuple, then -1 +/// is returned. +template <typename TUPLE, std::size_t START_IDX = 0> +constexpr int IndexOfDefaultCase() { + if constexpr (START_IDX < std::tuple_size_v<TUPLE>) { + return IsDefaultCase<std::tuple_element_t<START_IDX, TUPLE>> + ? static_cast<int>(START_IDX) + : IndexOfDefaultCase<TUPLE, START_IDX + 1>(); + } else { + return -1; + } +} + +/// The implementation of Switch() for non-Default cases. +/// Switch splits the cases into two a low and high block of cases, and quickly +/// rules out blocks that cannot match by comparing the TypeInfo::HashCode of +/// the object and the cases in the block. If a block of cases may match the +/// given object's type, then that block is split into two, and the process +/// recurses. When NonDefaultCases() is called with a single case, then As<> +/// will be used to dynamically cast to the case type and if the cast succeeds, +/// then the case handler is called. +/// @returns true if a case handler was found, otherwise false. +template <typename T, typename RETURN_TYPE, typename... CASES> +inline bool NonDefaultCases(T* object, + const TypeInfo* type, + RETURN_TYPE* result, + std::tuple<CASES...>&& cases) { + using Cases = std::tuple<CASES...>; + + (void)result; // Not always used, avoid warning. + + static constexpr bool kHasReturnType = !std::is_same_v<RETURN_TYPE, void>; + static constexpr size_t kNumCases = sizeof...(CASES); + + if constexpr (kNumCases == 0) { + // No cases. Nothing to do. + return false; + } else if constexpr (kNumCases == 1) { // NOLINT: cpplint doesn't understand + // `else if constexpr` + // Single case. + using CaseFunc = std::tuple_element_t<0, Cases>; + static_assert(!IsDefaultCase<CaseFunc>, + "NonDefaultCases called with a Default case"); + // Attempt to dynamically cast the object to the handler type. If that + // succeeds, call the case handler with the cast object. + using CaseType = SwitchCaseType<CaseFunc>; + if (type->Is(&TypeInfo::Of<CaseType>())) { + auto* ptr = static_cast<CaseType*>(object); + if constexpr (kHasReturnType) { + *result = static_cast<RETURN_TYPE>(std::get<0>(cases)(ptr)); + } else { + std::get<0>(cases)(ptr); + } + return true; + } + return false; + } else { + // Multiple cases. + // Check the hashcode bits to see if there's any possibility of a case + // matching in these cases. If there isn't, we can skip all these cases. + if (type->full_hashcode & + TypeInfo::CombinedHashCodeOf<SwitchCaseType<CASES>...>()) { + // There's a possibility. We need to scan further. + // Split the cases into two, and recurse. + constexpr size_t kMid = kNumCases / 2; + return NonDefaultCases(object, type, result, + traits::Slice<0, kMid>(cases)) || + NonDefaultCases(object, type, result, + traits::Slice<kMid, kNumCases - kMid>(cases)); + } else { + return false; + } + } +} + +/// The implementation of Switch() for all cases. +/// @see NonDefaultCases +template <typename T, typename RETURN_TYPE, typename... CASES> +inline void SwitchCases(T* object, + RETURN_TYPE* result, + std::tuple<CASES...>&& cases) { + using Cases = std::tuple<CASES...>; + static constexpr int kDefaultIndex = detail::IndexOfDefaultCase<Cases>(); + static_assert( + kDefaultIndex == -1 || kDefaultIndex == std::tuple_size_v<Cases> - 1, + "Default case must be last in Switch()"); + static constexpr bool kHasDefaultCase = kDefaultIndex >= 0; + static constexpr bool kHasReturnType = !std::is_same_v<RETURN_TYPE, void>; + + if (object) { + auto* type = &object->TypeInfo(); + if constexpr (kHasDefaultCase) { + // Evaluate non-default cases. + if (!detail::NonDefaultCases<T>(object, type, result, + traits::Slice<0, kDefaultIndex>(cases))) { + // Nothing matched. Evaluate default case. + if constexpr (kHasReturnType) { + *result = + static_cast<RETURN_TYPE>(std::get<kDefaultIndex>(cases)({})); + } else { + std::get<kDefaultIndex>(cases)({}); + } + } + } else { + detail::NonDefaultCases<T>(object, type, result, std::move(cases)); + } + } else { + // Object is nullptr, so no cases can match + if constexpr (kHasDefaultCase) { + // Evaluate default case. + if constexpr (kHasReturnType) { + *result = static_cast<RETURN_TYPE>(std::get<kDefaultIndex>(cases)({})); + } else { + std::get<kDefaultIndex>(cases)({}); + } + } + } +} + +/// Resolves to T if T is not nullptr_t, otherwise resolves to Ignore. +template <typename T> +using NullptrToIgnore = + std::conditional_t<std::is_same_v<T, std::nullptr_t>, Ignore, T>; + +/// Resolves to `const TYPE` if any of `CASE_RETURN_TYPES` are const or +/// pointer-to-const, otherwise resolves to TYPE. +template <typename TYPE, typename... CASE_RETURN_TYPES> +using PropagateReturnConst = std::conditional_t< + // Are any of the pointer-stripped types const? + (std::is_const_v<std::remove_pointer_t<CASE_RETURN_TYPES>> || ...), + const TYPE, // Yes: Apply const to TYPE + TYPE>; // No: Passthrough + +/// SwitchReturnTypeImpl is the implementation of SwitchReturnType +template <bool IS_CASTABLE, + typename REQUESTED_TYPE, + typename... CASE_RETURN_TYPES> +struct SwitchReturnTypeImpl; + +/// SwitchReturnTypeImpl specialization for non-castable case types and an +/// explicitly specified return type. +template <typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES> +struct SwitchReturnTypeImpl</*IS_CASTABLE*/ false, + REQUESTED_TYPE, + CASE_RETURN_TYPES...> { + /// Resolves to `REQUESTED_TYPE` + using type = REQUESTED_TYPE; +}; + +/// SwitchReturnTypeImpl specialization for non-castable case types and an +/// inferred return type. +template <typename... CASE_RETURN_TYPES> +struct SwitchReturnTypeImpl</*IS_CASTABLE*/ false, + Infer, + CASE_RETURN_TYPES...> { + /// Resolves to the common type for all the cases return types. + using type = std::common_type_t<CASE_RETURN_TYPES...>; +}; + +/// SwitchReturnTypeImpl specialization for castable case types and an +/// explicitly specified return type. +template <typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES> +struct SwitchReturnTypeImpl</*IS_CASTABLE*/ true, + REQUESTED_TYPE, + CASE_RETURN_TYPES...> { + public: + /// Resolves to `const REQUESTED_TYPE*` or `REQUESTED_TYPE*` + using type = PropagateReturnConst<std::remove_pointer_t<REQUESTED_TYPE>, + CASE_RETURN_TYPES...>*; +}; + +/// SwitchReturnTypeImpl specialization for castable case types and an infered +/// return type. +template <typename... CASE_RETURN_TYPES> +struct SwitchReturnTypeImpl</*IS_CASTABLE*/ true, Infer, CASE_RETURN_TYPES...> { + private: + using InferredType = CastableCommonBase< + detail::NullptrToIgnore<std::remove_pointer_t<CASE_RETURN_TYPES>>...>; + + public: + /// `const T*` or `T*`, where T is the common base type for all the castable + /// case types. + using type = PropagateReturnConst<InferredType, CASE_RETURN_TYPES...>*; +}; + +/// Resolves to the return type for a Switch() with the requested return type +/// `REQUESTED_TYPE` and case statement return types. If `REQUESTED_TYPE` is +/// Infer then the return type will be inferred from the case return types. +template <typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES> +using SwitchReturnType = typename SwitchReturnTypeImpl< + IsCastable<NullptrToIgnore<std::remove_pointer_t<CASE_RETURN_TYPES>>...>, + REQUESTED_TYPE, + CASE_RETURN_TYPES...>::type; + +} // namespace detail + +/// Switch is used to dispatch one of the provided callback case handler +/// functions based on the type of `object` and the parameter type of the case +/// handlers. Switch will sequentially check the type of `object` against each +/// of the switch case handler functions, and will invoke the first case handler +/// function which has a parameter type that matches the object type. When a +/// case handler is matched, it will be called with the single argument of +/// `object` cast to the case handler's parameter type. Switch will invoke at +/// most one case handler. Each of the case functions must have the signature +/// `R(T*)` or `R(const T*)`, where `T` is the type matched by that case and `R` +/// is the return type, consistent across all case handlers. +/// +/// An optional default case function with the signature `R(Default)` can be +/// used as the last case. This default case will be called if all previous +/// cases failed to match. +/// +/// If `object` is nullptr and a default case is provided, then the default case +/// will be called. If `object` is nullptr and no default case is provided, then +/// no cases will be called. +/// +/// Example: +/// ``` +/// Switch(object, +/// [&](TypeA*) { /* ... */ }, +/// [&](TypeB*) { /* ... */ }); +/// +/// Switch(object, +/// [&](TypeA*) { /* ... */ }, +/// [&](TypeB*) { /* ... */ }, +/// [&](Default) { /* Called if object is not TypeA or TypeB */ }); +/// ``` +/// +/// @param object the object who's type is used to +/// @param cases the switch cases +/// @return the value returned by the called case. If no cases matched, then the +/// zero value for the consistent case type. +template <typename RETURN_TYPE = detail::Infer, + typename T = CastableBase, + typename... CASES> +inline auto Switch(T* object, CASES&&... cases) { + using ReturnType = + detail::SwitchReturnType<RETURN_TYPE, traits::ReturnType<CASES>...>; + static constexpr bool kHasReturnType = !std::is_same_v<ReturnType, void>; + + if constexpr (kHasReturnType) { + ReturnType res = {}; + detail::SwitchCases(object, &res, + std::forward_as_tuple(std::forward<CASES>(cases)...)); + return res; + } else { + detail::SwitchCases<T, void>( + object, nullptr, std::forward_as_tuple(std::forward<CASES>(cases)...)); + } +} + +} // namespace tint + +TINT_CASTABLE_POP_DISABLE_WARNINGS(); + +#endif // SRC_TINT_CASTABLE_H_
diff --git a/src/tint/castable_bench.cc b/src/tint/castable_bench.cc new file mode 100644 index 0000000..839a932 --- /dev/null +++ b/src/tint/castable_bench.cc
@@ -0,0 +1,270 @@ +// Copyright 2022 The Tint Authors. +// +// 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 "bench/benchmark.h" + +namespace tint { +namespace { + +struct Base : public tint::Castable<Base> {}; +struct A : public tint::Castable<A, Base> {}; +struct AA : public tint::Castable<AA, A> {}; +struct AAA : public tint::Castable<AAA, AA> {}; +struct AAB : public tint::Castable<AAB, AA> {}; +struct AAC : public tint::Castable<AAC, AA> {}; +struct AB : public tint::Castable<AB, A> {}; +struct ABA : public tint::Castable<ABA, AB> {}; +struct ABB : public tint::Castable<ABB, AB> {}; +struct ABC : public tint::Castable<ABC, AB> {}; +struct AC : public tint::Castable<AC, A> {}; +struct ACA : public tint::Castable<ACA, AC> {}; +struct ACB : public tint::Castable<ACB, AC> {}; +struct ACC : public tint::Castable<ACC, AC> {}; +struct B : public tint::Castable<B, Base> {}; +struct BA : public tint::Castable<BA, B> {}; +struct BAA : public tint::Castable<BAA, BA> {}; +struct BAB : public tint::Castable<BAB, BA> {}; +struct BAC : public tint::Castable<BAC, BA> {}; +struct BB : public tint::Castable<BB, B> {}; +struct BBA : public tint::Castable<BBA, BB> {}; +struct BBB : public tint::Castable<BBB, BB> {}; +struct BBC : public tint::Castable<BBC, BB> {}; +struct BC : public tint::Castable<BC, B> {}; +struct BCA : public tint::Castable<BCA, BC> {}; +struct BCB : public tint::Castable<BCB, BC> {}; +struct BCC : public tint::Castable<BCC, BC> {}; +struct C : public tint::Castable<C, Base> {}; +struct CA : public tint::Castable<CA, C> {}; +struct CAA : public tint::Castable<CAA, CA> {}; +struct CAB : public tint::Castable<CAB, CA> {}; +struct CAC : public tint::Castable<CAC, CA> {}; +struct CB : public tint::Castable<CB, C> {}; +struct CBA : public tint::Castable<CBA, CB> {}; +struct CBB : public tint::Castable<CBB, CB> {}; +struct CBC : public tint::Castable<CBC, CB> {}; +struct CC : public tint::Castable<CC, C> {}; +struct CCA : public tint::Castable<CCA, CC> {}; +struct CCB : public tint::Castable<CCB, CC> {}; +struct CCC : public tint::Castable<CCC, CC> {}; + +using AllTypes = std::tuple<Base, + A, + AA, + AAA, + AAB, + AAC, + AB, + ABA, + ABB, + ABC, + AC, + ACA, + ACB, + ACC, + B, + BA, + BAA, + BAB, + BAC, + BB, + BBA, + BBB, + BBC, + BC, + BCA, + BCB, + BCC, + C, + CA, + CAA, + CAB, + CAC, + CB, + CBA, + CBB, + CBC, + CC, + CCA, + CCB, + CCC>; + +std::vector<std::unique_ptr<Base>> MakeObjects() { + std::vector<std::unique_ptr<Base>> out; + out.emplace_back(std::make_unique<Base>()); + out.emplace_back(std::make_unique<A>()); + out.emplace_back(std::make_unique<AA>()); + out.emplace_back(std::make_unique<AAA>()); + out.emplace_back(std::make_unique<AAB>()); + out.emplace_back(std::make_unique<AAC>()); + out.emplace_back(std::make_unique<AB>()); + out.emplace_back(std::make_unique<ABA>()); + out.emplace_back(std::make_unique<ABB>()); + out.emplace_back(std::make_unique<ABC>()); + out.emplace_back(std::make_unique<AC>()); + out.emplace_back(std::make_unique<ACA>()); + out.emplace_back(std::make_unique<ACB>()); + out.emplace_back(std::make_unique<ACC>()); + out.emplace_back(std::make_unique<B>()); + out.emplace_back(std::make_unique<BA>()); + out.emplace_back(std::make_unique<BAA>()); + out.emplace_back(std::make_unique<BAB>()); + out.emplace_back(std::make_unique<BAC>()); + out.emplace_back(std::make_unique<BB>()); + out.emplace_back(std::make_unique<BBA>()); + out.emplace_back(std::make_unique<BBB>()); + out.emplace_back(std::make_unique<BBC>()); + out.emplace_back(std::make_unique<BC>()); + out.emplace_back(std::make_unique<BCA>()); + out.emplace_back(std::make_unique<BCB>()); + out.emplace_back(std::make_unique<BCC>()); + out.emplace_back(std::make_unique<C>()); + out.emplace_back(std::make_unique<CA>()); + out.emplace_back(std::make_unique<CAA>()); + out.emplace_back(std::make_unique<CAB>()); + out.emplace_back(std::make_unique<CAC>()); + out.emplace_back(std::make_unique<CB>()); + out.emplace_back(std::make_unique<CBA>()); + out.emplace_back(std::make_unique<CBB>()); + out.emplace_back(std::make_unique<CBC>()); + out.emplace_back(std::make_unique<CC>()); + out.emplace_back(std::make_unique<CCA>()); + out.emplace_back(std::make_unique<CCB>()); + out.emplace_back(std::make_unique<CCC>()); + return out; +} + +void CastableLargeSwitch(::benchmark::State& state) { + auto objects = MakeObjects(); + size_t i = 0; + for (auto _ : state) { + auto* object = objects[i % objects.size()].get(); + Switch( + object, // + [&](const AAA*) { ::benchmark::DoNotOptimize(i += 40); }, + [&](const AAB*) { ::benchmark::DoNotOptimize(i += 50); }, + [&](const AAC*) { ::benchmark::DoNotOptimize(i += 60); }, + [&](const ABA*) { ::benchmark::DoNotOptimize(i += 80); }, + [&](const ABB*) { ::benchmark::DoNotOptimize(i += 90); }, + [&](const ABC*) { ::benchmark::DoNotOptimize(i += 100); }, + [&](const ACA*) { ::benchmark::DoNotOptimize(i += 120); }, + [&](const ACB*) { ::benchmark::DoNotOptimize(i += 130); }, + [&](const ACC*) { ::benchmark::DoNotOptimize(i += 140); }, + [&](const BAA*) { ::benchmark::DoNotOptimize(i += 170); }, + [&](const BAB*) { ::benchmark::DoNotOptimize(i += 180); }, + [&](const BAC*) { ::benchmark::DoNotOptimize(i += 190); }, + [&](const BBA*) { ::benchmark::DoNotOptimize(i += 210); }, + [&](const BBB*) { ::benchmark::DoNotOptimize(i += 220); }, + [&](const BBC*) { ::benchmark::DoNotOptimize(i += 230); }, + [&](const BCA*) { ::benchmark::DoNotOptimize(i += 250); }, + [&](const BCB*) { ::benchmark::DoNotOptimize(i += 260); }, + [&](const BCC*) { ::benchmark::DoNotOptimize(i += 270); }, + [&](const CA*) { ::benchmark::DoNotOptimize(i += 290); }, + [&](const CAA*) { ::benchmark::DoNotOptimize(i += 300); }, + [&](const CAB*) { ::benchmark::DoNotOptimize(i += 310); }, + [&](const CAC*) { ::benchmark::DoNotOptimize(i += 320); }, + [&](const CBA*) { ::benchmark::DoNotOptimize(i += 340); }, + [&](const CBB*) { ::benchmark::DoNotOptimize(i += 350); }, + [&](const CBC*) { ::benchmark::DoNotOptimize(i += 360); }, + [&](const CCA*) { ::benchmark::DoNotOptimize(i += 380); }, + [&](const CCB*) { ::benchmark::DoNotOptimize(i += 390); }, + [&](const CCC*) { ::benchmark::DoNotOptimize(i += 400); }, + [&](Default) { ::benchmark::DoNotOptimize(i += 123); }); + i = (i * 31) ^ (i << 5); + } +} + +BENCHMARK(CastableLargeSwitch); + +void CastableMediumSwitch(::benchmark::State& state) { + auto objects = MakeObjects(); + size_t i = 0; + for (auto _ : state) { + auto* object = objects[i % objects.size()].get(); + Switch( + object, // + [&](const ACB*) { ::benchmark::DoNotOptimize(i += 130); }, + [&](const BAA*) { ::benchmark::DoNotOptimize(i += 170); }, + [&](const BAB*) { ::benchmark::DoNotOptimize(i += 180); }, + [&](const BBA*) { ::benchmark::DoNotOptimize(i += 210); }, + [&](const BBB*) { ::benchmark::DoNotOptimize(i += 220); }, + [&](const CAA*) { ::benchmark::DoNotOptimize(i += 300); }, + [&](const CCA*) { ::benchmark::DoNotOptimize(i += 380); }, + [&](const CCB*) { ::benchmark::DoNotOptimize(i += 390); }, + [&](const CCC*) { ::benchmark::DoNotOptimize(i += 400); }, + [&](Default) { ::benchmark::DoNotOptimize(i += 123); }); + i = (i * 31) ^ (i << 5); + } +} + +BENCHMARK(CastableMediumSwitch); + +void CastableSmallSwitch(::benchmark::State& state) { + auto objects = MakeObjects(); + size_t i = 0; + for (auto _ : state) { + auto* object = objects[i % objects.size()].get(); + Switch( + object, // + [&](const AAB*) { ::benchmark::DoNotOptimize(i += 30); }, + [&](const CAC*) { ::benchmark::DoNotOptimize(i += 290); }, + [&](const CAA*) { ::benchmark::DoNotOptimize(i += 300); }); + i = (i * 31) ^ (i << 5); + } +} + +BENCHMARK(CastableSmallSwitch); + +} // namespace +} // namespace tint + +TINT_INSTANTIATE_TYPEINFO(tint::Base); +TINT_INSTANTIATE_TYPEINFO(tint::A); +TINT_INSTANTIATE_TYPEINFO(tint::AA); +TINT_INSTANTIATE_TYPEINFO(tint::AAA); +TINT_INSTANTIATE_TYPEINFO(tint::AAB); +TINT_INSTANTIATE_TYPEINFO(tint::AAC); +TINT_INSTANTIATE_TYPEINFO(tint::AB); +TINT_INSTANTIATE_TYPEINFO(tint::ABA); +TINT_INSTANTIATE_TYPEINFO(tint::ABB); +TINT_INSTANTIATE_TYPEINFO(tint::ABC); +TINT_INSTANTIATE_TYPEINFO(tint::AC); +TINT_INSTANTIATE_TYPEINFO(tint::ACA); +TINT_INSTANTIATE_TYPEINFO(tint::ACB); +TINT_INSTANTIATE_TYPEINFO(tint::ACC); +TINT_INSTANTIATE_TYPEINFO(tint::B); +TINT_INSTANTIATE_TYPEINFO(tint::BA); +TINT_INSTANTIATE_TYPEINFO(tint::BAA); +TINT_INSTANTIATE_TYPEINFO(tint::BAB); +TINT_INSTANTIATE_TYPEINFO(tint::BAC); +TINT_INSTANTIATE_TYPEINFO(tint::BB); +TINT_INSTANTIATE_TYPEINFO(tint::BBA); +TINT_INSTANTIATE_TYPEINFO(tint::BBB); +TINT_INSTANTIATE_TYPEINFO(tint::BBC); +TINT_INSTANTIATE_TYPEINFO(tint::BC); +TINT_INSTANTIATE_TYPEINFO(tint::BCA); +TINT_INSTANTIATE_TYPEINFO(tint::BCB); +TINT_INSTANTIATE_TYPEINFO(tint::BCC); +TINT_INSTANTIATE_TYPEINFO(tint::C); +TINT_INSTANTIATE_TYPEINFO(tint::CA); +TINT_INSTANTIATE_TYPEINFO(tint::CAA); +TINT_INSTANTIATE_TYPEINFO(tint::CAB); +TINT_INSTANTIATE_TYPEINFO(tint::CAC); +TINT_INSTANTIATE_TYPEINFO(tint::CB); +TINT_INSTANTIATE_TYPEINFO(tint::CBA); +TINT_INSTANTIATE_TYPEINFO(tint::CBB); +TINT_INSTANTIATE_TYPEINFO(tint::CBC); +TINT_INSTANTIATE_TYPEINFO(tint::CC); +TINT_INSTANTIATE_TYPEINFO(tint::CCA); +TINT_INSTANTIATE_TYPEINFO(tint::CCB); +TINT_INSTANTIATE_TYPEINFO(tint::CCC);
diff --git a/src/tint/castable_test.cc b/src/tint/castable_test.cc new file mode 100644 index 0000000..7ed66cb --- /dev/null +++ b/src/tint/castable_test.cc
@@ -0,0 +1,787 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/castable.h" + +#include <memory> +#include <string> + +#include "gtest/gtest.h" + +namespace tint { + +struct Animal : public tint::Castable<Animal> {}; +struct Amphibian : public tint::Castable<Amphibian, Animal> {}; +struct Mammal : public tint::Castable<Mammal, Animal> {}; +struct Reptile : public tint::Castable<Reptile, Animal> {}; +struct Frog : public tint::Castable<Frog, Amphibian> {}; +struct Bear : public tint::Castable<Bear, Mammal> {}; +struct Lizard : public tint::Castable<Lizard, Reptile> {}; +struct Gecko : public tint::Castable<Gecko, Lizard> {}; +struct Iguana : public tint::Castable<Iguana, Lizard> {}; + +namespace { + +TEST(CastableBase, Is) { + std::unique_ptr<CastableBase> frog = std::make_unique<Frog>(); + std::unique_ptr<CastableBase> bear = std::make_unique<Bear>(); + std::unique_ptr<CastableBase> gecko = std::make_unique<Gecko>(); + + ASSERT_TRUE(frog->Is<Animal>()); + ASSERT_TRUE(bear->Is<Animal>()); + ASSERT_TRUE(gecko->Is<Animal>()); + + ASSERT_TRUE(frog->Is<Amphibian>()); + ASSERT_FALSE(bear->Is<Amphibian>()); + ASSERT_FALSE(gecko->Is<Amphibian>()); + + ASSERT_FALSE(frog->Is<Mammal>()); + ASSERT_TRUE(bear->Is<Mammal>()); + ASSERT_FALSE(gecko->Is<Mammal>()); + + ASSERT_FALSE(frog->Is<Reptile>()); + ASSERT_FALSE(bear->Is<Reptile>()); + ASSERT_TRUE(gecko->Is<Reptile>()); +} + +TEST(CastableBase, Is_kDontErrorOnImpossibleCast) { + // Unlike TEST(CastableBase, Is), we're dynamically querying [A -> B] without + // going via CastableBase. + auto frog = std::make_unique<Frog>(); + auto bear = std::make_unique<Bear>(); + auto gecko = std::make_unique<Gecko>(); + + ASSERT_TRUE((frog->Is<Animal, kDontErrorOnImpossibleCast>())); + ASSERT_TRUE((bear->Is<Animal, kDontErrorOnImpossibleCast>())); + ASSERT_TRUE((gecko->Is<Animal, kDontErrorOnImpossibleCast>())); + + ASSERT_TRUE((frog->Is<Amphibian, kDontErrorOnImpossibleCast>())); + ASSERT_FALSE((bear->Is<Amphibian, kDontErrorOnImpossibleCast>())); + ASSERT_FALSE((gecko->Is<Amphibian, kDontErrorOnImpossibleCast>())); + + ASSERT_FALSE((frog->Is<Mammal, kDontErrorOnImpossibleCast>())); + ASSERT_TRUE((bear->Is<Mammal, kDontErrorOnImpossibleCast>())); + ASSERT_FALSE((gecko->Is<Mammal, kDontErrorOnImpossibleCast>())); + + ASSERT_FALSE((frog->Is<Reptile, kDontErrorOnImpossibleCast>())); + ASSERT_FALSE((bear->Is<Reptile, kDontErrorOnImpossibleCast>())); + ASSERT_TRUE((gecko->Is<Reptile, kDontErrorOnImpossibleCast>())); +} + +TEST(CastableBase, IsWithPredicate) { + std::unique_ptr<CastableBase> frog = std::make_unique<Frog>(); + + frog->Is<Animal>([&frog](const Animal* a) { + EXPECT_EQ(a, frog.get()); + return true; + }); + + ASSERT_TRUE((frog->Is<Animal>([](const Animal*) { return true; }))); + ASSERT_FALSE((frog->Is<Animal>([](const Animal*) { return false; }))); + + // Predicate not called if cast is invalid + auto expect_not_called = [] { FAIL() << "Should not be called"; }; + ASSERT_FALSE((frog->Is<Bear>([&](const Animal*) { + expect_not_called(); + return true; + }))); +} + +TEST(CastableBase, IsAnyOf) { + std::unique_ptr<CastableBase> frog = std::make_unique<Frog>(); + std::unique_ptr<CastableBase> bear = std::make_unique<Bear>(); + std::unique_ptr<CastableBase> gecko = std::make_unique<Gecko>(); + + ASSERT_TRUE((frog->IsAnyOf<Animal, Mammal, Amphibian, Reptile>())); + ASSERT_TRUE((frog->IsAnyOf<Mammal, Amphibian>())); + ASSERT_TRUE((frog->IsAnyOf<Amphibian, Reptile>())); + ASSERT_FALSE((frog->IsAnyOf<Mammal, Reptile>())); + + ASSERT_TRUE((bear->IsAnyOf<Animal, Mammal, Amphibian, Reptile>())); + ASSERT_TRUE((bear->IsAnyOf<Mammal, Amphibian>())); + ASSERT_TRUE((bear->IsAnyOf<Mammal, Reptile>())); + ASSERT_FALSE((bear->IsAnyOf<Amphibian, Reptile>())); + + ASSERT_TRUE((gecko->IsAnyOf<Animal, Mammal, Amphibian, Reptile>())); + ASSERT_TRUE((gecko->IsAnyOf<Mammal, Reptile>())); + ASSERT_TRUE((gecko->IsAnyOf<Amphibian, Reptile>())); + ASSERT_FALSE((gecko->IsAnyOf<Mammal, Amphibian>())); +} + +TEST(CastableBase, As) { + std::unique_ptr<CastableBase> frog = std::make_unique<Frog>(); + std::unique_ptr<CastableBase> bear = std::make_unique<Bear>(); + std::unique_ptr<CastableBase> gecko = std::make_unique<Gecko>(); + + ASSERT_EQ(frog->As<Animal>(), static_cast<Animal*>(frog.get())); + ASSERT_EQ(bear->As<Animal>(), static_cast<Animal*>(bear.get())); + ASSERT_EQ(gecko->As<Animal>(), static_cast<Animal*>(gecko.get())); + + ASSERT_EQ(frog->As<Amphibian>(), static_cast<Amphibian*>(frog.get())); + ASSERT_EQ(bear->As<Amphibian>(), nullptr); + ASSERT_EQ(gecko->As<Amphibian>(), nullptr); + + ASSERT_EQ(frog->As<Mammal>(), nullptr); + ASSERT_EQ(bear->As<Mammal>(), static_cast<Mammal*>(bear.get())); + ASSERT_EQ(gecko->As<Mammal>(), nullptr); + + ASSERT_EQ(frog->As<Reptile>(), nullptr); + ASSERT_EQ(bear->As<Reptile>(), nullptr); + ASSERT_EQ(gecko->As<Reptile>(), static_cast<Reptile*>(gecko.get())); +} + +TEST(CastableBase, As_kDontErrorOnImpossibleCast) { + // Unlike TEST(CastableBase, As), we're dynamically casting [A -> B] without + // going via CastableBase. + auto frog = std::make_unique<Frog>(); + auto bear = std::make_unique<Bear>(); + auto gecko = std::make_unique<Gecko>(); + + ASSERT_EQ((frog->As<Animal, kDontErrorOnImpossibleCast>()), + static_cast<Animal*>(frog.get())); + ASSERT_EQ((bear->As<Animal, kDontErrorOnImpossibleCast>()), + static_cast<Animal*>(bear.get())); + ASSERT_EQ((gecko->As<Animal, kDontErrorOnImpossibleCast>()), + static_cast<Animal*>(gecko.get())); + + ASSERT_EQ((frog->As<Amphibian, kDontErrorOnImpossibleCast>()), + static_cast<Amphibian*>(frog.get())); + ASSERT_EQ((bear->As<Amphibian, kDontErrorOnImpossibleCast>()), nullptr); + ASSERT_EQ((gecko->As<Amphibian, kDontErrorOnImpossibleCast>()), nullptr); + + ASSERT_EQ((frog->As<Mammal, kDontErrorOnImpossibleCast>()), nullptr); + ASSERT_EQ((bear->As<Mammal, kDontErrorOnImpossibleCast>()), + static_cast<Mammal*>(bear.get())); + ASSERT_EQ((gecko->As<Mammal, kDontErrorOnImpossibleCast>()), nullptr); + + ASSERT_EQ((frog->As<Reptile, kDontErrorOnImpossibleCast>()), nullptr); + ASSERT_EQ((bear->As<Reptile, kDontErrorOnImpossibleCast>()), nullptr); + ASSERT_EQ((gecko->As<Reptile, kDontErrorOnImpossibleCast>()), + static_cast<Reptile*>(gecko.get())); +} + +TEST(Castable, Is) { + std::unique_ptr<Animal> frog = std::make_unique<Frog>(); + std::unique_ptr<Animal> bear = std::make_unique<Bear>(); + std::unique_ptr<Animal> gecko = std::make_unique<Gecko>(); + + ASSERT_TRUE(frog->Is<Animal>()); + ASSERT_TRUE(bear->Is<Animal>()); + ASSERT_TRUE(gecko->Is<Animal>()); + + ASSERT_TRUE(frog->Is<Amphibian>()); + ASSERT_FALSE(bear->Is<Amphibian>()); + ASSERT_FALSE(gecko->Is<Amphibian>()); + + ASSERT_FALSE(frog->Is<Mammal>()); + ASSERT_TRUE(bear->Is<Mammal>()); + ASSERT_FALSE(gecko->Is<Mammal>()); + + ASSERT_FALSE(frog->Is<Reptile>()); + ASSERT_FALSE(bear->Is<Reptile>()); + ASSERT_TRUE(gecko->Is<Reptile>()); +} + +TEST(Castable, IsWithPredicate) { + std::unique_ptr<Animal> frog = std::make_unique<Frog>(); + + frog->Is([&frog](const Animal* a) { + EXPECT_EQ(a, frog.get()); + return true; + }); + + ASSERT_TRUE((frog->Is([](const Animal*) { return true; }))); + ASSERT_FALSE((frog->Is([](const Animal*) { return false; }))); + + // Predicate not called if cast is invalid + auto expect_not_called = [] { FAIL() << "Should not be called"; }; + ASSERT_FALSE((frog->Is([&](const Bear*) { + expect_not_called(); + return true; + }))); +} + +TEST(Castable, As) { + std::unique_ptr<Animal> frog = std::make_unique<Frog>(); + std::unique_ptr<Animal> bear = std::make_unique<Bear>(); + std::unique_ptr<Animal> gecko = std::make_unique<Gecko>(); + + ASSERT_EQ(frog->As<Animal>(), static_cast<Animal*>(frog.get())); + ASSERT_EQ(bear->As<Animal>(), static_cast<Animal*>(bear.get())); + ASSERT_EQ(gecko->As<Animal>(), static_cast<Animal*>(gecko.get())); + + ASSERT_EQ(frog->As<Amphibian>(), static_cast<Amphibian*>(frog.get())); + ASSERT_EQ(bear->As<Amphibian>(), nullptr); + ASSERT_EQ(gecko->As<Amphibian>(), nullptr); + + ASSERT_EQ(frog->As<Mammal>(), nullptr); + ASSERT_EQ(bear->As<Mammal>(), static_cast<Mammal*>(bear.get())); + ASSERT_EQ(gecko->As<Mammal>(), nullptr); + + ASSERT_EQ(frog->As<Reptile>(), nullptr); + ASSERT_EQ(bear->As<Reptile>(), nullptr); + ASSERT_EQ(gecko->As<Reptile>(), static_cast<Reptile*>(gecko.get())); +} + +TEST(Castable, SwitchNoDefault) { + std::unique_ptr<Animal> frog = std::make_unique<Frog>(); + std::unique_ptr<Animal> bear = std::make_unique<Bear>(); + std::unique_ptr<Animal> gecko = std::make_unique<Gecko>(); + { + bool frog_matched_amphibian = false; + Switch( + frog.get(), // + [&](Reptile*) { FAIL() << "frog is not reptile"; }, + [&](Mammal*) { FAIL() << "frog is not mammal"; }, + [&](Amphibian* amphibian) { + EXPECT_EQ(amphibian, frog.get()); + frog_matched_amphibian = true; + }); + EXPECT_TRUE(frog_matched_amphibian); + } + { + bool bear_matched_mammal = false; + Switch( + bear.get(), // + [&](Reptile*) { FAIL() << "bear is not reptile"; }, + [&](Amphibian*) { FAIL() << "bear is not amphibian"; }, + [&](Mammal* mammal) { + EXPECT_EQ(mammal, bear.get()); + bear_matched_mammal = true; + }); + EXPECT_TRUE(bear_matched_mammal); + } + { + bool gecko_matched_reptile = false; + Switch( + gecko.get(), // + [&](Mammal*) { FAIL() << "gecko is not mammal"; }, + [&](Amphibian*) { FAIL() << "gecko is not amphibian"; }, + [&](Reptile* reptile) { + EXPECT_EQ(reptile, gecko.get()); + gecko_matched_reptile = true; + }); + EXPECT_TRUE(gecko_matched_reptile); + } +} + +TEST(Castable, SwitchWithUnusedDefault) { + std::unique_ptr<Animal> frog = std::make_unique<Frog>(); + std::unique_ptr<Animal> bear = std::make_unique<Bear>(); + std::unique_ptr<Animal> gecko = std::make_unique<Gecko>(); + { + bool frog_matched_amphibian = false; + Switch( + frog.get(), // + [&](Reptile*) { FAIL() << "frog is not reptile"; }, + [&](Mammal*) { FAIL() << "frog is not mammal"; }, + [&](Amphibian* amphibian) { + EXPECT_EQ(amphibian, frog.get()); + frog_matched_amphibian = true; + }, + [&](Default) { FAIL() << "default should not have been selected"; }); + EXPECT_TRUE(frog_matched_amphibian); + } + { + bool bear_matched_mammal = false; + Switch( + bear.get(), // + [&](Reptile*) { FAIL() << "bear is not reptile"; }, + [&](Amphibian*) { FAIL() << "bear is not amphibian"; }, + [&](Mammal* mammal) { + EXPECT_EQ(mammal, bear.get()); + bear_matched_mammal = true; + }, + [&](Default) { FAIL() << "default should not have been selected"; }); + EXPECT_TRUE(bear_matched_mammal); + } + { + bool gecko_matched_reptile = false; + Switch( + gecko.get(), // + [&](Mammal*) { FAIL() << "gecko is not mammal"; }, + [&](Amphibian*) { FAIL() << "gecko is not amphibian"; }, + [&](Reptile* reptile) { + EXPECT_EQ(reptile, gecko.get()); + gecko_matched_reptile = true; + }, + [&](Default) { FAIL() << "default should not have been selected"; }); + EXPECT_TRUE(gecko_matched_reptile); + } +} + +TEST(Castable, SwitchDefault) { + std::unique_ptr<Animal> frog = std::make_unique<Frog>(); + std::unique_ptr<Animal> bear = std::make_unique<Bear>(); + std::unique_ptr<Animal> gecko = std::make_unique<Gecko>(); + { + bool frog_matched_default = false; + Switch( + frog.get(), // + [&](Reptile*) { FAIL() << "frog is not reptile"; }, + [&](Mammal*) { FAIL() << "frog is not mammal"; }, + [&](Default) { frog_matched_default = true; }); + EXPECT_TRUE(frog_matched_default); + } + { + bool bear_matched_default = false; + Switch( + bear.get(), // + [&](Reptile*) { FAIL() << "bear is not reptile"; }, + [&](Amphibian*) { FAIL() << "bear is not amphibian"; }, + [&](Default) { bear_matched_default = true; }); + EXPECT_TRUE(bear_matched_default); + } + { + bool gecko_matched_default = false; + Switch( + gecko.get(), // + [&](Mammal*) { FAIL() << "gecko is not mammal"; }, + [&](Amphibian*) { FAIL() << "gecko is not amphibian"; }, + [&](Default) { gecko_matched_default = true; }); + EXPECT_TRUE(gecko_matched_default); + } +} + +TEST(Castable, SwitchMatchFirst) { + std::unique_ptr<Animal> frog = std::make_unique<Frog>(); + { + bool frog_matched_animal = false; + Switch( + frog.get(), + [&](Animal* animal) { + EXPECT_EQ(animal, frog.get()); + frog_matched_animal = true; + }, + [&](Amphibian*) { FAIL() << "animal should have been matched first"; }); + EXPECT_TRUE(frog_matched_animal); + } + { + bool frog_matched_amphibian = false; + Switch( + frog.get(), + [&](Amphibian* amphibain) { + EXPECT_EQ(amphibain, frog.get()); + frog_matched_amphibian = true; + }, + [&](Animal*) { FAIL() << "amphibian should have been matched first"; }); + EXPECT_TRUE(frog_matched_amphibian); + } +} + +TEST(Castable, SwitchReturnValueWithDefault) { + std::unique_ptr<Animal> frog = std::make_unique<Frog>(); + std::unique_ptr<Animal> bear = std::make_unique<Bear>(); + std::unique_ptr<Animal> gecko = std::make_unique<Gecko>(); + { + const char* result = Switch( + frog.get(), // + [](Mammal*) { return "mammal"; }, // + [](Amphibian*) { return "amphibian"; }, // + [](Default) { return "unknown"; }); + static_assert(std::is_same_v<decltype(result), const char*>); + EXPECT_EQ(std::string(result), "amphibian"); + } + { + const char* result = Switch( + bear.get(), // + [](Mammal*) { return "mammal"; }, // + [](Amphibian*) { return "amphibian"; }, // + [](Default) { return "unknown"; }); + static_assert(std::is_same_v<decltype(result), const char*>); + EXPECT_EQ(std::string(result), "mammal"); + } + { + const char* result = Switch( + gecko.get(), // + [](Mammal*) { return "mammal"; }, // + [](Amphibian*) { return "amphibian"; }, // + [](Default) { return "unknown"; }); + static_assert(std::is_same_v<decltype(result), const char*>); + EXPECT_EQ(std::string(result), "unknown"); + } +} + +TEST(Castable, SwitchReturnValueWithoutDefault) { + std::unique_ptr<Animal> frog = std::make_unique<Frog>(); + std::unique_ptr<Animal> bear = std::make_unique<Bear>(); + std::unique_ptr<Animal> gecko = std::make_unique<Gecko>(); + { + const char* result = Switch( + frog.get(), // + [](Mammal*) { return "mammal"; }, // + [](Amphibian*) { return "amphibian"; }); + static_assert(std::is_same_v<decltype(result), const char*>); + EXPECT_EQ(std::string(result), "amphibian"); + } + { + const char* result = Switch( + bear.get(), // + [](Mammal*) { return "mammal"; }, // + [](Amphibian*) { return "amphibian"; }); + static_assert(std::is_same_v<decltype(result), const char*>); + EXPECT_EQ(std::string(result), "mammal"); + } + { + auto* result = Switch( + gecko.get(), // + [](Mammal*) { return "mammal"; }, // + [](Amphibian*) { return "amphibian"; }); + static_assert(std::is_same_v<decltype(result), const char*>); + EXPECT_EQ(result, nullptr); + } +} + +TEST(Castable, SwitchInferPODReturnTypeWithDefault) { + std::unique_ptr<Animal> frog = std::make_unique<Frog>(); + std::unique_ptr<Animal> bear = std::make_unique<Bear>(); + std::unique_ptr<Animal> gecko = std::make_unique<Gecko>(); + { + auto result = Switch( + frog.get(), // + [](Mammal*) { return 1; }, // + [](Amphibian*) { return 2.0f; }, // + [](Default) { return 3.0; }); + static_assert(std::is_same_v<decltype(result), double>); + EXPECT_EQ(result, 2.0); + } + { + auto result = Switch( + bear.get(), // + [](Mammal*) { return 1.0; }, // + [](Amphibian*) { return 2.0f; }, // + [](Default) { return 3; }); + static_assert(std::is_same_v<decltype(result), double>); + EXPECT_EQ(result, 1.0); + } + { + auto result = Switch( + gecko.get(), // + [](Mammal*) { return 1.0f; }, // + [](Amphibian*) { return 2; }, // + [](Default) { return 3.0; }); + static_assert(std::is_same_v<decltype(result), double>); + EXPECT_EQ(result, 3.0); + } +} + +TEST(Castable, SwitchInferPODReturnTypeWithoutDefault) { + std::unique_ptr<Animal> frog = std::make_unique<Frog>(); + std::unique_ptr<Animal> bear = std::make_unique<Bear>(); + std::unique_ptr<Animal> gecko = std::make_unique<Gecko>(); + { + auto result = Switch( + frog.get(), // + [](Mammal*) { return 1; }, // + [](Amphibian*) { return 2.0f; }); + static_assert(std::is_same_v<decltype(result), float>); + EXPECT_EQ(result, 2.0f); + } + { + auto result = Switch( + bear.get(), // + [](Mammal*) { return 1.0f; }, // + [](Amphibian*) { return 2; }); + static_assert(std::is_same_v<decltype(result), float>); + EXPECT_EQ(result, 1.0f); + } + { + auto result = Switch( + gecko.get(), // + [](Mammal*) { return 1.0; }, // + [](Amphibian*) { return 2.0f; }); + static_assert(std::is_same_v<decltype(result), double>); + EXPECT_EQ(result, 0.0); + } +} + +TEST(Castable, SwitchInferCastableReturnTypeWithDefault) { + std::unique_ptr<Animal> frog = std::make_unique<Frog>(); + std::unique_ptr<Animal> bear = std::make_unique<Bear>(); + std::unique_ptr<Animal> gecko = std::make_unique<Gecko>(); + { + auto* result = Switch( + frog.get(), // + [](Mammal* p) { return p; }, // + [](Amphibian*) { return nullptr; }, // + [](Default) { return nullptr; }); + static_assert(std::is_same_v<decltype(result), Mammal*>); + EXPECT_EQ(result, nullptr); + } + { + auto* result = Switch( + bear.get(), // + [](Mammal* p) { return p; }, // + [](Amphibian* p) { return const_cast<const Amphibian*>(p); }, + [](Default) { return nullptr; }); + static_assert(std::is_same_v<decltype(result), const Animal*>); + EXPECT_EQ(result, bear.get()); + } + { + auto* result = Switch( + gecko.get(), // + [](Mammal* p) { return p; }, // + [](Amphibian* p) { return p; }, // + [](Default) -> CastableBase* { return nullptr; }); + static_assert(std::is_same_v<decltype(result), CastableBase*>); + EXPECT_EQ(result, nullptr); + } +} + +TEST(Castable, SwitchInferCastableReturnTypeWithoutDefault) { + std::unique_ptr<Animal> frog = std::make_unique<Frog>(); + std::unique_ptr<Animal> bear = std::make_unique<Bear>(); + std::unique_ptr<Animal> gecko = std::make_unique<Gecko>(); + { + auto* result = Switch( + frog.get(), // + [](Mammal* p) { return p; }, // + [](Amphibian*) { return nullptr; }); + static_assert(std::is_same_v<decltype(result), Mammal*>); + EXPECT_EQ(result, nullptr); + } + { + auto* result = Switch( + bear.get(), // + [](Mammal* p) { return p; }, // + [](Amphibian* p) { return const_cast<const Amphibian*>(p); }); // + static_assert(std::is_same_v<decltype(result), const Animal*>); + EXPECT_EQ(result, bear.get()); + } + { + auto* result = Switch( + gecko.get(), // + [](Mammal* p) { return p; }, // + [](Amphibian* p) { return p; }); + static_assert(std::is_same_v<decltype(result), Animal*>); + EXPECT_EQ(result, nullptr); + } +} + +TEST(Castable, SwitchExplicitPODReturnTypeWithDefault) { + std::unique_ptr<Animal> frog = std::make_unique<Frog>(); + std::unique_ptr<Animal> bear = std::make_unique<Bear>(); + std::unique_ptr<Animal> gecko = std::make_unique<Gecko>(); + { + auto result = Switch<double>( + frog.get(), // + [](Mammal*) { return 1; }, // + [](Amphibian*) { return 2.0f; }, // + [](Default) { return 3.0; }); + static_assert(std::is_same_v<decltype(result), double>); + EXPECT_EQ(result, 2.0f); + } + { + auto result = Switch<double>( + bear.get(), // + [](Mammal*) { return 1; }, // + [](Amphibian*) { return 2; }, // + [](Default) { return 3; }); + static_assert(std::is_same_v<decltype(result), double>); + EXPECT_EQ(result, 1.0f); + } + { + auto result = Switch<double>( + gecko.get(), // + [](Mammal*) { return 1.0f; }, // + [](Amphibian*) { return 2.0f; }, // + [](Default) { return 3.0f; }); + static_assert(std::is_same_v<decltype(result), double>); + EXPECT_EQ(result, 3.0f); + } +} + +TEST(Castable, SwitchExplicitPODReturnTypeWithoutDefault) { + std::unique_ptr<Animal> frog = std::make_unique<Frog>(); + std::unique_ptr<Animal> bear = std::make_unique<Bear>(); + std::unique_ptr<Animal> gecko = std::make_unique<Gecko>(); + { + auto result = Switch<double>( + frog.get(), // + [](Mammal*) { return 1; }, // + [](Amphibian*) { return 2.0f; }); + static_assert(std::is_same_v<decltype(result), double>); + EXPECT_EQ(result, 2.0f); + } + { + auto result = Switch<double>( + bear.get(), // + [](Mammal*) { return 1.0f; }, // + [](Amphibian*) { return 2; }); + static_assert(std::is_same_v<decltype(result), double>); + EXPECT_EQ(result, 1.0f); + } + { + auto result = Switch<double>( + gecko.get(), // + [](Mammal*) { return 1.0; }, // + [](Amphibian*) { return 2.0f; }); + static_assert(std::is_same_v<decltype(result), double>); + EXPECT_EQ(result, 0.0); + } +} + +TEST(Castable, SwitchExplicitCastableReturnTypeWithDefault) { + std::unique_ptr<Animal> frog = std::make_unique<Frog>(); + std::unique_ptr<Animal> bear = std::make_unique<Bear>(); + std::unique_ptr<Animal> gecko = std::make_unique<Gecko>(); + { + auto* result = Switch<Animal>( + frog.get(), // + [](Mammal* p) { return p; }, // + [](Amphibian*) { return nullptr; }, // + [](Default) { return nullptr; }); + static_assert(std::is_same_v<decltype(result), Animal*>); + EXPECT_EQ(result, nullptr); + } + { + auto* result = Switch<CastableBase>( + bear.get(), // + [](Mammal* p) { return p; }, // + [](Amphibian* p) { return const_cast<const Amphibian*>(p); }, + [](Default) { return nullptr; }); + static_assert(std::is_same_v<decltype(result), const CastableBase*>); + EXPECT_EQ(result, bear.get()); + } + { + auto* result = Switch<const Animal>( + gecko.get(), // + [](Mammal* p) { return p; }, // + [](Amphibian* p) { return p; }, // + [](Default) { return nullptr; }); + static_assert(std::is_same_v<decltype(result), const Animal*>); + EXPECT_EQ(result, nullptr); + } +} + +TEST(Castable, SwitchExplicitCastableReturnTypeWithoutDefault) { + std::unique_ptr<Animal> frog = std::make_unique<Frog>(); + std::unique_ptr<Animal> bear = std::make_unique<Bear>(); + std::unique_ptr<Animal> gecko = std::make_unique<Gecko>(); + { + auto* result = Switch<Animal>( + frog.get(), // + [](Mammal* p) { return p; }, // + [](Amphibian*) { return nullptr; }); + static_assert(std::is_same_v<decltype(result), Animal*>); + EXPECT_EQ(result, nullptr); + } + { + auto* result = Switch<CastableBase>( + bear.get(), // + [](Mammal* p) { return p; }, // + [](Amphibian* p) { return const_cast<const Amphibian*>(p); }); // + static_assert(std::is_same_v<decltype(result), const CastableBase*>); + EXPECT_EQ(result, bear.get()); + } + { + auto* result = Switch<const Animal*>( + gecko.get(), // + [](Mammal* p) { return p; }, // + [](Amphibian* p) { return p; }); + static_assert(std::is_same_v<decltype(result), const Animal*>); + EXPECT_EQ(result, nullptr); + } +} + +TEST(Castable, SwitchNull) { + Animal* null = nullptr; + Switch( + null, // + [&](Amphibian*) { FAIL() << "should not be called"; }, + [&](Animal*) { FAIL() << "should not be called"; }); +} + +TEST(Castable, SwitchNullNoDefault) { + Animal* null = nullptr; + bool default_called = false; + Switch( + null, // + [&](Amphibian*) { FAIL() << "should not be called"; }, + [&](Animal*) { FAIL() << "should not be called"; }, + [&](Default) { default_called = true; }); + EXPECT_TRUE(default_called); +} + +// IsCastable static tests +static_assert(IsCastable<CastableBase>); +static_assert(IsCastable<Animal>); +static_assert(IsCastable<Ignore, Frog, Bear>); +static_assert(IsCastable<Mammal, Ignore, Amphibian, Gecko>); +static_assert(!IsCastable<Mammal, int, Amphibian, Ignore, Gecko>); +static_assert(!IsCastable<bool>); +static_assert(!IsCastable<int, float>); +static_assert(!IsCastable<Ignore>); + +// CastableCommonBase static tests +static_assert(std::is_same_v<Animal, CastableCommonBase<Animal>>); +static_assert(std::is_same_v<Amphibian, CastableCommonBase<Amphibian>>); +static_assert(std::is_same_v<Mammal, CastableCommonBase<Mammal>>); +static_assert(std::is_same_v<Reptile, CastableCommonBase<Reptile>>); +static_assert(std::is_same_v<Frog, CastableCommonBase<Frog>>); +static_assert(std::is_same_v<Bear, CastableCommonBase<Bear>>); +static_assert(std::is_same_v<Lizard, CastableCommonBase<Lizard>>); +static_assert(std::is_same_v<Gecko, CastableCommonBase<Gecko>>); +static_assert(std::is_same_v<Iguana, CastableCommonBase<Iguana>>); + +static_assert(std::is_same_v<Animal, CastableCommonBase<Animal, Animal>>); +static_assert( + std::is_same_v<Amphibian, CastableCommonBase<Amphibian, Amphibian>>); +static_assert(std::is_same_v<Mammal, CastableCommonBase<Mammal, Mammal>>); +static_assert(std::is_same_v<Reptile, CastableCommonBase<Reptile, Reptile>>); +static_assert(std::is_same_v<Frog, CastableCommonBase<Frog, Frog>>); +static_assert(std::is_same_v<Bear, CastableCommonBase<Bear, Bear>>); +static_assert(std::is_same_v<Lizard, CastableCommonBase<Lizard, Lizard>>); +static_assert(std::is_same_v<Gecko, CastableCommonBase<Gecko, Gecko>>); +static_assert(std::is_same_v<Iguana, CastableCommonBase<Iguana, Iguana>>); + +static_assert( + std::is_same_v<CastableBase, CastableCommonBase<CastableBase, Animal>>); +static_assert( + std::is_same_v<CastableBase, CastableCommonBase<Animal, CastableBase>>); +static_assert(std::is_same_v<Amphibian, CastableCommonBase<Amphibian, Frog>>); +static_assert(std::is_same_v<Amphibian, CastableCommonBase<Frog, Amphibian>>); +static_assert(std::is_same_v<Animal, CastableCommonBase<Reptile, Frog>>); +static_assert(std::is_same_v<Animal, CastableCommonBase<Frog, Reptile>>); +static_assert(std::is_same_v<Animal, CastableCommonBase<Bear, Frog>>); +static_assert(std::is_same_v<Animal, CastableCommonBase<Frog, Bear>>); +static_assert(std::is_same_v<Lizard, CastableCommonBase<Gecko, Iguana>>); + +static_assert(std::is_same_v<Animal, CastableCommonBase<Bear, Frog, Iguana>>); +static_assert( + std::is_same_v<Lizard, CastableCommonBase<Lizard, Gecko, Iguana>>); +static_assert( + std::is_same_v<Lizard, CastableCommonBase<Gecko, Iguana, Lizard>>); +static_assert( + std::is_same_v<Lizard, CastableCommonBase<Gecko, Lizard, Iguana>>); +static_assert(std::is_same_v<Animal, CastableCommonBase<Frog, Gecko, Iguana>>); +static_assert(std::is_same_v<Animal, CastableCommonBase<Gecko, Iguana, Frog>>); +static_assert(std::is_same_v<Animal, CastableCommonBase<Gecko, Frog, Iguana>>); + +static_assert( + std::is_same_v<CastableBase, + CastableCommonBase<Bear, Frog, Iguana, CastableBase>>); + +} // namespace + +TINT_INSTANTIATE_TYPEINFO(Animal); +TINT_INSTANTIATE_TYPEINFO(Amphibian); +TINT_INSTANTIATE_TYPEINFO(Mammal); +TINT_INSTANTIATE_TYPEINFO(Reptile); +TINT_INSTANTIATE_TYPEINFO(Frog); +TINT_INSTANTIATE_TYPEINFO(Bear); +TINT_INSTANTIATE_TYPEINFO(Lizard); +TINT_INSTANTIATE_TYPEINFO(Gecko); + +} // namespace tint
diff --git a/src/tint/clone_context.cc b/src/tint/clone_context.cc new file mode 100644 index 0000000..afdf488 --- /dev/null +++ b/src/tint/clone_context.cc
@@ -0,0 +1,116 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/clone_context.h" + +#include <string> + +#include "src/tint/program_builder.h" +#include "src/tint/utils/map.h" + +TINT_INSTANTIATE_TYPEINFO(tint::Cloneable); + +namespace tint { + +CloneContext::ListTransforms::ListTransforms() = default; +CloneContext::ListTransforms::~ListTransforms() = default; + +CloneContext::CloneContext(ProgramBuilder* to, + Program const* from, + bool auto_clone_symbols) + : dst(to), src(from) { + if (auto_clone_symbols) { + // Almost all transforms will want to clone all symbols before doing any + // work, to avoid any newly created symbols clashing with existing symbols + // in the source program and causing them to be renamed. + from->Symbols().Foreach([&](Symbol s, const std::string&) { Clone(s); }); + } +} + +CloneContext::CloneContext(ProgramBuilder* builder) + : CloneContext(builder, nullptr, false) {} + +CloneContext::~CloneContext() = default; + +Symbol CloneContext::Clone(Symbol s) { + if (!src) { + return s; // In-place clone + } + return utils::GetOrCreate(cloned_symbols_, s, [&]() -> Symbol { + if (symbol_transform_) { + return symbol_transform_(s); + } + return dst->Symbols().New(src->Symbols().NameFor(s)); + }); +} + +void CloneContext::Clone() { + dst->AST().Copy(this, &src->AST()); +} + +ast::FunctionList CloneContext::Clone(const ast::FunctionList& v) { + ast::FunctionList out; + out.reserve(v.size()); + for (const ast::Function* el : v) { + out.Add(Clone(el)); + } + return out; +} + +const tint::Cloneable* CloneContext::CloneCloneable(const Cloneable* object) { + // If the input is nullptr, there's nothing to clone - just return nullptr. + if (object == nullptr) { + return nullptr; + } + + // Was Replace() called for this object? + auto it = replacements_.find(object); + if (it != replacements_.end()) { + return it->second(); + } + + // Attempt to clone using the registered replacer functions. + auto& typeinfo = object->TypeInfo(); + for (auto& transform : transforms_) { + if (typeinfo.Is(transform.typeinfo)) { + if (auto* transformed = transform.function(object)) { + return transformed; + } + break; + } + } + + // No transform for this type, or the transform returned nullptr. + // Clone with T::Clone(). + return object->Clone(this); +} + +void CloneContext::CheckedCastFailure(const Cloneable* got, + const TypeInfo& expected) { + TINT_ICE(Clone, Diagnostics()) + << "Cloned object was not of the expected type\n" + << "got: " << got->TypeInfo().name << "\n" + << "expected: " << expected.name; +} + +diag::List& CloneContext::Diagnostics() const { + return dst->Diagnostics(); +} + +CloneContext::CloneableTransform::CloneableTransform() = default; +CloneContext::CloneableTransform::CloneableTransform( + const CloneableTransform&) = default; +CloneContext::CloneableTransform::~CloneableTransform() = default; + +} // namespace tint
diff --git a/src/tint/clone_context.h b/src/tint/clone_context.h new file mode 100644 index 0000000..8c02c83 --- /dev/null +++ b/src/tint/clone_context.h
@@ -0,0 +1,584 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_CLONE_CONTEXT_H_ +#define SRC_TINT_CLONE_CONTEXT_H_ + +#include <algorithm> +#include <functional> +#include <unordered_map> +#include <unordered_set> +#include <utility> +#include <vector> + +#include "src/tint/castable.h" +#include "src/tint/debug.h" +#include "src/tint/program_id.h" +#include "src/tint/symbol.h" +#include "src/tint/traits.h" + +namespace tint { + +// Forward declarations +class CloneContext; +class Program; +class ProgramBuilder; +namespace ast { +class FunctionList; +class Node; +} // namespace ast + +ProgramID ProgramIDOf(const Program*); +ProgramID ProgramIDOf(const ProgramBuilder*); + +/// Cloneable is the base class for all objects that can be cloned +class Cloneable : public Castable<Cloneable> { + public: + /// Performs a deep clone of this object using the CloneContext `ctx`. + /// @param ctx the clone context + /// @return the newly cloned object + virtual const Cloneable* Clone(CloneContext* ctx) const = 0; +}; + +/// @returns an invalid ProgramID +inline ProgramID ProgramIDOf(const Cloneable*) { + return ProgramID(); +} + +/// CloneContext holds the state used while cloning AST nodes. +class CloneContext { + /// ParamTypeIsPtrOf<F, T> is true iff the first parameter of + /// F is a pointer of (or derives from) type T. + template <typename F, typename T> + static constexpr bool ParamTypeIsPtrOf = traits::IsTypeOrDerived< + typename std::remove_pointer<traits::ParameterType<F, 0>>::type, + T>; + + public: + /// SymbolTransform is a function that takes a symbol and returns a new + /// symbol. + using SymbolTransform = std::function<Symbol(Symbol)>; + + /// Constructor for cloning objects from `from` into `to`. + /// @param to the target ProgramBuilder to clone into + /// @param from the source Program to clone from + /// @param auto_clone_symbols clone all symbols in `from` before returning + CloneContext(ProgramBuilder* to, + Program const* from, + bool auto_clone_symbols = true); + + /// Constructor for cloning objects from and to the ProgramBuilder `builder`. + /// @param builder the ProgramBuilder + explicit CloneContext(ProgramBuilder* builder); + + /// Destructor + ~CloneContext(); + + /// Clones the Node or sem::Type `a` into the ProgramBuilder #dst if `a` is + /// not null. If `a` is null, then Clone() returns null. + /// + /// Clone() may use a function registered with ReplaceAll() to create a + /// transformed version of the object. See ReplaceAll() for more information. + /// + /// If the CloneContext is cloning from a Program to a ProgramBuilder, then + /// the Node or sem::Type `a` must be owned by the Program #src. + /// + /// @param object the type deriving from Cloneable to clone + /// @return the cloned node + template <typename T> + const T* Clone(const T* object) { + if (src) { + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, object); + } + if (auto* cloned = CloneCloneable(object)) { + auto* out = CheckedCast<T>(cloned); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, out); + return out; + } + return nullptr; + } + + /// Clones the Node or sem::Type `a` into the ProgramBuilder #dst if `a` is + /// not null. If `a` is null, then Clone() returns null. + /// + /// Unlike Clone(), this method does not invoke or use any transformations + /// registered by ReplaceAll(). + /// + /// If the CloneContext is cloning from a Program to a ProgramBuilder, then + /// the Node or sem::Type `a` must be owned by the Program #src. + /// + /// @param a the type deriving from Cloneable to clone + /// @return the cloned node + template <typename T> + const T* CloneWithoutTransform(const T* a) { + // If the input is nullptr, there's nothing to clone - just return nullptr. + if (a == nullptr) { + return nullptr; + } + if (src) { + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, a); + } + auto* c = a->Clone(this); + return CheckedCast<T>(c); + } + + /// Clones the Source `s` into #dst + /// TODO(bclayton) - Currently this 'clone' is a shallow copy. If/when + /// `Source.File`s are owned by the Program this should make a copy of the + /// file. + /// @param s the `Source` to clone + /// @return the cloned source + Source Clone(const Source& s) const { return s; } + + /// Clones the Symbol `s` into #dst + /// + /// The Symbol `s` must be owned by the Program #src. + /// + /// @param s the Symbol to clone + /// @return the cloned source + Symbol Clone(Symbol s); + + /// Clones each of the elements of the vector `v` into the ProgramBuilder + /// #dst. + /// + /// All the elements of the vector `v` must be owned by the Program #src. + /// + /// @param v the vector to clone + /// @return the cloned vector + template <typename T> + std::vector<T> Clone(const std::vector<T>& v) { + std::vector<T> out; + out.reserve(v.size()); + for (auto& el : v) { + out.emplace_back(Clone(el)); + } + return out; + } + + /// Clones each of the elements of the vector `v` using the ProgramBuilder + /// #dst, inserting any additional elements into the list that were registered + /// with calls to InsertBefore(). + /// + /// All the elements of the vector `v` must be owned by the Program #src. + /// + /// @param v the vector to clone + /// @return the cloned vector + template <typename T> + std::vector<T*> Clone(const std::vector<T*>& v) { + std::vector<T*> out; + Clone(out, v); + return out; + } + + /// Clones each of the elements of the vector `from` into the vector `to`, + /// inserting any additional elements into the list that were registered with + /// calls to InsertBefore(). + /// + /// All the elements of the vector `from` must be owned by the Program #src. + /// + /// @param from the vector to clone + /// @param to the cloned result + template <typename T> + void Clone(std::vector<T*>& to, const std::vector<T*>& from) { + to.reserve(from.size()); + + auto list_transform_it = list_transforms_.find(&from); + if (list_transform_it != list_transforms_.end()) { + const auto& transforms = list_transform_it->second; + for (auto* o : transforms.insert_front_) { + to.emplace_back(CheckedCast<T>(o)); + } + for (auto& el : from) { + auto insert_before_it = transforms.insert_before_.find(el); + if (insert_before_it != transforms.insert_before_.end()) { + for (auto insert : insert_before_it->second) { + to.emplace_back(CheckedCast<T>(insert)); + } + } + if (transforms.remove_.count(el) == 0) { + to.emplace_back(Clone(el)); + } + auto insert_after_it = transforms.insert_after_.find(el); + if (insert_after_it != transforms.insert_after_.end()) { + for (auto insert : insert_after_it->second) { + to.emplace_back(CheckedCast<T>(insert)); + } + } + } + for (auto* o : transforms.insert_back_) { + to.emplace_back(CheckedCast<T>(o)); + } + } else { + for (auto& el : from) { + to.emplace_back(Clone(el)); + + // Clone(el) may have inserted after + list_transform_it = list_transforms_.find(&from); + if (list_transform_it != list_transforms_.end()) { + const auto& transforms = list_transform_it->second; + + auto insert_after_it = transforms.insert_after_.find(el); + if (insert_after_it != transforms.insert_after_.end()) { + for (auto insert : insert_after_it->second) { + to.emplace_back(CheckedCast<T>(insert)); + } + } + } + } + + // Clone(el)s may have inserted back + list_transform_it = list_transforms_.find(&from); + if (list_transform_it != list_transforms_.end()) { + const auto& transforms = list_transform_it->second; + + for (auto* o : transforms.insert_back_) { + to.emplace_back(CheckedCast<T>(o)); + } + } + } + } + + /// Clones each of the elements of the vector `v` into the ProgramBuilder + /// #dst. + /// + /// All the elements of the vector `v` must be owned by the Program #src. + /// + /// @param v the vector to clone + /// @return the cloned vector + ast::FunctionList Clone(const ast::FunctionList& v); + + /// ReplaceAll() registers `replacer` to be called whenever the Clone() method + /// is called with a Cloneable type that matches (or derives from) the type of + /// the single parameter of `replacer`. + /// The returned Cloneable of `replacer` will be used as the replacement for + /// all references to the object that's being cloned. This returned Cloneable + /// must be owned by the Program #dst. + /// + /// `replacer` must be function-like with the signature: `T* (T*)` + /// where `T` is a type deriving from Cloneable. + /// + /// If `replacer` returns a nullptr then Clone() will call `T::Clone()` to + /// clone the object. + /// + /// Example: + /// + /// ``` + /// // Replace all ast::UintLiteralExpressions with the number 42 + /// CloneCtx ctx(&out, in); + /// ctx.ReplaceAll([&] (ast::UintLiteralExpression* l) { + /// return ctx->dst->create<ast::UintLiteralExpression>( + /// ctx->Clone(l->source), + /// ctx->Clone(l->type), + /// 42); + /// }); + /// ctx.Clone(); + /// ``` + /// + /// @warning a single handler can only be registered for any given type. + /// Attempting to register two handlers for the same type will result in an + /// ICE. + /// @warning The replacement object must be of the correct type for all + /// references of the original object. A type mismatch will result in an + /// assertion in debug builds, and undefined behavior in release builds. + /// @param replacer a function or function-like object with the signature + /// `T* (T*)`, where `T` derives from Cloneable + /// @returns this CloneContext so calls can be chained + template <typename F> + traits::EnableIf<ParamTypeIsPtrOf<F, Cloneable>, CloneContext>& ReplaceAll( + F&& replacer) { + using TPtr = traits::ParameterType<F, 0>; + using T = typename std::remove_pointer<TPtr>::type; + for (auto& transform : transforms_) { + if (transform.typeinfo->Is(&TypeInfo::Of<T>()) || + TypeInfo::Of<T>().Is(transform.typeinfo)) { + TINT_ICE(Clone, Diagnostics()) + << "ReplaceAll() called with a handler for type " + << TypeInfo::Of<T>().name + << " that is already handled by a handler for type " + << transform.typeinfo->name; + return *this; + } + } + CloneableTransform transform; + transform.typeinfo = &TypeInfo::Of<T>(); + transform.function = [=](const Cloneable* in) { + return replacer(in->As<T>()); + }; + transforms_.emplace_back(std::move(transform)); + return *this; + } + + /// ReplaceAll() registers `replacer` to be called whenever the Clone() method + /// is called with a Symbol. + /// The returned symbol of `replacer` will be used as the replacement for + /// all references to the symbol that's being cloned. This returned Symbol + /// must be owned by the Program #dst. + /// @param replacer a function the signature `Symbol(Symbol)`. + /// @warning a SymbolTransform can only be registered once. Attempting to + /// register a SymbolTransform more than once will result in an ICE. + /// @returns this CloneContext so calls can be chained + CloneContext& ReplaceAll(const SymbolTransform& replacer) { + if (symbol_transform_) { + TINT_ICE(Clone, Diagnostics()) + << "ReplaceAll(const SymbolTransform&) called " + "multiple times on the same CloneContext"; + return *this; + } + symbol_transform_ = replacer; + return *this; + } + + /// Replace replaces all occurrences of `what` in #src with the pointer `with` + /// in #dst when calling Clone(). + /// [DEPRECATED]: This function cannot handle nested replacements. Use the + /// overload of Replace() that take a function for the `WITH` argument. + /// @param what a pointer to the object in #src that will be replaced with + /// `with` + /// @param with a pointer to the replacement object owned by #dst that will be + /// used as a replacement for `what` + /// @warning The replacement object must be of the correct type for all + /// references of the original object. A type mismatch will result in an + /// assertion in debug builds, and undefined behavior in release builds. + /// @returns this CloneContext so calls can be chained + template <typename WHAT, + typename WITH, + typename = traits::EnableIfIsType<WITH, Cloneable>> + CloneContext& Replace(const WHAT* what, const WITH* with) { + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, what); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, with); + replacements_[what] = [with]() -> const Cloneable* { return with; }; + return *this; + } + + /// Replace replaces all occurrences of `what` in #src with the result of the + /// function `with` in #dst when calling Clone(). `with` will be called each + /// time `what` is cloned by this context. If `what` is not cloned, then + /// `with` may never be called. + /// @param what a pointer to the object in #src that will be replaced with + /// `with` + /// @param with a function that takes no arguments and returns a pointer to + /// the replacement object owned by #dst. The returned pointer will be used as + /// a replacement for `what`. + /// @warning The replacement object must be of the correct type for all + /// references of the original object. A type mismatch will result in an + /// assertion in debug builds, and undefined behavior in release builds. + /// @returns this CloneContext so calls can be chained + template <typename WHAT, typename WITH, typename = std::result_of_t<WITH()>> + CloneContext& Replace(const WHAT* what, WITH&& with) { + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, what); + replacements_[what] = with; + return *this; + } + + /// Removes `object` from the cloned copy of `vector`. + /// @param vector the vector in #src + /// @param object a pointer to the object in #src that will be omitted from + /// the cloned vector. + /// @returns this CloneContext so calls can be chained + template <typename T, typename OBJECT> + CloneContext& Remove(const std::vector<T>& vector, OBJECT* object) { + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, object); + if (std::find(vector.begin(), vector.end(), object) == vector.end()) { + TINT_ICE(Clone, Diagnostics()) + << "CloneContext::Remove() vector does not contain object"; + return *this; + } + + list_transforms_[&vector].remove_.emplace(object); + return *this; + } + + /// Inserts `object` before any other objects of `vector`, when it is cloned. + /// @param vector the vector in #src + /// @param object a pointer to the object in #dst that will be inserted at the + /// front of the vector + /// @returns this CloneContext so calls can be chained + template <typename T, typename OBJECT> + CloneContext& InsertFront(const std::vector<T>& vector, OBJECT* object) { + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, object); + auto& transforms = list_transforms_[&vector]; + auto& list = transforms.insert_front_; + list.emplace_back(object); + return *this; + } + + /// Inserts `object` after any other objects of `vector`, when it is cloned. + /// @param vector the vector in #src + /// @param object a pointer to the object in #dst that will be inserted at the + /// end of the vector + /// @returns this CloneContext so calls can be chained + template <typename T, typename OBJECT> + CloneContext& InsertBack(const std::vector<T>& vector, OBJECT* object) { + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, object); + auto& transforms = list_transforms_[&vector]; + auto& list = transforms.insert_back_; + list.emplace_back(object); + return *this; + } + + /// Inserts `object` before `before` whenever `vector` is cloned. + /// @param vector the vector in #src + /// @param before a pointer to the object in #src + /// @param object a pointer to the object in #dst that will be inserted before + /// any occurrence of the clone of `before` + /// @returns this CloneContext so calls can be chained + template <typename T, typename BEFORE, typename OBJECT> + CloneContext& InsertBefore(const std::vector<T>& vector, + const BEFORE* before, + const OBJECT* object) { + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, before); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, object); + if (std::find(vector.begin(), vector.end(), before) == vector.end()) { + TINT_ICE(Clone, Diagnostics()) + << "CloneContext::InsertBefore() vector does not contain before"; + return *this; + } + + auto& transforms = list_transforms_[&vector]; + auto& list = transforms.insert_before_[before]; + list.emplace_back(object); + return *this; + } + + /// Inserts `object` after `after` whenever `vector` is cloned. + /// @param vector the vector in #src + /// @param after a pointer to the object in #src + /// @param object a pointer to the object in #dst that will be inserted after + /// any occurrence of the clone of `after` + /// @returns this CloneContext so calls can be chained + template <typename T, typename AFTER, typename OBJECT> + CloneContext& InsertAfter(const std::vector<T>& vector, + const AFTER* after, + const OBJECT* object) { + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, after); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, object); + if (std::find(vector.begin(), vector.end(), after) == vector.end()) { + TINT_ICE(Clone, Diagnostics()) + << "CloneContext::InsertAfter() vector does not contain after"; + return *this; + } + + auto& transforms = list_transforms_[&vector]; + auto& list = transforms.insert_after_[after]; + list.emplace_back(object); + return *this; + } + + /// Clone performs the clone of the Program's AST nodes, types and symbols + /// from #src to #dst. Semantic nodes are not cloned, as these will be rebuilt + /// when the ProgramBuilder #dst builds its Program. + void Clone(); + + /// The target ProgramBuilder to clone into. + ProgramBuilder* const dst; + + /// The source Program to clone from. + Program const* const src; + + private: + struct CloneableTransform { + /// Constructor + CloneableTransform(); + /// Copy constructor + /// @param other the CloneableTransform to copy + CloneableTransform(const CloneableTransform& other); + /// Destructor + ~CloneableTransform(); + + // TypeInfo of the Cloneable that the transform operates on + const TypeInfo* typeinfo; + std::function<const Cloneable*(const Cloneable*)> function; + }; + + CloneContext(const CloneContext&) = delete; + CloneContext& operator=(const CloneContext&) = delete; + + /// Cast `obj` from type `FROM` to type `TO`, returning the cast object. + /// Reports an internal compiler error if the cast failed. + template <typename TO, typename FROM> + const TO* CheckedCast(const FROM* obj) { + if (obj == nullptr) { + return nullptr; + } + if (const TO* cast = obj->template As<TO>()) { + return cast; + } + CheckedCastFailure(obj, TypeInfo::Of<TO>()); + return nullptr; + } + + /// Clones a Cloneable object, using any replacements or transforms that have + /// been configured. + const Cloneable* CloneCloneable(const Cloneable* object); + + /// Adds an error diagnostic to Diagnostics() that the cloned object was not + /// of the expected type. + void CheckedCastFailure(const Cloneable* got, const TypeInfo& expected); + + /// @returns the diagnostic list of #dst + diag::List& Diagnostics() const; + + /// A vector of const Cloneable* + using CloneableList = std::vector<const Cloneable*>; + + /// Transformations to be applied to a list (vector) + struct ListTransforms { + /// Constructor + ListTransforms(); + /// Destructor + ~ListTransforms(); + + /// A map of object in #src to omit when cloned into #dst. + std::unordered_set<const Cloneable*> remove_; + + /// A list of objects in #dst to insert before any others when the vector is + /// cloned. + CloneableList insert_front_; + + /// A list of objects in #dst to insert befor after any others when the + /// vector is cloned. + CloneableList insert_back_; + + /// A map of object in #src to the list of cloned objects in #dst. + /// Clone(const std::vector<T*>& v) will use this to insert the map-value + /// list into the target vector before cloning and inserting the map-key. + std::unordered_map<const Cloneable*, CloneableList> insert_before_; + + /// A map of object in #src to the list of cloned objects in #dst. + /// Clone(const std::vector<T*>& v) will use this to insert the map-value + /// list into the target vector after cloning and inserting the map-key. + std::unordered_map<const Cloneable*, CloneableList> insert_after_; + }; + + /// A map of object in #src to functions that create their replacement in + /// #dst + std::unordered_map<const Cloneable*, std::function<const Cloneable*()>> + replacements_; + + /// A map of symbol in #src to their cloned equivalent in #dst + std::unordered_map<Symbol, Symbol> cloned_symbols_; + + /// Cloneable transform functions registered with ReplaceAll() + std::vector<CloneableTransform> transforms_; + + /// Map of std::vector pointer to transforms for that list + std::unordered_map<const void*, ListTransforms> list_transforms_; + + /// Symbol transform registered with ReplaceAll() + SymbolTransform symbol_transform_; +}; + +} // namespace tint + +#endif // SRC_TINT_CLONE_CONTEXT_H_
diff --git a/src/tint/clone_context_test.cc b/src/tint/clone_context_test.cc new file mode 100644 index 0000000..3a5a8c9 --- /dev/null +++ b/src/tint/clone_context_test.cc
@@ -0,0 +1,950 @@ +// Copyright 2020 The Tint Authors. +// +// 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 <unordered_set> + +#include "gtest/gtest-spi.h" +#include "src/tint/program_builder.h" + +namespace tint { +namespace { + +struct Allocator { + template <typename T, typename... ARGS> + T* Create(ARGS&&... args) { + return alloc.Create<T>(this, std::forward<ARGS>(args)...); + } + + private: + utils::BlockAllocator<Cloneable> alloc; +}; + +struct Node : public Castable<Node, Cloneable> { + Node(Allocator* alloc, + Symbol n, + const Node* node_a = nullptr, + const Node* node_b = nullptr, + const Node* node_c = nullptr) + : allocator(alloc), name(n), a(node_a), b(node_b), c(node_c) {} + Allocator* const allocator; + Symbol name; + const Node* a = nullptr; + const Node* b = nullptr; + const Node* c = nullptr; + std::vector<const Node*> vec; + + Node* Clone(CloneContext* ctx) const override { + auto* out = allocator->Create<Node>(ctx->Clone(name)); + out->a = ctx->Clone(a); + out->b = ctx->Clone(b); + out->c = ctx->Clone(c); + out->vec = ctx->Clone(vec); + return out; + } +}; + +struct Replaceable : public Castable<Replaceable, Node> { + Replaceable(Allocator* alloc, + Symbol n, + const Node* node_a = nullptr, + const Node* node_b = nullptr, + const Node* node_c = nullptr) + : Base(alloc, n, node_a, node_b, node_c) {} +}; + +struct Replacement : public Castable<Replacement, Replaceable> { + Replacement(Allocator* alloc, Symbol n) : Base(alloc, n) {} +}; + +struct NotANode : public Castable<NotANode, Cloneable> { + explicit NotANode(Allocator* alloc) : allocator(alloc) {} + + Allocator* const allocator; + NotANode* Clone(CloneContext*) const override { + return allocator->Create<NotANode>(); + } +}; + +struct ProgramNode : public Castable<ProgramNode, Cloneable> { + ProgramNode(Allocator* alloc, ProgramID id, ProgramID cloned_id) + : allocator(alloc), program_id(id), cloned_program_id(cloned_id) {} + + Allocator* const allocator; + const ProgramID program_id; + const ProgramID cloned_program_id; + + ProgramNode* Clone(CloneContext*) const override { + return allocator->Create<ProgramNode>(cloned_program_id, cloned_program_id); + } +}; + +ProgramID ProgramIDOf(const ProgramNode* node) { + return node->program_id; +} + +using CloneContextNodeTest = ::testing::Test; + +TEST_F(CloneContextNodeTest, Clone) { + Allocator alloc; + + ProgramBuilder builder; + Node* original_root; + { + auto* a_b = alloc.Create<Node>(builder.Symbols().New("a->b")); + auto* a = alloc.Create<Node>(builder.Symbols().New("a"), nullptr, a_b); + auto* b_a = a; // Aliased + auto* b_b = alloc.Create<Node>(builder.Symbols().New("b->b")); + auto* b = alloc.Create<Node>(builder.Symbols().New("b"), b_a, b_b); + auto* c = b; // Aliased + original_root = alloc.Create<Node>(builder.Symbols().New("root"), a, b, c); + } + Program original(std::move(builder)); + + // root + // ╭──────────────────┼──────────────────╮ + // (a) (b) (c) + // N <──────┐ N <───────────────┘ + // ╭────┼────╮ │ ╭────┼────╮ + // (a) (b) (c) │ (a) (b) (c) + // N └───┘ N + // + // N: Node + + ProgramBuilder cloned; + auto* cloned_root = CloneContext(&cloned, &original).Clone(original_root); + + EXPECT_NE(cloned_root->a, nullptr); + EXPECT_EQ(cloned_root->a->a, nullptr); + EXPECT_NE(cloned_root->a->b, nullptr); + EXPECT_EQ(cloned_root->a->c, nullptr); + EXPECT_NE(cloned_root->b, nullptr); + EXPECT_NE(cloned_root->b->a, nullptr); + EXPECT_NE(cloned_root->b->b, nullptr); + EXPECT_EQ(cloned_root->b->c, nullptr); + EXPECT_NE(cloned_root->c, nullptr); + + EXPECT_NE(cloned_root->a, original_root->a); + EXPECT_NE(cloned_root->a->b, original_root->a->b); + EXPECT_NE(cloned_root->b, original_root->b); + EXPECT_NE(cloned_root->b->a, original_root->b->a); + EXPECT_NE(cloned_root->b->b, original_root->b->b); + EXPECT_NE(cloned_root->c, original_root->c); + + EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root")); + EXPECT_EQ(cloned_root->a->name, cloned.Symbols().Get("a")); + EXPECT_EQ(cloned_root->a->b->name, cloned.Symbols().Get("a->b")); + EXPECT_EQ(cloned_root->b->name, cloned.Symbols().Get("b")); + EXPECT_EQ(cloned_root->b->b->name, cloned.Symbols().Get("b->b")); + + EXPECT_NE(cloned_root->b->a, cloned_root->a); // De-aliased + EXPECT_NE(cloned_root->c, cloned_root->b); // De-aliased + + EXPECT_EQ(cloned_root->b->a->name, cloned_root->a->name); + EXPECT_EQ(cloned_root->c->name, cloned_root->b->name); +} + +TEST_F(CloneContextNodeTest, CloneWithReplaceAll_Cloneable) { + Allocator alloc; + + ProgramBuilder builder; + Node* original_root; + { + auto* a_b = alloc.Create<Replaceable>(builder.Symbols().New("a->b")); + auto* a = alloc.Create<Node>(builder.Symbols().New("a"), nullptr, a_b); + auto* b_a = a; // Aliased + auto* b = + alloc.Create<Replaceable>(builder.Symbols().New("b"), b_a, nullptr); + auto* c = b; // Aliased + original_root = alloc.Create<Node>(builder.Symbols().New("root"), a, b, c); + } + Program original(std::move(builder)); + + // root + // ╭──────────────────┼──────────────────╮ + // (a) (b) (c) + // N <──────┐ R <───────────────┘ + // ╭────┼────╮ │ ╭────┼────╮ + // (a) (b) (c) │ (a) (b) (c) + // R └───┘ + // + // N: Node + // R: Replaceable + + ProgramBuilder cloned; + + CloneContext ctx(&cloned, &original); + ctx.ReplaceAll([&](const Replaceable* in) { + auto out_name = cloned.Symbols().Register( + "replacement:" + original.Symbols().NameFor(in->name)); + auto b_name = cloned.Symbols().Register( + "replacement-child:" + original.Symbols().NameFor(in->name)); + auto* out = alloc.Create<Replacement>(out_name); + out->b = alloc.Create<Node>(b_name); + out->c = ctx.Clone(in->a); + return out; + }); + auto* cloned_root = ctx.Clone(original_root); + + // root + // ╭─────────────────┼──────────────────╮ + // (a) (b) (c) + // N <──────┐ R <───────────────┘ + // ╭────┼────╮ │ ╭────┼────╮ + // (a) (b) (c) │ (a) (b) (c) + // R │ N | + // ╭────┼────╮ └────────────┘ + // (a) (b) (c) + // N + // + // N: Node + // R: Replacement + + EXPECT_NE(cloned_root->a, nullptr); + EXPECT_EQ(cloned_root->a->a, nullptr); + EXPECT_NE(cloned_root->a->b, nullptr); // Replaced + EXPECT_EQ(cloned_root->a->b->a, nullptr); // From replacement + EXPECT_NE(cloned_root->a->b->b, nullptr); // From replacement + EXPECT_EQ(cloned_root->a->b->c, nullptr); // From replacement + EXPECT_EQ(cloned_root->a->c, nullptr); + EXPECT_NE(cloned_root->b, nullptr); + EXPECT_EQ(cloned_root->b->a, nullptr); // From replacement + EXPECT_NE(cloned_root->b->b, nullptr); // From replacement + EXPECT_NE(cloned_root->b->c, nullptr); // From replacement + EXPECT_NE(cloned_root->c, nullptr); + + EXPECT_NE(cloned_root->a, original_root->a); + EXPECT_NE(cloned_root->a->b, original_root->a->b); + EXPECT_NE(cloned_root->b, original_root->b); + EXPECT_NE(cloned_root->b->a, original_root->b->a); + EXPECT_NE(cloned_root->c, original_root->c); + + EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root")); + EXPECT_EQ(cloned_root->a->name, cloned.Symbols().Get("a")); + EXPECT_EQ(cloned_root->a->b->name, cloned.Symbols().Get("replacement:a->b")); + EXPECT_EQ(cloned_root->a->b->b->name, + cloned.Symbols().Get("replacement-child:a->b")); + EXPECT_EQ(cloned_root->b->name, cloned.Symbols().Get("replacement:b")); + EXPECT_EQ(cloned_root->b->b->name, + cloned.Symbols().Get("replacement-child:b")); + + EXPECT_NE(cloned_root->b->c, cloned_root->a); // De-aliased + EXPECT_NE(cloned_root->c, cloned_root->b); // De-aliased + + EXPECT_EQ(cloned_root->b->c->name, cloned_root->a->name); + EXPECT_EQ(cloned_root->c->name, cloned_root->b->name); + + EXPECT_FALSE(Is<Replacement>(cloned_root->a)); + EXPECT_TRUE(Is<Replacement>(cloned_root->a->b)); + EXPECT_FALSE(Is<Replacement>(cloned_root->a->b->b)); + EXPECT_TRUE(Is<Replacement>(cloned_root->b)); + EXPECT_FALSE(Is<Replacement>(cloned_root->b->b)); +} + +TEST_F(CloneContextNodeTest, CloneWithReplaceAll_Symbols) { + Allocator alloc; + + ProgramBuilder builder; + Node* original_root; + { + auto* a_b = alloc.Create<Node>(builder.Symbols().New("a->b")); + auto* a = alloc.Create<Node>(builder.Symbols().New("a"), nullptr, a_b); + auto* b_a = a; // Aliased + auto* b_b = alloc.Create<Node>(builder.Symbols().New("b->b")); + auto* b = alloc.Create<Node>(builder.Symbols().New("b"), b_a, b_b); + auto* c = b; // Aliased + original_root = alloc.Create<Node>(builder.Symbols().New("root"), a, b, c); + } + Program original(std::move(builder)); + + // root + // ╭──────────────────┼──────────────────╮ + // (a) (b) (c) + // N <──────┐ N <───────────────┘ + // ╭────┼────╮ │ ╭────┼────╮ + // (a) (b) (c) │ (a) (b) (c) + // N └───┘ N + // + // N: Node + + ProgramBuilder cloned; + auto* cloned_root = CloneContext(&cloned, &original, false) + .ReplaceAll([&](Symbol sym) { + auto in = original.Symbols().NameFor(sym); + auto out = "transformed<" + in + ">"; + return cloned.Symbols().New(out); + }) + .Clone(original_root); + + EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("transformed<root>")); + EXPECT_EQ(cloned_root->a->name, cloned.Symbols().Get("transformed<a>")); + EXPECT_EQ(cloned_root->a->b->name, cloned.Symbols().Get("transformed<a->b>")); + EXPECT_EQ(cloned_root->b->name, cloned.Symbols().Get("transformed<b>")); + EXPECT_EQ(cloned_root->b->b->name, cloned.Symbols().Get("transformed<b->b>")); +} + +TEST_F(CloneContextNodeTest, CloneWithoutTransform) { + Allocator a; + + ProgramBuilder builder; + auto* original_node = a.Create<Node>(builder.Symbols().New("root")); + Program original(std::move(builder)); + + ProgramBuilder cloned; + CloneContext ctx(&cloned, &original); + ctx.ReplaceAll([&](const Node*) { + return a.Create<Replacement>(builder.Symbols().New("<unexpected-node>")); + }); + + auto* cloned_node = ctx.CloneWithoutTransform(original_node); + EXPECT_NE(cloned_node, original_node); + EXPECT_EQ(cloned_node->name, cloned.Symbols().Get("root")); +} + +TEST_F(CloneContextNodeTest, CloneWithReplacePointer) { + Allocator a; + + ProgramBuilder builder; + auto* original_root = a.Create<Node>(builder.Symbols().New("root")); + original_root->a = a.Create<Node>(builder.Symbols().New("a")); + original_root->b = a.Create<Node>(builder.Symbols().New("b")); + original_root->c = a.Create<Node>(builder.Symbols().New("c")); + Program original(std::move(builder)); + + // root + // ╭──────────────────┼──────────────────╮ + // (a) (b) (c) + // Replaced + + ProgramBuilder cloned; + auto* replacement = a.Create<Node>(cloned.Symbols().New("replacement")); + + auto* cloned_root = CloneContext(&cloned, &original) + .Replace(original_root->b, replacement) + .Clone(original_root); + + EXPECT_NE(cloned_root->a, replacement); + EXPECT_EQ(cloned_root->b, replacement); + EXPECT_NE(cloned_root->c, replacement); + + EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root")); + EXPECT_EQ(cloned_root->a->name, cloned.Symbols().Get("a")); + EXPECT_EQ(cloned_root->b->name, cloned.Symbols().Get("replacement")); + EXPECT_EQ(cloned_root->c->name, cloned.Symbols().Get("c")); +} + +TEST_F(CloneContextNodeTest, CloneWithReplaceFunction) { + Allocator a; + + ProgramBuilder builder; + auto* original_root = a.Create<Node>(builder.Symbols().New("root")); + original_root->a = a.Create<Node>(builder.Symbols().New("a")); + original_root->b = a.Create<Node>(builder.Symbols().New("b")); + original_root->c = a.Create<Node>(builder.Symbols().New("c")); + Program original(std::move(builder)); + + // root + // ╭──────────────────┼──────────────────╮ + // (a) (b) (c) + // Replaced + + ProgramBuilder cloned; + auto* replacement = a.Create<Node>(cloned.Symbols().New("replacement")); + + auto* cloned_root = + CloneContext(&cloned, &original) + .Replace(original_root->b, [=] { return replacement; }) + .Clone(original_root); + + EXPECT_NE(cloned_root->a, replacement); + EXPECT_EQ(cloned_root->b, replacement); + EXPECT_NE(cloned_root->c, replacement); + + EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root")); + EXPECT_EQ(cloned_root->a->name, cloned.Symbols().Get("a")); + EXPECT_EQ(cloned_root->b->name, cloned.Symbols().Get("replacement")); + EXPECT_EQ(cloned_root->c->name, cloned.Symbols().Get("c")); +} + +TEST_F(CloneContextNodeTest, CloneWithRemove) { + Allocator a; + + ProgramBuilder builder; + auto* original_root = a.Create<Node>(builder.Symbols().Register("root")); + original_root->vec = { + a.Create<Node>(builder.Symbols().Register("a")), + a.Create<Node>(builder.Symbols().Register("b")), + a.Create<Node>(builder.Symbols().Register("c")), + }; + Program original(std::move(builder)); + + ProgramBuilder cloned; + auto* cloned_root = CloneContext(&cloned, &original) + .Remove(original_root->vec, original_root->vec[1]) + .Clone(original_root); + + EXPECT_EQ(cloned_root->vec.size(), 2u); + + EXPECT_NE(cloned_root->vec[0], cloned_root->a); + EXPECT_NE(cloned_root->vec[1], cloned_root->c); + + EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root")); + EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a")); + EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("c")); +} + +TEST_F(CloneContextNodeTest, CloneWithInsertFront) { + Allocator a; + + ProgramBuilder builder; + auto* original_root = a.Create<Node>(builder.Symbols().Register("root")); + original_root->vec = { + a.Create<Node>(builder.Symbols().Register("a")), + a.Create<Node>(builder.Symbols().Register("b")), + a.Create<Node>(builder.Symbols().Register("c")), + }; + Program original(std::move(builder)); + + ProgramBuilder cloned; + auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion")); + + auto* cloned_root = CloneContext(&cloned, &original) + .InsertFront(original_root->vec, insertion) + .Clone(original_root); + + EXPECT_EQ(cloned_root->vec.size(), 4u); + + EXPECT_NE(cloned_root->vec[0], cloned_root->a); + EXPECT_NE(cloned_root->vec[1], cloned_root->b); + EXPECT_NE(cloned_root->vec[2], cloned_root->c); + + EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root")); + EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("insertion")); + EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("a")); + EXPECT_EQ(cloned_root->vec[2]->name, cloned.Symbols().Get("b")); + EXPECT_EQ(cloned_root->vec[3]->name, cloned.Symbols().Get("c")); +} + +TEST_F(CloneContextNodeTest, CloneWithInsertFront_Empty) { + Allocator a; + + ProgramBuilder builder; + auto* original_root = a.Create<Node>(builder.Symbols().Register("root")); + original_root->vec = {}; + Program original(std::move(builder)); + + ProgramBuilder cloned; + auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion")); + + auto* cloned_root = CloneContext(&cloned, &original) + .InsertFront(original_root->vec, insertion) + .Clone(original_root); + + EXPECT_EQ(cloned_root->vec.size(), 1u); + + EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root")); + EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("insertion")); +} + +TEST_F(CloneContextNodeTest, CloneWithInsertBack) { + Allocator a; + + ProgramBuilder builder; + auto* original_root = a.Create<Node>(builder.Symbols().Register("root")); + original_root->vec = { + a.Create<Node>(builder.Symbols().Register("a")), + a.Create<Node>(builder.Symbols().Register("b")), + a.Create<Node>(builder.Symbols().Register("c")), + }; + Program original(std::move(builder)); + + ProgramBuilder cloned; + auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion")); + + auto* cloned_root = CloneContext(&cloned, &original) + .InsertBack(original_root->vec, insertion) + .Clone(original_root); + + EXPECT_EQ(cloned_root->vec.size(), 4u); + + EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root")); + EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a")); + EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("b")); + EXPECT_EQ(cloned_root->vec[2]->name, cloned.Symbols().Get("c")); + EXPECT_EQ(cloned_root->vec[3]->name, cloned.Symbols().Get("insertion")); +} + +TEST_F(CloneContextNodeTest, CloneWithInsertBack_Empty) { + Allocator a; + + ProgramBuilder builder; + auto* original_root = a.Create<Node>(builder.Symbols().Register("root")); + original_root->vec = {}; + Program original(std::move(builder)); + + ProgramBuilder cloned; + auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion")); + + auto* cloned_root = CloneContext(&cloned, &original) + .InsertBack(original_root->vec, insertion) + .Clone(original_root); + + EXPECT_EQ(cloned_root->vec.size(), 1u); + + EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root")); + EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("insertion")); +} + +TEST_F(CloneContextNodeTest, CloneWithInsertFrontAndBack_Empty) { + Allocator a; + + ProgramBuilder builder; + auto* original_root = a.Create<Node>(builder.Symbols().Register("root")); + original_root->vec = {}; + Program original(std::move(builder)); + + ProgramBuilder cloned; + auto* insertion_front = + a.Create<Node>(cloned.Symbols().New("insertion_front")); + auto* insertion_back = a.Create<Node>(cloned.Symbols().New("insertion_back")); + + auto* cloned_root = CloneContext(&cloned, &original) + .InsertBack(original_root->vec, insertion_back) + .InsertFront(original_root->vec, insertion_front) + .Clone(original_root); + + EXPECT_EQ(cloned_root->vec.size(), 2u); + + EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root")); + EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("insertion_front")); + EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("insertion_back")); +} + +TEST_F(CloneContextNodeTest, CloneWithInsertBefore) { + Allocator a; + + ProgramBuilder builder; + auto* original_root = a.Create<Node>(builder.Symbols().Register("root")); + original_root->vec = { + a.Create<Node>(builder.Symbols().Register("a")), + a.Create<Node>(builder.Symbols().Register("b")), + a.Create<Node>(builder.Symbols().Register("c")), + }; + Program original(std::move(builder)); + + ProgramBuilder cloned; + auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion")); + + auto* cloned_root = + CloneContext(&cloned, &original) + .InsertBefore(original_root->vec, original_root->vec[1], insertion) + .Clone(original_root); + + EXPECT_EQ(cloned_root->vec.size(), 4u); + + EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root")); + EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a")); + EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("insertion")); + EXPECT_EQ(cloned_root->vec[2]->name, cloned.Symbols().Get("b")); + EXPECT_EQ(cloned_root->vec[3]->name, cloned.Symbols().Get("c")); +} + +TEST_F(CloneContextNodeTest, CloneWithInsertAfter) { + Allocator a; + + ProgramBuilder builder; + auto* original_root = a.Create<Node>(builder.Symbols().Register("root")); + original_root->vec = { + a.Create<Node>(builder.Symbols().Register("a")), + a.Create<Node>(builder.Symbols().Register("b")), + a.Create<Node>(builder.Symbols().Register("c")), + }; + Program original(std::move(builder)); + + ProgramBuilder cloned; + auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion")); + + auto* cloned_root = + CloneContext(&cloned, &original) + .InsertAfter(original_root->vec, original_root->vec[1], insertion) + .Clone(original_root); + + EXPECT_EQ(cloned_root->vec.size(), 4u); + + EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root")); + EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a")); + EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("b")); + EXPECT_EQ(cloned_root->vec[2]->name, cloned.Symbols().Get("insertion")); + EXPECT_EQ(cloned_root->vec[3]->name, cloned.Symbols().Get("c")); +} + +TEST_F(CloneContextNodeTest, CloneWithInsertAfterInVectorNodeClone) { + Allocator a; + + ProgramBuilder builder; + auto* original_root = a.Create<Node>(builder.Symbols().Register("root")); + original_root->vec = { + a.Create<Node>(builder.Symbols().Register("a")), + a.Create<Replaceable>(builder.Symbols().Register("b")), + a.Create<Node>(builder.Symbols().Register("c")), + }; + + Program original(std::move(builder)); + + ProgramBuilder cloned; + CloneContext ctx(&cloned, &original); + ctx.ReplaceAll([&](const Replaceable* r) { + auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion")); + ctx.InsertAfter(original_root->vec, r, insertion); + return nullptr; + }); + + auto* cloned_root = ctx.Clone(original_root); + + EXPECT_EQ(cloned_root->vec.size(), 4u); + + EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root")); + EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a")); + EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("b")); + EXPECT_EQ(cloned_root->vec[2]->name, cloned.Symbols().Get("insertion")); + EXPECT_EQ(cloned_root->vec[3]->name, cloned.Symbols().Get("c")); +} + +TEST_F(CloneContextNodeTest, CloneWithInsertBackInVectorNodeClone) { + Allocator a; + + ProgramBuilder builder; + auto* original_root = a.Create<Node>(builder.Symbols().Register("root")); + original_root->vec = { + a.Create<Node>(builder.Symbols().Register("a")), + a.Create<Replaceable>(builder.Symbols().Register("b")), + a.Create<Node>(builder.Symbols().Register("c")), + }; + + Program original(std::move(builder)); + + ProgramBuilder cloned; + CloneContext ctx(&cloned, &original); + ctx.ReplaceAll([&](const Replaceable* /*r*/) { + auto* insertion = a.Create<Node>(cloned.Symbols().New("insertion")); + ctx.InsertBack(original_root->vec, insertion); + return nullptr; + }); + + auto* cloned_root = ctx.Clone(original_root); + + EXPECT_EQ(cloned_root->vec.size(), 4u); + + EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root")); + EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a")); + EXPECT_EQ(cloned_root->vec[1]->name, cloned.Symbols().Get("b")); + EXPECT_EQ(cloned_root->vec[2]->name, cloned.Symbols().Get("c")); + EXPECT_EQ(cloned_root->vec[3]->name, cloned.Symbols().Get("insertion")); +} + +TEST_F(CloneContextNodeTest, CloneWithInsertBeforeAndAfterRemoved) { + Allocator a; + + ProgramBuilder builder; + auto* original_root = a.Create<Node>(builder.Symbols().Register("root")); + original_root->vec = { + a.Create<Node>(builder.Symbols().Register("a")), + a.Create<Node>(builder.Symbols().Register("b")), + a.Create<Node>(builder.Symbols().Register("c")), + }; + Program original(std::move(builder)); + + ProgramBuilder cloned; + auto* insertion_before = + a.Create<Node>(cloned.Symbols().New("insertion_before")); + auto* insertion_after = + a.Create<Node>(cloned.Symbols().New("insertion_after")); + + auto* cloned_root = CloneContext(&cloned, &original) + .InsertBefore(original_root->vec, + original_root->vec[1], insertion_before) + .InsertAfter(original_root->vec, + original_root->vec[1], insertion_after) + .Remove(original_root->vec, original_root->vec[1]) + .Clone(original_root); + + EXPECT_EQ(cloned_root->vec.size(), 4u); + + EXPECT_EQ(cloned_root->name, cloned.Symbols().Get("root")); + EXPECT_EQ(cloned_root->vec[0]->name, cloned.Symbols().Get("a")); + EXPECT_EQ(cloned_root->vec[1]->name, + cloned.Symbols().Get("insertion_before")); + EXPECT_EQ(cloned_root->vec[2]->name, cloned.Symbols().Get("insertion_after")); + EXPECT_EQ(cloned_root->vec[3]->name, cloned.Symbols().Get("c")); +} + +TEST_F(CloneContextNodeTest, CloneIntoSameBuilder) { + ProgramBuilder builder; + CloneContext ctx(&builder); + Allocator allocator; + auto* original = allocator.Create<Node>(builder.Symbols().New()); + auto* cloned_a = ctx.Clone(original); + auto* cloned_b = ctx.Clone(original); + EXPECT_NE(original, cloned_a); + EXPECT_NE(original, cloned_b); + + EXPECT_NE(cloned_a, cloned_b); +} + +TEST_F(CloneContextNodeTest, CloneWithReplaceAll_SameTypeTwice) { + std::string node_name = TypeInfo::Of<Node>().name; + + EXPECT_FATAL_FAILURE( + { + ProgramBuilder cloned; + Program original; + CloneContext ctx(&cloned, &original); + ctx.ReplaceAll([](const Node*) { return nullptr; }); + ctx.ReplaceAll([](const Node*) { return nullptr; }); + }, + "internal compiler error: ReplaceAll() called with a handler for type " + + node_name + " that is already handled by a handler for type " + + node_name); +} + +TEST_F(CloneContextNodeTest, CloneWithReplaceAll_BaseThenDerived) { + std::string node_name = TypeInfo::Of<Node>().name; + std::string replaceable_name = TypeInfo::Of<Replaceable>().name; + + EXPECT_FATAL_FAILURE( + { + ProgramBuilder cloned; + Program original; + CloneContext ctx(&cloned, &original); + ctx.ReplaceAll([](const Node*) { return nullptr; }); + ctx.ReplaceAll([](const Replaceable*) { return nullptr; }); + }, + "internal compiler error: ReplaceAll() called with a handler for type " + + replaceable_name + " that is already handled by a handler for type " + + node_name); +} + +TEST_F(CloneContextNodeTest, CloneWithReplaceAll_DerivedThenBase) { + std::string node_name = TypeInfo::Of<Node>().name; + std::string replaceable_name = TypeInfo::Of<Replaceable>().name; + + EXPECT_FATAL_FAILURE( + { + ProgramBuilder cloned; + Program original; + CloneContext ctx(&cloned, &original); + ctx.ReplaceAll([](const Replaceable*) { return nullptr; }); + ctx.ReplaceAll([](const Node*) { return nullptr; }); + }, + "internal compiler error: ReplaceAll() called with a handler for type " + + node_name + " that is already handled by a handler for type " + + replaceable_name); +} + +TEST_F(CloneContextNodeTest, CloneWithReplacePointer_WithNotANode) { + EXPECT_FATAL_FAILURE( + { + Allocator allocator; + ProgramBuilder builder; + auto* original_root = + allocator.Create<Node>(builder.Symbols().New("root")); + original_root->a = allocator.Create<Node>(builder.Symbols().New("a")); + original_root->b = allocator.Create<Node>(builder.Symbols().New("b")); + original_root->c = allocator.Create<Node>(builder.Symbols().New("c")); + Program original(std::move(builder)); + + // root + // ╭──────────────────┼──────────────────╮ + // (a) (b) (c) + // Replaced + + ProgramBuilder cloned; + auto* replacement = allocator.Create<NotANode>(); + + CloneContext ctx(&cloned, &original); + ctx.Replace(original_root->b, replacement); + + ctx.Clone(original_root); + }, + "internal compiler error"); +} + +TEST_F(CloneContextNodeTest, CloneWithReplaceFunction_WithNotANode) { + EXPECT_FATAL_FAILURE( + { + Allocator allocator; + ProgramBuilder builder; + auto* original_root = + allocator.Create<Node>(builder.Symbols().New("root")); + original_root->a = allocator.Create<Node>(builder.Symbols().New("a")); + original_root->b = allocator.Create<Node>(builder.Symbols().New("b")); + original_root->c = allocator.Create<Node>(builder.Symbols().New("c")); + Program original(std::move(builder)); + + // root + // ╭──────────────────┼──────────────────╮ + // (a) (b) (c) + // Replaced + + ProgramBuilder cloned; + auto* replacement = allocator.Create<NotANode>(); + + CloneContext ctx(&cloned, &original); + ctx.Replace(original_root->b, [=] { return replacement; }); + + ctx.Clone(original_root); + }, + "internal compiler error"); +} + +using CloneContextTest = ::testing::Test; + +TEST_F(CloneContextTest, CloneWithReplaceAll_SymbolsTwice) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder cloned; + Program original; + CloneContext ctx(&cloned, &original); + ctx.ReplaceAll([](const Symbol s) { return s; }); + ctx.ReplaceAll([](const Symbol s) { return s; }); + }, + "internal compiler error: ReplaceAll(const SymbolTransform&) called " + "multiple times on the same CloneContext"); +} + +TEST_F(CloneContextTest, CloneNewUnnamedSymbols) { + ProgramBuilder builder; + Symbol old_a = builder.Symbols().New(); + Symbol old_b = builder.Symbols().New(); + Symbol old_c = builder.Symbols().New(); + EXPECT_EQ(builder.Symbols().NameFor(old_a), "tint_symbol"); + EXPECT_EQ(builder.Symbols().NameFor(old_b), "tint_symbol_1"); + EXPECT_EQ(builder.Symbols().NameFor(old_c), "tint_symbol_2"); + + Program original(std::move(builder)); + + ProgramBuilder cloned; + CloneContext ctx(&cloned, &original, false); + Symbol new_x = cloned.Symbols().New(); + Symbol new_a = ctx.Clone(old_a); + Symbol new_y = cloned.Symbols().New(); + Symbol new_b = ctx.Clone(old_b); + Symbol new_z = cloned.Symbols().New(); + Symbol new_c = ctx.Clone(old_c); + + EXPECT_EQ(cloned.Symbols().NameFor(new_x), "tint_symbol"); + EXPECT_EQ(cloned.Symbols().NameFor(new_a), "tint_symbol_1"); + EXPECT_EQ(cloned.Symbols().NameFor(new_y), "tint_symbol_2"); + EXPECT_EQ(cloned.Symbols().NameFor(new_b), "tint_symbol_1_1"); + EXPECT_EQ(cloned.Symbols().NameFor(new_z), "tint_symbol_3"); + EXPECT_EQ(cloned.Symbols().NameFor(new_c), "tint_symbol_2_1"); +} + +TEST_F(CloneContextTest, CloneNewSymbols) { + ProgramBuilder builder; + Symbol old_a = builder.Symbols().New("a"); + Symbol old_b = builder.Symbols().New("b"); + Symbol old_c = builder.Symbols().New("c"); + EXPECT_EQ(builder.Symbols().NameFor(old_a), "a"); + EXPECT_EQ(builder.Symbols().NameFor(old_b), "b"); + EXPECT_EQ(builder.Symbols().NameFor(old_c), "c"); + + Program original(std::move(builder)); + + ProgramBuilder cloned; + CloneContext ctx(&cloned, &original, false); + Symbol new_x = cloned.Symbols().New("a"); + Symbol new_a = ctx.Clone(old_a); + Symbol new_y = cloned.Symbols().New("b"); + Symbol new_b = ctx.Clone(old_b); + Symbol new_z = cloned.Symbols().New("c"); + Symbol new_c = ctx.Clone(old_c); + + EXPECT_EQ(cloned.Symbols().NameFor(new_x), "a"); + EXPECT_EQ(cloned.Symbols().NameFor(new_a), "a_1"); + EXPECT_EQ(cloned.Symbols().NameFor(new_y), "b"); + EXPECT_EQ(cloned.Symbols().NameFor(new_b), "b_1"); + EXPECT_EQ(cloned.Symbols().NameFor(new_z), "c"); + EXPECT_EQ(cloned.Symbols().NameFor(new_c), "c_1"); +} + +TEST_F(CloneContextTest, CloneNewSymbols_AfterCloneSymbols) { + ProgramBuilder builder; + Symbol old_a = builder.Symbols().New("a"); + Symbol old_b = builder.Symbols().New("b"); + Symbol old_c = builder.Symbols().New("c"); + EXPECT_EQ(builder.Symbols().NameFor(old_a), "a"); + EXPECT_EQ(builder.Symbols().NameFor(old_b), "b"); + EXPECT_EQ(builder.Symbols().NameFor(old_c), "c"); + + Program original(std::move(builder)); + + ProgramBuilder cloned; + CloneContext ctx(&cloned, &original); + Symbol new_x = cloned.Symbols().New("a"); + Symbol new_a = ctx.Clone(old_a); + Symbol new_y = cloned.Symbols().New("b"); + Symbol new_b = ctx.Clone(old_b); + Symbol new_z = cloned.Symbols().New("c"); + Symbol new_c = ctx.Clone(old_c); + + EXPECT_EQ(cloned.Symbols().NameFor(new_x), "a_1"); + EXPECT_EQ(cloned.Symbols().NameFor(new_a), "a"); + EXPECT_EQ(cloned.Symbols().NameFor(new_y), "b_1"); + EXPECT_EQ(cloned.Symbols().NameFor(new_b), "b"); + EXPECT_EQ(cloned.Symbols().NameFor(new_z), "c_1"); + EXPECT_EQ(cloned.Symbols().NameFor(new_c), "c"); +} + +TEST_F(CloneContextTest, ProgramIDs) { + ProgramBuilder dst; + Program src(ProgramBuilder{}); + CloneContext ctx(&dst, &src); + Allocator allocator; + auto* cloned = ctx.Clone(allocator.Create<ProgramNode>(src.ID(), dst.ID())); + EXPECT_EQ(cloned->program_id, dst.ID()); +} + +TEST_F(CloneContextTest, ProgramIDs_Clone_ObjectNotOwnedBySrc) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder dst; + Program src(ProgramBuilder{}); + CloneContext ctx(&dst, &src); + Allocator allocator; + ctx.Clone(allocator.Create<ProgramNode>(ProgramID::New(), dst.ID())); + }, + R"(internal compiler error: TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, src, object))"); +} + +TEST_F(CloneContextTest, ProgramIDs_Clone_ObjectNotOwnedByDst) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder dst; + Program src(ProgramBuilder{}); + CloneContext ctx(&dst, &src); + Allocator allocator; + ctx.Clone(allocator.Create<ProgramNode>(src.ID(), ProgramID::New())); + }, + R"(internal compiler error: TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Clone, dst, out))"); +} + +} // namespace + +TINT_INSTANTIATE_TYPEINFO(Node); +TINT_INSTANTIATE_TYPEINFO(Replaceable); +TINT_INSTANTIATE_TYPEINFO(Replacement); +TINT_INSTANTIATE_TYPEINFO(NotANode); +TINT_INSTANTIATE_TYPEINFO(ProgramNode); + +} // namespace tint
diff --git a/src/tint/cmd/BUILD.gn b/src/tint/cmd/BUILD.gn new file mode 100644 index 0000000..c2f4322 --- /dev/null +++ b/src/tint/cmd/BUILD.gn
@@ -0,0 +1,44 @@ +# Copyright 2021 The Tint Authors +# +# 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("//build_overrides/build.gni") +import("../../../tint_overrides_with_defaults.gni") + +executable("tint") { + sources = [ "main.cc" ] + deps = [ + "${tint_root_dir}/src/tint:libtint", + "${tint_root_dir}/src/tint:tint_val", + "${tint_spirv_tools_dir}/:spvtools", + "${tint_spirv_tools_dir}/:spvtools_opt", + "${tint_spirv_tools_dir}/:spvtools_val", + ] + + if (tint_build_glsl_writer) { + deps += [ + "${tint_root_dir}/third_party/vulkan-deps/glslang/src:glslang_default_resource_limits_sources", + "${tint_root_dir}/third_party/vulkan-deps/glslang/src:glslang_lib_sources", + ] + } + + configs += [ + "${tint_root_dir}/src/tint:tint_common_config", + "${tint_root_dir}/src/tint:tint_config", + ] + + if (build_with_chromium) { + configs -= [ "//build/config/compiler:chromium_code" ] + configs += [ "//build/config/compiler:no_chromium_code" ] + } +}
diff --git a/src/tint/cmd/CMakeLists.txt b/src/tint/cmd/CMakeLists.txt new file mode 100644 index 0000000..5c6c6c9 --- /dev/null +++ b/src/tint/cmd/CMakeLists.txt
@@ -0,0 +1,39 @@ +# Copyright 2020 The Tint Authors. +# +# 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. + +set(TINT_SRCS + main.cc +) + +## Tint executable +add_executable(tint ${TINT_SRCS}) +tint_default_compile_options(tint) +target_link_libraries(tint libtint tint_val) + +if(${TINT_BUILD_SPV_READER} OR ${TINT_BUILD_SPV_WRITER}) + target_link_libraries(tint SPIRV-Tools) +endif() + +if(${TINT_BUILD_GLSL_WRITER}) + target_link_libraries(tint glslang) + target_link_libraries(tint glslang-default-resource-limits) + if(NOT MSVC) + target_compile_options(tint PRIVATE + -Wno-reserved-id-macro + -Wno-shadow-field-in-constructor + -Wno-shadow + -Wno-weak-vtables + ) + endif() +endif()
diff --git a/src/tint/cmd/main.cc b/src/tint/cmd/main.cc new file mode 100644 index 0000000..8a0d293 --- /dev/null +++ b/src/tint/cmd/main.cc
@@ -0,0 +1,1220 @@ +// Copyright 2020 The Tint Authors. +// +// 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 <fstream> +#include <iostream> +#include <memory> +#include <sstream> +#include <string> +#include <vector> + +#if TINT_BUILD_GLSL_WRITER +#include "StandAlone/ResourceLimits.h" +#include "glslang/Public/ShaderLang.h" +#endif + +#if TINT_BUILD_SPV_READER +#include "spirv-tools/libspirv.hpp" +#endif // TINT_BUILD_SPV_READER + +#include "src/tint/utils/io/command.h" +#include "src/tint/utils/string.h" +#include "src/tint/val/val.h" +#include "tint/tint.h" + +namespace { + +[[noreturn]] void TintInternalCompilerErrorReporter( + const tint::diag::List& diagnostics) { + auto printer = tint::diag::Printer::create(stderr, true); + tint::diag::Formatter{}.format(diagnostics, printer.get()); + tint::diag::Style bold_red{tint::diag::Color::kRed, true}; + constexpr const char* please_file_bug = R"( +******************************************************************** +* The tint shader compiler has encountered an unexpected error. * +* * +* Please help us fix this issue by submitting a bug report at * +* crbug.com/tint with the source program that triggered the bug. * +******************************************************************** +)"; + printer->write(please_file_bug, bold_red); + exit(1); +} + +enum class Format { + kNone = -1, + kSpirv, + kSpvAsm, + kWgsl, + kMsl, + kHlsl, + kGlsl, +}; + +struct Options { + bool show_help = false; + + std::string input_filename; + std::string output_file = "-"; // Default to stdout + + bool parse_only = false; + bool disable_workgroup_init = false; + bool validate = false; + bool demangle = false; + bool dump_inspector_bindings = false; + + Format format = Format::kNone; + + bool emit_single_entry_point = false; + std::string ep_name; + + std::vector<std::string> transforms; + + bool use_fxc = false; + std::string dxc_path; + std::string xcrun_path; +}; + +const char kUsage[] = R"(Usage: tint [options] <input-file> + + options: + --format <spirv|spvasm|wgsl|msl|hlsl> -- Output format. + If not provided, will be inferred from output + filename extension: + .spvasm -> spvasm + .spv -> spirv + .wgsl -> wgsl + .metal -> msl + .hlsl -> hlsl + If none matches, then default to SPIR-V assembly. + -ep <name> -- Output single entry point + --output-file <name> -- Output file name. Use "-" for standard output + -o <name> -- Output file name. Use "-" for standard output + --transform <name list> -- Runs transforms, name list is comma separated + Available transforms: +${transforms} + --parse-only -- Stop after parsing the input + --disable-workgroup-init -- Disable workgroup memory zero initialization. + --demangle -- Preserve original source names. Demangle them. + Affects AST dumping, and text-based output languages. + --dump-inspector-bindings -- Dump reflection data about bindins to stdout. + -h -- This help text + --validate -- Validates the generated shader + --fxc -- Ask to validate HLSL output using FXC instead of DXC. + When specified, automatically enables --validate + --dxc -- Path to DXC executable, used to validate HLSL output. + When specified, automatically enables --validate + --xcrun -- Path to xcrun executable, used to validate MSL output. + When specified, automatically enables --validate)"; + +Format parse_format(const std::string& fmt) { + (void)fmt; + +#if TINT_BUILD_SPV_WRITER + if (fmt == "spirv") + return Format::kSpirv; + if (fmt == "spvasm") + return Format::kSpvAsm; +#endif // TINT_BUILD_SPV_WRITER + +#if TINT_BUILD_WGSL_WRITER + if (fmt == "wgsl") + return Format::kWgsl; +#endif // TINT_BUILD_WGSL_WRITER + +#if TINT_BUILD_MSL_WRITER + if (fmt == "msl") + return Format::kMsl; +#endif // TINT_BUILD_MSL_WRITER + +#if TINT_BUILD_HLSL_WRITER + if (fmt == "hlsl") + return Format::kHlsl; +#endif // TINT_BUILD_HLSL_WRITER + +#if TINT_BUILD_GLSL_WRITER + if (fmt == "glsl") + return Format::kGlsl; +#endif // TINT_BUILD_GLSL_WRITER + + return Format::kNone; +} + +#if TINT_BUILD_SPV_WRITER || TINT_BUILD_WGSL_WRITER || \ + TINT_BUILD_MSL_WRITER || TINT_BUILD_HLSL_WRITER +/// @param input input string +/// @param suffix potential suffix string +/// @returns true if input ends with the given suffix. +bool ends_with(const std::string& input, const std::string& suffix) { + const auto input_len = input.size(); + const auto suffix_len = suffix.size(); + // Avoid integer overflow. + return (input_len >= suffix_len) && + (input_len - suffix_len == input.rfind(suffix)); +} +#endif + +/// @param filename the filename to inspect +/// @returns the inferred format for the filename suffix +Format infer_format(const std::string& filename) { + (void)filename; + +#if TINT_BUILD_SPV_WRITER + if (ends_with(filename, ".spv")) { + return Format::kSpirv; + } + if (ends_with(filename, ".spvasm")) { + return Format::kSpvAsm; + } +#endif // TINT_BUILD_SPV_WRITER + +#if TINT_BUILD_WGSL_WRITER + if (ends_with(filename, ".wgsl")) { + return Format::kWgsl; + } +#endif // TINT_BUILD_WGSL_WRITER + +#if TINT_BUILD_MSL_WRITER + if (ends_with(filename, ".metal")) { + return Format::kMsl; + } +#endif // TINT_BUILD_MSL_WRITER + +#if TINT_BUILD_HLSL_WRITER + if (ends_with(filename, ".hlsl")) { + return Format::kHlsl; + } +#endif // TINT_BUILD_HLSL_WRITER + + return Format::kNone; +} + +std::vector<std::string> split_transform_names(std::string list) { + std::vector<std::string> res; + + std::stringstream str(list); + while (str.good()) { + std::string substr; + getline(str, substr, ','); + res.push_back(substr); + } + return res; +} + +std::string TextureDimensionToString( + tint::inspector::ResourceBinding::TextureDimension dim) { + switch (dim) { + case tint::inspector::ResourceBinding::TextureDimension::kNone: + return "None"; + case tint::inspector::ResourceBinding::TextureDimension::k1d: + return "1d"; + case tint::inspector::ResourceBinding::TextureDimension::k2d: + return "2d"; + case tint::inspector::ResourceBinding::TextureDimension::k2dArray: + return "2dArray"; + case tint::inspector::ResourceBinding::TextureDimension::k3d: + return "3d"; + case tint::inspector::ResourceBinding::TextureDimension::kCube: + return "Cube"; + case tint::inspector::ResourceBinding::TextureDimension::kCubeArray: + return "CubeArray"; + } + + return "Unknown"; +} + +std::string SampledKindToString( + tint::inspector::ResourceBinding::SampledKind kind) { + switch (kind) { + case tint::inspector::ResourceBinding::SampledKind::kFloat: + return "Float"; + case tint::inspector::ResourceBinding::SampledKind::kUInt: + return "UInt"; + case tint::inspector::ResourceBinding::SampledKind::kSInt: + return "SInt"; + case tint::inspector::ResourceBinding::SampledKind::kUnknown: + break; + } + + return "Unknown"; +} + +std::string TexelFormatToString( + tint::inspector::ResourceBinding::TexelFormat format) { + switch (format) { + case tint::inspector::ResourceBinding::TexelFormat::kR32Uint: + return "R32Uint"; + case tint::inspector::ResourceBinding::TexelFormat::kR32Sint: + return "R32Sint"; + case tint::inspector::ResourceBinding::TexelFormat::kR32Float: + return "R32Float"; + case tint::inspector::ResourceBinding::TexelFormat::kRgba8Unorm: + return "Rgba8Unorm"; + case tint::inspector::ResourceBinding::TexelFormat::kRgba8Snorm: + return "Rgba8Snorm"; + case tint::inspector::ResourceBinding::TexelFormat::kRgba8Uint: + return "Rgba8Uint"; + case tint::inspector::ResourceBinding::TexelFormat::kRgba8Sint: + return "Rgba8Sint"; + case tint::inspector::ResourceBinding::TexelFormat::kRg32Uint: + return "Rg32Uint"; + case tint::inspector::ResourceBinding::TexelFormat::kRg32Sint: + return "Rg32Sint"; + case tint::inspector::ResourceBinding::TexelFormat::kRg32Float: + return "Rg32Float"; + case tint::inspector::ResourceBinding::TexelFormat::kRgba16Uint: + return "Rgba16Uint"; + case tint::inspector::ResourceBinding::TexelFormat::kRgba16Sint: + return "Rgba16Sint"; + case tint::inspector::ResourceBinding::TexelFormat::kRgba16Float: + return "Rgba16Float"; + case tint::inspector::ResourceBinding::TexelFormat::kRgba32Uint: + return "Rgba32Uint"; + case tint::inspector::ResourceBinding::TexelFormat::kRgba32Sint: + return "Rgba32Sint"; + case tint::inspector::ResourceBinding::TexelFormat::kRgba32Float: + return "Rgba32Float"; + case tint::inspector::ResourceBinding::TexelFormat::kNone: + return "None"; + } + return "Unknown"; +} + +std::string ResourceTypeToString( + tint::inspector::ResourceBinding::ResourceType type) { + switch (type) { + case tint::inspector::ResourceBinding::ResourceType::kUniformBuffer: + return "UniformBuffer"; + case tint::inspector::ResourceBinding::ResourceType::kStorageBuffer: + return "StorageBuffer"; + case tint::inspector::ResourceBinding::ResourceType::kReadOnlyStorageBuffer: + return "ReadOnlyStorageBuffer"; + case tint::inspector::ResourceBinding::ResourceType::kSampler: + return "Sampler"; + case tint::inspector::ResourceBinding::ResourceType::kComparisonSampler: + return "ComparisonSampler"; + case tint::inspector::ResourceBinding::ResourceType::kSampledTexture: + return "SampledTexture"; + case tint::inspector::ResourceBinding::ResourceType::kMultisampledTexture: + return "MultisampledTexture"; + case tint::inspector::ResourceBinding::ResourceType:: + kWriteOnlyStorageTexture: + return "WriteOnlyStorageTexture"; + case tint::inspector::ResourceBinding::ResourceType::kDepthTexture: + return "DepthTexture"; + case tint::inspector::ResourceBinding::ResourceType:: + kDepthMultisampledTexture: + return "DepthMultisampledTexture"; + case tint::inspector::ResourceBinding::ResourceType::kExternalTexture: + return "ExternalTexture"; + } + + return "Unknown"; +} + +bool ParseArgs(const std::vector<std::string>& args, Options* opts) { + for (size_t i = 1; i < args.size(); ++i) { + const std::string& arg = args[i]; + if (arg == "--format") { + ++i; + if (i >= args.size()) { + std::cerr << "Missing value for --format argument." << std::endl; + return false; + } + opts->format = parse_format(args[i]); + + if (opts->format == Format::kNone) { + std::cerr << "Unknown output format: " << args[i] << std::endl; + return false; + } + } else if (arg == "-ep") { + if (i + 1 >= args.size()) { + std::cerr << "Missing value for -ep" << std::endl; + return false; + } + i++; + opts->ep_name = args[i]; + opts->emit_single_entry_point = true; + + } else if (arg == "-o" || arg == "--output-name") { + ++i; + if (i >= args.size()) { + std::cerr << "Missing value for " << arg << std::endl; + return false; + } + opts->output_file = args[i]; + + } else if (arg == "-h" || arg == "--help") { + opts->show_help = true; + } else if (arg == "--transform") { + ++i; + if (i >= args.size()) { + std::cerr << "Missing value for " << arg << std::endl; + return false; + } + opts->transforms = split_transform_names(args[i]); + } else if (arg == "--parse-only") { + opts->parse_only = true; + } else if (arg == "--disable-workgroup-init") { + opts->disable_workgroup_init = true; + } else if (arg == "--demangle") { + opts->demangle = true; + } else if (arg == "--dump-inspector-bindings") { + opts->dump_inspector_bindings = true; + } else if (arg == "--validate") { + opts->validate = true; + } else if (arg == "--fxc") { + opts->validate = true; + opts->use_fxc = true; + } else if (arg == "--dxc") { + ++i; + if (i >= args.size()) { + std::cerr << "Missing value for " << arg << std::endl; + return false; + } + opts->dxc_path = args[i]; + opts->validate = true; + } else if (arg == "--xcrun") { + ++i; + if (i >= args.size()) { + std::cerr << "Missing value for " << arg << std::endl; + return false; + } + opts->xcrun_path = args[i]; + opts->validate = true; + } else if (!arg.empty()) { + if (arg[0] == '-') { + std::cerr << "Unrecognized option: " << arg << std::endl; + return false; + } + if (!opts->input_filename.empty()) { + std::cerr << "More than one input file specified: '" + << opts->input_filename << "' and '" << arg << "'" + << std::endl; + return false; + } + opts->input_filename = arg; + } + } + return true; +} + +/// Copies the content from the file named `input_file` to `buffer`, +/// assuming each element in the file is of type `T`. If any error occurs, +/// writes error messages to the standard error stream and returns false. +/// Assumes the size of a `T` object is divisible by its required alignment. +/// @returns true if we successfully read the file. +template <typename T> +bool ReadFile(const std::string& input_file, std::vector<T>* buffer) { + if (!buffer) { + std::cerr << "The buffer pointer was null" << std::endl; + return false; + } + + FILE* file = nullptr; +#if defined(_MSC_VER) + fopen_s(&file, input_file.c_str(), "rb"); +#else + file = fopen(input_file.c_str(), "rb"); +#endif + if (!file) { + std::cerr << "Failed to open " << input_file << std::endl; + return false; + } + + fseek(file, 0, SEEK_END); + const auto file_size = static_cast<size_t>(ftell(file)); + if (0 != (file_size % sizeof(T))) { + std::cerr << "File " << input_file + << " does not contain an integral number of objects: " + << file_size << " bytes in the file, require " << sizeof(T) + << " bytes per object" << std::endl; + fclose(file); + return false; + } + fseek(file, 0, SEEK_SET); + + buffer->clear(); + buffer->resize(file_size / sizeof(T)); + + size_t bytes_read = fread(buffer->data(), 1, file_size, file); + fclose(file); + if (bytes_read != file_size) { + std::cerr << "Failed to read " << input_file << std::endl; + return false; + } + + return true; +} + +/// Writes the given `buffer` into the file named as `output_file` using the +/// given `mode`. If `output_file` is empty or "-", writes to standard +/// output. If any error occurs, returns false and outputs error message to +/// standard error. The ContainerT type must have data() and size() methods, +/// like `std::string` and `std::vector` do. +/// @returns true on success +template <typename ContainerT> +bool WriteFile(const std::string& output_file, + const std::string mode, + const ContainerT& buffer) { + const bool use_stdout = output_file.empty() || output_file == "-"; + FILE* file = stdout; + + if (!use_stdout) { +#if defined(_MSC_VER) + fopen_s(&file, output_file.c_str(), mode.c_str()); +#else + file = fopen(output_file.c_str(), mode.c_str()); +#endif + if (!file) { + std::cerr << "Could not open file " << output_file << " for writing" + << std::endl; + return false; + } + } + + size_t written = + fwrite(buffer.data(), sizeof(typename ContainerT::value_type), + buffer.size(), file); + if (buffer.size() != written) { + if (use_stdout) { + std::cerr << "Could not write all output to standard output" << std::endl; + } else { + std::cerr << "Could not write to file " << output_file << std::endl; + fclose(file); + } + return false; + } + if (!use_stdout) { + fclose(file); + } + + return true; +} + +#if TINT_BUILD_SPV_WRITER +std::string Disassemble(const std::vector<uint32_t>& data) { + std::string spv_errors; + spv_target_env target_env = SPV_ENV_UNIVERSAL_1_0; + + auto msg_consumer = [&spv_errors](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: + spv_errors += "error: line " + std::to_string(position.index) + ": " + + message + "\n"; + break; + case SPV_MSG_WARNING: + spv_errors += "warning: line " + std::to_string(position.index) + ": " + + message + "\n"; + break; + case SPV_MSG_INFO: + spv_errors += "info: line " + std::to_string(position.index) + ": " + + message + "\n"; + break; + case SPV_MSG_DEBUG: + break; + } + }; + + spvtools::SpirvTools tools(target_env); + tools.SetMessageConsumer(msg_consumer); + + std::string result; + if (!tools.Disassemble(data, &result, + SPV_BINARY_TO_TEXT_OPTION_INDENT | + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES)) { + std::cerr << spv_errors << std::endl; + } + return result; +} +#endif // TINT_BUILD_SPV_WRITER + +/// PrintWGSL writes the WGSL of the program to the provided ostream, if the +/// WGSL writer is enabled, otherwise it does nothing. +/// @param out the output stream to write the WGSL to +/// @param program the program +void PrintWGSL(std::ostream& out, const tint::Program& program) { +#if TINT_BUILD_WGSL_WRITER + tint::writer::wgsl::Options options; + auto result = tint::writer::wgsl::Generate(&program, options); + out << std::endl << result.wgsl << std::endl; +#else + (void)out; + (void)program; +#endif +} + +/// Generate SPIR-V code for a program. +/// @param program the program to generate +/// @param options the options that Tint was invoked with +/// @returns true on success +bool GenerateSpirv(const tint::Program* program, const Options& options) { +#if TINT_BUILD_SPV_WRITER + // TODO(jrprice): Provide a way for the user to set non-default options. + tint::writer::spirv::Options gen_options; + gen_options.disable_workgroup_init = options.disable_workgroup_init; + gen_options.generate_external_texture_bindings = true; + auto result = tint::writer::spirv::Generate(program, gen_options); + if (!result.success) { + PrintWGSL(std::cerr, *program); + std::cerr << "Failed to generate: " << result.error << std::endl; + return false; + } + + if (options.format == Format::kSpvAsm) { + if (!WriteFile(options.output_file, "w", Disassemble(result.spirv))) { + return false; + } + } else { + if (!WriteFile(options.output_file, "wb", result.spirv)) { + return false; + } + } + + if (options.validate) { + // Use Vulkan 1.1, since this is what Tint, internally, uses. + spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); + tools.SetMessageConsumer([](spv_message_level_t, const char*, + const spv_position_t& pos, const char* msg) { + std::cerr << (pos.line + 1) << ":" << (pos.column + 1) << ": " << msg + << std::endl; + }); + if (!tools.Validate(result.spirv.data(), result.spirv.size(), + spvtools::ValidatorOptions())) { + return false; + } + } + + return true; +#else + (void)program; + (void)options; + std::cerr << "SPIR-V writer not enabled in tint build" << std::endl; + return false; +#endif // TINT_BUILD_SPV_WRITER +} + +/// Generate WGSL code for a program. +/// @param program the program to generate +/// @param options the options that Tint was invoked with +/// @returns true on success +bool GenerateWgsl(const tint::Program* program, const Options& options) { +#if TINT_BUILD_WGSL_WRITER + // TODO(jrprice): Provide a way for the user to set non-default options. + tint::writer::wgsl::Options gen_options; + auto result = tint::writer::wgsl::Generate(program, gen_options); + if (!result.success) { + std::cerr << "Failed to generate: " << result.error << std::endl; + return false; + } + + if (!WriteFile(options.output_file, "w", result.wgsl)) { + return false; + } + + if (options.validate) { + // Attempt to re-parse the output program with Tint's WGSL reader. + auto source = std::make_unique<tint::Source::File>(options.input_filename, + result.wgsl); + auto reparsed_program = tint::reader::wgsl::Parse(source.get()); + if (!reparsed_program.IsValid()) { + auto diag_printer = tint::diag::Printer::create(stderr, true); + tint::diag::Formatter diag_formatter; + diag_formatter.format(reparsed_program.Diagnostics(), diag_printer.get()); + return false; + } + } + + return true; +#else + (void)program; + (void)options; + std::cerr << "WGSL writer not enabled in tint build" << std::endl; + return false; +#endif // TINT_BUILD_WGSL_WRITER +} + +/// Generate MSL code for a program. +/// @param program the program to generate +/// @param options the options that Tint was invoked with +/// @returns true on success +bool GenerateMsl(const tint::Program* program, const Options& options) { +#if TINT_BUILD_MSL_WRITER + const tint::Program* input_program = program; + + // Remap resource numbers to a flat namespace. + // TODO(crbug.com/tint/1101): Make this more robust for multiple entry points. + using BindingPoint = tint::transform::BindingPoint; + tint::transform::BindingRemapper::BindingPoints binding_points; + uint32_t next_buffer_idx = 0; + uint32_t next_sampler_idx = 0; + uint32_t next_texture_idx = 0; + + tint::inspector::Inspector inspector(program); + auto entry_points = inspector.GetEntryPoints(); + for (auto& entry_point : entry_points) { + auto bindings = inspector.GetResourceBindings(entry_point.name); + for (auto& binding : bindings) { + BindingPoint src = {binding.bind_group, binding.binding}; + if (binding_points.count(src)) { + continue; + } + switch (binding.resource_type) { + case tint::inspector::ResourceBinding::ResourceType::kUniformBuffer: + case tint::inspector::ResourceBinding::ResourceType::kStorageBuffer: + case tint::inspector::ResourceBinding::ResourceType:: + kReadOnlyStorageBuffer: + binding_points.emplace(src, BindingPoint{0, next_buffer_idx++}); + break; + case tint::inspector::ResourceBinding::ResourceType::kSampler: + case tint::inspector::ResourceBinding::ResourceType::kComparisonSampler: + binding_points.emplace(src, BindingPoint{0, next_sampler_idx++}); + break; + case tint::inspector::ResourceBinding::ResourceType::kSampledTexture: + case tint::inspector::ResourceBinding::ResourceType:: + kMultisampledTexture: + case tint::inspector::ResourceBinding::ResourceType:: + kWriteOnlyStorageTexture: + case tint::inspector::ResourceBinding::ResourceType::kDepthTexture: + case tint::inspector::ResourceBinding::ResourceType:: + kDepthMultisampledTexture: + case tint::inspector::ResourceBinding::ResourceType::kExternalTexture: + binding_points.emplace(src, BindingPoint{0, next_texture_idx++}); + break; + } + } + } + + // Run the binding remapper transform. + tint::transform::Output transform_output; + if (!binding_points.empty()) { + tint::transform::Manager manager; + tint::transform::DataMap inputs; + inputs.Add<tint::transform::BindingRemapper::Remappings>( + std::move(binding_points), + tint::transform::BindingRemapper::AccessControls{}, + /* mayCollide */ true); + manager.Add<tint::transform::BindingRemapper>(); + transform_output = manager.Run(program, inputs); + input_program = &transform_output.program; + } + + // TODO(jrprice): Provide a way for the user to set non-default options. + tint::writer::msl::Options gen_options; + gen_options.disable_workgroup_init = options.disable_workgroup_init; + gen_options.generate_external_texture_bindings = true; + auto result = tint::writer::msl::Generate(input_program, gen_options); + if (!result.success) { + PrintWGSL(std::cerr, *program); + std::cerr << "Failed to generate: " << result.error << std::endl; + return false; + } + + if (!WriteFile(options.output_file, "w", result.msl)) { + return false; + } + + if (options.validate) { + tint::val::Result res; +#ifdef TINT_ENABLE_MSL_VALIDATION_USING_METAL_API + res = tint::val::MslUsingMetalAPI(result.msl); +#else +#ifdef _WIN32 + const char* default_xcrun_exe = "metal.exe"; +#else + const char* default_xcrun_exe = "xcrun"; +#endif + auto xcrun = tint::utils::Command::LookPath( + options.xcrun_path.empty() ? default_xcrun_exe : options.xcrun_path); + if (xcrun.Found()) { + res = tint::val::Msl(xcrun.Path(), result.msl); + } else { + res.output = "xcrun executable not found. Cannot validate."; + res.failed = true; + } +#endif // TINT_ENABLE_MSL_VALIDATION_USING_METAL_API + if (res.failed) { + std::cerr << res.output << std::endl; + return false; + } + } + + return true; +#else + (void)program; + (void)options; + std::cerr << "MSL writer not enabled in tint build" << std::endl; + return false; +#endif // TINT_BUILD_MSL_WRITER +} + +/// Generate HLSL code for a program. +/// @param program the program to generate +/// @param options the options that Tint was invoked with +/// @returns true on success +bool GenerateHlsl(const tint::Program* program, const Options& options) { +#if TINT_BUILD_HLSL_WRITER + // TODO(jrprice): Provide a way for the user to set non-default options. + tint::writer::hlsl::Options gen_options; + gen_options.disable_workgroup_init = options.disable_workgroup_init; + gen_options.generate_external_texture_bindings = true; + auto result = tint::writer::hlsl::Generate(program, gen_options); + if (!result.success) { + PrintWGSL(std::cerr, *program); + std::cerr << "Failed to generate: " << result.error << std::endl; + return false; + } + + if (!WriteFile(options.output_file, "w", result.hlsl)) { + return false; + } + + if (options.validate) { + tint::val::Result res; + if (options.use_fxc) { +#ifdef _WIN32 + res = tint::val::HlslUsingFXC(result.hlsl, result.entry_points); +#else + res.failed = true; + res.output = "FXC can only be used on Windows. Sorry :X"; +#endif // _WIN32 + } else { + auto dxc = tint::utils::Command::LookPath( + options.dxc_path.empty() ? "dxc" : options.dxc_path); + if (dxc.Found()) { + res = tint::val::HlslUsingDXC(dxc.Path(), result.hlsl, + result.entry_points); + } else { + res.failed = true; + res.output = "DXC executable not found. Cannot validate"; + } + } + if (res.failed) { + std::cerr << res.output << std::endl; + return false; + } + } + + return true; +#else + (void)program; + (void)options; + std::cerr << "HLSL writer not enabled in tint build" << std::endl; + return false; +#endif // TINT_BUILD_HLSL_WRITER +} + +#if TINT_BUILD_GLSL_WRITER +EShLanguage pipeline_stage_to_esh_language(tint::ast::PipelineStage stage) { + switch (stage) { + case tint::ast::PipelineStage::kFragment: + return EShLangFragment; + case tint::ast::PipelineStage::kVertex: + return EShLangVertex; + case tint::ast::PipelineStage::kCompute: + return EShLangCompute; + default: + TINT_ASSERT(AST, false); + return EShLangVertex; + } +} +#endif + +/// Generate GLSL code for a program. +/// @param program the program to generate +/// @param options the options that Tint was invoked with +/// @returns true on success +bool GenerateGlsl(const tint::Program* program, const Options& options) { +#if TINT_BUILD_GLSL_WRITER + if (options.validate) { + glslang::InitializeProcess(); + } + + auto generate = [&](const tint::Program* prg, + const std::string entry_point_name) -> bool { + tint::writer::glsl::Options gen_options; + gen_options.generate_external_texture_bindings = true; + auto result = + tint::writer::glsl::Generate(prg, gen_options, entry_point_name); + if (!result.success) { + PrintWGSL(std::cerr, *prg); + std::cerr << "Failed to generate: " << result.error << std::endl; + return false; + } + + if (!WriteFile(options.output_file, "w", result.glsl)) { + return false; + } + + if (options.validate) { + for (auto entry_pt : result.entry_points) { + EShLanguage lang = pipeline_stage_to_esh_language(entry_pt.second); + glslang::TShader shader(lang); + const char* strings[1] = {result.glsl.c_str()}; + int lengths[1] = {static_cast<int>(result.glsl.length())}; + shader.setStringsWithLengths(strings, lengths, 1); + shader.setEntryPoint("main"); + bool glslang_result = + shader.parse(&glslang::DefaultTBuiltInResource, 310, EEsProfile, + false, false, EShMsgDefault); + if (!glslang_result) { + std::cerr << "Error parsing GLSL shader:\n" + << shader.getInfoLog() << "\n" + << shader.getInfoDebugLog() << "\n"; + return false; + } + } + } + return true; + }; + + tint::inspector::Inspector inspector(program); + + if (inspector.GetEntryPoints().empty()) { + // Pass empty string here so that the GLSL generator will generate + // code for all functions, reachable or not. + return generate(program, ""); + } + + bool success = true; + for (auto& entry_point : inspector.GetEntryPoints()) { + success &= generate(program, entry_point.name); + } + return success; +#else + (void)program; + (void)options; + std::cerr << "GLSL writer not enabled in tint build" << std::endl; + return false; +#endif // TINT_BUILD_GLSL_WRITER +} + +} // namespace + +int main(int argc, const char** argv) { + std::vector<std::string> args(argv, argv + argc); + Options options; + + tint::SetInternalCompilerErrorReporter(&TintInternalCompilerErrorReporter); + +#if TINT_BUILD_WGSL_WRITER + tint::Program::printer = [](const tint::Program* program) { + auto result = tint::writer::wgsl::Generate(program, {}); + if (!result.error.empty()) { + return "error: " + result.error; + } + return result.wgsl; + }; +#endif // TINT_BUILD_WGSL_WRITER + + if (!ParseArgs(args, &options)) { + std::cerr << "Failed to parse arguments." << std::endl; + return 1; + } + + struct TransformFactory { + const char* name; + std::function<void(tint::transform::Manager& manager, + tint::transform::DataMap& inputs)> + make; + }; + std::vector<TransformFactory> transforms = { + {"first_index_offset", + [](tint::transform::Manager& m, tint::transform::DataMap& i) { + i.Add<tint::transform::FirstIndexOffset::BindingPoint>(0, 0); + m.Add<tint::transform::FirstIndexOffset>(); + }}, + {"fold_trivial_single_use_lets", + [](tint::transform::Manager& m, tint::transform::DataMap&) { + m.Add<tint::transform::FoldTrivialSingleUseLets>(); + }}, + {"renamer", + [](tint::transform::Manager& m, tint::transform::DataMap&) { + m.Add<tint::transform::Renamer>(); + }}, + {"robustness", + [](tint::transform::Manager& m, tint::transform::DataMap&) { + m.Add<tint::transform::Robustness>(); + }}, + }; + auto transform_names = [&] { + std::stringstream names; + for (auto& t : transforms) { + names << " " << t.name << std::endl; + } + return names.str(); + }; + + if (options.show_help) { + std::string usage = + tint::utils::ReplaceAll(kUsage, "${transforms}", transform_names()); + std::cout << usage << std::endl; + return 0; + } + + // Implement output format defaults. + if (options.format == Format::kNone) { + // Try inferring from filename. + options.format = infer_format(options.output_file); + } + if (options.format == Format::kNone) { + // Ultimately, default to SPIR-V assembly. That's nice for interactive use. + options.format = Format::kSpvAsm; + } + + auto diag_printer = tint::diag::Printer::create(stderr, true); + tint::diag::Formatter diag_formatter; + + std::unique_ptr<tint::Program> program; + std::unique_ptr<tint::Source::File> source_file; + + enum class InputFormat { + kUnknown, + kWgsl, + kSpirvBin, + kSpirvAsm, + }; + auto input_format = InputFormat::kUnknown; + + if (options.input_filename.size() > 5 && + options.input_filename.substr(options.input_filename.size() - 5) == + ".wgsl") { + input_format = InputFormat::kWgsl; + } else if (options.input_filename.size() > 4 && + options.input_filename.substr(options.input_filename.size() - 4) == + ".spv") { + input_format = InputFormat::kSpirvBin; + } else if (options.input_filename.size() > 7 && + options.input_filename.substr(options.input_filename.size() - 7) == + ".spvasm") { + input_format = InputFormat::kSpirvAsm; + } + + switch (input_format) { + case InputFormat::kUnknown: { + std::cerr << "Unknown input format" << std::endl; + return 1; + } + case InputFormat::kWgsl: { +#if TINT_BUILD_WGSL_READER + std::vector<uint8_t> data; + if (!ReadFile<uint8_t>(options.input_filename, &data)) { + return 1; + } + source_file = std::make_unique<tint::Source::File>( + options.input_filename, std::string(data.begin(), data.end())); + program = std::make_unique<tint::Program>( + tint::reader::wgsl::Parse(source_file.get())); + break; +#else + std::cerr << "Tint not built with the WGSL reader enabled" << std::endl; + return 1; +#endif // TINT_BUILD_WGSL_READER + } + case InputFormat::kSpirvBin: { +#if TINT_BUILD_SPV_READER + std::vector<uint32_t> data; + if (!ReadFile<uint32_t>(options.input_filename, &data)) { + return 1; + } + program = + std::make_unique<tint::Program>(tint::reader::spirv::Parse(data)); + break; +#else + std::cerr << "Tint not built with the SPIR-V reader enabled" << std::endl; + return 1; +#endif // TINT_BUILD_SPV_READER + } + case InputFormat::kSpirvAsm: { +#if TINT_BUILD_SPV_READER + std::vector<char> text; + if (!ReadFile<char>(options.input_filename, &text)) { + return 1; + } + // Use Vulkan 1.1, since this is what Tint, internally, is expecting. + spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); + tools.SetMessageConsumer([](spv_message_level_t, const char*, + const spv_position_t& pos, const char* msg) { + std::cerr << (pos.line + 1) << ":" << (pos.column + 1) << ": " << msg + << std::endl; + }); + std::vector<uint32_t> data; + if (!tools.Assemble(text.data(), text.size(), &data, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS)) { + return 1; + } + program = + std::make_unique<tint::Program>(tint::reader::spirv::Parse(data)); + break; +#else + std::cerr << "Tint not built with the SPIR-V reader enabled" << std::endl; + return 1; +#endif // TINT_BUILD_SPV_READER + } + } + + if (!program) { + std::cerr << "Failed to parse input file: " << options.input_filename + << std::endl; + return 1; + } + if (program->Diagnostics().count() > 0) { + if (!program->IsValid() && input_format != InputFormat::kWgsl) { + // Invalid program from a non-wgsl source. Print the WGSL, to help + // understand the diagnostics. + PrintWGSL(std::cout, *program); + } + diag_formatter.format(program->Diagnostics(), diag_printer.get()); + } + + if (!program->IsValid()) { + return 1; + } + if (options.parse_only) { + return 1; + } + + tint::transform::Manager transform_manager; + tint::transform::DataMap transform_inputs; + for (const auto& name : options.transforms) { + // TODO(dsinclair): The vertex pulling transform requires setup code to + // be run that needs user input. Should we find a way to support that here + // maybe through a provided file? + + bool found = false; + for (auto& t : transforms) { + if (t.name == name) { + t.make(transform_manager, transform_inputs); + found = true; + break; + } + } + if (!found) { + std::cerr << "Unknown transform: " << name << std::endl; + std::cerr << "Available transforms: " << std::endl << transform_names(); + return 1; + } + } + + if (options.emit_single_entry_point) { + transform_manager.append( + std::make_unique<tint::transform::SingleEntryPoint>()); + transform_inputs.Add<tint::transform::SingleEntryPoint::Config>( + options.ep_name); + } + + switch (options.format) { + case Format::kMsl: { +#if TINT_BUILD_MSL_WRITER + transform_inputs.Add<tint::transform::Renamer::Config>( + tint::transform::Renamer::Target::kMslKeywords, + /* preserve_unicode */ false); + transform_manager.Add<tint::transform::Renamer>(); +#endif // TINT_BUILD_MSL_WRITER + break; + } +#if TINT_BUILD_GLSL_WRITER + case Format::kGlsl: { + break; + } +#endif // TINT_BUILD_GLSL_WRITER + case Format::kHlsl: { +#if TINT_BUILD_HLSL_WRITER + transform_inputs.Add<tint::transform::Renamer::Config>( + tint::transform::Renamer::Target::kHlslKeywords, + /* preserve_unicode */ false); + transform_manager.Add<tint::transform::Renamer>(); +#endif // TINT_BUILD_HLSL_WRITER + break; + } + default: + break; + } + + auto out = transform_manager.Run(program.get(), std::move(transform_inputs)); + if (!out.program.IsValid()) { + PrintWGSL(std::cerr, out.program); + diag_formatter.format(out.program.Diagnostics(), diag_printer.get()); + return 1; + } + + *program = std::move(out.program); + + if (options.dump_inspector_bindings) { + std::cout << std::string(80, '-') << std::endl; + tint::inspector::Inspector inspector(program.get()); + auto entry_points = inspector.GetEntryPoints(); + if (!inspector.error().empty()) { + std::cerr << "Failed to get entry points from Inspector: " + << inspector.error() << std::endl; + return 1; + } + + for (auto& entry_point : entry_points) { + auto bindings = inspector.GetResourceBindings(entry_point.name); + if (!inspector.error().empty()) { + std::cerr << "Failed to get bindings from Inspector: " + << inspector.error() << std::endl; + return 1; + } + std::cout << "Entry Point = " << entry_point.name << std::endl; + for (auto& binding : bindings) { + std::cout << "\t[" << binding.bind_group << "][" << binding.binding + << "]:" << std::endl; + std::cout << "\t\t resource_type = " + << ResourceTypeToString(binding.resource_type) << std::endl; + std::cout << "\t\t dim = " << TextureDimensionToString(binding.dim) + << std::endl; + std::cout << "\t\t sampled_kind = " + << SampledKindToString(binding.sampled_kind) << std::endl; + std::cout << "\t\t image_format = " + << TexelFormatToString(binding.image_format) << std::endl; + } + } + std::cout << std::string(80, '-') << std::endl; + } + + bool success = false; + switch (options.format) { + case Format::kSpirv: + case Format::kSpvAsm: + success = GenerateSpirv(program.get(), options); + break; + case Format::kWgsl: + success = GenerateWgsl(program.get(), options); + break; + case Format::kMsl: + success = GenerateMsl(program.get(), options); + break; + case Format::kHlsl: + success = GenerateHlsl(program.get(), options); + break; + case Format::kGlsl: + success = GenerateGlsl(program.get(), options); + break; + default: + std::cerr << "Unknown output format specified" << std::endl; + return 1; + } + if (!success) { + return 1; + } + + return 0; +}
diff --git a/src/tint/debug.cc b/src/tint/debug.cc new file mode 100644 index 0000000..c51cf3f --- /dev/null +++ b/src/tint/debug.cc
@@ -0,0 +1,50 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/debug.h" + +#include <memory> + +#include "src/tint/utils/debugger.h" + +namespace tint { +namespace { + +InternalCompilerErrorReporter* ice_reporter = nullptr; + +} // namespace + +void SetInternalCompilerErrorReporter(InternalCompilerErrorReporter* reporter) { + ice_reporter = reporter; +} + +InternalCompilerError::InternalCompilerError(const char* file, + size_t line, + diag::System system, + diag::List& diagnostics) + : file_(file), line_(line), system_(system), diagnostics_(diagnostics) {} + +InternalCompilerError::~InternalCompilerError() { + auto file = std::make_shared<Source::File>(file_, ""); + Source source{Source::Range{{line_}}, file.get()}; + diagnostics_.add_ice(system_, msg_.str(), source, std::move(file)); + + if (ice_reporter) { + ice_reporter(diagnostics_); + } + + debugger::Break(); +} + +} // namespace tint
diff --git a/src/tint/debug.h b/src/tint/debug.h new file mode 100644 index 0000000..90e6b66 --- /dev/null +++ b/src/tint/debug.h
@@ -0,0 +1,123 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_DEBUG_H_ +#define SRC_TINT_DEBUG_H_ + +#include <utility> + +#include "src/tint/diagnostic/diagnostic.h" +#include "src/tint/diagnostic/formatter.h" +#include "src/tint/diagnostic/printer.h" + +namespace tint { + +/// Function type used for registering an internal compiler error reporter +using InternalCompilerErrorReporter = void(const diag::List&); + +/// Sets the global error reporter to be called in case of internal compiler +/// errors. +/// @param reporter the error reporter +void SetInternalCompilerErrorReporter(InternalCompilerErrorReporter* reporter); + +/// InternalCompilerError is a helper for reporting internal compiler errors. +/// Construct the InternalCompilerError with the source location of the ICE +/// fault and append any error details with the `<<` operator. +/// When the InternalCompilerError is destructed, the concatenated error message +/// is appended to the diagnostics list with the severity of +/// tint::diag::Severity::InternalCompilerError, and if a +/// InternalCompilerErrorReporter is set, then it is called with the diagnostic +/// list. +class InternalCompilerError { + public: + /// Constructor + /// @param file the file containing the ICE + /// @param line the line containing the ICE + /// @param system the Tint system that has raised the ICE + /// @param diagnostics the list of diagnostics to append the ICE message to + InternalCompilerError(const char* file, + size_t line, + diag::System system, + diag::List& diagnostics); + + /// Destructor. + /// Adds the internal compiler error message to the diagnostics list, and then + /// calls the InternalCompilerErrorReporter if one is set. + ~InternalCompilerError(); + + /// Appends `arg` to the ICE message. + /// @param arg the argument to append to the ICE message + /// @returns this object so calls can be chained + template <typename T> + InternalCompilerError& operator<<(T&& arg) { + msg_ << std::forward<T>(arg); + return *this; + } + + private: + char const* const file_; + const size_t line_; + diag::System system_; + diag::List& diagnostics_; + std::stringstream msg_; +}; + +} // namespace tint + +/// TINT_ICE() is a macro for appending an internal compiler error message +/// to the diagnostics list `diagnostics`, and calling the +/// InternalCompilerErrorReporter with the full diagnostic list if a reporter is +/// set. +/// The ICE message contains the callsite's file and line. +/// Use the `<<` operator to append an error message to the ICE. +#define TINT_ICE(system, diagnostics) \ + tint::InternalCompilerError(__FILE__, __LINE__, \ + ::tint::diag::System::system, diagnostics) + +/// TINT_UNREACHABLE() is a macro for appending a "TINT_UNREACHABLE" +/// internal compiler error message to the diagnostics list `diagnostics`, and +/// calling the InternalCompilerErrorReporter with the full diagnostic list if a +/// reporter is set. +/// The ICE message contains the callsite's file and line. +/// Use the `<<` operator to append an error message to the ICE. +#define TINT_UNREACHABLE(system, diagnostics) \ + TINT_ICE(system, diagnostics) << "TINT_UNREACHABLE " + +/// TINT_UNIMPLEMENTED() is a macro for appending a "TINT_UNIMPLEMENTED" +/// internal compiler error message to the diagnostics list `diagnostics`, and +/// calling the InternalCompilerErrorReporter with the full diagnostic list if a +/// reporter is set. +/// The ICE message contains the callsite's file and line. +/// Use the `<<` operator to append an error message to the ICE. +#define TINT_UNIMPLEMENTED(system, diagnostics) \ + TINT_ICE(system, diagnostics) << "TINT_UNIMPLEMENTED " + +/// TINT_ASSERT() is a macro for checking the expression is true, triggering a +/// TINT_ICE if it is not. +/// The ICE message contains the callsite's file and line. +/// @warning: Unlike TINT_ICE() and TINT_UNREACHABLE(), TINT_ASSERT() does not +/// append a message to an existing tint::diag::List. As such, TINT_ASSERT() +/// may silently fail in builds where SetInternalCompilerErrorReporter() is not +/// called. Only use in places where there's no sensible place to put proper +/// error handling. +#define TINT_ASSERT(system, condition) \ + do { \ + if (!(condition)) { \ + tint::diag::List diagnostics; \ + TINT_ICE(system, diagnostics) \ + << "TINT_ASSERT(" #system ", " #condition ")"; \ + } \ + } while (false) + +#endif // SRC_TINT_DEBUG_H_
diff --git a/src/tint/debug_test.cc b/src/tint/debug_test.cc new file mode 100644 index 0000000..257b312 --- /dev/null +++ b/src/tint/debug_test.cc
@@ -0,0 +1,41 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/debug.h" + +#include "gtest/gtest-spi.h" + +namespace tint { +namespace { + +TEST(DebugTest, Unreachable) { + EXPECT_FATAL_FAILURE( + { + diag::List diagnostics; + TINT_UNREACHABLE(Test, diagnostics); + }, + "internal compiler error"); +} + +TEST(DebugTest, AssertTrue) { + TINT_ASSERT(Test, true); +} + +TEST(DebugTest, AssertFalse) { + EXPECT_FATAL_FAILURE({ TINT_ASSERT(Test, false); }, + "internal compiler error"); +} + +} // namespace +} // namespace tint
diff --git a/src/tint/demangler.cc b/src/tint/demangler.cc new file mode 100644 index 0000000..cf5e4d6 --- /dev/null +++ b/src/tint/demangler.cc
@@ -0,0 +1,62 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/demangler.h" + +#include "src/tint/program.h" + +namespace tint { +namespace { + +constexpr char kSymbol[] = "$"; +constexpr size_t kSymbolLen = sizeof(kSymbol) - 1; + +} // namespace + +Demangler::Demangler() = default; + +Demangler::~Demangler() = default; + +std::string Demangler::Demangle(const SymbolTable& symbols, + const std::string& str) const { + std::stringstream out; + + size_t pos = 0; + for (;;) { + auto idx = str.find(kSymbol, pos); + if (idx == std::string::npos) { + out << str.substr(pos); + break; + } + + out << str.substr(pos, idx - pos); + + auto start_idx = idx + kSymbolLen; + auto end_idx = start_idx; + while (str[end_idx] >= '0' && str[end_idx] <= '9') { + end_idx++; + } + auto len = end_idx - start_idx; + + auto id = str.substr(start_idx, len); + Symbol sym(std::stoi(id), symbols.ProgramID()); + out << symbols.NameFor(sym); + + pos = end_idx; + } + + return out.str(); +} + +} // namespace tint
diff --git a/src/tint/demangler.h b/src/tint/demangler.h new file mode 100644 index 0000000..8c0c964 --- /dev/null +++ b/src/tint/demangler.h
@@ -0,0 +1,42 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_DEMANGLER_H_ +#define SRC_TINT_DEMANGLER_H_ + +#include <string> + +namespace tint { + +class SymbolTable; + +/// Helper to demangle strings and replace symbols with original names +class Demangler { + public: + /// Constructor + Demangler(); + /// Destructor + ~Demangler(); + + /// Transforms given string and replaces any symbols with original names + /// @param symbols the symbol table + /// @param str the string to replace + /// @returns the string with any symbol replacements performed. + std::string Demangle(const SymbolTable& symbols, + const std::string& str) const; +}; + +} // namespace tint + +#endif // SRC_TINT_DEMANGLER_H_
diff --git a/src/tint/demangler_test.cc b/src/tint/demangler_test.cc new file mode 100644 index 0000000..f2c7658 --- /dev/null +++ b/src/tint/demangler_test.cc
@@ -0,0 +1,51 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/demangler.h" +#include "src/tint/symbol_table.h" + +#include "gtest/gtest.h" + +namespace tint { +namespace { + +using DemanglerTest = testing::Test; + +TEST_F(DemanglerTest, NoSymbols) { + SymbolTable t{ProgramID::New()}; + t.Register("sym1"); + + Demangler d; + EXPECT_EQ("test str", d.Demangle(t, "test str")); +} + +TEST_F(DemanglerTest, Symbol) { + SymbolTable t{ProgramID::New()}; + t.Register("sym1"); + + Demangler d; + EXPECT_EQ("test sym1 str", d.Demangle(t, "test $1 str")); +} + +TEST_F(DemanglerTest, MultipleSymbols) { + SymbolTable t{ProgramID::New()}; + t.Register("sym1"); + t.Register("sym2"); + + Demangler d; + EXPECT_EQ("test sym1 sym2 sym1 str", d.Demangle(t, "test $1 $2 $1 str")); +} + +} // namespace +} // namespace tint
diff --git a/src/tint/diagnostic/diagnostic.cc b/src/tint/diagnostic/diagnostic.cc new file mode 100644 index 0000000..f7b36a4 --- /dev/null +++ b/src/tint/diagnostic/diagnostic.cc
@@ -0,0 +1,48 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/diagnostic/diagnostic.h" + +#include <unordered_map> + +#include "src/tint/diagnostic/formatter.h" + +namespace tint { +namespace diag { + +Diagnostic::Diagnostic() = default; +Diagnostic::Diagnostic(const Diagnostic&) = default; +Diagnostic::~Diagnostic() = default; +Diagnostic& Diagnostic::operator=(const Diagnostic&) = default; + +List::List() = default; +List::List(std::initializer_list<Diagnostic> list) : entries_(list) {} +List::List(const List& rhs) = default; + +List::List(List&& rhs) = default; + +List::~List() = default; + +List& List::operator=(const List& rhs) = default; + +List& List::operator=(List&& rhs) = default; + +std::string List::str() const { + diag::Formatter::Style style; + style.print_newline_at_end = false; + return Formatter{style}.format(*this); +} + +} // namespace diag +} // namespace tint
diff --git a/src/tint/diagnostic/diagnostic.h b/src/tint/diagnostic/diagnostic.h new file mode 100644 index 0000000..28b0c66 --- /dev/null +++ b/src/tint/diagnostic/diagnostic.h
@@ -0,0 +1,252 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_DIAGNOSTIC_DIAGNOSTIC_H_ +#define SRC_TINT_DIAGNOSTIC_DIAGNOSTIC_H_ + +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "src/tint/source.h" + +namespace tint { +namespace diag { + +/// Severity is an enumerator of diagnostic severities. +enum class Severity { Note, Warning, Error, InternalCompilerError, Fatal }; + +/// @return true iff `a` is more than, or of equal severity to `b` +inline bool operator>=(Severity a, Severity b) { + return static_cast<int>(a) >= static_cast<int>(b); +} + +/// System is an enumerator of Tint systems that can be the originator of a +/// diagnostic message. +enum class System { + AST, + Clone, + Inspector, + Program, + ProgramBuilder, + Reader, + Resolver, + Semantic, + Symbol, + Test, + Transform, + Utils, + Writer, +}; + +/// Diagnostic holds all the information for a single compiler diagnostic +/// message. +class Diagnostic { + public: + /// Constructor + Diagnostic(); + /// Copy constructor + Diagnostic(const Diagnostic&); + /// Destructor + ~Diagnostic(); + + /// Copy assignment operator + /// @return this diagnostic + Diagnostic& operator=(const Diagnostic&); + + /// severity is the severity of the diagnostic message. + Severity severity = Severity::Error; + /// source is the location of the diagnostic. + Source source; + /// message is the text associated with the diagnostic. + std::string message; + /// system is the Tint system that raised the diagnostic. + System system; + /// code is the error code, for example a validation error might have the code + /// `"v-0001"`. + const char* code = nullptr; + /// A shared pointer to a Source::File. Only used if the diagnostic Source + /// points to a file that was created specifically for this diagnostic + /// (usually an ICE). + std::shared_ptr<Source::File> owned_file = nullptr; +}; + +/// List is a container of Diagnostic messages. +class List { + public: + /// iterator is the type used for range based iteration. + using iterator = std::vector<Diagnostic>::const_iterator; + + /// Constructs the list with no elements. + List(); + + /// Copy constructor. Copies the diagnostics from `list` into this list. + /// @param list the list of diagnostics to copy into this list. + List(std::initializer_list<Diagnostic> list); + + /// Copy constructor. Copies the diagnostics from `list` into this list. + /// @param list the list of diagnostics to copy into this list. + List(const List& list); + + /// Move constructor. Moves the diagnostics from `list` into this list. + /// @param list the list of diagnostics to move into this list. + List(List&& list); + + /// Destructor + ~List(); + + /// Assignment operator. Copies the diagnostics from `list` into this list. + /// @param list the list to copy into this list. + /// @return this list. + List& operator=(const List& list); + + /// Assignment move operator. Moves the diagnostics from `list` into this + /// list. + /// @param list the list to move into this list. + /// @return this list. + List& operator=(List&& list); + + /// adds a diagnostic to the end of this list. + /// @param diag the diagnostic to append to this list. + void add(Diagnostic&& diag) { + if (diag.severity >= Severity::Error) { + error_count_++; + } + entries_.emplace_back(std::move(diag)); + } + + /// adds a list of diagnostics to the end of this list. + /// @param list the diagnostic to append to this list. + void add(const List& list) { + for (auto diag : list) { + add(std::move(diag)); + } + } + + /// adds the note message with the given Source to the end of this list. + /// @param system the system raising the note message + /// @param note_msg the note message + /// @param source the source of the note diagnostic + void add_note(System system, + const std::string& note_msg, + const Source& source) { + diag::Diagnostic note{}; + note.severity = diag::Severity::Note; + note.system = system; + note.source = source; + note.message = note_msg; + add(std::move(note)); + } + + /// adds the warning message with the given Source to the end of this list. + /// @param system the system raising the warning message + /// @param warning_msg the warning message + /// @param source the source of the warning diagnostic + void add_warning(System system, + const std::string& warning_msg, + const Source& source) { + diag::Diagnostic warning{}; + warning.severity = diag::Severity::Warning; + warning.system = system; + warning.source = source; + warning.message = warning_msg; + add(std::move(warning)); + } + + /// adds the error message without a source to the end of this list. + /// @param system the system raising the error message + /// @param err_msg the error message + void add_error(System system, std::string err_msg) { + diag::Diagnostic error{}; + error.severity = diag::Severity::Error; + error.system = system; + error.message = std::move(err_msg); + add(std::move(error)); + } + + /// adds the error message with the given Source to the end of this list. + /// @param system the system raising the error message + /// @param err_msg the error message + /// @param source the source of the error diagnostic + void add_error(System system, std::string err_msg, const Source& source) { + diag::Diagnostic error{}; + error.severity = diag::Severity::Error; + error.system = system; + error.source = source; + error.message = std::move(err_msg); + add(std::move(error)); + } + + /// adds the error message with the given code and Source to the end of this + /// list. + /// @param system the system raising the error message + /// @param code the error code + /// @param err_msg the error message + /// @param source the source of the error diagnostic + void add_error(System system, + const char* code, + std::string err_msg, + const Source& source) { + diag::Diagnostic error{}; + error.code = code; + error.severity = diag::Severity::Error; + error.system = system; + error.source = source; + error.message = std::move(err_msg); + add(std::move(error)); + } + + /// adds an internal compiler error message to the end of this list. + /// @param system the system raising the error message + /// @param err_msg the error message + /// @param source the source of the internal compiler error + /// @param file the Source::File owned by this diagnostic + void add_ice(System system, + const std::string& err_msg, + const Source& source, + std::shared_ptr<Source::File> file) { + diag::Diagnostic ice{}; + ice.severity = diag::Severity::InternalCompilerError; + ice.system = system; + ice.source = source; + ice.message = err_msg; + ice.owned_file = std::move(file); + add(std::move(ice)); + } + + /// @returns true iff the diagnostic list contains errors diagnostics (or of + /// higher severity). + bool contains_errors() const { return error_count_ > 0; } + /// @returns the number of error diagnostics (or of higher severity). + size_t error_count() const { return error_count_; } + /// @returns the number of entries in the list. + size_t count() const { return entries_.size(); } + /// @returns the first diagnostic in the list. + iterator begin() const { return entries_.begin(); } + /// @returns the last diagnostic in the list. + iterator end() const { return entries_.end(); } + + /// @returns a formatted string of all the diagnostics in this list. + std::string str() const; + + private: + std::vector<Diagnostic> entries_; + size_t error_count_ = 0; +}; + +} // namespace diag +} // namespace tint + +#endif // SRC_TINT_DIAGNOSTIC_DIAGNOSTIC_H_
diff --git a/src/tint/diagnostic/diagnostic_test.cc b/src/tint/diagnostic/diagnostic_test.cc new file mode 100644 index 0000000..940971b --- /dev/null +++ b/src/tint/diagnostic/diagnostic_test.cc
@@ -0,0 +1,42 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/diagnostic/formatter.h" + +#include "gtest/gtest.h" +#include "src/tint/diagnostic/diagnostic.h" + +namespace tint { +namespace diag { +namespace { + +TEST(DiagListTest, OwnedFilesShared) { + auto file = std::make_shared<Source::File>("path", "content"); + + diag::List list_a, list_b; + { + diag::Diagnostic diag{}; + diag.source = Source{Source::Range{{0, 0}}, file.get()}; + list_a.add(std::move(diag)); + } + + list_b = list_a; + + ASSERT_EQ(list_b.count(), list_a.count()); + EXPECT_EQ(list_b.begin()->source.file, file.get()); +} + +} // namespace +} // namespace diag +} // namespace tint
diff --git a/src/tint/diagnostic/formatter.cc b/src/tint/diagnostic/formatter.cc new file mode 100644 index 0000000..1eabe91 --- /dev/null +++ b/src/tint/diagnostic/formatter.cc
@@ -0,0 +1,271 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/diagnostic/formatter.h" + +#include <algorithm> +#include <iterator> +#include <vector> + +#include "src/tint/diagnostic/diagnostic.h" +#include "src/tint/diagnostic/printer.h" + +namespace tint { +namespace diag { +namespace { + +const char* to_str(Severity severity) { + switch (severity) { + case Severity::Note: + return "note"; + case Severity::Warning: + return "warning"; + case Severity::Error: + return "error"; + case Severity::InternalCompilerError: + return "internal compiler error"; + case Severity::Fatal: + return "fatal"; + } + return ""; +} + +std::string to_str(const Source::Location& location) { + std::stringstream ss; + if (location.line > 0) { + ss << location.line; + if (location.column > 0) { + ss << ":" << location.column; + } + } + return ss.str(); +} + +} // namespace + +/// State holds the internal formatter state for a format() call. +struct Formatter::State { + /// Constructs a State associated with the given printer. + /// @param p the printer to write formatted messages to. + explicit State(Printer* p) : printer(p) {} + ~State() { flush(); } + + /// set_style() sets the current style to new_style, flushing any pending + /// messages to the printer if the style changed. + /// @param new_style the new style to apply for future written messages. + void set_style(const diag::Style& new_style) { + if (style.color != new_style.color || style.bold != new_style.bold) { + flush(); + style = new_style; + } + } + + /// flush writes any pending messages to the printer, clearing the buffer. + void flush() { + auto str = stream.str(); + if (str.length() > 0) { + printer->write(str, style); + std::stringstream reset; + stream.swap(reset); + } + } + + /// operator<< queues msg to be written to the printer. + /// @param msg the value or string to write to the printer + /// @returns this State so that calls can be chained + template <typename T> + State& operator<<(const T& msg) { + stream << msg; + return *this; + } + + /// newline queues a newline to be written to the printer. + void newline() { stream << std::endl; } + + /// repeat queues the character c to be written to the printer n times. + /// @param c the character to print `n` times + /// @param n the number of times to print character `c` + void repeat(char c, size_t n) { + std::fill_n(std::ostream_iterator<char>(stream), n, c); + } + + private: + Printer* printer; + diag::Style style; + std::stringstream stream; +}; + +Formatter::Formatter() {} +Formatter::Formatter(const Style& style) : style_(style) {} + +void Formatter::format(const List& list, Printer* printer) const { + State state{printer}; + + bool first = true; + for (auto diag : list) { + state.set_style({}); + if (!first) { + state.newline(); + } + format(diag, state); + first = false; + } + + if (style_.print_newline_at_end) { + state.newline(); + } +} + +void Formatter::format(const Diagnostic& diag, State& state) const { + auto const& src = diag.source; + auto const& rng = src.range; + bool has_code = diag.code != nullptr && diag.code[0] != '\0'; + + state.set_style({Color::kDefault, true}); + + struct TextAndColor { + std::string text; + Color color; + bool bold = false; + }; + std::vector<TextAndColor> prefix; + prefix.reserve(6); + + if (style_.print_file && src.file != nullptr) { + if (rng.begin.line > 0) { + prefix.emplace_back(TextAndColor{src.file->path + ":" + to_str(rng.begin), + Color::kDefault}); + } else { + prefix.emplace_back(TextAndColor{src.file->path, Color::kDefault}); + } + } else if (rng.begin.line > 0) { + prefix.emplace_back(TextAndColor{to_str(rng.begin), Color::kDefault}); + } + + Color severity_color = Color::kDefault; + switch (diag.severity) { + case Severity::Note: + break; + case Severity::Warning: + severity_color = Color::kYellow; + break; + case Severity::Error: + severity_color = Color::kRed; + break; + case Severity::Fatal: + case Severity::InternalCompilerError: + severity_color = Color::kMagenta; + break; + } + if (style_.print_severity) { + prefix.emplace_back( + TextAndColor{to_str(diag.severity), severity_color, true}); + } + if (has_code) { + prefix.emplace_back(TextAndColor{diag.code, severity_color}); + } + + for (size_t i = 0; i < prefix.size(); i++) { + if (i > 0) { + state << " "; + } + state.set_style({prefix[i].color, prefix[i].bold}); + state << prefix[i].text; + } + + state.set_style({Color::kDefault, true}); + if (!prefix.empty()) { + state << ": "; + } + state << diag.message; + + if (style_.print_line && src.file && rng.begin.line > 0) { + state.newline(); + state.set_style({Color::kDefault, false}); + + for (size_t line_num = rng.begin.line; + (line_num <= rng.end.line) && + (line_num <= src.file->content.lines.size()); + line_num++) { + auto& line = src.file->content.lines[line_num - 1]; + auto line_len = line.size(); + + bool is_ascii = true; + for (auto c : line) { + if (c == '\t') { + state.repeat(' ', style_.tab_width); + } else { + state << c; + } + if (c & 0x80) { + is_ascii = false; + } + } + + state.newline(); + + // If the line contains non-ascii characters, then we cannot assume that + // a single utf8 code unit represents a single glyph, so don't attempt to + // draw squiggles. + if (!is_ascii) { + continue; + } + + state.set_style({Color::kCyan, false}); + + // Count the number of glyphs in the line span. + // start and end use 1-based indexing. + auto num_glyphs = [&](size_t start, size_t end) { + size_t count = 0; + start = (start > 0) ? (start - 1) : 0; + end = (end > 0) ? (end - 1) : 0; + for (size_t i = start; (i < end) && (i < line_len); i++) { + count += (line[i] == '\t') ? style_.tab_width : 1; + } + return count; + }; + + if (line_num == rng.begin.line && line_num == rng.end.line) { + // Single line + state.repeat(' ', num_glyphs(1, rng.begin.column)); + state.repeat('^', std::max<size_t>( + num_glyphs(rng.begin.column, rng.end.column), 1)); + } else if (line_num == rng.begin.line) { + // Start of multi-line + state.repeat(' ', num_glyphs(1, rng.begin.column)); + state.repeat('^', num_glyphs(rng.begin.column, line_len + 1)); + } else if (line_num == rng.end.line) { + // End of multi-line + state.repeat('^', num_glyphs(1, rng.end.column)); + } else { + // Middle of multi-line + state.repeat('^', num_glyphs(1, line_len + 1)); + } + state.newline(); + } + + state.set_style({}); + } +} + +std::string Formatter::format(const List& list) const { + StringPrinter printer; + format(list, &printer); + return printer.str(); +} + +Formatter::~Formatter() = default; + +} // namespace diag +} // namespace tint
diff --git a/src/tint/diagnostic/formatter.h b/src/tint/diagnostic/formatter.h new file mode 100644 index 0000000..209bacb --- /dev/null +++ b/src/tint/diagnostic/formatter.h
@@ -0,0 +1,72 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_DIAGNOSTIC_FORMATTER_H_ +#define SRC_TINT_DIAGNOSTIC_FORMATTER_H_ + +#include <string> + +namespace tint { +namespace diag { + +class Diagnostic; +class List; +class Printer; + +/// Formatter are used to print a list of diagnostics messages. +class Formatter { + public: + /// Style controls the formatter's output style. + struct Style { + /// include the file path for each diagnostic + bool print_file = true; + /// include the severity for each diagnostic + bool print_severity = true; + /// include the source line(s) for the diagnostic + bool print_line = true; + /// print a newline at the end of a diagnostic list + bool print_newline_at_end = true; + /// width of a tab character + size_t tab_width = 2u; + }; + + /// Constructor for the formatter using a default style. + Formatter(); + + /// Constructor for the formatter using the custom style. + /// @param style the style used for the formatter. + explicit Formatter(const Style& style); + + ~Formatter(); + + /// @param list the list of diagnostic messages to format + /// @param printer the printer used to display the formatted diagnostics + void format(const List& list, Printer* printer) const; + + /// @return the list of diagnostics `list` formatted to a string. + /// @param list the list of diagnostic messages to format + std::string format(const List& list) const; + + private: + struct State; + + void format(const Diagnostic& diag, State& state) const; + + const Style style_; +}; + +} // namespace diag +} // namespace tint + +#endif // SRC_TINT_DIAGNOSTIC_FORMATTER_H_
diff --git a/src/tint/diagnostic/formatter_test.cc b/src/tint/diagnostic/formatter_test.cc new file mode 100644 index 0000000..cee4140 --- /dev/null +++ b/src/tint/diagnostic/formatter_test.cc
@@ -0,0 +1,310 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/diagnostic/formatter.h" + +#include <utility> + +#include "gtest/gtest.h" +#include "src/tint/diagnostic/diagnostic.h" + +namespace tint { +namespace diag { +namespace { + +Diagnostic Diag(Severity severity, + Source source, + std::string message, + System system, + const char* code = nullptr) { + Diagnostic d; + d.severity = severity; + d.source = source; + d.message = std::move(message); + d.system = system; + d.code = code; + return d; +} + +constexpr const char* ascii_content = // Note: words are tab-delimited + R"(the cat says meow +the dog says woof +the snake says quack +the snail says ??? +)"; + +constexpr const char* utf8_content = // Note: words are tab-delimited + "the \xf0\x9f\x90\xb1 says meow\n" // NOLINT: tabs + "the \xf0\x9f\x90\x95 says woof\n" // NOLINT: tabs + "the \xf0\x9f\x90\x8d says quack\n" // NOLINT: tabs + "the \xf0\x9f\x90\x8c says ???\n"; // NOLINT: tabs + +class DiagFormatterTest : public testing::Test { + public: + Source::File ascii_file{"file.name", ascii_content}; + Source::File utf8_file{"file.name", utf8_content}; + Diagnostic ascii_diag_note = + Diag(Severity::Note, + Source{Source::Range{Source::Location{1, 14}}, &ascii_file}, + "purr", + System::Test); + Diagnostic ascii_diag_warn = + Diag(Severity::Warning, + Source{Source::Range{{2, 14}, {2, 18}}, &ascii_file}, + "grrr", + System::Test); + Diagnostic ascii_diag_err = + Diag(Severity::Error, + Source{Source::Range{{3, 16}, {3, 21}}, &ascii_file}, + "hiss", + System::Test, + "abc123"); + Diagnostic ascii_diag_ice = + Diag(Severity::InternalCompilerError, + Source{Source::Range{{4, 16}, {4, 19}}, &ascii_file}, + "unreachable", + System::Test); + Diagnostic ascii_diag_fatal = + Diag(Severity::Fatal, + Source{Source::Range{{4, 16}, {4, 19}}, &ascii_file}, + "nothing", + System::Test); + + Diagnostic utf8_diag_note = + Diag(Severity::Note, + Source{Source::Range{Source::Location{1, 15}}, &utf8_file}, + "purr", + System::Test); + Diagnostic utf8_diag_warn = + Diag(Severity::Warning, + Source{Source::Range{{2, 15}, {2, 19}}, &utf8_file}, + "grrr", + System::Test); + Diagnostic utf8_diag_err = + Diag(Severity::Error, + Source{Source::Range{{3, 15}, {3, 20}}, &utf8_file}, + "hiss", + System::Test, + "abc123"); + Diagnostic utf8_diag_ice = + Diag(Severity::InternalCompilerError, + Source{Source::Range{{4, 15}, {4, 18}}, &utf8_file}, + "unreachable", + System::Test); + Diagnostic utf8_diag_fatal = + Diag(Severity::Fatal, + Source{Source::Range{{4, 15}, {4, 18}}, &utf8_file}, + "nothing", + System::Test); +}; + +TEST_F(DiagFormatterTest, Simple) { + Formatter fmt{{false, false, false, false}}; + auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err}); + auto* expect = R"(1:14: purr +2:14: grrr +3:16 abc123: hiss)"; + ASSERT_EQ(expect, got); +} + +TEST_F(DiagFormatterTest, SimpleNewlineAtEnd) { + Formatter fmt{{false, false, false, true}}; + auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err}); + auto* expect = R"(1:14: purr +2:14: grrr +3:16 abc123: hiss +)"; + ASSERT_EQ(expect, got); +} + +TEST_F(DiagFormatterTest, SimpleNoSource) { + Formatter fmt{{false, false, false, false}}; + auto diag = Diag(Severity::Note, Source{}, "no source!", System::Test); + auto got = fmt.format(List{diag}); + auto* expect = "no source!"; + ASSERT_EQ(expect, got); +} + +TEST_F(DiagFormatterTest, WithFile) { + Formatter fmt{{true, false, false, false}}; + auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err}); + auto* expect = R"(file.name:1:14: purr +file.name:2:14: grrr +file.name:3:16 abc123: hiss)"; + ASSERT_EQ(expect, got); +} + +TEST_F(DiagFormatterTest, WithSeverity) { + Formatter fmt{{false, true, false, false}}; + auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err}); + auto* expect = R"(1:14 note: purr +2:14 warning: grrr +3:16 error abc123: hiss)"; + ASSERT_EQ(expect, got); +} + +TEST_F(DiagFormatterTest, WithLine) { + Formatter fmt{{false, false, true, false}}; + auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err}); + auto* expect = R"(1:14: purr +the cat says meow + ^ + +2:14: grrr +the dog says woof + ^^^^ + +3:16 abc123: hiss +the snake says quack + ^^^^^ +)"; + ASSERT_EQ(expect, got); +} + +TEST_F(DiagFormatterTest, UnicodeWithLine) { + Formatter fmt{{false, false, true, false}}; + auto got = fmt.format(List{utf8_diag_note, utf8_diag_warn, utf8_diag_err}); + auto* expect = + "1:15: purr\n" + "the \xf0\x9f\x90\xb1 says meow\n" + "\n" + "2:15: grrr\n" + "the \xf0\x9f\x90\x95 says woof\n" + "\n" + "3:15 abc123: hiss\n" + "the \xf0\x9f\x90\x8d says quack\n"; + ASSERT_EQ(expect, got); +} + +TEST_F(DiagFormatterTest, BasicWithFileSeverityLine) { + Formatter fmt{{true, true, true, false}}; + auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err}); + auto* expect = R"(file.name:1:14 note: purr +the cat says meow + ^ + +file.name:2:14 warning: grrr +the dog says woof + ^^^^ + +file.name:3:16 error abc123: hiss +the snake says quack + ^^^^^ +)"; + ASSERT_EQ(expect, got); +} + +TEST_F(DiagFormatterTest, BasicWithMultiLine) { + auto multiline = Diag(Severity::Warning, + Source{Source::Range{{2, 9}, {4, 15}}, &ascii_file}, + "multiline", System::Test); + Formatter fmt{{false, false, true, false}}; + auto got = fmt.format(List{multiline}); + auto* expect = R"(2:9: multiline +the dog says woof + ^^^^^^^^^^ +the snake says quack +^^^^^^^^^^^^^^^^^^^^^^^ +the snail says ??? +^^^^^^^^^^^^^^^^ +)"; + ASSERT_EQ(expect, got); +} + +TEST_F(DiagFormatterTest, UnicodeWithMultiLine) { + auto multiline = Diag(Severity::Warning, + Source{Source::Range{{2, 9}, {4, 15}}, &utf8_file}, + "multiline", System::Test); + Formatter fmt{{false, false, true, false}}; + auto got = fmt.format(List{multiline}); + auto* expect = + "2:9: multiline\n" + "the \xf0\x9f\x90\x95 says woof\n" + "the \xf0\x9f\x90\x8d says quack\n" + "the \xf0\x9f\x90\x8c says ???\n"; + ASSERT_EQ(expect, got); +} + +TEST_F(DiagFormatterTest, BasicWithFileSeverityLineTab4) { + Formatter fmt{{true, true, true, false, 4u}}; + auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err}); + auto* expect = R"(file.name:1:14 note: purr +the cat says meow + ^ + +file.name:2:14 warning: grrr +the dog says woof + ^^^^ + +file.name:3:16 error abc123: hiss +the snake says quack + ^^^^^ +)"; + ASSERT_EQ(expect, got); +} + +TEST_F(DiagFormatterTest, BasicWithMultiLineTab4) { + auto multiline = Diag(Severity::Warning, + Source{Source::Range{{2, 9}, {4, 15}}, &ascii_file}, + "multiline", System::Test); + Formatter fmt{{false, false, true, false, 4u}}; + auto got = fmt.format(List{multiline}); + auto* expect = R"(2:9: multiline +the dog says woof + ^^^^^^^^^^^^ +the snake says quack +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +the snail says ??? +^^^^^^^^^^^^^^^^^^^^ +)"; + ASSERT_EQ(expect, got); +} + +TEST_F(DiagFormatterTest, ICE) { + Formatter fmt{{}}; + auto got = fmt.format(List{ascii_diag_ice}); + auto* expect = R"(file.name:4:16 internal compiler error: unreachable +the snail says ??? + ^^^ + +)"; + ASSERT_EQ(expect, got); +} + +TEST_F(DiagFormatterTest, Fatal) { + Formatter fmt{{}}; + auto got = fmt.format(List{ascii_diag_fatal}); + auto* expect = R"(file.name:4:16 fatal: nothing +the snail says ??? + ^^^ + +)"; + ASSERT_EQ(expect, got); +} + +TEST_F(DiagFormatterTest, RangeOOB) { + Formatter fmt{{true, true, true, true}}; + diag::List list; + list.add_error(System::Test, "oob", + Source{{{10, 20}, {30, 20}}, &ascii_file}); + auto got = fmt.format(list); + auto* expect = R"(file.name:10:20 error: oob + +)"; + ASSERT_EQ(expect, got); +} + +} // namespace +} // namespace diag +} // namespace tint
diff --git a/src/tint/diagnostic/printer.cc b/src/tint/diagnostic/printer.cc new file mode 100644 index 0000000..54fd5f7 --- /dev/null +++ b/src/tint/diagnostic/printer.cc
@@ -0,0 +1,34 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/diagnostic/printer.h" + +namespace tint { +namespace diag { + +Printer::~Printer() = default; + +StringPrinter::StringPrinter() = default; +StringPrinter::~StringPrinter() = default; + +std::string StringPrinter::str() const { + return stream.str(); +} + +void StringPrinter::write(const std::string& str, const Style&) { + stream << str; +} + +} // namespace diag +} // namespace tint
diff --git a/src/tint/diagnostic/printer.h b/src/tint/diagnostic/printer.h new file mode 100644 index 0000000..570223b --- /dev/null +++ b/src/tint/diagnostic/printer.h
@@ -0,0 +1,83 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_DIAGNOSTIC_PRINTER_H_ +#define SRC_TINT_DIAGNOSTIC_PRINTER_H_ + +#include <memory> +#include <sstream> +#include <string> + +namespace tint { +namespace diag { + +class List; + +/// Color is an enumerator of colors used by Style. +enum class Color { + kDefault, + kBlack, + kRed, + kGreen, + kYellow, + kBlue, + kMagenta, + kCyan, + kWhite, +}; + +/// Style describes how a diagnostic message should be printed. +struct Style { + /// The foreground text color + Color color = Color::kDefault; + /// If true the text will be displayed with a strong weight + bool bold = false; +}; + +/// Printers are used to print formatted diagnostic messages to a terminal. +class Printer { + public: + /// @returns a diagnostic Printer + /// @param out the file to print to. + /// @param use_colors if true, the printer will use colors if `out` is a + /// terminal and supports them. + static std::unique_ptr<Printer> create(FILE* out, bool use_colors); + + virtual ~Printer(); + + /// writes the string str to the printer with the given style. + /// @param str the string to write to the printer + /// @param style the style used to print `str` + virtual void write(const std::string& str, const Style& style) = 0; +}; + +/// StringPrinter is an implementation of Printer that writes to a std::string. +class StringPrinter : public Printer { + public: + StringPrinter(); + ~StringPrinter() override; + + /// @returns the printed string. + std::string str() const; + + void write(const std::string& str, const Style&) override; + + private: + std::stringstream stream; +}; + +} // namespace diag +} // namespace tint + +#endif // SRC_TINT_DIAGNOSTIC_PRINTER_H_
diff --git a/src/tint/diagnostic/printer_linux.cc b/src/tint/diagnostic/printer_linux.cc new file mode 100644 index 0000000..fc40cf3 --- /dev/null +++ b/src/tint/diagnostic/printer_linux.cc
@@ -0,0 +1,100 @@ +// Copyright 2020 The Tint Authors. +// +// 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 <unistd.h> + +#include <cstring> + +#include "src/tint/diagnostic/printer.h" + +namespace tint { +namespace diag { +namespace { + +bool supports_colors(FILE* f) { + if (!isatty(fileno(f))) { + return false; + } + + const char* cterm = getenv("TERM"); + if (cterm == nullptr) { + return false; + } + + std::string term = getenv("TERM"); + if (term != "cygwin" && term != "linux" && term != "rxvt-unicode-256color" && + term != "rxvt-unicode" && term != "screen-256color" && term != "screen" && + term != "tmux-256color" && term != "tmux" && term != "xterm-256color" && + term != "xterm-color" && term != "xterm") { + return false; + } + + return true; +} + +class PrinterLinux : public Printer { + public: + PrinterLinux(FILE* f, bool colors) + : file(f), use_colors(colors && supports_colors(f)) {} + + void write(const std::string& str, const Style& style) override { + write_color(style.color, style.bold); + fwrite(str.data(), 1, str.size(), file); + write_color(Color::kDefault, false); + } + + private: + constexpr const char* color_code(Color color, bool bold) { + switch (color) { + case Color::kDefault: + return bold ? "\u001b[1m" : "\u001b[0m"; + case Color::kBlack: + return bold ? "\u001b[30;1m" : "\u001b[30m"; + case Color::kRed: + return bold ? "\u001b[31;1m" : "\u001b[31m"; + case Color::kGreen: + return bold ? "\u001b[32;1m" : "\u001b[32m"; + case Color::kYellow: + return bold ? "\u001b[33;1m" : "\u001b[33m"; + case Color::kBlue: + return bold ? "\u001b[34;1m" : "\u001b[34m"; + case Color::kMagenta: + return bold ? "\u001b[35;1m" : "\u001b[35m"; + case Color::kCyan: + return bold ? "\u001b[36;1m" : "\u001b[36m"; + case Color::kWhite: + return bold ? "\u001b[37;1m" : "\u001b[37m"; + } + return ""; // unreachable + } + + void write_color(Color color, bool bold) { + if (use_colors) { + auto* code = color_code(color, bold); + fwrite(code, 1, strlen(code), file); + } + } + + FILE* const file; + const bool use_colors; +}; + +} // namespace + +std::unique_ptr<Printer> Printer::create(FILE* out, bool use_colors) { + return std::make_unique<PrinterLinux>(out, use_colors); +} + +} // namespace diag +} // namespace tint
diff --git a/src/tint/diagnostic/printer_other.cc b/src/tint/diagnostic/printer_other.cc new file mode 100644 index 0000000..21498be --- /dev/null +++ b/src/tint/diagnostic/printer_other.cc
@@ -0,0 +1,42 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/diagnostic/printer.h" + +namespace tint { +namespace diag { +namespace { + +class PrinterOther : public Printer { + public: + explicit PrinterOther(FILE* f) : file(f) {} + + void write(const std::string& str, const Style&) override { + fwrite(str.data(), 1, str.size(), file); + } + + private: + FILE* file; +}; + +} // namespace + +std::unique_ptr<Printer> Printer::create(FILE* out, bool) { + return std::make_unique<PrinterOther>(out); +} + +} // namespace diag +} // namespace tint
diff --git a/src/tint/diagnostic/printer_test.cc b/src/tint/diagnostic/printer_test.cc new file mode 100644 index 0000000..f0cf374 --- /dev/null +++ b/src/tint/diagnostic/printer_test.cc
@@ -0,0 +1,98 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/diagnostic/printer.h" + +#include "gtest/gtest.h" + +namespace tint { +namespace diag { +namespace { + +// Actually verifying that the expected colors are printed is exceptionally +// difficult as: +// a) The color emission varies by OS. +// b) The logic checks to see if the printer is writing to a terminal, making +// mocking hard. +// c) Actually probing what gets written to a FILE* is notoriously tricky. +// +// The least we can do is to exersice the code - which is what we do here. +// The test will print each of the colors, and can be examined with human +// eyeballs. +// This can be enabled or disabled with ENABLE_PRINTER_TESTS +#define ENABLE_PRINTER_TESTS 0 +#if ENABLE_PRINTER_TESTS + +using PrinterTest = testing::Test; + +TEST_F(PrinterTest, WithColors) { + auto printer = Printer::create(stdout, true); + printer->write("Default", Style{Color::kDefault, false}); + printer->write("Black", Style{Color::kBlack, false}); + printer->write("Red", Style{Color::kRed, false}); + printer->write("Green", Style{Color::kGreen, false}); + printer->write("Yellow", Style{Color::kYellow, false}); + printer->write("Blue", Style{Color::kBlue, false}); + printer->write("Magenta", Style{Color::kMagenta, false}); + printer->write("Cyan", Style{Color::kCyan, false}); + printer->write("White", Style{Color::kWhite, false}); + printf("\n"); +} + +TEST_F(PrinterTest, BoldWithColors) { + auto printer = Printer::create(stdout, true); + printer->write("Default", Style{Color::kDefault, true}); + printer->write("Black", Style{Color::kBlack, true}); + printer->write("Red", Style{Color::kRed, true}); + printer->write("Green", Style{Color::kGreen, true}); + printer->write("Yellow", Style{Color::kYellow, true}); + printer->write("Blue", Style{Color::kBlue, true}); + printer->write("Magenta", Style{Color::kMagenta, true}); + printer->write("Cyan", Style{Color::kCyan, true}); + printer->write("White", Style{Color::kWhite, true}); + printf("\n"); +} + +TEST_F(PrinterTest, WithoutColors) { + auto printer = Printer::create(stdout, false); + printer->write("Default", Style{Color::kDefault, false}); + printer->write("Black", Style{Color::kBlack, false}); + printer->write("Red", Style{Color::kRed, false}); + printer->write("Green", Style{Color::kGreen, false}); + printer->write("Yellow", Style{Color::kYellow, false}); + printer->write("Blue", Style{Color::kBlue, false}); + printer->write("Magenta", Style{Color::kMagenta, false}); + printer->write("Cyan", Style{Color::kCyan, false}); + printer->write("White", Style{Color::kWhite, false}); + printf("\n"); +} + +TEST_F(PrinterTest, BoldWithoutColors) { + auto printer = Printer::create(stdout, false); + printer->write("Default", Style{Color::kDefault, true}); + printer->write("Black", Style{Color::kBlack, true}); + printer->write("Red", Style{Color::kRed, true}); + printer->write("Green", Style{Color::kGreen, true}); + printer->write("Yellow", Style{Color::kYellow, true}); + printer->write("Blue", Style{Color::kBlue, true}); + printer->write("Magenta", Style{Color::kMagenta, true}); + printer->write("Cyan", Style{Color::kCyan, true}); + printer->write("White", Style{Color::kWhite, true}); + printf("\n"); +} + +#endif // ENABLE_PRINTER_TESTS +} // namespace +} // namespace diag +} // namespace tint
diff --git a/src/tint/diagnostic/printer_windows.cc b/src/tint/diagnostic/printer_windows.cc new file mode 100644 index 0000000..9dcb43c --- /dev/null +++ b/src/tint/diagnostic/printer_windows.cc
@@ -0,0 +1,113 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/diagnostic/printer.h" + +#define WIN32_LEAN_AND_MEAN 1 +#include <Windows.h> + +namespace tint { +namespace diag { +namespace { + +struct ConsoleInfo { + HANDLE handle = INVALID_HANDLE_VALUE; + WORD default_attributes = 0; + operator bool() const { return handle != INVALID_HANDLE_VALUE; } +}; + +ConsoleInfo console_info(FILE* file) { + if (file == nullptr) { + return {}; + } + + ConsoleInfo console{}; + if (file == stdout) { + console.handle = GetStdHandle(STD_OUTPUT_HANDLE); + } else if (file == stderr) { + console.handle = GetStdHandle(STD_ERROR_HANDLE); + } else { + return {}; + } + + CONSOLE_SCREEN_BUFFER_INFO info{}; + if (GetConsoleScreenBufferInfo(console.handle, &info) == 0) { + return {}; + } + + console.default_attributes = info.wAttributes; + return console; +} + +class PrinterWindows : public Printer { + public: + PrinterWindows(FILE* f, bool use_colors) + : file(f), console(console_info(use_colors ? f : nullptr)) {} + + void write(const std::string& str, const Style& style) override { + write_color(style.color, style.bold); + fwrite(str.data(), 1, str.size(), file); + write_color(Color::kDefault, false); + } + + private: + WORD attributes(Color color, bool bold) { + switch (color) { + case Color::kDefault: + return console.default_attributes; + case Color::kBlack: + return 0; + case Color::kRed: + return FOREGROUND_RED | (bold ? FOREGROUND_INTENSITY : 0); + case Color::kGreen: + return FOREGROUND_GREEN | (bold ? FOREGROUND_INTENSITY : 0); + case Color::kYellow: + return FOREGROUND_RED | FOREGROUND_GREEN | + (bold ? FOREGROUND_INTENSITY : 0); + case Color::kBlue: + return FOREGROUND_BLUE | (bold ? FOREGROUND_INTENSITY : 0); + case Color::kMagenta: + return FOREGROUND_RED | FOREGROUND_BLUE | + (bold ? FOREGROUND_INTENSITY : 0); + case Color::kCyan: + return FOREGROUND_GREEN | FOREGROUND_BLUE | + (bold ? FOREGROUND_INTENSITY : 0); + case Color::kWhite: + return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | + (bold ? FOREGROUND_INTENSITY : 0); + } + return 0; // unreachable + } + + void write_color(Color color, bool bold) { + if (console) { + SetConsoleTextAttribute(console.handle, attributes(color, bold)); + fflush(file); + } + } + + FILE* const file; + const ConsoleInfo console; +}; + +} // namespace + +std::unique_ptr<Printer> Printer::create(FILE* out, bool use_colors) { + return std::make_unique<PrinterWindows>(out, use_colors); +} + +} // namespace diag +} // namespace tint
diff --git a/src/tint/fuzzers/BUILD.gn b/src/tint/fuzzers/BUILD.gn index f2291d7..3253e94 100644 --- a/src/tint/fuzzers/BUILD.gn +++ b/src/tint/fuzzers/BUILD.gn
@@ -1,4 +1,4 @@ -# Copyright 2022 The Dawn Authors +# Copyright 2022 The Dawn & Tint Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,11 +12,323 @@ # See the License for the specific language governing permissions and # limitations under the License. -import("../../../scripts/dawn_overrides_with_defaults.gni") +import("//build_overrides/build.gni") +import("../../../tint_overrides_with_defaults.gni") -# Target aliases to ease merging Tint->Dawn +# Fuzzers - Libfuzzer based fuzzing targets for Chromium +# To run the fuzzers outside of Chromium, use the CMake based builds. -group("fuzzers") { - deps = [ "${dawn_tint_dir}/src/tint/fuzzers:fuzzers" ] - testonly = true +if (build_with_chromium) { + import("//testing/libfuzzer/fuzzer_test.gni") + + fuzzer_corpus_wgsl_dir = "${target_gen_dir}/fuzzer_corpus_wgsl" + action("tint_generate_wgsl_corpus") { + script = "generate_wgsl_corpus.py" + sources = [ "generate_wgsl_corpus.py" ] + args = [ + rebase_path("${tint_root_dir}/test", root_build_dir), + rebase_path(fuzzer_corpus_wgsl_dir, root_build_dir), + ] + outputs = [ fuzzer_corpus_wgsl_dir ] + } + + tint_fuzzer_common_libfuzzer_options = [ + "only_ascii=1", + "max_len=10000", + ] + + tint_ast_fuzzer_common_libfuzzer_options = + tint_fuzzer_common_libfuzzer_options + [ + "cross_over=0", + "mutate_depth=1", + "tint_enable_all_mutations=false", + "tint_mutation_batch_size=5", + ] + + tint_regex_fuzzer_common_libfuzzer_options = + tint_fuzzer_common_libfuzzer_options + [ + "cross_over=0", + "mutate_depth=1", + ] + + # fuzzer_test doesn't have configs members, so need to define them in an empty + # source_set. + + source_set("tint_fuzzer_common_src") { + public_configs = [ + "${tint_root_dir}/src/tint:tint_config", + "${tint_root_dir}/src/tint:tint_common_config", + ] + + public_deps = [ + "${tint_root_dir}/src/tint:libtint", + "${tint_spirv_tools_dir}/:spvtools_val", + ] + + sources = [ + "data_builder.h", + "mersenne_twister_engine.cc", + "mersenne_twister_engine.h", + "random_generator.cc", + "random_generator.h", + "random_generator_engine.cc", + "random_generator_engine.h", + "shuffle_transform.cc", + "shuffle_transform.h", + "tint_common_fuzzer.cc", + "tint_common_fuzzer.h", + "tint_reader_writer_fuzzer.h", + "transform_builder.h", + ] + } + + source_set("tint_fuzzer_common_with_init_src") { + public_deps = [ ":tint_fuzzer_common_src" ] + + sources = [ + "cli.cc", + "cli.h", + "fuzzer_init.cc", + "fuzzer_init.h", + ] + } + + if (tint_build_wgsl_reader && tint_build_wgsl_writer) { + fuzzer_test("tint_ast_clone_fuzzer") { + sources = [ "tint_ast_clone_fuzzer.cc" ] + deps = [ ":tint_fuzzer_common_with_init_src" ] + dict = "dictionary.txt" + libfuzzer_options = tint_fuzzer_common_libfuzzer_options + seed_corpus = fuzzer_corpus_wgsl_dir + seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ] + } + + fuzzer_test("tint_ast_wgsl_writer_fuzzer") { + sources = [ "tint_ast_fuzzer/tint_ast_wgsl_writer_fuzzer.cc" ] + deps = [ "tint_ast_fuzzer:tint_ast_fuzzer" ] + libfuzzer_options = tint_ast_fuzzer_common_libfuzzer_options + seed_corpus = fuzzer_corpus_wgsl_dir + seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ] + } + + fuzzer_test("tint_regex_wgsl_writer_fuzzer") { + sources = [ "tint_regex_fuzzer/tint_regex_wgsl_writer_fuzzer.cc" ] + deps = [ "tint_regex_fuzzer:tint_regex_fuzzer" ] + libfuzzer_options = tint_regex_fuzzer_common_libfuzzer_options + seed_corpus = fuzzer_corpus_wgsl_dir + seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ] + } + + fuzzer_test("tint_wgsl_reader_wgsl_writer_fuzzer") { + sources = [ "tint_wgsl_reader_wgsl_writer_fuzzer.cc" ] + deps = [ ":tint_fuzzer_common_with_init_src" ] + dict = "dictionary.txt" + libfuzzer_options = tint_fuzzer_common_libfuzzer_options + seed_corpus = fuzzer_corpus_wgsl_dir + seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ] + } + } + + if (tint_build_wgsl_reader && tint_build_spv_writer) { + fuzzer_test("tint_all_transforms_fuzzer") { + sources = [ "tint_all_transforms_fuzzer.cc" ] + deps = [ ":tint_fuzzer_common_with_init_src" ] + dict = "dictionary.txt" + libfuzzer_options = tint_fuzzer_common_libfuzzer_options + seed_corpus = fuzzer_corpus_wgsl_dir + seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ] + } + + fuzzer_test("tint_ast_spv_writer_fuzzer") { + sources = [ "tint_ast_fuzzer/tint_ast_spv_writer_fuzzer.cc" ] + deps = [ "tint_ast_fuzzer:tint_ast_fuzzer" ] + libfuzzer_options = tint_ast_fuzzer_common_libfuzzer_options + seed_corpus = fuzzer_corpus_wgsl_dir + seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ] + } + + fuzzer_test("tint_binding_remapper_fuzzer") { + sources = [ "tint_binding_remapper_fuzzer.cc" ] + deps = [ ":tint_fuzzer_common_with_init_src" ] + dict = "dictionary.txt" + libfuzzer_options = tint_fuzzer_common_libfuzzer_options + seed_corpus = fuzzer_corpus_wgsl_dir + seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ] + } + + fuzzer_test("tint_first_index_offset_fuzzer") { + sources = [ "tint_first_index_offset_fuzzer.cc" ] + deps = [ ":tint_fuzzer_common_with_init_src" ] + dict = "dictionary.txt" + libfuzzer_options = tint_fuzzer_common_libfuzzer_options + seed_corpus = fuzzer_corpus_wgsl_dir + seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ] + } + + fuzzer_test("tint_regex_spv_writer_fuzzer") { + sources = [ "tint_regex_fuzzer/tint_regex_spv_writer_fuzzer.cc" ] + deps = [ "tint_regex_fuzzer:tint_regex_fuzzer" ] + libfuzzer_options = tint_regex_fuzzer_common_libfuzzer_options + seed_corpus = fuzzer_corpus_wgsl_dir + seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ] + } + + fuzzer_test("tint_renamer_fuzzer") { + sources = [ "tint_renamer_fuzzer.cc" ] + deps = [ ":tint_fuzzer_common_with_init_src" ] + dict = "dictionary.txt" + libfuzzer_options = tint_fuzzer_common_libfuzzer_options + seed_corpus = fuzzer_corpus_wgsl_dir + seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ] + } + + fuzzer_test("tint_robustness_fuzzer") { + sources = [ "tint_robustness_fuzzer.cc" ] + deps = [ ":tint_fuzzer_common_with_init_src" ] + dict = "dictionary.txt" + libfuzzer_options = tint_fuzzer_common_libfuzzer_options + seed_corpus = fuzzer_corpus_wgsl_dir + seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ] + } + + fuzzer_test("tint_single_entry_point_fuzzer") { + sources = [ "tint_single_entry_point_fuzzer.cc" ] + deps = [ ":tint_fuzzer_common_with_init_src" ] + dict = "dictionary.txt" + libfuzzer_options = tint_fuzzer_common_libfuzzer_options + seed_corpus = fuzzer_corpus_wgsl_dir + seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ] + } + + fuzzer_test("tint_vertex_pulling_fuzzer") { + sources = [ "tint_vertex_pulling_fuzzer.cc" ] + deps = [ ":tint_fuzzer_common_with_init_src" ] + dict = "dictionary.txt" + libfuzzer_options = tint_fuzzer_common_libfuzzer_options + seed_corpus = fuzzer_corpus_wgsl_dir + seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ] + } + + fuzzer_test("tint_wgsl_reader_spv_writer_fuzzer") { + sources = [ "tint_wgsl_reader_spv_writer_fuzzer.cc" ] + deps = [ ":tint_fuzzer_common_with_init_src" ] + dict = "dictionary.txt" + libfuzzer_options = tint_fuzzer_common_libfuzzer_options + seed_corpus = fuzzer_corpus_wgsl_dir + seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ] + } + } + + if (tint_build_wgsl_reader && tint_build_hlsl_writer) { + fuzzer_test("tint_ast_hlsl_writer_fuzzer") { + sources = [ "tint_ast_fuzzer/tint_ast_hlsl_writer_fuzzer.cc" ] + deps = [ "tint_ast_fuzzer:tint_ast_fuzzer" ] + libfuzzer_options = tint_ast_fuzzer_common_libfuzzer_options + seed_corpus = fuzzer_corpus_wgsl_dir + seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ] + } + + fuzzer_test("tint_regex_hlsl_writer_fuzzer") { + sources = [ "tint_regex_fuzzer/tint_regex_hlsl_writer_fuzzer.cc" ] + deps = [ "tint_regex_fuzzer:tint_regex_fuzzer" ] + libfuzzer_options = tint_regex_fuzzer_common_libfuzzer_options + seed_corpus = fuzzer_corpus_wgsl_dir + seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ] + } + + fuzzer_test("tint_wgsl_reader_hlsl_writer_fuzzer") { + sources = [ "tint_wgsl_reader_hlsl_writer_fuzzer.cc" ] + deps = [ ":tint_fuzzer_common_with_init_src" ] + dict = "dictionary.txt" + libfuzzer_options = tint_fuzzer_common_libfuzzer_options + seed_corpus = fuzzer_corpus_wgsl_dir + seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ] + } + } + + if (tint_build_wgsl_reader && tint_build_msl_writer) { + fuzzer_test("tint_ast_msl_writer_fuzzer") { + sources = [ "tint_ast_fuzzer/tint_ast_msl_writer_fuzzer.cc" ] + deps = [ "tint_ast_fuzzer:tint_ast_fuzzer" ] + libfuzzer_options = tint_ast_fuzzer_common_libfuzzer_options + seed_corpus = fuzzer_corpus_wgsl_dir + seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ] + } + + fuzzer_test("tint_regex_msl_writer_fuzzer") { + sources = [ "tint_regex_fuzzer/tint_regex_msl_writer_fuzzer.cc" ] + deps = [ "tint_regex_fuzzer:tint_regex_fuzzer" ] + libfuzzer_options = tint_regex_fuzzer_common_libfuzzer_options + seed_corpus = fuzzer_corpus_wgsl_dir + seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ] + } + + fuzzer_test("tint_wgsl_reader_msl_writer_fuzzer") { + sources = [ "tint_wgsl_reader_msl_writer_fuzzer.cc" ] + deps = [ ":tint_fuzzer_common_with_init_src" ] + dict = "dictionary.txt" + libfuzzer_options = tint_fuzzer_common_libfuzzer_options + seed_corpus = fuzzer_corpus_wgsl_dir + seed_corpus_deps = [ ":tint_generate_wgsl_corpus" ] + } + } + + if (tint_build_wgsl_reader && tint_build_hlsl_writer && + tint_build_msl_writer && tint_build_spv_writer && + tint_build_wgsl_writer) { + executable("tint_black_box_fuzz_target") { + sources = [ "tint_black_box_fuzz_target.cc" ] + deps = [ ":tint_fuzzer_common_src" ] + } + } + + group("fuzzers") { + testonly = true + deps = [] + + if (tint_build_wgsl_reader && tint_build_wgsl_writer) { + deps += [ + ":tint_ast_clone_fuzzer", + ":tint_ast_wgsl_writer_fuzzer", + ":tint_regex_wgsl_writer_fuzzer", + ":tint_wgsl_reader_wgsl_writer_fuzzer", + ] + } + if (tint_build_wgsl_reader && tint_build_spv_writer) { + deps += [ + ":tint_all_transforms_fuzzer", + ":tint_ast_spv_writer_fuzzer", + ":tint_binding_remapper_fuzzer", + ":tint_first_index_offset_fuzzer", + ":tint_regex_spv_writer_fuzzer", + ":tint_renamer_fuzzer", + ":tint_robustness_fuzzer", + ":tint_single_entry_point_fuzzer", + ":tint_vertex_pulling_fuzzer", + ":tint_wgsl_reader_spv_writer_fuzzer", + ] + } + if (tint_build_wgsl_reader && tint_build_hlsl_writer) { + deps += [ + ":tint_ast_hlsl_writer_fuzzer", + ":tint_regex_hlsl_writer_fuzzer", + ":tint_wgsl_reader_hlsl_writer_fuzzer", + ] + } + if (tint_build_wgsl_reader && tint_build_msl_writer) { + deps += [ + ":tint_ast_msl_writer_fuzzer", + ":tint_regex_msl_writer_fuzzer", + ":tint_wgsl_reader_msl_writer_fuzzer", + ] + } + if (tint_build_wgsl_reader && tint_build_hlsl_writer && + tint_build_msl_writer && tint_build_spv_writer && + tint_build_wgsl_writer) { + deps += [ ":tint_black_box_fuzz_target" ] + } + } +} else { + group("fuzzers") { + } }
diff --git a/src/tint/fuzzers/CMakeLists.txt b/src/tint/fuzzers/CMakeLists.txt new file mode 100644 index 0000000..55c9963 --- /dev/null +++ b/src/tint/fuzzers/CMakeLists.txt
@@ -0,0 +1,114 @@ +# Copyright 2020 The Tint Authors. +# +# 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. + +function(add_tint_fuzzer NAME) + add_executable(${NAME} + ${NAME}.cc + cli.cc + cli.h + data_builder.h + fuzzer_init.cc + fuzzer_init.h + mersenne_twister_engine.cc + mersenne_twister_engine.h + random_generator.cc + random_generator.h + random_generator_engine.cc + random_generator_engine.h + shuffle_transform.cc + shuffle_transform.h + tint_common_fuzzer.cc + tint_common_fuzzer.h + tint_reader_writer_fuzzer.h + transform_builder.h + ) + target_link_libraries(${NAME} libtint-fuzz) + tint_default_compile_options(${NAME}) + target_compile_options(${NAME} PRIVATE -Wno-missing-prototypes) +endfunction() + +if (${TINT_BUILD_WGSL_READER} AND ${TINT_BUILD_WGSL_WRITER}) + add_tint_fuzzer(tint_wgsl_reader_wgsl_writer_fuzzer) +endif() + +if (${TINT_BUILD_WGSL_READER} AND ${TINT_BUILD_SPV_WRITER}) + add_tint_fuzzer(tint_all_transforms_fuzzer) + add_tint_fuzzer(tint_binding_remapper_fuzzer) + add_tint_fuzzer(tint_first_index_offset_fuzzer) + add_tint_fuzzer(tint_renamer_fuzzer) + add_tint_fuzzer(tint_robustness_fuzzer) + add_tint_fuzzer(tint_single_entry_point_fuzzer) + add_tint_fuzzer(tint_vertex_pulling_fuzzer) + add_tint_fuzzer(tint_wgsl_reader_spv_writer_fuzzer) +endif() + +if (${TINT_BUILD_WGSL_READER} AND ${TINT_BUILD_HLSL_WRITER}) + add_tint_fuzzer(tint_wgsl_reader_hlsl_writer_fuzzer) +endif() + +if (${TINT_BUILD_WGSL_READER} AND ${TINT_BUILD_MSL_WRITER}) + add_tint_fuzzer(tint_wgsl_reader_msl_writer_fuzzer) +endif() + +if (${TINT_BUILD_SPV_READER} AND ${TINT_BUILD_WGSL_WRITER}) + add_tint_fuzzer(tint_spv_reader_wgsl_writer_fuzzer) +endif() + +if (${TINT_BUILD_SPV_READER} AND ${TINT_BUILD_SPV_WRITER}) + add_tint_fuzzer(tint_spv_reader_spv_writer_fuzzer) +endif() + +if (${TINT_BUILD_SPV_READER} AND ${TINT_BUILD_HLSL_WRITER}) + add_tint_fuzzer(tint_spv_reader_hlsl_writer_fuzzer) +endif() + +if (${TINT_BUILD_SPV_READER} AND ${TINT_BUILD_MSL_WRITER}) + add_tint_fuzzer(tint_spv_reader_msl_writer_fuzzer) +endif() + +if (${TINT_BUILD_WGSL_READER} AND ${TINT_BUILD_WGSL_WRITER}) + add_tint_fuzzer(tint_ast_clone_fuzzer) +endif() + +if (${TINT_BUILD_SPIRV_TOOLS_FUZZER}) + add_subdirectory(tint_spirv_tools_fuzzer) +endif() + +if (${TINT_BUILD_AST_FUZZER}) + add_subdirectory(tint_ast_fuzzer) +endif() + +if (${TINT_BUILD_REGEX_FUZZER}) + add_subdirectory(tint_regex_fuzzer) +endif() + +if (${TINT_BUILD_WGSL_READER} + AND ${TINT_BUILD_HLSL_WRITER} + AND ${TINT_BUILD_MSL_WRITER} + AND ${TINT_BUILD_SPV_WRITER} + AND ${TINT_BUILD_WGSL_WRITER}) + add_executable(tint_black_box_fuzz_target + mersenne_twister_engine.cc + mersenne_twister_engine.h + random_generator.cc + random_generator.h + random_generator_engine.cc + random_generator_engine.h + tint_black_box_fuzz_target.cc + tint_common_fuzzer.cc + tint_common_fuzzer.h + ) + target_link_libraries(tint_black_box_fuzz_target libtint) + tint_default_compile_options(tint_black_box_fuzz_target) +endif()
diff --git a/src/tint/fuzzers/cli.cc b/src/tint/fuzzers/cli.cc new file mode 100644 index 0000000..2a0b814 --- /dev/null +++ b/src/tint/fuzzers/cli.cc
@@ -0,0 +1,116 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/cli.h" + +#include <cstring> +#include <iostream> +#include <limits> +#include <sstream> +#include <string> +#include <utility> + +namespace tint { +namespace fuzzers { +namespace { + +const char* const kHelpMessage = R"( +This is a fuzzer for the Tint compiler that works by mutating the AST. + +Below is a list of all supported parameters for this fuzzer. You may want to +run it with -help=1 to check out libfuzzer parameters. + + -tint_dump_input= + If `true`, the fuzzer will dump input data to a file with + name tint_input_<hash>.spv/wgsl, where the hash is the hash + of the input data. + + -tint_help + Show this message. Note that there is also a -help=1 + parameter that will display libfuzzer's help message. + + -tint_enforce_validity= + If `true`, the fuzzer will enforce that Tint does not + generate invalid shaders. Currently `false` by default + since options provided by the fuzzer are not guaranteed + to be correct. + See https://bugs.chromium.org/p/tint/issues/detail?id=1356 +)"; + +[[noreturn]] void InvalidParam(const std::string& param) { + std::cout << "Invalid value for " << param << std::endl; + std::cout << kHelpMessage << std::endl; + exit(1); +} + +bool ParseBool(const std::string& value, bool* out) { + if (value.compare("true") == 0) { + *out = true; + } else if (value.compare("false") == 0) { + *out = false; + } else { + return false; + } + return true; +} + +} // namespace + +CliParams ParseCliParams(int* argc, char** argv) { + CliParams cli_params; + auto help = false; + + for (int i = *argc - 1; i > 0; --i) { + std::string param(argv[i]); + auto recognized_parameter = true; + + if (std::string::npos != param.find("-tint_dump_input=")) { + if (!ParseBool(param.substr(std::string("-tint_dump_input=").length()), + &cli_params.dump_input)) { + InvalidParam(param); + } + } else if (std::string::npos != param.find("-tint_help")) { + help = true; + } else if (std::string::npos != param.find("-tint_enforce_validity=")) { + if (!ParseBool( + param.substr(std::string("-tint_enforce_validity=").length()), + &cli_params.enforce_validity)) { + InvalidParam(param); + } + } else { + recognized_parameter = false; + } + + if (recognized_parameter) { + // Remove the recognized parameter from the list of all parameters by + // swapping it with the last one. This will suppress warnings in the + // libFuzzer about unrecognized parameters. By default, libFuzzer thinks + // that all user-defined parameters start with two dashes. However, we are + // forced to use a single one to make the fuzzer compatible with the + // ClusterFuzz. + std::swap(argv[i], argv[*argc - 1]); + *argc -= 1; + } + } + + if (help) { + std::cout << kHelpMessage << std::endl; + exit(0); + } + + return cli_params; +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/cli.h b/src/tint/fuzzers/cli.h new file mode 100644 index 0000000..02ca3db --- /dev/null +++ b/src/tint/fuzzers/cli.h
@@ -0,0 +1,46 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_CLI_H_ +#define SRC_TINT_FUZZERS_CLI_H_ + +#include <cstdint> + +namespace tint { +namespace fuzzers { + +/// CLI parameters accepted by the fuzzer. Type -tint_help in the CLI to see the +/// help message +struct CliParams { + /// Log contents of input shader + bool dump_input = false; + /// Throw error if shader becomes invalid during run + bool enforce_validity = false; +}; + +/// @brief Parses CLI parameters. +/// +/// This function will exit the process with non-zero return code if some +/// parameters are invalid. This function will remove recognized parameters from +/// `argv` and adjust `argc` accordingly. +/// +/// @param argc - the total number of parameters. +/// @param argv - array of all CLI parameters. +/// @return parsed parameters. +CliParams ParseCliParams(int* argc, char** argv); + +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_CLI_H_
diff --git a/src/tint/fuzzers/data_builder.h b/src/tint/fuzzers/data_builder.h new file mode 100644 index 0000000..e0c104a --- /dev/null +++ b/src/tint/fuzzers/data_builder.h
@@ -0,0 +1,246 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_DATA_BUILDER_H_ +#define SRC_TINT_FUZZERS_DATA_BUILDER_H_ + +#include <cassert> +#include <functional> +#include <string> +#include <unordered_map> +#include <vector> + +#include "src/tint/fuzzers/random_generator.h" +#include "src/tint/writer/hlsl/generator.h" +#include "src/tint/writer/msl/generator.h" + +namespace tint { +namespace fuzzers { + +/// Builder for generic pseudo-random data +class DataBuilder { + public: + /// @brief Initializes the internal engine using a seed value + /// @param seed - seed value passed to engine + explicit DataBuilder(uint64_t seed) : generator_(seed) {} + + /// @brief Initializes the internal engine using seed data + /// @param data - data fuzzer to calculate seed from + /// @param size - size of data buffer + explicit DataBuilder(const uint8_t* data, size_t size) + : generator_(RandomGenerator::CalculateSeed(data, size)) { + assert(data != nullptr && "|data| must be !nullptr"); + } + + /// Destructor + ~DataBuilder() = default; + + /// Move Constructor + DataBuilder(DataBuilder&&) = default; + + /// Generate pseudo-random data of a specific type + /// @tparam T - type of data to produce + /// @returns pseudo-random data of type T + template <typename T> + T build() { + return BuildImpl<T>::impl(this); + } + + /// Generate pseudo-random data of a specific type in a vector + /// @tparam T - data type held vector + /// @returns pseudo-random data of type std::vector<T> + template <typename T> + std::vector<T> vector() { + auto count = build<uint8_t>(); + std::vector<T> out(count); + for (uint8_t i = 0; i < count; i++) { + out[i] = build<T>(); + } + return out; + } + + /// Generate complex pseudo-random data of a specific type in a vector + /// @tparam T - data type held vector + /// @tparam Callback - callback that takes in a DataBuilder* and returns a T + /// @param generate - callback for generating each instance of T + /// @returns pseudo-random data of type std::vector<T> + template <typename T, typename Callback> + std::vector<T> vector(Callback generate) { + auto count = build<uint8_t>(); + std::vector<T> out(count); + for (size_t i = 0; i < count; i++) { + out[i] = generate(this); + } + return out; + } + + /// Generate an pseudo-random entry to a enum class. + /// Assumes enum is tightly packed starting at 0. + /// @tparam T - type of enum class + /// @param count - number of entries in enum class + /// @returns a random enum class entry + template <typename T> + T enum_class(uint32_t count) { + return static_cast<T>(generator_.Get4Bytes() % count); + } + + private: + RandomGenerator generator_; + + // Disallow copy & assign + DataBuilder(const DataBuilder&) = delete; + DataBuilder& operator=(const DataBuilder&) = delete; + + /// Get N bytes of pseudo-random data + /// @param out - pointer to location to save data + /// @param n - number of bytes to get + void build(void* out, size_t n) { + assert(out != nullptr && "|out| cannot be nullptr"); + assert(n > 0 && "|n| must be > 0"); + + generator_.GetNBytes(reinterpret_cast<uint8_t*>(out), n); + } + + /// Generate pseudo-random data of a specific type into an output var + /// @tparam T - type of data to produce + /// @param out - output var to generate into + template <typename T> + void build(T& out) { + out = build<T>(); + } + + /// Implementation of ::build<T>() + /// @tparam T - type of data to produce + template <typename T> + struct BuildImpl { + /// Generate a pseudo-random variable of type T + /// @param b - data builder to use + /// @returns a variable of type T filled with pseudo-random data + static T impl(DataBuilder* b) { + T out{}; + b->build(&out, sizeof(T)); + return out; + } + }; + + /// Specialization for std::string + template <> + struct BuildImpl<std::string> { + /// Generate a pseudo-random string + /// @param b - data builder to use + /// @returns a string filled with pseudo-random data + static std::string impl(DataBuilder* b) { + auto count = b->build<uint8_t>(); + if (count == 0) { + return ""; + } + std::vector<uint8_t> source(count); + b->build(source.data(), count); + return {source.begin(), source.end()}; + } + }; + + /// Specialization for bool + template <> + struct BuildImpl<bool> { + /// Generate a pseudo-random bool + /// @param b - data builder to use + /// @returns a boolean with even odds of being true or false + static bool impl(DataBuilder* b) { return b->generator_.GetBool(); } + }; + + /// Specialization for writer::msl::Options + template <> + struct BuildImpl<writer::msl::Options> { + /// Generate a pseudo-random writer::msl::Options struct + /// @param b - data builder to use + /// @returns writer::msl::Options filled with pseudo-random data + static writer::msl::Options impl(DataBuilder* b) { + writer::msl::Options out{}; + b->build(out.buffer_size_ubo_index); + b->build(out.fixed_sample_mask); + b->build(out.emit_vertex_point_size); + b->build(out.disable_workgroup_init); + b->build(out.generate_external_texture_bindings); + b->build(out.array_length_from_uniform); + return out; + } + }; + + /// Specialization for writer::hlsl::Options + template <> + struct BuildImpl<writer::hlsl::Options> { + /// Generate a pseudo-random writer::hlsl::Options struct + /// @param b - data builder to use + /// @returns writer::hlsl::Options filled with pseudo-random data + static writer::hlsl::Options impl(DataBuilder* b) { + writer::hlsl::Options out{}; + b->build(out.root_constant_binding_point); + b->build(out.disable_workgroup_init); + b->build(out.array_length_from_uniform); + return out; + } + }; + + /// Specialization for writer::spirv::Options + template <> + struct BuildImpl<writer::spirv::Options> { + /// Generate a pseudo-random writer::spirv::Options struct + /// @param b - data builder to use + /// @returns writer::spirv::Options filled with pseudo-random data + static writer::spirv::Options impl(DataBuilder* b) { + writer::spirv::Options out{}; + b->build(out.emit_vertex_point_size); + b->build(out.disable_workgroup_init); + return out; + } + }; + + /// Specialization for writer::ArrayLengthFromUniformOptions + template <> + struct BuildImpl<writer::ArrayLengthFromUniformOptions> { + /// Generate a pseudo-random writer::ArrayLengthFromUniformOptions struct + /// @param b - data builder to use + /// @returns writer::ArrayLengthFromUniformOptions filled with pseudo-random + /// data + static writer::ArrayLengthFromUniformOptions impl(DataBuilder* b) { + writer::ArrayLengthFromUniformOptions out{}; + b->build(out.ubo_binding); + b->build(out.bindpoint_to_size_index); + return out; + } + }; + + /// Specialization for std::unordered_map<K, V> + template <typename K, typename V> + struct BuildImpl<std::unordered_map<K, V>> { + /// Generate a pseudo-random std::unordered_map<K, V> + /// @param b - data builder to use + /// @returns std::unordered_map<K, V> filled with + /// pseudo-random data + static std::unordered_map<K, V> impl(DataBuilder* b) { + std::unordered_map<K, V> out; + uint8_t count = b->build<uint8_t>(); + for (uint8_t i = 0; i < count; ++i) { + out.emplace(b->build<K>(), b->build<V>()); + } + return out; + } + }; +}; + +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_DATA_BUILDER_H_
diff --git a/src/tint/fuzzers/dictionary.txt b/src/tint/fuzzers/dictionary.txt new file mode 100644 index 0000000..8f15ff6 --- /dev/null +++ b/src/tint/fuzzers/dictionary.txt
@@ -0,0 +1,112 @@ +"&" +"&&" +"->" +"[[" +"]]" +"/" +"!" +"[" +"]" +"{" +"}" +":" +"," +"=" +"==" +">" +">=" +"<" +"<=" +"%" +"-" +"::" +"!=" +"." +"+" +"|" +"||" +"(" +")" +";" +"*" +"^" +"array" +"binding" +"bitcast" +"bool" +"block" +"break" +"builtin" +"case" +"compute" +"const" +"continue" +"continuing" +"discard" +"default" +"else" +"elseif" +"f32" +"fallthrough" +"false" +"fn" +"fragment" +"function" +"i32" +"if" +"image" +"import" +"in" +"location" +"loop" +"mat2x2" +"mat2x3" +"mat2x4" +"mat3x2" +"mat3x3" +"mat3x4" +"mat4x2" +"mat4x3" +"mat4x4" +"offset" +"out" +"private" +"ptr" +"return" +"sampler" +"sampler_comparison" +"set" +"storage" +"stage" +"stride" +"struct" +"switch" +"texture_depth_2d" +"texture_depth_2d_array" +"texture_depth_cube" +"texture_depth_cube_array" +"texture_depth_multisampled_2d" +"texture_multisampled_2d" +"texture_storage_1d" +"texture_storage_2d_array" +"texture_storage_2d" +"texture_storage_2d_array" +"texture_storage_3d" +"texture_1d" +"texture_2d" +"texture_2d_array" +"texture_3d" +"texture_cube" +"texture_cube_array" +"true" +"type" +"u32" +"uniform" +"var" +"vec2" +"vec3" +"vec4" +"vertex" +"void" +"workgroup" +"workgroup_size"
diff --git a/src/tint/fuzzers/fuzzer_init.cc b/src/tint/fuzzers/fuzzer_init.cc new file mode 100644 index 0000000..ac9a4cf --- /dev/null +++ b/src/tint/fuzzers/fuzzer_init.cc
@@ -0,0 +1,35 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/fuzzer_init.h" +#include "src/tint/fuzzers/cli.h" + +namespace tint { +namespace fuzzers { + +namespace { +CliParams cli_params; +} + +const CliParams& GetCliParams() { + return cli_params; +} + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) { + cli_params = ParseCliParams(argc, *argv); + return 0; +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/fuzzer_init.h b/src/tint/fuzzers/fuzzer_init.h new file mode 100644 index 0000000..b81a850 --- /dev/null +++ b/src/tint/fuzzers/fuzzer_init.h
@@ -0,0 +1,29 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_FUZZER_INIT_H_ +#define SRC_TINT_FUZZERS_FUZZER_INIT_H_ + +#include "src/tint/fuzzers/cli.h" + +namespace tint { +namespace fuzzers { + +/// Returns the common CliParams parsed and populated by LLVMFuzzerInitialize() +const CliParams& GetCliParams(); + +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_FUZZER_INIT_H_
diff --git a/src/tint/fuzzers/generate_spirv_corpus.py b/src/tint/fuzzers/generate_spirv_corpus.py new file mode 100644 index 0000000..c6089015 --- /dev/null +++ b/src/tint/fuzzers/generate_spirv_corpus.py
@@ -0,0 +1,90 @@ +#!/usr/bin/env python3 + +# Copyright 2021 The Tint Authors. +# +# 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. + +# Collect all .spvasm files under a given directory, assemble them using +# spirv-as, and emit the assembled binaries to a given corpus directory, +# flattening their file names by replacing path separators with underscores. +# If the output directory already exists, it will be deleted and re-created. +# Files ending with ".expected.spvasm" are skipped. +# +# The intended use of this script is to generate a corpus of SPIR-V +# binaries for fuzzing. +# +# Usage: +# generate_spirv_corpus.py <input_dir> <corpus_dir> <path to spirv-as> + +import os +import pathlib +import shutil +import subprocess +import sys + + +def list_spvasm_files(root_search_dir): + for root, folders, files in os.walk(root_search_dir): + for filename in folders + files: + if pathlib.Path(filename).suffix == ".spvasm": + yield os.path.join(root, filename) + + +def main(): + if len(sys.argv) != 4: + print("Usage: " + sys.argv[0] + + " <input dir> <output dir> <spirv-as path>") + return 1 + input_dir: str = os.path.abspath(sys.argv[1].rstrip(os.sep)) + corpus_dir: str = os.path.abspath(sys.argv[2]) + spirv_as_path: str = os.path.abspath(sys.argv[3]) + if os.path.exists(corpus_dir): + shutil.rmtree(corpus_dir) + os.makedirs(corpus_dir) + + # It might be that some of the attempts to convert SPIR-V assembly shaders + # into SPIR-V binaries go wrong. It is sensible to tolerate a small number + # of such errors, to avoid fuzzer preparation failing due to bugs in + # spirv-as. But it is important to know when a large number of failures + # occur, in case something is more deeply wrong. + num_errors = 0 + max_tolerated_errors = 10 + logged_errors = "" + + for in_file in list_spvasm_files(input_dir): + if in_file.endswith(".expected.spvasm"): + continue + out_file = os.path.splitext( + corpus_dir + os.sep + + in_file[len(input_dir) + 1:].replace(os.sep, '_'))[0] + ".spv" + cmd = [ + spirv_as_path, "--target-env", "spv1.3", in_file, "-o", out_file + ] + proc = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = proc.communicate() + if proc.returncode != 0: + num_errors += 1 + logged_errors += "Error running " + " ".join( + cmd) + ": " + stdout.decode('utf-8') + stderr.decode('utf-8') + + if num_errors > max_tolerated_errors: + print("Too many (" + str(num_errors) + + ") errors occured while generating the SPIR-V corpus.") + print(logged_errors) + return 1 + + +if __name__ == "__main__": + sys.exit(main())
diff --git a/src/tint/fuzzers/generate_wgsl_corpus.py b/src/tint/fuzzers/generate_wgsl_corpus.py new file mode 100644 index 0000000..65c564f --- /dev/null +++ b/src/tint/fuzzers/generate_wgsl_corpus.py
@@ -0,0 +1,59 @@ +#!/usr/bin/env python3 + +# Copyright 2021 The Tint Authors. +# +# 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. + +# Collect all .wgsl files under a given directory and copy them to a given +# corpus directory, flattening their file names by replacing path +# separators with underscores. If the output directory already exists, it +# will be deleted and re-created. Files ending with ".expected.spvasm" are +# skipped. +# +# The intended use of this script is to generate a corpus of WGSL shaders +# for fuzzing. +# +# Usage: +# generate_wgsl_corpus.py <input_dir> <corpus_dir> + +import os +import pathlib +import shutil +import sys + + +def list_wgsl_files(root_search_dir): + for root, folders, files in os.walk(root_search_dir): + for filename in folders + files: + if pathlib.Path(filename).suffix == '.wgsl': + yield os.path.join(root, filename) + + +def main(): + if len(sys.argv) != 3: + print("Usage: " + sys.argv[0] + " <input dir> <output dir>") + return 1 + input_dir: str = os.path.abspath(sys.argv[1].rstrip(os.sep)) + corpus_dir: str = os.path.abspath(sys.argv[2]) + if os.path.exists(corpus_dir): + shutil.rmtree(corpus_dir) + os.makedirs(corpus_dir) + for in_file in list_wgsl_files(input_dir): + if in_file.endswith(".expected.wgsl"): + continue + out_file = in_file[len(input_dir) + 1:].replace(os.sep, '_') + shutil.copy(in_file, corpus_dir + os.sep + out_file) + + +if __name__ == "__main__": + sys.exit(main())
diff --git a/src/tint/fuzzers/mersenne_twister_engine.cc b/src/tint/fuzzers/mersenne_twister_engine.cc new file mode 100644 index 0000000..5acba2b --- /dev/null +++ b/src/tint/fuzzers/mersenne_twister_engine.cc
@@ -0,0 +1,59 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/mersenne_twister_engine.h" + +#include <algorithm> +#include <cassert> + +#include "src/tint/utils/hash.h" + +namespace tint { +namespace fuzzers { + +namespace { + +/// Generate integer from uniform distribution +/// @tparam I - integer type +/// @param engine - random number engine to use +/// @param lower - Lower bound of integer generated +/// @param upper - Upper bound of integer generated +/// @returns i, where lower <= i < upper +template <typename I> +I RandomInteger(std::mt19937_64* engine, I lower, I upper) { + assert(lower < upper && "|lower| must be strictly less than |upper|"); + return std::uniform_int_distribution<I>(lower, upper - 1)(*engine); +} + +} // namespace + +MersenneTwisterEngine::MersenneTwisterEngine(uint64_t seed) : engine_(seed) {} + +uint32_t MersenneTwisterEngine::RandomUInt32(uint32_t lower, uint32_t upper) { + return RandomInteger(&engine_, lower, upper); +} + +uint64_t MersenneTwisterEngine::RandomUInt64(uint64_t lower, uint64_t upper) { + return RandomInteger(&engine_, lower, upper); +} + +void MersenneTwisterEngine::RandomNBytes(uint8_t* dest, size_t n) { + assert(dest && "|dest| must not be nullptr"); + std::generate( + dest, dest + n, + std::independent_bits_engine<std::mt19937_64, 8, uint8_t>(engine_)); +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/mersenne_twister_engine.h b/src/tint/fuzzers/mersenne_twister_engine.h new file mode 100644 index 0000000..c482953 --- /dev/null +++ b/src/tint/fuzzers/mersenne_twister_engine.h
@@ -0,0 +1,61 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_MERSENNE_TWISTER_ENGINE_H_ +#define SRC_TINT_FUZZERS_MERSENNE_TWISTER_ENGINE_H_ + +#include <random> + +#include "src/tint/fuzzers/random_generator_engine.h" + +namespace tint { +namespace fuzzers { + +/// Standard MT based random number generation +class MersenneTwisterEngine : public RandomGeneratorEngine { + public: + /// @brief Initializes using provided seed + /// @param seed - seed value to use + explicit MersenneTwisterEngine(uint64_t seed); + ~MersenneTwisterEngine() override = default; + + /// Generate random uint32_t value from uniform distribution. + /// @param lower - lower bound of integer generated + /// @param upper - upper bound of integer generated + /// @returns i, where lower <= i < upper + uint32_t RandomUInt32(uint32_t lower, uint32_t upper) override; + + /// Get random uint64_t value from uniform distribution. + /// @param lower - lower bound of integer generated + /// @param upper - upper bound of integer generated + /// @returns i, where lower <= i < upper + uint64_t RandomUInt64(uint64_t lower, uint64_t upper) override; + + /// Get N bytes of pseudo-random data + /// @param dest - memory location to store data + /// @param n - number of bytes of data to generate + void RandomNBytes(uint8_t* dest, size_t n) override; + + private: + // Disallow copy & assign + MersenneTwisterEngine(const MersenneTwisterEngine&) = delete; + MersenneTwisterEngine& operator=(const MersenneTwisterEngine&) = delete; + + std::mt19937_64 engine_; +}; // class MersenneTwisterEngine + +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_MERSENNE_TWISTER_ENGINE_H_
diff --git a/src/tint/fuzzers/random_generator.cc b/src/tint/fuzzers/random_generator.cc new file mode 100644 index 0000000..6b3c98a --- /dev/null +++ b/src/tint/fuzzers/random_generator.cc
@@ -0,0 +1,124 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/random_generator.h" + +#include <algorithm> +#include <cassert> +#include <utility> + +#include "src/tint/fuzzers/mersenne_twister_engine.h" +#include "src/tint/fuzzers/random_generator_engine.h" +#include "src/tint/utils/hash.h" + +namespace tint { +namespace fuzzers { + +namespace { + +/// Calculate the hash for the contents of a c-style data buffer +/// This is intentionally not implemented as a generic override of HashCombine +/// in "src/tint/utils/hash.h", because it conflicts with the vardiac override +/// for the case where a pointer and an integer are being hashed. +/// @param data - pointer to buffer to be hashed +/// @param size - number of elements in buffer +/// @returns hash of the data in the buffer +size_t HashBuffer(const uint8_t* data, const size_t size) { + size_t hash = 102931; + utils::HashCombine(&hash, size); + for (size_t i = 0; i < size; i++) { + utils::HashCombine(&hash, data[i]); + } + return hash; +} + +} // namespace + +RandomGenerator::RandomGenerator(std::unique_ptr<RandomGeneratorEngine> engine) + : engine_(std::move(engine)) {} + +RandomGenerator::RandomGenerator(uint64_t seed) + : RandomGenerator(std::make_unique<MersenneTwisterEngine>(seed)) {} + +uint32_t RandomGenerator::GetUInt32(uint32_t lower, uint32_t upper) { + assert(lower < upper && "|lower| must be strictly less than |upper|"); + return engine_->RandomUInt32(lower, upper); +} + +uint32_t RandomGenerator::GetUInt32(uint32_t bound) { + assert(bound > 0 && "|bound| must be greater than 0"); + return engine_->RandomUInt32(0u, bound); +} + +uint64_t RandomGenerator::GetUInt64(uint64_t lower, uint64_t upper) { + assert(lower < upper && "|lower| must be strictly less than |upper|"); + return engine_->RandomUInt64(lower, upper); +} + +uint64_t RandomGenerator::GetUInt64(uint64_t bound) { + assert(bound > 0 && "|bound| must be greater than 0"); + return engine_->RandomUInt64(static_cast<uint64_t>(0), bound); +} + +uint8_t RandomGenerator::GetByte() { + uint8_t result; + engine_->RandomNBytes(&result, 1); + return result; +} + +uint32_t RandomGenerator::Get4Bytes() { + uint32_t result; + engine_->RandomNBytes(reinterpret_cast<uint8_t*>(&result), 4); + return result; +} + +void RandomGenerator::GetNBytes(uint8_t* dest, size_t n) { + assert(dest && "|dest| must not be nullptr"); + engine_->RandomNBytes(dest, n); +} + +bool RandomGenerator::GetBool() { + return engine_->RandomUInt32(0u, 2u); +} + +bool RandomGenerator::GetWeightedBool(uint32_t percentage) { + static const uint32_t kMaxPercentage = 100; + assert(percentage <= kMaxPercentage && + "|percentage| needs to be within [0, 100]"); + return engine_->RandomUInt32(0u, kMaxPercentage) < percentage; +} + +uint64_t RandomGenerator::CalculateSeed(const uint8_t* data, size_t size) { + assert(data != nullptr && "|data| must be !nullptr"); + + // Number of bytes we want to skip at the start of data for the hash. + // Fewer bytes may be skipped when `size` is small. + // Has lower precedence than kHashDesiredMinBytes. + static const int64_t kHashDesiredLeadingSkipBytes = 5; + // Minimum number of bytes we want to use in the hash. + // Used for short buffers. + static const int64_t kHashDesiredMinBytes = 4; + // Maximum number of bytes we want to use in the hash. + static const int64_t kHashDesiredMaxBytes = 32; + auto size_i64 = static_cast<int64_t>(size); + auto hash_begin_i64 = + std::min(kHashDesiredLeadingSkipBytes, + std::max<int64_t>(size_i64 - kHashDesiredMinBytes, 0)); + auto hash_end_i64 = std::min(hash_begin_i64 + kHashDesiredMaxBytes, size_i64); + auto hash_begin = static_cast<size_t>(hash_begin_i64); + auto hash_size = static_cast<size_t>(hash_end_i64) - hash_begin; + return HashBuffer(data + hash_begin, hash_size); +} +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/random_generator.h b/src/tint/fuzzers/random_generator.h new file mode 100644 index 0000000..bb9a46f --- /dev/null +++ b/src/tint/fuzzers/random_generator.h
@@ -0,0 +1,118 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_RANDOM_GENERATOR_H_ +#define SRC_TINT_FUZZERS_RANDOM_GENERATOR_H_ + +#include <memory> +#include <random> +#include <vector> + +#include "src/tint/fuzzers/random_generator_engine.h" + +namespace tint { +namespace fuzzers { + +/// Pseudo random generator utility class for fuzzing +class RandomGenerator { + public: + /// @brief Initializes using provided engine + /// @param engine - engine implementation to use + explicit RandomGenerator(std::unique_ptr<RandomGeneratorEngine> engine); + + /// @brief Creates a MersenneTwisterEngine and initializes using that + /// @param seed - seed value to use for engine + explicit RandomGenerator(uint64_t seed); + + /// Destructor + ~RandomGenerator() = default; + + /// Move Constructor + RandomGenerator(RandomGenerator&&) = default; + + /// Get uint32_t value from uniform distribution. + /// @param lower - lower bound of integer generated + /// @param upper - upper bound of integer generated + /// @returns i, where lower <= i < upper + uint32_t GetUInt32(uint32_t lower, uint32_t upper); + + /// Get uint32_t value from uniform distribution. + /// @param bound - Upper bound of integer generated + /// @returns i, where 0 <= i < bound + uint32_t GetUInt32(uint32_t bound); + + /// Get uint32_t value from uniform distribution. + /// @param lower - lower bound of integer generated + /// @param upper - upper bound of integer generated + /// @returns i, where lower <= i < upper + uint64_t GetUInt64(uint64_t lower, uint64_t upper); + + /// Get uint64_t value from uniform distribution. + /// @param bound - Upper bound of integer generated + /// @returns i, where 0 <= i < bound + uint64_t GetUInt64(uint64_t bound); + + /// Get 1 byte of pseudo-random data + /// Should be more efficient then calling GetNBytes(1); + /// @returns 1-byte of random data + uint8_t GetByte(); + + /// Get 4 bytes of pseudo-random data + /// Should be more efficient then calling GetNBytes(4); + /// @returns 4-bytes of random data + uint32_t Get4Bytes(); + + /// Get N bytes of pseudo-random data + /// @param dest - memory location to store data + /// @param n - number of bytes of data to get + void GetNBytes(uint8_t* dest, size_t n); + + /// Get random bool with even odds + /// @returns true 50% of the time and false %50 of time. + bool GetBool(); + + /// Get random bool with weighted odds + /// @param percentage - likelihood of true being returned + /// @returns true |percentage|% of the time, and false (100 - |percentage|)% + /// of the time. + bool GetWeightedBool(uint32_t percentage); + + /// Returns a randomly-chosen element from vector v. + /// @param v - the vector from which the random element will be selected. + /// @return a random element of vector v. + template <typename T> + inline T GetRandomElement(const std::vector<T>& v) { + return v[GetUInt64(0, v.size())]; + } + + /// Calculate a seed value based on a blob of data. + /// Currently hashes bytes near the front of the buffer, after skipping N + /// bytes. + /// @param data - pointer to data to base calculation off of, must be !nullptr + /// @param size - number of elements in |data|, must be > 0 + /// @returns calculated seed value + static uint64_t CalculateSeed(const uint8_t* data, size_t size); + + private: + // Disallow copy & assign + RandomGenerator(const RandomGenerator&) = delete; + RandomGenerator& operator=(const RandomGenerator&) = delete; + + std::unique_ptr<RandomGeneratorEngine> engine_; +}; // class RandomGenerator + +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_RANDOM_GENERATOR_H_
diff --git a/src/tint/fuzzers/random_generator_engine.cc b/src/tint/fuzzers/random_generator_engine.cc new file mode 100644 index 0000000..2e861e3 --- /dev/null +++ b/src/tint/fuzzers/random_generator_engine.cc
@@ -0,0 +1,26 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/random_generator_engine.h" + +namespace tint { +namespace fuzzers { + +// Not in header to avoid weak vtable warnings from clang +RandomGeneratorEngine::RandomGeneratorEngine() = default; +RandomGeneratorEngine::~RandomGeneratorEngine() = default; +RandomGeneratorEngine::RandomGeneratorEngine(RandomGeneratorEngine&&) = default; + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/random_generator_engine.h b/src/tint/fuzzers/random_generator_engine.h new file mode 100644 index 0000000..eb9e716 --- /dev/null +++ b/src/tint/fuzzers/random_generator_engine.h
@@ -0,0 +1,63 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_RANDOM_GENERATOR_ENGINE_H_ +#define SRC_TINT_FUZZERS_RANDOM_GENERATOR_ENGINE_H_ + +#include <memory> +#include <random> +#include <vector> + +namespace tint { +namespace fuzzers { + +/// Wrapper interface around STL random number engine +class RandomGeneratorEngine { + public: + /// Constructor + RandomGeneratorEngine(); + + /// Destructor + virtual ~RandomGeneratorEngine(); + + /// Move Constructor + RandomGeneratorEngine(RandomGeneratorEngine&&); + + /// Generates a random uint32_t value from uniform distribution. + /// @param lower - lower bound of integer generated + /// @param upper - upper bound of integer generated + /// @returns i, where lower <= i < upper + virtual uint32_t RandomUInt32(uint32_t lower, uint32_t upper) = 0; + + /// Generates a random uint64_t value from uniform distribution. + /// @param lower - lower bound of integer generated + /// @param upper - upper bound of integer generated + /// @returns i, where lower <= i < upper + virtual uint64_t RandomUInt64(uint64_t lower, uint64_t upper) = 0; + + /// Generates N bytes of pseudo-random data + /// @param dest - memory location to store data + /// @param n - number of bytes of data to generate + virtual void RandomNBytes(uint8_t* dest, size_t n) = 0; + + private: + // Disallow copy & assign + RandomGeneratorEngine(const RandomGeneratorEngine&) = delete; + RandomGeneratorEngine& operator=(const RandomGeneratorEngine&) = delete; +}; // class RandomGeneratorEngine + +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_RANDOM_GENERATOR_ENGINE_H_
diff --git a/src/tint/fuzzers/random_generator_test.cc b/src/tint/fuzzers/random_generator_test.cc new file mode 100644 index 0000000..182e7ab --- /dev/null +++ b/src/tint/fuzzers/random_generator_test.cc
@@ -0,0 +1,202 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/random_generator.h" + +#include <memory> + +#include "gtest/gtest.h" + +#include "src/tint/fuzzers/mersenne_twister_engine.h" + +namespace tint { +namespace fuzzers { +namespace { + +/// Implementation of RandomGeneratorEngine that just returns a stream of +/// monotonically increasing numbers. +class MonotonicEngine : public RandomGeneratorEngine { + public: + uint32_t RandomUInt32(uint32_t, uint32_t) override { return next_++; } + + uint64_t RandomUInt64(uint64_t, uint64_t) override { return next_++; } + + void RandomNBytes(uint8_t*, size_t) override { + assert(false && "MonotonicDelegate does not implement RandomNBytes"); + } + + private: + uint32_t next_ = 0; +}; + +class RandomGeneratorTest : public testing::Test { + public: + void SetUp() override { rng_ = std::make_unique<RandomGenerator>(0); } + + void TearDown() override {} + + protected: + std::unique_ptr<RandomGenerator> rng_; +}; + +#ifndef NDEBUG +TEST_F(RandomGeneratorTest, GetUInt32ReversedBoundsCrashes) { + EXPECT_DEATH(rng_->GetUInt32(10, 5), ".*"); +} + +TEST_F(RandomGeneratorTest, GetUInt32EmptyBoundsCrashes) { + EXPECT_DEATH(rng_->GetUInt32(5, 5), ".*"); +} + +TEST_F(RandomGeneratorTest, GetUInt32ZeroBoundCrashes) { + EXPECT_DEATH(rng_->GetUInt32(0u), ".*"); +} +#endif // NDEBUG + +TEST_F(RandomGeneratorTest, GetUInt32SingularReturnsOneValue) { + { + uint32_t result = rng_->GetUInt32(5u, 6u); + ASSERT_EQ(5u, result); + } + { + uint32_t result = rng_->GetUInt32(1u); + ASSERT_EQ(0u, result); + } +} + +TEST_F(RandomGeneratorTest, GetUInt32StaysInBounds) { + { + uint32_t result = rng_->GetUInt32(5u, 10u); + ASSERT_LE(5u, result); + ASSERT_GT(10u, result); + } + { + uint32_t result = rng_->GetUInt32(10u); + ASSERT_LE(0u, result); + ASSERT_GT(10u, result); + } +} + +#ifndef NDEBUG +TEST_F(RandomGeneratorTest, GetUInt64ReversedBoundsCrashes) { + EXPECT_DEATH(rng_->GetUInt64(10, 5), ".*"); +} + +TEST_F(RandomGeneratorTest, GetUInt64EmptyBoundsCrashes) { + EXPECT_DEATH(rng_->GetUInt64(5, 5), ".*"); +} + +TEST_F(RandomGeneratorTest, GetUInt64ZeroBoundCrashes) { + EXPECT_DEATH(rng_->GetUInt64(0u), ".*"); +} +#endif // NDEBUG + +TEST_F(RandomGeneratorTest, GetUInt64SingularReturnsOneValue) { + { + uint64_t result = rng_->GetUInt64(5u, 6u); + ASSERT_EQ(5u, result); + } + { + uint64_t result = rng_->GetUInt64(1u); + ASSERT_EQ(0u, result); + } +} + +TEST_F(RandomGeneratorTest, GetUInt64StaysInBounds) { + { + uint64_t result = rng_->GetUInt64(5u, 10u); + ASSERT_LE(5u, result); + ASSERT_GT(10u, result); + } + { + uint64_t result = rng_->GetUInt64(10u); + ASSERT_LE(0u, result); + ASSERT_GT(10u, result); + } +} + +TEST_F(RandomGeneratorTest, GetByte) { + rng_->GetByte(); +} + +#ifndef NDEBUG +TEST_F(RandomGeneratorTest, GetNBytesNullDataBufferCrashes) { + EXPECT_DEATH(rng_->GetNBytes(nullptr, 5), ".*"); +} +#endif // NDEBUG + +TEST_F(RandomGeneratorTest, GetNBytes) { + std::vector<uint8_t> data; + for (uint32_t i = 25; i < 1000u; i = i + 25) { + data.resize(i); + rng_->GetNBytes(data.data(), data.size()); + } +} + +TEST_F(RandomGeneratorTest, GetBool) { + rng_->GetBool(); +} + +TEST_F(RandomGeneratorTest, GetWeightedBoolZeroAlwaysFalse) { + ASSERT_FALSE(rng_->GetWeightedBool(0)); +} + +TEST_F(RandomGeneratorTest, GetWeightedBoolHundredAlwaysTrue) { + ASSERT_TRUE(rng_->GetWeightedBool(100)); +} + +#ifndef NDEBUG +TEST_F(RandomGeneratorTest, GetWeightedBoolAboveHundredCrashes) { + EXPECT_DEATH(rng_->GetWeightedBool(101), ".*"); + EXPECT_DEATH(rng_->GetWeightedBool(500), ".*"); +} +#endif // NDEBUG + +TEST_F(RandomGeneratorTest, GetWeightedBool) { + for (uint32_t i = 0; i <= 100; i++) { + rng_ = + std::make_unique<RandomGenerator>(std::make_unique<MonotonicEngine>()); + for (uint32_t j = 0; j <= 100; j++) { + if (j < i) { + ASSERT_TRUE(rng_->GetWeightedBool(i)); + } else { + ASSERT_FALSE(rng_->GetWeightedBool(i)); + } + } + } +} + +#ifndef NDEBUG +TEST_F(RandomGeneratorTest, GetRandomElementEmptyVectorCrashes) { + std::vector<uint8_t> v; + EXPECT_DEATH(rng_->GetRandomElement(v), ".*"); +} +#endif // NDEBUG + +TEST_F(RandomGeneratorTest, GetRandomElement) { + std::vector<uint32_t> v; + for (uint32_t i = 25; i < 100u; i = i + 25) { + rng_ = + std::make_unique<RandomGenerator>(std::make_unique<MonotonicEngine>()); + v.resize(i); + std::iota(v.begin(), v.end(), 0); + for (uint32_t j = 0; j < i; j++) { + EXPECT_EQ(j, rng_->GetRandomElement(v)); + } + } +} + +} // namespace +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/shuffle_transform.cc b/src/tint/fuzzers/shuffle_transform.cc new file mode 100644 index 0000000..4061104 --- /dev/null +++ b/src/tint/fuzzers/shuffle_transform.cc
@@ -0,0 +1,38 @@ +// Copyright 2022 The Tint Authors. +// +// 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 "src/tint/fuzzers/shuffle_transform.h" + +#include <random> + +#include "src/tint/program_builder.h" + +namespace tint { +namespace fuzzers { + +ShuffleTransform::ShuffleTransform(size_t seed) : seed_(seed) {} + +void ShuffleTransform::Run(CloneContext& ctx, + const tint::transform::DataMap&, + tint::transform::DataMap&) const { + auto decls = ctx.src->AST().GlobalDeclarations(); + auto rng = std::mt19937_64{seed_}; + std::shuffle(std::begin(decls), std::end(decls), rng); + for (auto* decl : decls) { + ctx.dst->AST().AddGlobalDeclaration(ctx.Clone(decl)); + } +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/shuffle_transform.h b/src/tint/fuzzers/shuffle_transform.h new file mode 100644 index 0000000..4674d35 --- /dev/null +++ b/src/tint/fuzzers/shuffle_transform.h
@@ -0,0 +1,42 @@ +// Copyright 2022 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_SHUFFLE_TRANSFORM_H_ +#define SRC_TINT_FUZZERS_SHUFFLE_TRANSFORM_H_ + +#include "src/tint/transform/transform.h" + +namespace tint { +namespace fuzzers { + +/// ShuffleTransform reorders the module scope declarations into a random order +class ShuffleTransform : public tint::transform::Transform { + public: + /// Constructor + /// @param seed the random seed to use for the shuffling + explicit ShuffleTransform(size_t seed); + + protected: + void Run(CloneContext& ctx, + const tint::transform::DataMap&, + tint::transform::DataMap&) const override; + + private: + size_t seed_; +}; + +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_SHUFFLE_TRANSFORM_H_
diff --git a/src/tint/fuzzers/tint_all_transforms_fuzzer.cc b/src/tint/fuzzers/tint_all_transforms_fuzzer.cc new file mode 100644 index 0000000..356a53c --- /dev/null +++ b/src/tint/fuzzers/tint_all_transforms_fuzzer.cc
@@ -0,0 +1,85 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/fuzzer_init.h" +#include "src/tint/fuzzers/random_generator.h" +#include "src/tint/fuzzers/tint_common_fuzzer.h" +#include "src/tint/fuzzers/transform_builder.h" + +namespace tint { +namespace fuzzers { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + { + TransformBuilder tb(data, size); + tb.AddTransform<ShuffleTransform>(); + tb.AddPlatformIndependentPasses(); + + fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kSpv); + fuzzer.SetTransformManager(tb.manager(), tb.data_map()); + fuzzer.SetDumpInput(GetCliParams().dump_input); + fuzzer.SetEnforceValidity(GetCliParams().enforce_validity); + + fuzzer.Run(data, size); + } + +#if TINT_BUILD_HLSL_WRITER + { + TransformBuilder tb(data, size); + tb.AddTransform<ShuffleTransform>(); + tb.AddPlatformIndependentPasses(); + + fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kHLSL); + fuzzer.SetTransformManager(tb.manager(), tb.data_map()); + fuzzer.SetDumpInput(GetCliParams().dump_input); + fuzzer.SetEnforceValidity(GetCliParams().enforce_validity); + + fuzzer.Run(data, size); + } +#endif // TINT_BUILD_HLSL_WRITER + +#if TINT_BUILD_MSL_WRITER + { + TransformBuilder tb(data, size); + tb.AddTransform<ShuffleTransform>(); + tb.AddPlatformIndependentPasses(); + + fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kMSL); + fuzzer.SetTransformManager(tb.manager(), tb.data_map()); + fuzzer.SetDumpInput(GetCliParams().dump_input); + fuzzer.SetEnforceValidity(GetCliParams().enforce_validity); + + fuzzer.Run(data, size); + } +#endif // TINT_BUILD_MSL_WRITER +#if TINT_BUILD_SPV_WRITER + { + TransformBuilder tb(data, size); + tb.AddTransform<ShuffleTransform>(); + tb.AddPlatformIndependentPasses(); + + fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kSpv); + fuzzer.SetTransformManager(tb.manager(), tb.data_map()); + fuzzer.SetDumpInput(GetCliParams().dump_input); + fuzzer.SetEnforceValidity(GetCliParams().enforce_validity); + + fuzzer.Run(data, size); + } +#endif // TINT_BUILD_SPV_WRITER + + return 0; +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_clone_fuzzer.cc b/src/tint/fuzzers/tint_ast_clone_fuzzer.cc new file mode 100644 index 0000000..5382f24 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_clone_fuzzer.cc
@@ -0,0 +1,116 @@ +// Copyright 2020 The Tint Authors. +// +// 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 <iostream> +#include <string> +#include <unordered_set> + +#include "src/tint/reader/wgsl/parser_impl.h" +#include "src/tint/writer/wgsl/generator.h" + +#define ASSERT_EQ(A, B) \ + do { \ + decltype(A) assert_a = (A); \ + decltype(B) assert_b = (B); \ + if (assert_a != assert_b) { \ + std::cerr << "ASSERT_EQ(" #A ", " #B ") failed:\n" \ + << #A << " was: " << assert_a << "\n" \ + << #B << " was: " << assert_b << "\n"; \ + __builtin_trap(); \ + } \ + } while (false) + +#define ASSERT_TRUE(A) \ + do { \ + decltype(A) assert_a = (A); \ + if (!assert_a) { \ + std::cerr << "ASSERT_TRUE(" #A ") failed:\n" \ + << #A << " was: " << assert_a << "\n"; \ + __builtin_trap(); \ + } \ + } while (false) + +[[noreturn]] void TintInternalCompilerErrorReporter( + const tint::diag::List& diagnostics) { + auto printer = tint::diag::Printer::create(stderr, true); + tint::diag::Formatter{}.format(diagnostics, printer.get()); + __builtin_trap(); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + std::string str(reinterpret_cast<const char*>(data), size); + + tint::SetInternalCompilerErrorReporter(&TintInternalCompilerErrorReporter); + + tint::Source::File file("test.wgsl", str); + + // Parse the wgsl, create the src program + tint::reader::wgsl::ParserImpl parser(&file); + parser.set_max_errors(1); + if (!parser.Parse()) { + return 0; + } + auto src = parser.program(); + if (!src.IsValid()) { + return 0; + } + + // Clone the src program to dst + tint::Program dst(src.Clone()); + + // Expect the printed strings to match + ASSERT_EQ(tint::Program::printer(&src), tint::Program::printer(&dst)); + + // Check that none of the AST nodes or type pointers in dst are found in src + std::unordered_set<const tint::ast::Node*> src_nodes; + for (auto* src_node : src.ASTNodes().Objects()) { + src_nodes.emplace(src_node); + } + std::unordered_set<const tint::sem::Type*> src_types; + for (auto* src_type : src.Types()) { + src_types.emplace(src_type); + } + for (auto* dst_node : dst.ASTNodes().Objects()) { + ASSERT_EQ(src_nodes.count(dst_node), 0u); + } + for (auto* dst_type : dst.Types()) { + ASSERT_EQ(src_types.count(dst_type), 0u); + } + + // Regenerate the wgsl for the src program. We use this instead of the + // original source so that reformatting doesn't impact the final wgsl + // comparison. + std::string src_wgsl; + tint::writer::wgsl::Options wgsl_options; + { + auto result = tint::writer::wgsl::Generate(&src, wgsl_options); + ASSERT_TRUE(result.success); + src_wgsl = result.wgsl; + + // Move the src program to a temporary that'll be dropped, so that the src + // program is released before we attempt to print the dst program. This + // guarantee that all the source program nodes and types are destructed and + // freed. ASAN should error if there's any remaining references in dst when + // we try to reconstruct the WGSL. + auto tmp = std::move(src); + } + + // Print the dst program, check it matches the original source + auto result = tint::writer::wgsl::Generate(&dst, wgsl_options); + ASSERT_TRUE(result.success); + auto dst_wgsl = result.wgsl; + ASSERT_EQ(src_wgsl, dst_wgsl); + + return 0; +}
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn b/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn index 2a312be..4c63bab 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn +++ b/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn
@@ -1,4 +1,4 @@ -# Copyright 2022 The Dawn Authors +# Copyright 2022 The Dawn & Tint Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,18 +12,63 @@ # See the License for the specific language governing permissions and # limitations under the License. -import("../../../../scripts/dawn_overrides_with_defaults.gni") +import("//build_overrides/build.gni") +import("../../../../tint_overrides_with_defaults.gni") -# Target aliases to ease merging Tint->Dawn +if (build_with_chromium) { + import("//third_party/protobuf/proto_library.gni") -group("tint_ast_fuzzer") { - deps = [ "${dawn_tint_dir}/src/tint/fuzzers/tint_ast_fuzzer:tint_ast_fuzzer" ] - testonly = true -} + proto_library("tint_ast_fuzzer_proto") { + sources = [ "protobufs/tint_ast_fuzzer.proto" ] + generate_python = false + use_protobuf_full = true + } -group("tint_ast_fuzzer_proto") { - deps = [ - "${dawn_tint_dir}/src/tint/fuzzers/tint_ast_fuzzer:tint_ast_fuzzer_proto", - ] - testonly = true + source_set("tint_ast_fuzzer") { + public_configs = [ + "${tint_root_dir}/src/tint:tint_config", + "${tint_root_dir}/src/tint:tint_common_config", + ] + + include_dirs = [ "${target_gen_dir}/../../../.." ] + + deps = [ + ":tint_ast_fuzzer_proto", + "${tint_root_dir}/src/tint/fuzzers:tint_fuzzer_common_src", + "//third_party/protobuf:protobuf_full", + ] + + sources = [ + "cli.cc", + "cli.h", + "expression_size.cc", + "expression_size.h", + "fuzzer.cc", + "mutation.cc", + "mutation.h", + "mutation_finder.cc", + "mutation_finder.h", + "mutation_finders/change_binary_operators.cc", + "mutation_finders/change_binary_operators.h", + "mutation_finders/replace_identifiers.cc", + "mutation_finders/replace_identifiers.h", + "mutation_finders/wrap_unary_operators.cc", + "mutation_finders/wrap_unary_operators.h", + "mutations/change_binary_operator.cc", + "mutations/change_binary_operator.h", + "mutations/replace_identifier.cc", + "mutations/replace_identifier.h", + "mutations/wrap_unary_operator.cc", + "mutations/wrap_unary_operator.h", + "mutator.cc", + "mutator.h", + "node_id_map.cc", + "node_id_map.h", + "override_cli_params.h", + "probability_context.cc", + "probability_context.h", + "protobufs/tint_ast_fuzzer.h", + "util.h", + ] + } }
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt b/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt new file mode 100644 index 0000000..1a45897 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt
@@ -0,0 +1,124 @@ +# Copyright 2021 The Tint Authors. +# +# 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. + +function(add_tint_ast_fuzzer NAME) + add_executable(${NAME} ${NAME}.cc ${AST_FUZZER_SOURCES}) + target_link_libraries(${NAME} libtint-fuzz libtint_ast_fuzzer) + tint_default_compile_options(${NAME}) + target_compile_definitions(${NAME} PRIVATE CUSTOM_MUTATOR) + target_include_directories(${NAME} PRIVATE ${CMAKE_BINARY_DIR}) +endfunction() + +set(PROTOBUF_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/protobufs/tint_ast_fuzzer.proto) + +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/protobufs) + +add_custom_command( + OUTPUT + ${CMAKE_CURRENT_BINARY_DIR}/protobufs/tint_ast_fuzzer.pb.cc + ${CMAKE_CURRENT_BINARY_DIR}/protobufs/tint_ast_fuzzer.pb.h + COMMAND + "protobuf::protoc" -I=${CMAKE_CURRENT_SOURCE_DIR}/protobufs + --cpp_out=${CMAKE_CURRENT_BINARY_DIR}/protobufs ${PROTOBUF_SOURCES} + DEPENDS ${PROTOBUF_SOURCES} + COMMENT "Generate protobuf sources from proto definition file.") + +set(LIBTINT_AST_FUZZER_SOURCES + ../mersenne_twister_engine.h + ../random_generator.h + ../random_generator_engine.h + expression_size.h + mutation.h + mutation_finder.h + mutation_finders/change_binary_operators.h + mutation_finders/replace_identifiers.h + mutation_finders/wrap_unary_operators.h + mutations/change_binary_operator.h + mutations/replace_identifier.h + mutations/wrap_unary_operator.h + mutator.h + node_id_map.h + probability_context.h + protobufs/tint_ast_fuzzer.h + util.h + ${CMAKE_CURRENT_BINARY_DIR}/protobufs/tint_ast_fuzzer.pb.h) + +set(LIBTINT_AST_FUZZER_SOURCES ${LIBTINT_AST_FUZZER_SOURCES} + ../mersenne_twister_engine.cc + ../random_generator.cc + ../random_generator_engine.cc + expression_size.cc + mutation.cc + mutation_finder.cc + mutation_finders/change_binary_operators.cc + mutation_finders/replace_identifiers.cc + mutation_finders/wrap_unary_operators.cc + mutations/change_binary_operator.cc + mutations/replace_identifier.cc + mutations/wrap_unary_operator.cc + mutator.cc + node_id_map.cc + probability_context.cc + ${CMAKE_CURRENT_BINARY_DIR}/protobufs/tint_ast_fuzzer.pb.cc) + +set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/protobufs/tint_ast_fuzzer.pb.cc PROPERTIES COMPILE_FLAGS -w) + +# Add static library target. +add_library(libtint_ast_fuzzer STATIC ${LIBTINT_AST_FUZZER_SOURCES}) +target_link_libraries(libtint_ast_fuzzer protobuf::libprotobuf libtint) +tint_default_compile_options(libtint_ast_fuzzer) +target_include_directories(libtint_ast_fuzzer PRIVATE ${CMAKE_BINARY_DIR}) + +set(AST_FUZZER_SOURCES + cli.cc + cli.h + fuzzer.cc + override_cli_params.h + ../tint_common_fuzzer.cc + ../tint_common_fuzzer.h) + +set_source_files_properties(fuzzer.cc PROPERTIES COMPILE_FLAGS -Wno-missing-prototypes) + +# Add libfuzzer targets. +# Targets back-ends according to command line arguments. +add_tint_ast_fuzzer(tint_ast_fuzzer) +# Targets back-ends individually. +add_tint_ast_fuzzer(tint_ast_hlsl_writer_fuzzer) +add_tint_ast_fuzzer(tint_ast_msl_writer_fuzzer) +add_tint_ast_fuzzer(tint_ast_spv_writer_fuzzer) +add_tint_ast_fuzzer(tint_ast_wgsl_writer_fuzzer) + +# Add tests. +if (${TINT_BUILD_TESTS}) + set(TEST_SOURCES + expression_size_test.cc + mutations/change_binary_operator_test.cc + mutations/replace_identifier_test.cc + mutations/wrap_unary_operator_test.cc) + + add_executable(tint_ast_fuzzer_unittests ${TEST_SOURCES}) + + target_include_directories( + tint_ast_fuzzer_unittests PRIVATE ${gmock_SOURCE_DIR}/include) + target_link_libraries(tint_ast_fuzzer_unittests gmock_main libtint_ast_fuzzer) + tint_default_compile_options(tint_ast_fuzzer_unittests) + target_compile_options(tint_ast_fuzzer_unittests PRIVATE + -Wno-global-constructors + -Wno-weak-vtables + -Wno-covered-switch-default) + + target_include_directories(tint_ast_fuzzer_unittests PRIVATE ${CMAKE_BINARY_DIR}) + + add_test(NAME tint_ast_fuzzer_unittests COMMAND tint_ast_fuzzer_unittests) +endif ()
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/cli.cc b/src/tint/fuzzers/tint_ast_fuzzer/cli.cc new file mode 100644 index 0000000..34a7d92 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/cli.cc
@@ -0,0 +1,167 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_ast_fuzzer/cli.h" + +#include <cstring> +#include <iostream> +#include <limits> +#include <sstream> +#include <string> +#include <utility> + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { +namespace { + +const char* const kHelpMessage = R"( +This is a fuzzer for the Tint compiler that works by mutating the AST. + +Below is a list of all supported parameters for this fuzzer. You may want to +run it with -help=1 to check out libfuzzer parameters. + + -tint_enable_all_mutations= + If `false`, the fuzzer will only apply mutations from a + randomly selected subset of mutation types. Otherwise, + all mutation types will be considered. This must be one + of `true` or `false` (without `). By default it's `false`. + + -tint_fuzzing_target= + Specifies the shading language to target during fuzzing. + This must be one or a combination of `wgsl`, `spv`, `hlsl`, + `msl` (without `) separated by commas. By default it's + `wgsl,msl,hlsl,spv`. + + -tint_help + Show this message. Note that there is also a -help=1 + parameter that will display libfuzzer's help message. + + -tint_mutation_batch_size= + The number of mutations to apply in a single libfuzzer + mutation session. This must be a numeric value that fits + in type `uint32_t`. By default it's 5. +)"; + +bool HasPrefix(const char* str, const char* prefix) { + return strncmp(str, prefix, strlen(prefix)) == 0; +} + +[[noreturn]] void InvalidParam(const char* param) { + std::cout << "Invalid value for " << param << std::endl; + std::cout << kHelpMessage << std::endl; + exit(1); +} + +bool ParseBool(const char* value, bool* out) { + if (!strcmp(value, "true")) { + *out = true; + } else if (!strcmp(value, "false")) { + *out = false; + } else { + return false; + } + return true; +} + +bool ParseUint32(const char* value, uint32_t* out) { + auto parsed = strtoul(value, nullptr, 10); + if (parsed > std::numeric_limits<uint32_t>::max()) { + return false; + } + *out = static_cast<uint32_t>(parsed); + return true; +} + +bool ParseFuzzingTarget(const char* value, FuzzingTarget* out) { + if (!strcmp(value, "wgsl")) { + *out = FuzzingTarget::kWgsl; + } else if (!strcmp(value, "spv")) { + *out = FuzzingTarget::kSpv; + } else if (!strcmp(value, "msl")) { + *out = FuzzingTarget::kMsl; + } else if (!strcmp(value, "hlsl")) { + *out = FuzzingTarget::kHlsl; + } else { + return false; + } + return true; +} + +} // namespace + +CliParams ParseCliParams(int* argc, char** argv) { + CliParams cli_params; + auto help = false; + + for (int i = *argc - 1; i > 0; --i) { + auto param = argv[i]; + auto recognized_parameter = true; + + if (HasPrefix(param, "-tint_enable_all_mutations=")) { + if (!ParseBool(param + sizeof("-tint_enable_all_mutations=") - 1, + &cli_params.enable_all_mutations)) { + InvalidParam(param); + } + } else if (HasPrefix(param, "-tint_mutation_batch_size=")) { + if (!ParseUint32(param + sizeof("-tint_mutation_batch_size=") - 1, + &cli_params.mutation_batch_size)) { + InvalidParam(param); + } + } else if (HasPrefix(param, "-tint_fuzzing_target=")) { + auto result = FuzzingTarget::kNone; + + std::stringstream ss(param + sizeof("-tint_fuzzing_target=") - 1); + for (std::string value; std::getline(ss, value, ',');) { + auto tmp = FuzzingTarget::kNone; + if (!ParseFuzzingTarget(value.c_str(), &tmp)) { + InvalidParam(param); + } + result = result | tmp; + } + + if (result == FuzzingTarget::kNone) { + InvalidParam(param); + } + + cli_params.fuzzing_target = result; + } else if (!strcmp(param, "-tint_help")) { + help = true; + } else { + recognized_parameter = false; + } + + if (recognized_parameter) { + // Remove the recognized parameter from the list of all parameters by + // swapping it with the last one. This will suppress warnings in the + // libFuzzer about unrecognized parameters. By default, libFuzzer thinks + // that all user-defined parameters start with two dashes. However, we are + // forced to use a single one to make the fuzzer compatible with the + // ClusterFuzz. + std::swap(argv[i], argv[*argc - 1]); + *argc -= 1; + } + } + + if (help) { + std::cout << kHelpMessage << std::endl; + exit(0); + } + + return cli_params; +} + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/cli.h b/src/tint/fuzzers/tint_ast_fuzzer/cli.h new file mode 100644 index 0000000..ed1bfaa --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/cli.h
@@ -0,0 +1,72 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_AST_FUZZER_CLI_H_ +#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_CLI_H_ + +#include <cstdint> + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +/// The backend this fuzzer will test. +enum class FuzzingTarget { + kNone = 0, + kHlsl = 1 << 0, + kMsl = 1 << 1, + kSpv = 1 << 2, + kWgsl = 1 << 3, + kAll = kHlsl | kMsl | kSpv | kWgsl +}; + +inline FuzzingTarget operator|(FuzzingTarget a, FuzzingTarget b) { + return static_cast<FuzzingTarget>(static_cast<int>(a) | static_cast<int>(b)); +} + +inline FuzzingTarget operator&(FuzzingTarget a, FuzzingTarget b) { + return static_cast<FuzzingTarget>(static_cast<int>(a) & static_cast<int>(b)); +} + +/// CLI parameters accepted by the fuzzer. Type -tint_help in the CLI to see the +/// help message +struct CliParams { + /// Whether to use all mutation finders or only a randomly selected subset of + /// them. + bool enable_all_mutations = false; + + /// The maximum number of mutations applied during a single mutation session + /// (i.e. a call to `ast_fuzzer::Mutate` function). + uint32_t mutation_batch_size = 5; + + /// Compiler backends we want to fuzz. + FuzzingTarget fuzzing_target = FuzzingTarget::kAll; +}; + +/// @brief Parses CLI parameters. +/// +/// This function will exit the process with non-zero return code if some +/// parameters are invalid. This function will remove recognized parameters from +/// `argv` and adjust `argc` accordingly. +/// +/// @param argc - the total number of parameters. +/// @param argv - array of all CLI parameters. +/// @return parsed parameters. +CliParams ParseCliParams(int* argc, char** argv); + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_CLI_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/expression_size.cc b/src/tint/fuzzers/tint_ast_fuzzer/expression_size.cc new file mode 100644 index 0000000..fe4a5c4 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/expression_size.cc
@@ -0,0 +1,46 @@ +// Copyright 2022 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_ast_fuzzer/expression_size.h" + +#include "src/tint/ast/traverse_expressions.h" + +namespace tint::fuzzers::ast_fuzzer { + +ExpressionSize::ExpressionSize(const Program& program) { + // By construction, all the children of an AST node are encountered before the + // node itself when iterating through a program's AST nodes. Computing + // expression sizes exploits this property: the size of a compound expression + // is computed based on the already-computed sizes of its sub-expressions. + for (const auto* node : program.ASTNodes().Objects()) { + const auto* expr_ast_node = node->As<ast::Expression>(); + if (expr_ast_node == nullptr) { + continue; + } + size_t expr_size = 0; + diag::List empty; + ast::TraverseExpressions(expr_ast_node, empty, + [&](const ast::Expression* expression) { + if (expression == expr_ast_node) { + expr_size++; + return ast::TraverseAction::Descend; + } + expr_size += expr_to_size_.at(expression); + return ast::TraverseAction::Skip; + }); + expr_to_size_[expr_ast_node] = expr_size; + } +} + +} // namespace tint::fuzzers::ast_fuzzer
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/expression_size.h b/src/tint/fuzzers/tint_ast_fuzzer/expression_size.h new file mode 100644 index 0000000..5a6d3dd --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/expression_size.h
@@ -0,0 +1,47 @@ +// Copyright 2022 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_AST_FUZZER_EXPRESSION_SIZE_H_ +#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_EXPRESSION_SIZE_H_ + +#include <unordered_map> + +#include "src/tint/ast/expression.h" +#include "src/tint/program.h" + +namespace tint::fuzzers::ast_fuzzer { + +/// This class computes the size of the subtree rooted at each expression in a +/// program, and allows these sizes to be subsequently queried. +class ExpressionSize { + public: + /// Initializes expression size information for the given program. + /// @param program - the program for which expression sizes will be computed; + /// must remain in scope as long as this instance exists. + explicit ExpressionSize(const Program& program); + + /// Returns the size of the subtree rooted at the given expression. + /// @param expression - the expression whose size should be returned. + /// @return the size of the subtree rooted at `expression`. + size_t operator()(const ast::Expression* expression) const { + return expr_to_size_.at(expression); + } + + private: + std::unordered_map<const ast::Expression*, size_t> expr_to_size_; +}; + +} // namespace tint::fuzzers::ast_fuzzer + +#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_EXPRESSION_SIZE_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/expression_size_test.cc b/src/tint/fuzzers/tint_ast_fuzzer/expression_size_test.cc new file mode 100644 index 0000000..bb6e5e7 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/expression_size_test.cc
@@ -0,0 +1,70 @@ +// Copyright 2022 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_ast_fuzzer/expression_size.h" + +#include <string> + +#include "gtest/gtest.h" + +#include "src/tint/ast/binary_expression.h" +#include "src/tint/ast/expression.h" +#include "src/tint/ast/int_literal_expression.h" +#include "src/tint/program.h" +#include "src/tint/reader/wgsl/parser.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { +namespace { + +TEST(ExpressionSizeTest, Basic) { + std::string content = R"( + fn main() { + let a = (0 + 0) * (0 + 0); + } + )"; + Source::File file("test.wgsl", content); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + ExpressionSize expression_size(program); + for (const auto* node : program.ASTNodes().Objects()) { + const auto* expr = node->As<ast::Expression>(); + if (expr == nullptr) { + continue; + } + if (expr->Is<ast::IntLiteralExpression>()) { + ASSERT_EQ(1, expression_size(expr)); + } else { + const auto* binary_expr = expr->As<ast::BinaryExpression>(); + ASSERT_TRUE(binary_expr != nullptr); + switch (binary_expr->op) { + case ast::BinaryOp::kAdd: + ASSERT_EQ(3, expression_size(expr)); + break; + case ast::BinaryOp::kMultiply: + ASSERT_EQ(7, expression_size(expr)); + break; + default: + FAIL(); + } + } + } +} + +} // namespace +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/fuzzer.cc b/src/tint/fuzzers/tint_ast_fuzzer/fuzzer.cc new file mode 100644 index 0000000..5570e06 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/fuzzer.cc
@@ -0,0 +1,134 @@ +// Copyright 2021 The Tint Authors. +// +// 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 <cstddef> +#include <cstdint> + +#include "src/tint/fuzzers/random_generator.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/cli.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/mutator.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/override_cli_params.h" +#include "src/tint/fuzzers/tint_common_fuzzer.h" +#include "src/tint/fuzzers/transform_builder.h" + +#include "src/tint/reader/wgsl/parser.h" +#include "src/tint/writer/wgsl/generator.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { +namespace { + +CliParams cli_params{}; + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) { + // Parse CLI parameters. `ParseCliParams` will call `exit` if some parameter + // is invalid. + cli_params = ParseCliParams(argc, *argv); + // For some fuzz targets it is desirable to force the values of certain CLI + // parameters after parsing. + OverrideCliParams(cli_params); + return 0; +} + +extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, + size_t size, + size_t max_size, + unsigned seed) { + Source::File file("test.wgsl", {reinterpret_cast<char*>(data), size}); + auto program = reader::wgsl::Parse(&file); + if (!program.IsValid()) { + std::cout << "Trying to mutate an invalid program:" << std::endl + << program.Diagnostics().str() << std::endl; + return 0; + } + + // Run the mutator. + RandomGenerator generator(seed); + ProbabilityContext probability_context(&generator); + program = Mutate(std::move(program), &probability_context, + cli_params.enable_all_mutations, + cli_params.mutation_batch_size, nullptr); + + if (!program.IsValid()) { + std::cout << "Mutator produced invalid WGSL:" << std::endl + << " seed: " << seed << std::endl + << program.Diagnostics().str() << std::endl; + return 0; + } + + auto result = writer::wgsl::Generate(&program, writer::wgsl::Options()); + if (!result.success) { + std::cout << "Can't generate WGSL for a valid tint::Program:" << std::endl + << result.error << std::endl; + return 0; + } + + if (result.wgsl.size() > max_size) { + return 0; + } + + // No need to worry about the \0 here. The reason is that if \0 is included by + // developer by mistake, it will be considered a part of the string and will + // cause all sorts of strange bugs. Thus, unless `data` below is used as a raw + // C string, the \0 symbol should be ignored. + std::memcpy( // NOLINT - clang-tidy warns about lack of null termination. + data, result.wgsl.data(), result.wgsl.size()); + return result.wgsl.size(); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size == 0) { + return 0; + } + + struct Target { + FuzzingTarget fuzzing_target; + OutputFormat output_format; + const char* name; + }; + + Target targets[] = {{FuzzingTarget::kWgsl, OutputFormat::kWGSL, "WGSL"}, + {FuzzingTarget::kHlsl, OutputFormat::kHLSL, "HLSL"}, + {FuzzingTarget::kMsl, OutputFormat::kMSL, "MSL"}, + {FuzzingTarget::kSpv, OutputFormat::kSpv, "SPV"}}; + + for (auto target : targets) { + if ((target.fuzzing_target & cli_params.fuzzing_target) != + target.fuzzing_target) { + continue; + } + + TransformBuilder tb(data, size); + tb.AddTransform<tint::transform::Robustness>(); + + CommonFuzzer fuzzer(InputFormat::kWGSL, target.output_format); + fuzzer.SetTransformManager(tb.manager(), tb.data_map()); + + fuzzer.Run(data, size); + if (fuzzer.HasErrors()) { + std::cout << "Fuzzing " << target.name << " produced an error" + << std::endl; + auto printer = tint::diag::Printer::create(stderr, true); + tint::diag::Formatter{}.format(fuzzer.Diagnostics(), printer.get()); + } + } + + return 0; +} + +} // namespace +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc new file mode 100644 index 0000000..c3c89a6 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc
@@ -0,0 +1,50 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_ast_fuzzer/mutation.h" + +#include <cassert> + +#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +Mutation::~Mutation() = default; + +std::unique_ptr<Mutation> Mutation::FromMessage( + const protobufs::Mutation& message) { + switch (message.mutation_case()) { + case protobufs::Mutation::kReplaceIdentifier: + return std::make_unique<MutationReplaceIdentifier>( + message.replace_identifier()); + case protobufs::Mutation::kChangeBinaryOperator: + return std::make_unique<MutationChangeBinaryOperator>( + message.change_binary_operator()); + case protobufs::Mutation::kWrapUnaryOperator: + return std::make_unique<MutationWrapUnaryOperator>( + message.wrap_unary_operator()); + case protobufs::Mutation::MUTATION_NOT_SET: + assert(false && "Mutation is not set"); + break; + } + return nullptr; +} + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation.h b/src/tint/fuzzers/tint_ast_fuzzer/mutation.h new file mode 100644 index 0000000..cc1afdd --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation.h
@@ -0,0 +1,86 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_H_ +#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_H_ + +#include <memory> +#include <vector> + +#include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.h" + +#include "src/tint/clone_context.h" +#include "src/tint/program.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +/// The base class for all the mutations in the fuzzer. Children must override +/// three methods: +/// - `IsApplicable` - checks whether it is possible to apply the mutation +/// in a manner that will lead to a valid program. +/// - `Apply` - applies the mutation. +/// - `ToMessage` - converts the mutation data into a protobuf message. +class Mutation { + public: + /// Virtual destructor. + virtual ~Mutation(); + + /// @brief Determines whether this mutation is applicable to the `program`. + /// + /// @param program - the program this mutation will be applied to. The program + /// must be valid. + /// @param node_id_map - the map from `tint::ast::` nodes to their ids. + /// @return `true` if `Apply` method can be called without breaking the + /// semantics of the `program`. + /// @return `false` otherwise. + virtual bool IsApplicable(const tint::Program& program, + const NodeIdMap& node_id_map) const = 0; + + /// @brief Applies this mutation to the `clone_context`. + /// + /// Precondition: `IsApplicable` must return `true` when invoked on the same + /// `node_id_map` and `clone_context->src` instance of `tint::Program`. A new + /// `tint::Program` that arises in `clone_context` must be valid. + /// + /// @param node_id_map - the map from `tint::ast::` nodes to their ids. + /// @param clone_context - the context that will clone the program with some + /// changes introduced by this mutation. + /// @param new_node_id_map - this map will store ids for the mutated and + /// cloned program. This argument cannot be a `nullptr` nor can it point + /// to the same object as `node_id_map`. + virtual void Apply(const NodeIdMap& node_id_map, + tint::CloneContext* clone_context, + NodeIdMap* new_node_id_map) const = 0; + + /// @return a protobuf message for this mutation. + virtual protobufs::Mutation ToMessage() const = 0; + + /// @brief Converts a protobuf message into the mutation instance. + /// + /// @param message - a protobuf message. + /// @return the instance of this class. + static std::unique_ptr<Mutation> FromMessage( + const protobufs::Mutation& message); +}; + +using MutationList = std::vector<std::unique_ptr<Mutation>>; + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.cc new file mode 100644 index 0000000..7344320 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.cc
@@ -0,0 +1,25 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +MutationFinder::~MutationFinder() = default; + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.h b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.h new file mode 100644 index 0000000..d13a440 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.h
@@ -0,0 +1,77 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDER_H_ +#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDER_H_ + +#include <memory> +#include <vector> + +#include "src/tint/fuzzers/tint_ast_fuzzer/mutation.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/probability_context.h" + +#include "src/tint/program.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +/// Instances of this class traverse the `tint::Program`, looking for +/// opportunities to apply mutations and return them to the caller. +/// +/// Ideally, the behaviour of this class (precisely, its `FindMutations` method) +/// should not be probabilistic. This is useful when mutation finders are used +/// for test case reduction, because it enables the test case reducer to +/// systematically explore all available mutations. There may be some +/// exceptions, however. For example, if a huge number of mutations is returned, +/// it would make sense to apply only a probabilistically selected subset of +/// them. +class MutationFinder { + public: + /// Virtual destructor. + virtual ~MutationFinder(); + + /// @brief Traverses the `program`, looking for opportunities to apply + /// mutations. + /// + /// @param program - the program being fuzzed. + /// @param node_id_map - a map from `tint::ast::` nodes in the `program` to + /// their unique ids. + /// @param probability_context - determines various probabilistic stuff in the + /// mutator. This should ideally be used as less as possible. + /// @return all the found mutations. + virtual MutationList FindMutations( + const tint::Program& program, + NodeIdMap* node_id_map, + ProbabilityContext* probability_context) const = 0; + + /// @brief Compute a probability of applying a single mutation, returned by + /// this class. + /// + /// @param probability_context - contains information about various + /// non-deterministic stuff in the fuzzer. + /// @return a number in the range [0; 100] which is a chance of applying a + /// mutation. + virtual uint32_t GetChanceOfApplyingMutation( + ProbabilityContext* probability_context) const = 0; +}; + +using MutationFinderList = std::vector<std::unique_ptr<MutationFinder>>; + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDER_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.cc new file mode 100644 index 0000000..dbfc36e --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.cc
@@ -0,0 +1,92 @@ +// Copyright 2022 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.h" + +#include <memory> +#include <vector> + +#include "src/tint/ast/binary_expression.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +MutationList MutationFinderChangeBinaryOperators::FindMutations( + const tint::Program& program, + NodeIdMap* node_id_map, + ProbabilityContext* probability_context) const { + MutationList result; + + // Go through each binary expression in the AST and add a mutation that + // replaces its operator with some other type-compatible operator. + + const std::vector<ast::BinaryOp> all_binary_operators = { + ast::BinaryOp::kAnd, + ast::BinaryOp::kOr, + ast::BinaryOp::kXor, + ast::BinaryOp::kLogicalAnd, + ast::BinaryOp::kLogicalOr, + ast::BinaryOp::kEqual, + ast::BinaryOp::kNotEqual, + ast::BinaryOp::kLessThan, + ast::BinaryOp::kGreaterThan, + ast::BinaryOp::kLessThanEqual, + ast::BinaryOp::kGreaterThanEqual, + ast::BinaryOp::kShiftLeft, + ast::BinaryOp::kShiftRight, + ast::BinaryOp::kAdd, + ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, + ast::BinaryOp::kDivide, + ast::BinaryOp::kModulo}; + + for (const auto* node : program.ASTNodes().Objects()) { + const auto* binary_expr = As<ast::BinaryExpression>(node); + if (!binary_expr) { + continue; + } + + // Get vector of all operators this could be replaced with. + std::vector<ast::BinaryOp> allowed_replacements; + for (auto candidate_op : all_binary_operators) { + if (MutationChangeBinaryOperator::CanReplaceBinaryOperator( + program, *binary_expr, candidate_op)) { + allowed_replacements.push_back(candidate_op); + } + } + + if (!allowed_replacements.empty()) { + // Choose an available replacement operator at random. + const ast::BinaryOp replacement = + allowed_replacements[probability_context->GetRandomIndex( + allowed_replacements)]; + // Add a mutation according to the chosen replacement. + result.push_back(std::make_unique<MutationChangeBinaryOperator>( + node_id_map->GetId(binary_expr), replacement)); + } + } + + return result; +} + +uint32_t MutationFinderChangeBinaryOperators::GetChanceOfApplyingMutation( + ProbabilityContext* probability_context) const { + return probability_context->GetChanceOfChangingBinaryOperators(); +} + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.h b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.h new file mode 100644 index 0000000..460196a --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.h
@@ -0,0 +1,42 @@ +// Copyright 2022 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_CHANGE_BINARY_OPERATORS_H_ +#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_CHANGE_BINARY_OPERATORS_H_ + +#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +/// Looks for opportunities to apply `MutationChangeBinaryOperator`. +/// +/// Concretely, for each binary expression in the module, tries to replace it +/// with a different, type-compatible operator. +class MutationFinderChangeBinaryOperators : public MutationFinder { + public: + MutationList FindMutations( + const tint::Program& program, + NodeIdMap* node_id_map, + ProbabilityContext* probability_context) const override; + uint32_t GetChanceOfApplyingMutation( + ProbabilityContext* probability_context) const override; +}; + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_CHANGE_BINARY_OPERATORS_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.cc new file mode 100644 index 0000000..59e0fa2 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.cc
@@ -0,0 +1,79 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h" + +#include <memory> + +#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/util.h" + +#include "src/tint/sem/expression.h" +#include "src/tint/sem/statement.h" +#include "src/tint/sem/variable.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +MutationList MutationFinderReplaceIdentifiers::FindMutations( + const tint::Program& program, + NodeIdMap* node_id_map, + ProbabilityContext* probability_context) const { + MutationList result; + + // Go through each variable in the AST and for each user of that variable, try + // to replace it with some other variable usage. + + for (const auto* node : program.SemNodes().Objects()) { + const auto* sem_variable = tint::As<sem::Variable>(node); + if (!sem_variable) { + continue; + } + + // Iterate over all users of `sem_variable`. + for (const auto* user : sem_variable->Users()) { + // Get all variables that can be used to replace the `user` of + // `sem_variable`. + auto candidate_variables = util::GetAllVarsInScope( + program, user->Stmt(), [user](const sem::Variable* var) { + return var != user->Variable() && var->Type() == user->Type(); + }); + + if (candidate_variables.empty()) { + // No suitable replacements have been found. + continue; + } + + const auto* replacement = + candidate_variables[probability_context->GetRandomIndex( + candidate_variables)]; + + result.push_back(std::make_unique<MutationReplaceIdentifier>( + node_id_map->GetId(user->Declaration()), + node_id_map->GetId(replacement->Declaration()))); + } + } + + return result; +} + +uint32_t MutationFinderReplaceIdentifiers::GetChanceOfApplyingMutation( + ProbabilityContext* probability_context) const { + return probability_context->GetChanceOfReplacingIdentifiers(); +} + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h new file mode 100644 index 0000000..2d8d70e --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h
@@ -0,0 +1,42 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_REPLACE_IDENTIFIERS_H_ +#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_REPLACE_IDENTIFIERS_H_ + +#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +/// Looks for opportunities to apply `MutationReplaceIdentifier`. +/// +/// Concretely, for each variable in the module, tries to replace its users with +/// the uses of some other variables. +class MutationFinderReplaceIdentifiers : public MutationFinder { + public: + MutationList FindMutations( + const tint::Program& program, + NodeIdMap* node_id_map, + ProbabilityContext* probability_context) const override; + uint32_t GetChanceOfApplyingMutation( + ProbabilityContext* probability_context) const override; +}; + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_REPLACE_IDENTIFIERS_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.cc new file mode 100644 index 0000000..52cbc45 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.cc
@@ -0,0 +1,92 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.h" + +#include <memory> +#include <vector> + +#include "src/tint/fuzzers/tint_ast_fuzzer/expression_size.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/util.h" +#include "src/tint/sem/expression.h" +#include "src/tint/sem/statement.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +namespace { +const size_t kMaxExpressionSize = 50; +} // namespace + +MutationList MutationFinderWrapUnaryOperators::FindMutations( + const tint::Program& program, + NodeIdMap* node_id_map, + ProbabilityContext* probability_context) const { + MutationList result; + + ExpressionSize expression_size(program); + + // Iterate through all ast nodes and for each expression node, try to wrap + // the inside a valid unary operator based on the type of the expression. + for (const auto* node : program.ASTNodes().Objects()) { + const auto* expr_ast_node = tint::As<ast::Expression>(node); + + // Transformation applies only when the node represents a valid expression. + if (!expr_ast_node) { + continue; + } + + if (expression_size(expr_ast_node) > kMaxExpressionSize) { + continue; + } + + const auto* expr_sem_node = + tint::As<sem::Expression>(program.Sem().Get(expr_ast_node)); + + // Transformation applies only when the semantic node for the given + // expression is present. + if (!expr_sem_node) { + continue; + } + + std::vector<ast::UnaryOp> valid_operators = + MutationWrapUnaryOperator::GetValidUnaryWrapper(*expr_sem_node); + + // Transformation only applies when there are available unary operators + // for the given expression. + if (valid_operators.empty()) { + continue; + } + + ast::UnaryOp unary_op_wrapper = + valid_operators[probability_context->GetRandomIndex(valid_operators)]; + + result.push_back(std::make_unique<MutationWrapUnaryOperator>( + node_id_map->GetId(expr_ast_node), node_id_map->TakeFreshId(), + unary_op_wrapper)); + } + + return result; +} + +uint32_t MutationFinderWrapUnaryOperators::GetChanceOfApplyingMutation( + ProbabilityContext* probability_context) const { + return probability_context->GetChanceOfWrappingUnaryOperators(); +} + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.h b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.h new file mode 100644 index 0000000..02538fc --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.h
@@ -0,0 +1,43 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_WRAP_UNARY_OPERATORS_H_ +#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_WRAP_UNARY_OPERATORS_H_ + +#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +/// Looks for opportunities to apply +/// `MutationFinderWrapUnaryOperators`. +/// +/// For each expression in the module, try to wrap it within +/// a unary operator. +class MutationFinderWrapUnaryOperators : public MutationFinder { + public: + MutationList FindMutations( + const tint::Program& program, + NodeIdMap* node_id_map, + ProbabilityContext* probability_context) const override; + uint32_t GetChanceOfApplyingMutation( + ProbabilityContext* probability_context) const override; +}; + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_WRAP_UNARY_OPERATORS_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.cc new file mode 100644 index 0000000..60a2a1c --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.cc
@@ -0,0 +1,489 @@ +// Copyright 2022 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.h" + +#include <utility> + +#include "src/tint/sem/reference_type.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +namespace { + +bool IsSuitableForShift(const sem::Type* lhs_type, const sem::Type* rhs_type) { + // `a << b` requires b to be an unsigned scalar or vector, and `a` to be an + // integer scalar or vector with the same width as `b`. Similar for `a >> b`. + + if (rhs_type->is_unsigned_integer_scalar()) { + return lhs_type->is_integer_scalar(); + } + if (rhs_type->is_unsigned_integer_vector()) { + return lhs_type->is_unsigned_integer_vector(); + } + return false; +} + +bool CanReplaceAddSubtractWith(const sem::Type* lhs_type, + const sem::Type* rhs_type, + ast::BinaryOp new_operator) { + // The program is assumed to be well-typed, so this method determines when + // 'new_operator' can be used as a type-preserving replacement in an '+' or + // '-' expression. + switch (new_operator) { + case ast::BinaryOp::kAdd: + case ast::BinaryOp::kSubtract: + // '+' and '-' are fully type compatible. + return true; + case ast::BinaryOp::kAnd: + case ast::BinaryOp::kOr: + case ast::BinaryOp::kXor: + // These operators do not have a mixed vector-scalar form, and only work + // on integer types. + return lhs_type == rhs_type && lhs_type->is_integer_scalar_or_vector(); + case ast::BinaryOp::kMultiply: + // '+' and '*' are largely type-compatible, but for matrices they are only + // type-compatible if the matrices are square. + return !lhs_type->is_float_matrix() || lhs_type->is_square_float_matrix(); + case ast::BinaryOp::kDivide: + case ast::BinaryOp::kModulo: + // '/' is not defined for matrices. + return lhs_type->is_numeric_scalar_or_vector() && + rhs_type->is_numeric_scalar_or_vector(); + case ast::BinaryOp::kShiftLeft: + case ast::BinaryOp::kShiftRight: + return IsSuitableForShift(lhs_type, rhs_type); + default: + return false; + } +} + +bool CanReplaceMultiplyWith(const sem::Type* lhs_type, + const sem::Type* rhs_type, + ast::BinaryOp new_operator) { + // The program is assumed to be well-typed, so this method determines when + // 'new_operator' can be used as a type-preserving replacement in a '*' + // expression. + switch (new_operator) { + case ast::BinaryOp::kMultiply: + return true; + case ast::BinaryOp::kAdd: + case ast::BinaryOp::kSubtract: + // '*' is type-compatible with '+' and '-' for square matrices, and for + // numeric scalars/vectors. + if (lhs_type->is_square_float_matrix() && + rhs_type->is_square_float_matrix()) { + return true; + } + return lhs_type->is_numeric_scalar_or_vector() && + rhs_type->is_numeric_scalar_or_vector(); + case ast::BinaryOp::kAnd: + case ast::BinaryOp::kOr: + case ast::BinaryOp::kXor: + // These operators require homogeneous integer types. + return lhs_type == rhs_type && lhs_type->is_integer_scalar_or_vector(); + case ast::BinaryOp::kDivide: + case ast::BinaryOp::kModulo: + // '/' is not defined for matrices. + return lhs_type->is_numeric_scalar_or_vector() && + rhs_type->is_numeric_scalar_or_vector(); + case ast::BinaryOp::kShiftLeft: + case ast::BinaryOp::kShiftRight: + return IsSuitableForShift(lhs_type, rhs_type); + default: + return false; + } +} + +bool CanReplaceDivideOrModuloWith(const sem::Type* lhs_type, + const sem::Type* rhs_type, + ast::BinaryOp new_operator) { + // The program is assumed to be well-typed, so this method determines when + // 'new_operator' can be used as a type-preserving replacement in a '/' + // expression. + switch (new_operator) { + case ast::BinaryOp::kAdd: + case ast::BinaryOp::kSubtract: + case ast::BinaryOp::kMultiply: + case ast::BinaryOp::kDivide: + case ast::BinaryOp::kModulo: + // These operators work in all contexts where '/' works. + return true; + case ast::BinaryOp::kAnd: + case ast::BinaryOp::kOr: + case ast::BinaryOp::kXor: + // These operators require homogeneous integer types. + return lhs_type == rhs_type && lhs_type->is_integer_scalar_or_vector(); + case ast::BinaryOp::kShiftLeft: + case ast::BinaryOp::kShiftRight: + return IsSuitableForShift(lhs_type, rhs_type); + default: + return false; + } +} + +bool CanReplaceLogicalAndLogicalOrWith(ast::BinaryOp new_operator) { + switch (new_operator) { + case ast::BinaryOp::kLogicalAnd: + case ast::BinaryOp::kLogicalOr: + case ast::BinaryOp::kAnd: + case ast::BinaryOp::kOr: + case ast::BinaryOp::kEqual: + case ast::BinaryOp::kNotEqual: + // These operators all work whenever '&&' and '||' work. + return true; + default: + return false; + } +} + +bool CanReplaceAndOrWith(const sem::Type* lhs_type, + const sem::Type* rhs_type, + ast::BinaryOp new_operator) { + switch (new_operator) { + case ast::BinaryOp::kAnd: + case ast::BinaryOp::kOr: + // '&' and '|' work in all the same contexts. + return true; + case ast::BinaryOp::kAdd: + case ast::BinaryOp::kSubtract: + case ast::BinaryOp::kMultiply: + case ast::BinaryOp::kDivide: + case ast::BinaryOp::kModulo: + case ast::BinaryOp::kXor: + // '&' and '|' can be applied to booleans. In all other contexts, + // integer numeric operators work. + return !lhs_type->is_bool_scalar_or_vector(); + case ast::BinaryOp::kShiftLeft: + case ast::BinaryOp::kShiftRight: + return IsSuitableForShift(lhs_type, rhs_type); + case ast::BinaryOp::kLogicalAnd: + case ast::BinaryOp::kLogicalOr: + // '&' and '|' can be applied to booleans, and for boolean scalar + // scalar contexts, their logical counterparts work. + return lhs_type->Is<sem::Bool>(); + case ast::BinaryOp::kEqual: + case ast::BinaryOp::kNotEqual: + // '&' and '|' can be applied to booleans, and in these contexts equality + // comparison operators also work. + return lhs_type->is_bool_scalar_or_vector(); + default: + return false; + } +} + +bool CanReplaceXorWith(const sem::Type* lhs_type, + const sem::Type* rhs_type, + ast::BinaryOp new_operator) { + switch (new_operator) { + case ast::BinaryOp::kAdd: + case ast::BinaryOp::kSubtract: + case ast::BinaryOp::kMultiply: + case ast::BinaryOp::kDivide: + case ast::BinaryOp::kModulo: + case ast::BinaryOp::kAnd: + case ast::BinaryOp::kOr: + case ast::BinaryOp::kXor: + // '^' only works on integer types, and in any such context, all other + // integer operators also work. + return true; + case ast::BinaryOp::kShiftLeft: + case ast::BinaryOp::kShiftRight: + return IsSuitableForShift(lhs_type, rhs_type); + default: + return false; + } +} + +bool CanReplaceShiftLeftShiftRightWith(const sem::Type* lhs_type, + const sem::Type* rhs_type, + ast::BinaryOp new_operator) { + switch (new_operator) { + case ast::BinaryOp::kShiftLeft: + case ast::BinaryOp::kShiftRight: + // These operators are type-compatible. + return true; + case ast::BinaryOp::kAdd: + case ast::BinaryOp::kSubtract: + case ast::BinaryOp::kMultiply: + case ast::BinaryOp::kDivide: + case ast::BinaryOp::kModulo: + case ast::BinaryOp::kAnd: + case ast::BinaryOp::kOr: + case ast::BinaryOp::kXor: + // Shift operators allow mixing of signed and unsigned arguments, but in + // the case where the arguments are homogeneous, they are type-compatible + // with other numeric operators. + return lhs_type == rhs_type; + default: + return false; + } +} + +bool CanReplaceEqualNotEqualWith(const sem::Type* lhs_type, + ast::BinaryOp new_operator) { + switch (new_operator) { + case ast::BinaryOp::kEqual: + case ast::BinaryOp::kNotEqual: + // These operators are type-compatible. + return true; + case ast::BinaryOp::kLessThan: + case ast::BinaryOp::kLessThanEqual: + case ast::BinaryOp::kGreaterThan: + case ast::BinaryOp::kGreaterThanEqual: + // An equality comparison between numeric types can be changed to an + // ordered comparison. + return lhs_type->is_numeric_scalar_or_vector(); + case ast::BinaryOp::kLogicalAnd: + case ast::BinaryOp::kLogicalOr: + // An equality comparison between boolean scalars can be turned into a + // logical operation. + return lhs_type->Is<sem::Bool>(); + case ast::BinaryOp::kAnd: + case ast::BinaryOp::kOr: + // An equality comparison between boolean scalars or vectors can be turned + // into a component-wise non-short-circuit logical operation. + return lhs_type->is_bool_scalar_or_vector(); + default: + return false; + } +} + +bool CanReplaceLessThanLessThanEqualGreaterThanGreaterThanEqualWith( + ast::BinaryOp new_operator) { + switch (new_operator) { + case ast::BinaryOp::kEqual: + case ast::BinaryOp::kNotEqual: + case ast::BinaryOp::kLessThan: + case ast::BinaryOp::kLessThanEqual: + case ast::BinaryOp::kGreaterThan: + case ast::BinaryOp::kGreaterThanEqual: + // Ordered comparison operators can be interchanged, and equality + // operators can be used in their place. + return true; + default: + return false; + } +} +} // namespace + +MutationChangeBinaryOperator::MutationChangeBinaryOperator( + protobufs::MutationChangeBinaryOperator message) + : message_(std::move(message)) {} + +MutationChangeBinaryOperator::MutationChangeBinaryOperator( + uint32_t binary_expr_id, + ast::BinaryOp new_operator) { + message_.set_binary_expr_id(binary_expr_id); + message_.set_new_operator(static_cast<uint32_t>(new_operator)); +} + +bool MutationChangeBinaryOperator::CanReplaceBinaryOperator( + const Program& program, + const ast::BinaryExpression& binary_expr, + ast::BinaryOp new_operator) { + if (new_operator == binary_expr.op) { + // An operator should not be replaced with itself, as this would be a no-op. + return false; + } + + // Get the types of the operators. + const auto* lhs_type = program.Sem().Get(binary_expr.lhs)->Type(); + const auto* rhs_type = program.Sem().Get(binary_expr.rhs)->Type(); + + // If these are reference types, unwrap them to get the pointee type. + const sem::Type* lhs_basic_type = + lhs_type->Is<sem::Reference>() + ? lhs_type->As<sem::Reference>()->StoreType() + : lhs_type; + const sem::Type* rhs_basic_type = + rhs_type->Is<sem::Reference>() + ? rhs_type->As<sem::Reference>()->StoreType() + : rhs_type; + + switch (binary_expr.op) { + case ast::BinaryOp::kAdd: + case ast::BinaryOp::kSubtract: + return CanReplaceAddSubtractWith(lhs_basic_type, rhs_basic_type, + new_operator); + case ast::BinaryOp::kMultiply: + return CanReplaceMultiplyWith(lhs_basic_type, rhs_basic_type, + new_operator); + case ast::BinaryOp::kDivide: + case ast::BinaryOp::kModulo: + return CanReplaceDivideOrModuloWith(lhs_basic_type, rhs_basic_type, + new_operator); + case ast::BinaryOp::kAnd: + case ast::BinaryOp::kOr: + return CanReplaceAndOrWith(lhs_basic_type, rhs_basic_type, new_operator); + case ast::BinaryOp::kXor: + return CanReplaceXorWith(lhs_basic_type, rhs_basic_type, new_operator); + case ast::BinaryOp::kShiftLeft: + case ast::BinaryOp::kShiftRight: + return CanReplaceShiftLeftShiftRightWith(lhs_basic_type, rhs_basic_type, + new_operator); + case ast::BinaryOp::kLogicalAnd: + case ast::BinaryOp::kLogicalOr: + return CanReplaceLogicalAndLogicalOrWith(new_operator); + case ast::BinaryOp::kEqual: + case ast::BinaryOp::kNotEqual: + return CanReplaceEqualNotEqualWith(lhs_basic_type, new_operator); + case ast::BinaryOp::kLessThan: + case ast::BinaryOp::kLessThanEqual: + case ast::BinaryOp::kGreaterThan: + case ast::BinaryOp::kGreaterThanEqual: + case ast::BinaryOp::kNone: + return CanReplaceLessThanLessThanEqualGreaterThanGreaterThanEqualWith( + new_operator); + assert(false && "Unreachable"); + return false; + } +} + +bool MutationChangeBinaryOperator::IsApplicable( + const Program& program, + const NodeIdMap& node_id_map) const { + const auto* binary_expr_node = + As<ast::BinaryExpression>(node_id_map.GetNode(message_.binary_expr_id())); + if (binary_expr_node == nullptr) { + // Either the id does not exist, or does not correspond to a binary + // expression. + return false; + } + // Check whether the replacement is acceptable. + const auto new_operator = static_cast<ast::BinaryOp>(message_.new_operator()); + return CanReplaceBinaryOperator(program, *binary_expr_node, new_operator); +} + +void MutationChangeBinaryOperator::Apply(const NodeIdMap& node_id_map, + CloneContext* clone_context, + NodeIdMap* new_node_id_map) const { + // Get the node whose operator is to be replaced. + const auto* binary_expr_node = + As<ast::BinaryExpression>(node_id_map.GetNode(message_.binary_expr_id())); + + // Clone the binary expression, with the appropriate new operator. + const ast::BinaryExpression* cloned_replacement; + switch (static_cast<ast::BinaryOp>(message_.new_operator())) { + case ast::BinaryOp::kAnd: + cloned_replacement = + clone_context->dst->And(clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kOr: + cloned_replacement = + clone_context->dst->Or(clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kXor: + cloned_replacement = + clone_context->dst->Xor(clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kLogicalAnd: + cloned_replacement = clone_context->dst->LogicalAnd( + clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kLogicalOr: + cloned_replacement = clone_context->dst->LogicalOr( + clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kEqual: + cloned_replacement = clone_context->dst->Equal( + clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kNotEqual: + cloned_replacement = clone_context->dst->NotEqual( + clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kLessThan: + cloned_replacement = clone_context->dst->LessThan( + clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kGreaterThan: + cloned_replacement = clone_context->dst->GreaterThan( + clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kLessThanEqual: + cloned_replacement = clone_context->dst->LessThanEqual( + clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kGreaterThanEqual: + cloned_replacement = clone_context->dst->GreaterThanEqual( + clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kShiftLeft: + cloned_replacement = + clone_context->dst->Shl(clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kShiftRight: + cloned_replacement = + clone_context->dst->Shr(clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kAdd: + cloned_replacement = + clone_context->dst->Add(clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kSubtract: + cloned_replacement = + clone_context->dst->Sub(clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kMultiply: + cloned_replacement = + clone_context->dst->Mul(clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kDivide: + cloned_replacement = + clone_context->dst->Div(clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kModulo: + cloned_replacement = + clone_context->dst->Mod(clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kNone: + cloned_replacement = nullptr; + assert(false && "Unreachable"); + } + // Set things up so that the original binary expression will be replaced with + // its clone, and update the id mapping. + clone_context->Replace(binary_expr_node, cloned_replacement); + new_node_id_map->Add(cloned_replacement, message_.binary_expr_id()); +} + +protobufs::Mutation MutationChangeBinaryOperator::ToMessage() const { + protobufs::Mutation mutation; + *mutation.mutable_change_binary_operator() = message_; + return mutation; +} + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.h b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.h new file mode 100644 index 0000000..73ac22b --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.h
@@ -0,0 +1,85 @@ +// Copyright 2022 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_CHANGE_BINARY_OPERATOR_H_ +#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_CHANGE_BINARY_OPERATOR_H_ + +#include "src/tint/fuzzers/tint_ast_fuzzer/mutation.h" + +#include "src/tint/ast/binary_expression.h" +#include "src/tint/program.h" +#include "src/tint/sem/variable.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +/// @see MutationChangeBinaryOperator::Apply +class MutationChangeBinaryOperator : public Mutation { + public: + /// @brief Constructs an instance of this mutation from a protobuf message. + /// @param message - protobuf message + explicit MutationChangeBinaryOperator( + protobufs::MutationChangeBinaryOperator message); + + /// @brief Constructor. + /// @param binary_expr_id - the id of a binary expression. + /// @param new_operator - a new binary operator to replace the one used in the + /// expression. + MutationChangeBinaryOperator(uint32_t binary_expr_id, + ast::BinaryOp new_operator); + + /// @copybrief Mutation::IsApplicable + /// + /// The mutation is applicable iff: + /// - `binary_expr_id` is a valid id of an `ast::BinaryExpression`. + /// - `new_operator` is type-compatible with the arguments of the binary + /// expression. + /// + /// @copydetails Mutation::IsApplicable + bool IsApplicable(const tint::Program& program, + const NodeIdMap& node_id_map) const override; + + /// @copybrief Mutation::Apply + /// + /// Replaces binary operator in the binary expression corresponding to + /// `binary_expr_id` with `new_operator`. + /// + /// @copydetails Mutation::Apply + void Apply(const NodeIdMap& node_id_map, + tint::CloneContext* clone_context, + NodeIdMap* new_node_id_map) const override; + + protobufs::Mutation ToMessage() const override; + + /// @brief Determines whether replacing the operator of a binary expression + /// with another operator would preserve well-typedness. + /// @param program - the program that owns the binary expression. + /// @param binary_expr - the binary expression being considered for mutation. + /// @param new_operator - a new binary operator to be checked as a candidate + /// replacement for the binary expression's operator. + /// @return `true` if and only if the replacement would be well-typed. + static bool CanReplaceBinaryOperator(const Program& program, + const ast::BinaryExpression& binary_expr, + ast::BinaryOp new_operator); + + private: + protobufs::MutationChangeBinaryOperator message_; +}; + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_CHANGE_BINARY_OPERATOR_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator_test.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator_test.cc new file mode 100644 index 0000000..d9994e3 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator_test.cc
@@ -0,0 +1,724 @@ +// Copyright 2022 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.h" + +#include <string> +#include <unordered_set> +#include <vector> + +#include "gtest/gtest.h" + +#include "src/tint/fuzzers/tint_ast_fuzzer/mutator.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h" +#include "src/tint/program_builder.h" +#include "src/tint/reader/wgsl/parser.h" +#include "src/tint/writer/wgsl/generator.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { +namespace { + +std::string OpToString(ast::BinaryOp op) { + switch (op) { + case ast::BinaryOp::kNone: + assert(false && "Unreachable"); + return ""; + case ast::BinaryOp::kAnd: + return "&"; + case ast::BinaryOp::kOr: + return "|"; + case ast::BinaryOp::kXor: + return "^"; + case ast::BinaryOp::kLogicalAnd: + return "&&"; + case ast::BinaryOp::kLogicalOr: + return "||"; + case ast::BinaryOp::kEqual: + return "=="; + case ast::BinaryOp::kNotEqual: + return "!="; + case ast::BinaryOp::kLessThan: + return "<"; + case ast::BinaryOp::kGreaterThan: + return ">"; + case ast::BinaryOp::kLessThanEqual: + return "<="; + case ast::BinaryOp::kGreaterThanEqual: + return ">="; + case ast::BinaryOp::kShiftLeft: + return "<<"; + case ast::BinaryOp::kShiftRight: + return ">>"; + case ast::BinaryOp::kAdd: + return "+"; + case ast::BinaryOp::kSubtract: + return "-"; + case ast::BinaryOp::kMultiply: + return "*"; + case ast::BinaryOp::kDivide: + return "/"; + case ast::BinaryOp::kModulo: + return "%"; + } +} + +TEST(ChangeBinaryOperatorTest, NotApplicable_Simple) { + std::string content = R"( + fn main() { + let a : i32 = 1 + 2; + } + )"; + Source::File file("test.wgsl", content); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + const auto& main_fn_stmts = program.AST().Functions()[0]->body->statements; + + const auto* a_var = + main_fn_stmts[0]->As<ast::VariableDeclStatement>()->variable; + ASSERT_NE(a_var, nullptr); + + auto a_var_id = node_id_map.GetId(a_var); + + const auto* sum_expr = a_var->constructor->As<ast::BinaryExpression>(); + ASSERT_NE(sum_expr, nullptr); + + auto sum_expr_id = node_id_map.GetId(sum_expr); + ASSERT_NE(sum_expr_id, 0); + + // binary_expr_id is invalid. + EXPECT_FALSE(MutationChangeBinaryOperator(0, ast::BinaryOp::kSubtract) + .IsApplicable(program, node_id_map)); + + // binary_expr_id is not a binary expression. + EXPECT_FALSE(MutationChangeBinaryOperator(a_var_id, ast::BinaryOp::kSubtract) + .IsApplicable(program, node_id_map)); + + // new_operator is applicable to the argument types. + EXPECT_FALSE(MutationChangeBinaryOperator(0, ast::BinaryOp::kLogicalAnd) + .IsApplicable(program, node_id_map)); + + // new_operator does not have the right result type. + EXPECT_FALSE(MutationChangeBinaryOperator(0, ast::BinaryOp::kLessThan) + .IsApplicable(program, node_id_map)); +} + +TEST(ChangeBinaryOperatorTest, Applicable_Simple) { + std::string shader = R"(fn main() { + let a : i32 = (1 + 2); +} +)"; + Source::File file("test.wgsl", shader); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + const auto& main_fn_stmts = program.AST().Functions()[0]->body->statements; + + const auto* a_var = + main_fn_stmts[0]->As<ast::VariableDeclStatement>()->variable; + ASSERT_NE(a_var, nullptr); + + const auto* sum_expr = a_var->constructor->As<ast::BinaryExpression>(); + ASSERT_NE(sum_expr, nullptr); + + auto sum_expr_id = node_id_map.GetId(sum_expr); + ASSERT_NE(sum_expr_id, 0); + + ASSERT_TRUE(MaybeApplyMutation( + program, + MutationChangeBinaryOperator(sum_expr_id, ast::BinaryOp::kSubtract), + node_id_map, &program, &node_id_map, nullptr)); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + writer::wgsl::Options options; + auto result = writer::wgsl::Generate(&program, options); + ASSERT_TRUE(result.success) << result.error; + + std::string expected_shader = R"(fn main() { + let a : i32 = (1 - 2); +} +)"; + ASSERT_EQ(expected_shader, result.wgsl); +} + +void CheckMutations( + const std::string& lhs_type, + const std::string& rhs_type, + const std::string& result_type, + ast::BinaryOp original_operator, + const std::unordered_set<ast::BinaryOp>& allowed_replacement_operators) { + std::stringstream shader; + shader << "fn foo(a : " << lhs_type << ", b : " << rhs_type + ") {\n" + << " let r : " << result_type + << " = (a " + OpToString(original_operator) << " b);\n}\n"; + + const std::vector<ast::BinaryOp> all_operators = { + ast::BinaryOp::kAnd, + ast::BinaryOp::kOr, + ast::BinaryOp::kXor, + ast::BinaryOp::kLogicalAnd, + ast::BinaryOp::kLogicalOr, + ast::BinaryOp::kEqual, + ast::BinaryOp::kNotEqual, + ast::BinaryOp::kLessThan, + ast::BinaryOp::kGreaterThan, + ast::BinaryOp::kLessThanEqual, + ast::BinaryOp::kGreaterThanEqual, + ast::BinaryOp::kShiftLeft, + ast::BinaryOp::kShiftRight, + ast::BinaryOp::kAdd, + ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, + ast::BinaryOp::kDivide, + ast::BinaryOp::kModulo}; + + for (auto new_operator : all_operators) { + Source::File file("test.wgsl", shader.str()); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + const auto& stmts = program.AST().Functions()[0]->body->statements; + + const auto* r_var = stmts[0]->As<ast::VariableDeclStatement>()->variable; + ASSERT_NE(r_var, nullptr); + + const auto* binary_expr = r_var->constructor->As<ast::BinaryExpression>(); + ASSERT_NE(binary_expr, nullptr); + + auto binary_expr_id = node_id_map.GetId(binary_expr); + ASSERT_NE(binary_expr_id, 0); + + MutationChangeBinaryOperator mutation(binary_expr_id, new_operator); + + std::stringstream expected_shader; + expected_shader << "fn foo(a : " << lhs_type << ", b : " << rhs_type + << ") {\n" + << " let r : " << result_type << " = (a " + << OpToString(new_operator) << " b);\n}\n"; + + if (allowed_replacement_operators.count(new_operator) == 0) { + ASSERT_FALSE(mutation.IsApplicable(program, node_id_map)); + if (new_operator != binary_expr->op) { + Source::File invalid_file("test.wgsl", expected_shader.str()); + auto invalid_program = reader::wgsl::Parse(&invalid_file); + ASSERT_FALSE(invalid_program.IsValid()) << program.Diagnostics().str(); + } + } else { + ASSERT_TRUE(MaybeApplyMutation(program, mutation, node_id_map, &program, + &node_id_map, nullptr)); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + writer::wgsl::Options options; + auto result = writer::wgsl::Generate(&program, options); + ASSERT_TRUE(result.success) << result.error; + + ASSERT_EQ(expected_shader.str(), result.wgsl); + } + } +} + +TEST(ChangeBinaryOperatorTest, AddSubtract) { + for (auto op : {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract}) { + const ast::BinaryOp other_op = op == ast::BinaryOp::kAdd + ? ast::BinaryOp::kSubtract + : ast::BinaryOp::kAdd; + for (std::string type : {"i32", "vec2<i32>", "vec3<i32>", "vec4<i32>"}) { + CheckMutations( + type, type, type, op, + {other_op, ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide, + ast::BinaryOp::kModulo, ast::BinaryOp::kAnd, ast::BinaryOp::kOr, + ast::BinaryOp::kXor}); + } + for (std::string type : {"u32", "vec2<u32>", "vec3<u32>", "vec4<u32>"}) { + CheckMutations( + type, type, type, op, + {other_op, ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide, + ast::BinaryOp::kModulo, ast::BinaryOp::kAnd, ast::BinaryOp::kOr, + ast::BinaryOp::kXor, ast::BinaryOp::kShiftLeft, + ast::BinaryOp::kShiftRight}); + } + for (std::string type : {"f32", "vec2<f32>", "vec3<f32>", "vec4<f32>"}) { + CheckMutations(type, type, type, op, + {other_op, ast::BinaryOp::kMultiply, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + } + for (std::string vector_type : {"vec2<i32>", "vec3<i32>", "vec4<i32>"}) { + std::string scalar_type = "i32"; + CheckMutations(vector_type, scalar_type, vector_type, op, + {other_op, ast::BinaryOp::kMultiply, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + CheckMutations(scalar_type, vector_type, vector_type, op, + {other_op, ast::BinaryOp::kMultiply, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + } + for (std::string vector_type : {"vec2<u32>", "vec3<u32>", "vec4<u32>"}) { + std::string scalar_type = "u32"; + CheckMutations(vector_type, scalar_type, vector_type, op, + {other_op, ast::BinaryOp::kMultiply, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + CheckMutations(scalar_type, vector_type, vector_type, op, + {other_op, ast::BinaryOp::kMultiply, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + } + for (std::string vector_type : {"vec2<f32>", "vec3<f32>", "vec4<f32>"}) { + std::string scalar_type = "f32"; + CheckMutations(vector_type, scalar_type, vector_type, op, + {other_op, ast::BinaryOp::kMultiply, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + CheckMutations(scalar_type, vector_type, vector_type, op, + {other_op, ast::BinaryOp::kMultiply, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + } + for (std::string square_matrix_type : + {"mat2x2<f32>", "mat3x3<f32>", "mat4x4<f32>"}) { + CheckMutations(square_matrix_type, square_matrix_type, square_matrix_type, + op, {other_op, ast::BinaryOp::kMultiply}); + } + for (std::string non_square_matrix_type : + {"mat2x3<f32>", "mat2x4<f32>", "mat3x2<f32>", "mat3x4<f32>", + "mat4x2<f32>", "mat4x3<f32>"}) { + CheckMutations(non_square_matrix_type, non_square_matrix_type, + non_square_matrix_type, op, {other_op}); + } + } +} + +TEST(ChangeBinaryOperatorTest, Mul) { + for (std::string type : {"i32", "vec2<i32>", "vec3<i32>", "vec4<i32>"}) { + CheckMutations( + type, type, type, ast::BinaryOp::kMultiply, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, ast::BinaryOp::kDivide, + ast::BinaryOp::kModulo, ast::BinaryOp::kAnd, ast::BinaryOp::kOr, + ast::BinaryOp::kXor}); + } + for (std::string type : {"u32", "vec2<u32>", "vec3<u32>", "vec4<u32>"}) { + CheckMutations( + type, type, type, ast::BinaryOp::kMultiply, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, ast::BinaryOp::kDivide, + ast::BinaryOp::kModulo, ast::BinaryOp::kAnd, ast::BinaryOp::kOr, + ast::BinaryOp::kXor, ast::BinaryOp::kShiftLeft, + ast::BinaryOp::kShiftRight}); + } + for (std::string type : {"f32", "vec2<f32>", "vec3<f32>", "vec4<f32>"}) { + CheckMutations(type, type, type, ast::BinaryOp::kMultiply, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + } + for (std::string vector_type : {"vec2<i32>", "vec3<i32>", "vec4<i32>"}) { + std::string scalar_type = "i32"; + CheckMutations(vector_type, scalar_type, vector_type, + ast::BinaryOp::kMultiply, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + CheckMutations(scalar_type, vector_type, vector_type, + ast::BinaryOp::kMultiply, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + } + for (std::string vector_type : {"vec2<u32>", "vec3<u32>", "vec4<u32>"}) { + std::string scalar_type = "u32"; + CheckMutations(vector_type, scalar_type, vector_type, + ast::BinaryOp::kMultiply, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + CheckMutations(scalar_type, vector_type, vector_type, + ast::BinaryOp::kMultiply, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + } + for (std::string vector_type : {"vec2<f32>", "vec3<f32>", "vec4<f32>"}) { + std::string scalar_type = "f32"; + CheckMutations(vector_type, scalar_type, vector_type, + ast::BinaryOp::kMultiply, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + CheckMutations(scalar_type, vector_type, vector_type, + ast::BinaryOp::kMultiply, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + } + for (std::string square_matrix_type : + {"mat2x2<f32>", "mat3x3<f32>", "mat4x4<f32>"}) { + CheckMutations(square_matrix_type, square_matrix_type, square_matrix_type, + ast::BinaryOp::kMultiply, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract}); + } + + CheckMutations("vec2<f32>", "mat2x2<f32>", "vec2<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("vec2<f32>", "mat3x2<f32>", "vec3<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("vec2<f32>", "mat4x2<f32>", "vec4<f32>", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("mat2x2<f32>", "vec2<f32>", "vec2<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat2x2<f32>", "mat3x2<f32>", "mat3x2<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat2x2<f32>", "mat4x2<f32>", "mat4x2<f32>", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("mat2x3<f32>", "vec2<f32>", "vec3<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat2x3<f32>", "mat2x2<f32>", "mat2x3<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat2x3<f32>", "mat3x2<f32>", "mat3x3<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat2x3<f32>", "mat4x2<f32>", "mat4x3<f32>", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("mat2x4<f32>", "vec2<f32>", "vec4<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat2x4<f32>", "mat2x2<f32>", "mat2x4<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat2x4<f32>", "mat3x2<f32>", "mat3x4<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat2x4<f32>", "mat4x2<f32>", "mat4x4<f32>", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("vec3<f32>", "mat2x3<f32>", "vec2<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("vec3<f32>", "mat3x3<f32>", "vec3<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("vec3<f32>", "mat4x3<f32>", "vec4<f32>", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("mat3x2<f32>", "vec3<f32>", "vec2<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat3x2<f32>", "mat2x3<f32>", "mat2x2<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat3x2<f32>", "mat3x3<f32>", "mat3x2<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat3x2<f32>", "mat4x3<f32>", "mat4x2<f32>", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("mat3x3<f32>", "vec3<f32>", "vec3<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat3x3<f32>", "mat2x3<f32>", "mat2x3<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat3x3<f32>", "mat4x3<f32>", "mat4x3<f32>", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("mat3x4<f32>", "vec3<f32>", "vec4<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat3x4<f32>", "mat2x3<f32>", "mat2x4<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat3x4<f32>", "mat3x3<f32>", "mat3x4<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat3x4<f32>", "mat4x3<f32>", "mat4x4<f32>", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("vec4<f32>", "mat2x4<f32>", "vec2<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("vec4<f32>", "mat3x4<f32>", "vec3<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("vec4<f32>", "mat4x4<f32>", "vec4<f32>", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("mat4x2<f32>", "vec4<f32>", "vec2<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat4x2<f32>", "mat2x4<f32>", "mat2x2<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat4x2<f32>", "mat3x4<f32>", "mat3x2<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat4x2<f32>", "mat4x4<f32>", "mat4x2<f32>", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("mat4x3<f32>", "vec4<f32>", "vec3<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat4x3<f32>", "mat2x4<f32>", "mat2x3<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat4x3<f32>", "mat3x4<f32>", "mat3x3<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat4x3<f32>", "mat4x4<f32>", "mat4x3<f32>", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("mat4x4<f32>", "vec4<f32>", "vec4<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat4x4<f32>", "mat2x4<f32>", "mat2x4<f32>", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat4x4<f32>", "mat3x4<f32>", "mat3x4<f32>", + ast::BinaryOp::kMultiply, {}); +} + +TEST(ChangeBinaryOperatorTest, DivideAndModulo) { + for (std::string type : {"i32", "vec2<i32>", "vec3<i32>", "vec4<i32>"}) { + CheckMutations( + type, type, type, ast::BinaryOp::kDivide, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kModulo, ast::BinaryOp::kAnd, + ast::BinaryOp::kOr, ast::BinaryOp::kXor}); + } + for (std::string type : {"u32", "vec2<u32>", "vec3<u32>", "vec4<u32>"}) { + CheckMutations( + type, type, type, ast::BinaryOp::kDivide, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kModulo, ast::BinaryOp::kAnd, + ast::BinaryOp::kOr, ast::BinaryOp::kXor, ast::BinaryOp::kShiftLeft, + ast::BinaryOp::kShiftRight}); + } + for (std::string type : {"f32", "vec2<f32>", "vec3<f32>", "vec4<f32>"}) { + CheckMutations(type, type, type, ast::BinaryOp::kDivide, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kModulo}); + } + for (std::string vector_type : {"vec2<i32>", "vec3<i32>", "vec4<i32>"}) { + std::string scalar_type = "i32"; + CheckMutations(vector_type, scalar_type, vector_type, + ast::BinaryOp::kDivide, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kModulo}); + CheckMutations(scalar_type, vector_type, vector_type, + ast::BinaryOp::kDivide, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kModulo}); + } + for (std::string vector_type : {"vec2<u32>", "vec3<u32>", "vec4<u32>"}) { + std::string scalar_type = "u32"; + CheckMutations(vector_type, scalar_type, vector_type, + ast::BinaryOp::kDivide, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kModulo}); + CheckMutations(scalar_type, vector_type, vector_type, + ast::BinaryOp::kDivide, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kModulo}); + } + for (std::string vector_type : {"vec2<f32>", "vec3<f32>", "vec4<f32>"}) { + std::string scalar_type = "f32"; + CheckMutations(vector_type, scalar_type, vector_type, + ast::BinaryOp::kDivide, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kModulo}); + CheckMutations(scalar_type, vector_type, vector_type, + ast::BinaryOp::kDivide, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kModulo}); + } + for (std::string type : {"i32", "vec2<i32>", "vec3<i32>", "vec4<i32>"}) { + CheckMutations( + type, type, type, ast::BinaryOp::kModulo, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide, ast::BinaryOp::kAnd, + ast::BinaryOp::kOr, ast::BinaryOp::kXor}); + } + for (std::string type : {"u32", "vec2<u32>", "vec3<u32>", "vec4<u32>"}) { + CheckMutations( + type, type, type, ast::BinaryOp::kModulo, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide, ast::BinaryOp::kAnd, + ast::BinaryOp::kOr, ast::BinaryOp::kXor, ast::BinaryOp::kShiftLeft, + ast::BinaryOp::kShiftRight}); + } + for (std::string type : {"f32", "vec2<f32>", "vec3<f32>", "vec4<f32>"}) { + CheckMutations(type, type, type, ast::BinaryOp::kModulo, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide}); + } + for (std::string vector_type : {"vec2<i32>", "vec3<i32>", "vec4<i32>"}) { + std::string scalar_type = "i32"; + CheckMutations(vector_type, scalar_type, vector_type, + ast::BinaryOp::kModulo, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide}); + CheckMutations(scalar_type, vector_type, vector_type, + ast::BinaryOp::kModulo, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide}); + } + for (std::string vector_type : {"vec2<u32>", "vec3<u32>", "vec4<u32>"}) { + std::string scalar_type = "u32"; + CheckMutations(vector_type, scalar_type, vector_type, + ast::BinaryOp::kModulo, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide}); + CheckMutations(scalar_type, vector_type, vector_type, + ast::BinaryOp::kModulo, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide}); + } +} + +TEST(ChangeBinaryOperatorTest, AndOrXor) { + for (auto op : + {ast::BinaryOp::kAnd, ast::BinaryOp::kOr, ast::BinaryOp::kXor}) { + std::unordered_set<ast::BinaryOp> allowed_replacement_operators_signed{ + ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide, + ast::BinaryOp::kModulo, ast::BinaryOp::kAnd, + ast::BinaryOp::kOr, ast::BinaryOp::kXor}; + allowed_replacement_operators_signed.erase(op); + for (std::string type : {"i32", "vec2<i32>", "vec3<i32>", "vec4<i32>"}) { + CheckMutations(type, type, type, op, + allowed_replacement_operators_signed); + } + std::unordered_set<ast::BinaryOp> allowed_replacement_operators_unsigned{ + ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide, + ast::BinaryOp::kModulo, ast::BinaryOp::kShiftLeft, + ast::BinaryOp::kShiftRight, ast::BinaryOp::kAnd, + ast::BinaryOp::kOr, ast::BinaryOp::kXor}; + allowed_replacement_operators_unsigned.erase(op); + for (std::string type : {"u32", "vec2<u32>", "vec3<u32>", "vec4<u32>"}) { + CheckMutations(type, type, type, op, + allowed_replacement_operators_unsigned); + } + if (op != ast::BinaryOp::kXor) { + for (std::string type : + {"bool", "vec2<bool>", "vec3<bool>", "vec4<bool>"}) { + std::unordered_set<ast::BinaryOp> allowed_replacement_operators_bool{ + ast::BinaryOp::kAnd, ast::BinaryOp::kOr, ast::BinaryOp::kEqual, + ast::BinaryOp::kNotEqual}; + allowed_replacement_operators_bool.erase(op); + if (type == "bool") { + allowed_replacement_operators_bool.insert(ast::BinaryOp::kLogicalAnd); + allowed_replacement_operators_bool.insert(ast::BinaryOp::kLogicalOr); + } + CheckMutations(type, type, type, op, + allowed_replacement_operators_bool); + } + } + } +} + +TEST(ChangeBinaryOperatorTest, EqualNotEqual) { + for (auto op : {ast::BinaryOp::kEqual, ast::BinaryOp::kNotEqual}) { + for (std::string element_type : {"i32", "u32", "f32"}) { + for (size_t element_count = 1; element_count <= 4; element_count++) { + std::stringstream argument_type; + std::stringstream result_type; + if (element_count == 1) { + argument_type << element_type; + result_type << "bool"; + } else { + argument_type << "vec" << element_count << "<" << element_type << ">"; + result_type << "vec" << element_count << "<bool>"; + } + std::unordered_set<ast::BinaryOp> allowed_replacement_operators{ + ast::BinaryOp::kLessThan, ast::BinaryOp::kLessThanEqual, + ast::BinaryOp::kGreaterThan, ast::BinaryOp::kGreaterThanEqual, + ast::BinaryOp::kEqual, ast::BinaryOp::kNotEqual}; + allowed_replacement_operators.erase(op); + CheckMutations(argument_type.str(), argument_type.str(), + result_type.str(), op, allowed_replacement_operators); + } + } + { + std::unordered_set<ast::BinaryOp> allowed_replacement_operators{ + ast::BinaryOp::kLogicalAnd, ast::BinaryOp::kLogicalOr, + ast::BinaryOp::kAnd, ast::BinaryOp::kOr, + ast::BinaryOp::kEqual, ast::BinaryOp::kNotEqual}; + allowed_replacement_operators.erase(op); + CheckMutations("bool", "bool", "bool", op, allowed_replacement_operators); + } + for (size_t element_count = 2; element_count <= 4; element_count++) { + std::stringstream argument_and_result_type; + argument_and_result_type << "vec" << element_count << "<bool>"; + std::unordered_set<ast::BinaryOp> allowed_replacement_operators{ + ast::BinaryOp::kAnd, ast::BinaryOp::kOr, ast::BinaryOp::kEqual, + ast::BinaryOp::kNotEqual}; + allowed_replacement_operators.erase(op); + CheckMutations( + argument_and_result_type.str(), argument_and_result_type.str(), + argument_and_result_type.str(), op, allowed_replacement_operators); + } + } +} + +TEST(ChangeBinaryOperatorTest, + LessThanLessThanEqualGreaterThanGreaterThanEqual) { + for (auto op : + {ast::BinaryOp::kLessThan, ast::BinaryOp::kLessThanEqual, + ast::BinaryOp::kGreaterThan, ast::BinaryOp::kGreaterThanEqual}) { + for (std::string element_type : {"i32", "u32", "f32"}) { + for (size_t element_count = 1; element_count <= 4; element_count++) { + std::stringstream argument_type; + std::stringstream result_type; + if (element_count == 1) { + argument_type << element_type; + result_type << "bool"; + } else { + argument_type << "vec" << element_count << "<" << element_type << ">"; + result_type << "vec" << element_count << "<bool>"; + } + std::unordered_set<ast::BinaryOp> allowed_replacement_operators{ + ast::BinaryOp::kLessThan, ast::BinaryOp::kLessThanEqual, + ast::BinaryOp::kGreaterThan, ast::BinaryOp::kGreaterThanEqual, + ast::BinaryOp::kEqual, ast::BinaryOp::kNotEqual}; + allowed_replacement_operators.erase(op); + CheckMutations(argument_type.str(), argument_type.str(), + result_type.str(), op, allowed_replacement_operators); + } + } + } +} + +TEST(ChangeBinaryOperatorTest, LogicalAndLogicalOr) { + for (auto op : {ast::BinaryOp::kLogicalAnd, ast::BinaryOp::kLogicalOr}) { + std::unordered_set<ast::BinaryOp> allowed_replacement_operators{ + ast::BinaryOp::kLogicalAnd, ast::BinaryOp::kLogicalOr, + ast::BinaryOp::kAnd, ast::BinaryOp::kOr, + ast::BinaryOp::kEqual, ast::BinaryOp::kNotEqual}; + allowed_replacement_operators.erase(op); + CheckMutations("bool", "bool", "bool", op, allowed_replacement_operators); + } +} + +TEST(ChangeBinaryOperatorTest, ShiftLeftShiftRight) { + for (auto op : {ast::BinaryOp::kShiftLeft, ast::BinaryOp::kShiftRight}) { + for (std::string lhs_element_type : {"i32", "u32"}) { + for (size_t element_count = 1; element_count <= 4; element_count++) { + std::stringstream lhs_and_result_type; + std::stringstream rhs_type; + if (element_count == 1) { + lhs_and_result_type << lhs_element_type; + rhs_type << "u32"; + } else { + lhs_and_result_type << "vec" << element_count << "<" + << lhs_element_type << ">"; + rhs_type << "vec" << element_count << "<u32>"; + } + std::unordered_set<ast::BinaryOp> allowed_replacement_operators{ + ast::BinaryOp::kShiftLeft, ast::BinaryOp::kShiftRight}; + allowed_replacement_operators.erase(op); + if (lhs_element_type == "u32") { + allowed_replacement_operators.insert(ast::BinaryOp::kAdd); + allowed_replacement_operators.insert(ast::BinaryOp::kSubtract); + allowed_replacement_operators.insert(ast::BinaryOp::kMultiply); + allowed_replacement_operators.insert(ast::BinaryOp::kDivide); + allowed_replacement_operators.insert(ast::BinaryOp::kModulo); + allowed_replacement_operators.insert(ast::BinaryOp::kAnd); + allowed_replacement_operators.insert(ast::BinaryOp::kOr); + allowed_replacement_operators.insert(ast::BinaryOp::kXor); + } + CheckMutations(lhs_and_result_type.str(), rhs_type.str(), + lhs_and_result_type.str(), op, + allowed_replacement_operators); + } + } + } +} + +} // namespace +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.cc new file mode 100644 index 0000000..7f09dcc --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.cc
@@ -0,0 +1,106 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h" + +#include <utility> + +#include "src/tint/fuzzers/tint_ast_fuzzer/util.h" +#include "src/tint/program_builder.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +MutationReplaceIdentifier::MutationReplaceIdentifier( + protobufs::MutationReplaceIdentifier message) + : message_(std::move(message)) {} + +MutationReplaceIdentifier::MutationReplaceIdentifier(uint32_t use_id, + uint32_t replacement_id) { + message_.set_use_id(use_id); + message_.set_replacement_id(replacement_id); +} + +bool MutationReplaceIdentifier::IsApplicable( + const tint::Program& program, + const NodeIdMap& node_id_map) const { + const auto* use_ast_node = tint::As<ast::IdentifierExpression>( + node_id_map.GetNode(message_.use_id())); + if (!use_ast_node) { + // Either the `use_id` is invalid or the node is not an + // `IdentifierExpression`. + return false; + } + + const auto* use_sem_node = + tint::As<sem::VariableUser>(program.Sem().Get(use_ast_node)); + if (!use_sem_node) { + // Either the semantic information is not present for a `use_node` or that + // node is not a variable user. + return false; + } + + const auto* replacement_ast_node = + tint::As<ast::Variable>(node_id_map.GetNode(message_.replacement_id())); + if (!replacement_ast_node) { + // Either the `replacement_id` is invalid or is not an id of a variable. + return false; + } + + const auto* replacement_sem_node = program.Sem().Get(replacement_ast_node); + if (!replacement_sem_node) { + return false; + } + + if (replacement_sem_node == use_sem_node->Variable()) { + return false; + } + + auto in_scope = + util::GetAllVarsInScope(program, use_sem_node->Stmt(), + [replacement_sem_node](const sem::Variable* var) { + return var == replacement_sem_node; + }); + if (in_scope.empty()) { + // The replacement variable is not in scope. + return false; + } + + return use_sem_node->Type() == replacement_sem_node->Type(); +} + +void MutationReplaceIdentifier::Apply(const NodeIdMap& node_id_map, + tint::CloneContext* clone_context, + NodeIdMap* new_node_id_map) const { + const auto* use_node = node_id_map.GetNode(message_.use_id()); + const auto* replacement_var = + tint::As<ast::Variable>(node_id_map.GetNode(message_.replacement_id())); + + auto* cloned_replacement = + clone_context->dst->Expr(clone_context->Clone(use_node->source), + clone_context->Clone(replacement_var->symbol)); + clone_context->Replace(use_node, cloned_replacement); + new_node_id_map->Add(cloned_replacement, message_.use_id()); +} + +protobufs::Mutation MutationReplaceIdentifier::ToMessage() const { + protobufs::Mutation mutation; + *mutation.mutable_replace_identifier() = message_; + return mutation; +} + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h b/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h new file mode 100644 index 0000000..553b8b4 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h
@@ -0,0 +1,77 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_REPLACE_IDENTIFIER_H_ +#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_REPLACE_IDENTIFIER_H_ + +#include "src/tint/fuzzers/tint_ast_fuzzer/mutation.h" + +#include "src/tint/sem/variable.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +/// @see MutationReplaceIdentifier::Apply +class MutationReplaceIdentifier : public Mutation { + public: + /// @brief Constructs an instance of this mutation from a protobuf message. + /// @param message - protobuf message + explicit MutationReplaceIdentifier( + protobufs::MutationReplaceIdentifier message); + + /// @brief Constructor. + /// @param use_id - the id of a variable user. + /// @param replacement_id - the id of a variable to replace the `use_id`. + MutationReplaceIdentifier(uint32_t use_id, uint32_t replacement_id); + + /// @copybrief Mutation::IsApplicable + /// + /// The mutation is applicable iff: + /// - `use_id` is a valid id of an `ast::IdentifierExpression`, that + /// references a variable. + /// - `replacement_id` is a valid id of an `ast::Variable`. + /// - The identifier expression doesn't reference the variable of a + /// `replacement_id`. + /// - The variable with `replacement_id` is in scope of an identifier + /// expression with `use_id`. + /// - The identifier expression and the variable have the same type. + /// + /// @copydetails Mutation::IsApplicable + bool IsApplicable(const tint::Program& program, + const NodeIdMap& node_id_map) const override; + + /// @copybrief Mutation::Apply + /// + /// Replaces the use of an identifier expression with `use_id` with a newly + /// created identifier expression, that references a variable with + /// `replacement_id`. The newly created identifier expression will have the + /// same id as the old one (i.e. `use_id`). + /// + /// @copydetails Mutation::Apply + void Apply(const NodeIdMap& node_id_map, + tint::CloneContext* clone_context, + NodeIdMap* new_node_id_map) const override; + + protobufs::Mutation ToMessage() const override; + + private: + protobufs::MutationReplaceIdentifier message_; +}; + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_REPLACE_IDENTIFIER_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier_test.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier_test.cc new file mode 100644 index 0000000..bdb730c --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier_test.cc
@@ -0,0 +1,668 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h" + +#include <string> + +#include "gtest/gtest.h" + +#include "src/tint/ast/call_statement.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/mutator.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h" +#include "src/tint/program_builder.h" +#include "src/tint/reader/wgsl/parser.h" +#include "src/tint/writer/wgsl/generator.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { +namespace { + +TEST(ReplaceIdentifierTest, NotApplicable_Simple) { + std::string content = R"( + fn main() { + let a = 5; + let c = 6; + let b = a + 5; + + let d = vec2<i32>(1, 2); + let e = d.x; + } + )"; + Source::File file("test.wgsl", content); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + const auto& main_fn_stmts = program.AST().Functions()[0]->body->statements; + + const auto* a_var = + main_fn_stmts[0]->As<ast::VariableDeclStatement>()->variable; + ASSERT_NE(a_var, nullptr); + + const auto* b_var = + main_fn_stmts[2]->As<ast::VariableDeclStatement>()->variable; + ASSERT_NE(b_var, nullptr); + + const auto* e_var = + main_fn_stmts[4]->As<ast::VariableDeclStatement>()->variable; + ASSERT_NE(e_var, nullptr); + + auto a_var_id = node_id_map.GetId(a_var); + ASSERT_NE(a_var_id, 0); + + auto b_var_id = node_id_map.GetId(b_var); + ASSERT_NE(b_var_id, 0); + + const auto* sum_expr = b_var->constructor->As<ast::BinaryExpression>(); + ASSERT_NE(sum_expr, nullptr); + + auto a_ident_id = node_id_map.GetId(sum_expr->lhs); + ASSERT_NE(a_ident_id, 0); + + auto sum_expr_id = node_id_map.GetId(sum_expr); + ASSERT_NE(sum_expr_id, 0); + + auto e_var_id = node_id_map.GetId(e_var); + ASSERT_NE(e_var_id, 0); + + auto vec_member_access_id = node_id_map.GetId( + e_var->constructor->As<ast::MemberAccessorExpression>()->member); + ASSERT_NE(vec_member_access_id, 0); + + // use_id is invalid. + EXPECT_FALSE(MutationReplaceIdentifier(0, a_var_id) + .IsApplicable(program, node_id_map)); + + // use_id is not an identifier expression. + EXPECT_FALSE(MutationReplaceIdentifier(sum_expr_id, a_var_id) + .IsApplicable(program, node_id_map)); + + // use_id is an identifier but not a variable user. + EXPECT_FALSE(MutationReplaceIdentifier(vec_member_access_id, a_var_id) + .IsApplicable(program, node_id_map)); + + // replacement_id is invalid. + EXPECT_FALSE(MutationReplaceIdentifier(a_ident_id, 0) + .IsApplicable(program, node_id_map)); + + // replacement_id is not a variable. + EXPECT_FALSE(MutationReplaceIdentifier(a_ident_id, sum_expr_id) + .IsApplicable(program, node_id_map)); + + // Can't replace a variable with itself. + EXPECT_FALSE(MutationReplaceIdentifier(a_ident_id, a_var_id) + .IsApplicable(program, node_id_map)); + + // Replacement is not in scope. + EXPECT_FALSE(MutationReplaceIdentifier(a_ident_id, b_var_id) + .IsApplicable(program, node_id_map)); + EXPECT_FALSE(MutationReplaceIdentifier(a_ident_id, e_var_id) + .IsApplicable(program, node_id_map)); +} + +TEST(ReplaceIdentifierTest, GlobalVarNotInScope) { + // Can't use the global variable if it's not in scope. + std::string shader = R"( +var<private> a: i32; + +fn f() { + a = 3; +} + +var<private> b: i32; +)"; + Source::File file("test.wgsl", shader); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + auto use_id = node_id_map.GetId(program.AST() + .Functions()[0] + ->body->statements[0] + ->As<ast::AssignmentStatement>() + ->lhs); + ASSERT_NE(use_id, 0); + + auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[1]); + ASSERT_NE(replacement_id, 0); + + ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id) + .IsApplicable(program, node_id_map)); +} + +TEST(ReplaceIdentifierTest, NotApplicable1) { + // Can't replace `a` with `b` since the store type is wrong (the same storage + // class though). + std::string shader = R"( +var<private> a: i32; +var<private> b: u32; +fn f() { + *&a = 4; +} +)"; + Source::File file("test.wgsl", shader); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[1]); + ASSERT_NE(replacement_id, 0); + + auto use_id = node_id_map.GetId(program.AST() + .Functions()[0] + ->body->statements[0] + ->As<ast::AssignmentStatement>() + ->lhs->As<ast::UnaryOpExpression>() + ->expr->As<ast::UnaryOpExpression>() + ->expr); + ASSERT_NE(use_id, 0); + + ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id) + .IsApplicable(program, node_id_map)); +} + +TEST(ReplaceIdentifierTest, NotApplicable2) { + // Can't replace `a` with `b` since the store type is wrong (the storage + // class is different though). + std::string shader = R"( +var<private> a: i32; +fn f() { + var b: u32; + *&a = 4; +} +)"; + Source::File file("test.wgsl", shader); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + auto replacement_id = node_id_map.GetId(program.AST() + .Functions()[0] + ->body->statements[0] + ->As<ast::VariableDeclStatement>() + ->variable); + ASSERT_NE(replacement_id, 0); + + auto use_id = node_id_map.GetId(program.AST() + .Functions()[0] + ->body->statements[1] + ->As<ast::AssignmentStatement>() + ->lhs->As<ast::UnaryOpExpression>() + ->expr->As<ast::UnaryOpExpression>() + ->expr); + ASSERT_NE(use_id, 0); + + ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id) + .IsApplicable(program, node_id_map)); +} + +TEST(ReplaceIdentifierTest, NotApplicable3) { + // Can't replace `a` with `b` since the latter is not a reference (the store + // type is the same, though). + std::string shader = R"( +var<private> a: i32; +fn f() { + let b = 45; + *&a = 4; +} +)"; + Source::File file("test.wgsl", shader); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + auto replacement_id = node_id_map.GetId(program.AST() + .Functions()[0] + ->body->statements[0] + ->As<ast::VariableDeclStatement>() + ->variable); + ASSERT_NE(replacement_id, 0); + + auto use_id = node_id_map.GetId(program.AST() + .Functions()[0] + ->body->statements[1] + ->As<ast::AssignmentStatement>() + ->lhs->As<ast::UnaryOpExpression>() + ->expr->As<ast::UnaryOpExpression>() + ->expr); + ASSERT_NE(use_id, 0); + + ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id) + .IsApplicable(program, node_id_map)); +} + +TEST(ReplaceIdentifierTest, NotApplicable4) { + // Can't replace `a` with `b` since the latter is not a reference (the store + // type is the same, though). + std::string shader = R"( +var<private> a: i32; +fn f(b: i32) { + *&a = 4; +} +)"; + Source::File file("test.wgsl", shader); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + auto replacement_id = + node_id_map.GetId(program.AST().Functions()[0]->params[0]); + ASSERT_NE(replacement_id, 0); + + auto use_id = node_id_map.GetId(program.AST() + .Functions()[0] + ->body->statements[0] + ->As<ast::AssignmentStatement>() + ->lhs->As<ast::UnaryOpExpression>() + ->expr->As<ast::UnaryOpExpression>() + ->expr); + ASSERT_NE(use_id, 0); + + ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id) + .IsApplicable(program, node_id_map)); +} + +TEST(ReplaceIdentifierTest, NotApplicable5) { + // Can't replace `a` with `b` since the latter has a wrong access mode + // (`read` for uniform storage class). + std::string shader = R"( +struct S { + a: i32; +}; + +var<private> a: S; +@group(1) @binding(1) var<uniform> b: S; +fn f() { + *&a = S(4); +} +)"; + Source::File file("test.wgsl", shader); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[1]); + ASSERT_NE(replacement_id, 0); + + auto use_id = node_id_map.GetId(program.AST() + .Functions()[0] + ->body->statements[0] + ->As<ast::AssignmentStatement>() + ->lhs->As<ast::UnaryOpExpression>() + ->expr->As<ast::UnaryOpExpression>() + ->expr); + ASSERT_NE(use_id, 0); + + ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id) + .IsApplicable(program, node_id_map)); +} + +TEST(ReplaceIdentifierTest, NotApplicable6) { + // Can't replace `ptr_b` with `a` since the latter is not a pointer. + std::string shader = R"( +struct S { + a: i32; +}; + +var<private> a: S; +@group(1) @binding(1) var<uniform> b: S; +fn f() { + let ptr_b = &b; + *&a = *ptr_b; +} +)"; + Source::File file("test.wgsl", shader); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[0]); + ASSERT_NE(replacement_id, 0); + + auto use_id = node_id_map.GetId(program.AST() + .Functions()[0] + ->body->statements[1] + ->As<ast::AssignmentStatement>() + ->rhs->As<ast::UnaryOpExpression>() + ->expr); + ASSERT_NE(use_id, 0); + + ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id) + .IsApplicable(program, node_id_map)); +} + +TEST(ReplaceIdentifierTest, NotApplicable8) { + // Can't replace `ptr_b` with `c` since the latter has a wrong access mode and + // storage class. + std::string shader = R"( +struct S { + a: i32; +}; + +var<private> a: S; +@group(1) @binding(1) var<uniform> b: S; +@group(1) @binding(2) var<storage, write> c: S; +fn f() { + let ptr_b = &b; + *&a = *ptr_b; +} +)"; + Source::File file("test.wgsl", shader); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[2]); + ASSERT_NE(replacement_id, 0); + + auto use_id = node_id_map.GetId(program.AST() + .Functions()[0] + ->body->statements[1] + ->As<ast::AssignmentStatement>() + ->rhs->As<ast::UnaryOpExpression>() + ->expr); + ASSERT_NE(use_id, 0); + + ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id) + .IsApplicable(program, node_id_map)); +} + +TEST(ReplaceIdentifierTest, NotApplicable9) { + // Can't replace `b` with `e` since the latter is not a reference. + std::string shader = R"( +struct S { + a: i32; +}; + +var<private> a: S; +let e = 3; +@group(1) @binding(1) var<uniform> b: S; +fn f() { + *&a = *&b; +} +)"; + Source::File file("test.wgsl", shader); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[1]); + ASSERT_NE(replacement_id, 0); + + auto use_id = node_id_map.GetId(program.AST() + .Functions()[0] + ->body->statements[0] + ->As<ast::AssignmentStatement>() + ->rhs->As<ast::UnaryOpExpression>() + ->expr->As<ast::UnaryOpExpression>() + ->expr); + ASSERT_NE(use_id, 0); + + ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id) + .IsApplicable(program, node_id_map)); +} + +TEST(ReplaceIdentifierTest, NotApplicable10) { + // Can't replace `b` with `e` since the latter has a wrong access mode. + std::string shader = R"( +struct S { + a: i32; +}; + +var<private> a: S; +@group(0) @binding(0) var<storage, write> e: S; +@group(1) @binding(1) var<uniform> b: S; +fn f() { + *&a = *&b; +} +)"; + Source::File file("test.wgsl", shader); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[1]); + ASSERT_NE(replacement_id, 0); + + auto use_id = node_id_map.GetId(program.AST() + .Functions()[0] + ->body->statements[0] + ->As<ast::AssignmentStatement>() + ->rhs->As<ast::UnaryOpExpression>() + ->expr->As<ast::UnaryOpExpression>() + ->expr); + ASSERT_NE(use_id, 0); + + ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id) + .IsApplicable(program, node_id_map)); +} + +TEST(ReplaceIdentifierTest, Applicable1) { + // Can replace `a` with `b` (same storage class). + std::string shader = R"( +fn f() { + var b : vec2<u32>; + var a = vec2<u32>(34u, 45u); + (*&a)[1] = 3u; +} +)"; + Source::File file("test.wgsl", shader); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + auto use_id = node_id_map.GetId(program.AST() + .Functions()[0] + ->body->statements[2] + ->As<ast::AssignmentStatement>() + ->lhs->As<ast::IndexAccessorExpression>() + ->object->As<ast::UnaryOpExpression>() + ->expr->As<ast::UnaryOpExpression>() + ->expr); + ASSERT_NE(use_id, 0); + + auto replacement_id = node_id_map.GetId(program.AST() + .Functions()[0] + ->body->statements[0] + ->As<ast::VariableDeclStatement>() + ->variable); + ASSERT_NE(replacement_id, 0); + + ASSERT_TRUE(MaybeApplyMutation( + program, MutationReplaceIdentifier(use_id, replacement_id), node_id_map, + &program, &node_id_map, nullptr)); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + writer::wgsl::Options options; + auto result = writer::wgsl::Generate(&program, options); + ASSERT_TRUE(result.success) << result.error; + + std::string expected_shader = R"(fn f() { + var b : vec2<u32>; + var a = vec2<u32>(34u, 45u); + (*(&(b)))[1] = 3u; +} +)"; + ASSERT_EQ(expected_shader, result.wgsl); +} + +TEST(ReplaceIdentifierTest, Applicable2) { + // Can replace `ptr_a` with `b` - the function parameter. + std::string shader = R"( +fn f(b: ptr<function, vec2<u32>>) { + var a = vec2<u32>(34u, 45u); + let ptr_a = &a; + (*ptr_a)[1] = 3u; +} +)"; + Source::File file("test.wgsl", shader); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + auto use_id = node_id_map.GetId(program.AST() + .Functions()[0] + ->body->statements[2] + ->As<ast::AssignmentStatement>() + ->lhs->As<ast::IndexAccessorExpression>() + ->object->As<ast::UnaryOpExpression>() + ->expr); + ASSERT_NE(use_id, 0); + + auto replacement_id = + node_id_map.GetId(program.AST().Functions()[0]->params[0]); + ASSERT_NE(replacement_id, 0); + + ASSERT_TRUE(MaybeApplyMutation( + program, MutationReplaceIdentifier(use_id, replacement_id), node_id_map, + &program, &node_id_map, nullptr)); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + writer::wgsl::Options options; + auto result = writer::wgsl::Generate(&program, options); + ASSERT_TRUE(result.success) << result.error; + + std::string expected_shader = R"(fn f(b : ptr<function, vec2<u32>>) { + var a = vec2<u32>(34u, 45u); + let ptr_a = &(a); + (*(b))[1] = 3u; +} +)"; + ASSERT_EQ(expected_shader, result.wgsl); +} + +TEST(ReplaceIdentifierTest, NotApplicable12) { + // Can't replace `a` with `b` (both are references with different storage + // class). + std::string shader = R"( +var<private> b : vec2<u32>; +fn f() { + var a = vec2<u32>(34u, 45u); + (*&a)[1] = 3u; +} +)"; + Source::File file("test.wgsl", shader); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + auto use_id = node_id_map.GetId(program.AST() + .Functions()[0] + ->body->statements[1] + ->As<ast::AssignmentStatement>() + ->lhs->As<ast::IndexAccessorExpression>() + ->object->As<ast::UnaryOpExpression>() + ->expr->As<ast::UnaryOpExpression>() + ->expr); + ASSERT_NE(use_id, 0); + + auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[0]); + ASSERT_NE(replacement_id, 0); + + ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id) + .IsApplicable(program, node_id_map)); +} + +TEST(ReplaceIdentifierTest, NotApplicable13) { + // Can't replace `a` with `b` (both are references with different storage + // class). + std::string shader = R"( +var<private> b : vec2<u32>; +fn f() { + var a = vec2<u32>(34u, 45u); + let c = (*&a)[1]; +} +)"; + Source::File file("test.wgsl", shader); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + auto use_id = node_id_map.GetId( + program.AST() + .Functions()[0] + ->body->statements[1] + ->As<ast::VariableDeclStatement>() + ->variable->constructor->As<ast::IndexAccessorExpression>() + ->object->As<ast::UnaryOpExpression>() + ->expr->As<ast::UnaryOpExpression>() + ->expr); + ASSERT_NE(use_id, 0); + + auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[0]); + ASSERT_NE(replacement_id, 0); + + ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id) + .IsApplicable(program, node_id_map)); +} + +TEST(ReplaceIdentifierTest, NotApplicable14) { + // Can't replace `ptr_a` with `ptr_b` (both are pointers with different + // storage class). + std::string shader = R"( +var<private> b: vec2<u32>; +fn f() { + var a = vec2<u32>(34u, 45u); + let ptr_a = &a; + let ptr_b = &b; + let c = (*ptr_a)[1]; +} +)"; + Source::File file("test.wgsl", shader); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + auto use_id = node_id_map.GetId( + program.AST() + .Functions()[0] + ->body->statements[3] + ->As<ast::VariableDeclStatement>() + ->variable->constructor->As<ast::IndexAccessorExpression>() + ->object->As<ast::UnaryOpExpression>() + ->expr); + ASSERT_NE(use_id, 0); + + auto replacement_id = node_id_map.GetId(program.AST() + .Functions()[0] + ->body->statements[2] + ->As<ast::VariableDeclStatement>() + ->variable); + ASSERT_NE(replacement_id, 0); + ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id) + .IsApplicable(program, node_id_map)); +} + +} // namespace +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc new file mode 100644 index 0000000..1f5d5ae --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc
@@ -0,0 +1,127 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h" + +#include <utility> +#include <vector> + +#include "src/tint/program_builder.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +MutationWrapUnaryOperator::MutationWrapUnaryOperator( + protobufs::MutationWrapUnaryOperator message) + : message_(std::move(message)) {} + +MutationWrapUnaryOperator::MutationWrapUnaryOperator( + uint32_t expression_id, + uint32_t fresh_id, + ast::UnaryOp unary_op_wrapper) { + message_.set_expression_id(expression_id); + message_.set_fresh_id(fresh_id); + message_.set_unary_op_wrapper(static_cast<uint32_t>(unary_op_wrapper)); +} + +bool MutationWrapUnaryOperator::IsApplicable( + const tint::Program& program, + const NodeIdMap& node_id_map) const { + // Check if id that will be assigned is fresh. + if (!node_id_map.IdIsFreshAndValid(message_.fresh_id())) { + return false; + } + + const auto* expression_ast_node = + tint::As<ast::Expression>(node_id_map.GetNode(message_.expression_id())); + + if (!expression_ast_node) { + // Either the node is not present with the given id or + // the node is not a valid expression type. + return false; + } + + const auto* expression_sem_node = + tint::As<sem::Expression>(program.Sem().Get(expression_ast_node)); + + if (!expression_sem_node) { + // Semantic information for the expression ast node is not present + // or the semantic node is not a valid expression type node. + return false; + } + + ast::UnaryOp unary_op_wrapper = + static_cast<ast::UnaryOp>(message_.unary_op_wrapper()); + + std::vector<ast::UnaryOp> valid_ops = + GetValidUnaryWrapper(*expression_sem_node); + + // There is no available unary operator or |unary_op_wrapper| is a + // type that is not allowed for the given expression. + if (std::find(valid_ops.begin(), valid_ops.end(), unary_op_wrapper) == + valid_ops.end()) { + return false; + } + + return true; +} + +void MutationWrapUnaryOperator::Apply(const NodeIdMap& node_id_map, + tint::CloneContext* clone_context, + NodeIdMap* new_node_id_map) const { + auto* expression_node = + tint::As<ast::Expression>(node_id_map.GetNode(message_.expression_id())); + + auto* replacement_expression_node = + clone_context->dst->create<ast::UnaryOpExpression>( + static_cast<ast::UnaryOp>(message_.unary_op_wrapper()), + clone_context->Clone(expression_node)); + + clone_context->Replace(expression_node, replacement_expression_node); + + new_node_id_map->Add(replacement_expression_node, message_.fresh_id()); +} + +protobufs::Mutation MutationWrapUnaryOperator::ToMessage() const { + protobufs::Mutation mutation; + *mutation.mutable_wrap_unary_operator() = message_; + return mutation; +} + +std::vector<ast::UnaryOp> MutationWrapUnaryOperator::GetValidUnaryWrapper( + const sem::Expression& expr) { + const auto* expr_type = expr.Type(); + if (expr_type->is_bool_scalar_or_vector()) { + return {ast::UnaryOp::kNot}; + } + + if (expr_type->is_signed_scalar_or_vector()) { + return {ast::UnaryOp::kNegation, ast::UnaryOp::kComplement}; + } + + if (expr_type->is_unsigned_scalar_or_vector()) { + return {ast::UnaryOp::kComplement}; + } + + if (expr_type->is_float_scalar_or_vector()) { + return {ast::UnaryOp::kNegation}; + } + + return {}; +} + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h new file mode 100644 index 0000000..25fec00 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h
@@ -0,0 +1,85 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_WRAP_UNARY_OPERATOR_H_ +#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_WRAP_UNARY_OPERATOR_H_ + +#include <vector> + +#include "src/tint/fuzzers/tint_ast_fuzzer/mutation.h" + +#include "src/tint/sem/variable.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +/// @see MutationWrapUnaryOperator::Apply +class MutationWrapUnaryOperator : public Mutation { + public: + /// @brief Constructs an instance of this mutation from a protobuf message. + /// @param message - protobuf message + explicit MutationWrapUnaryOperator( + protobufs::MutationWrapUnaryOperator message); + + /// @brief Constructor. + /// @param expression_id - the id of an expression. + /// @param fresh_id - a fresh id for the created expression node with + /// unary operator wrapper. + /// @param unary_op_wrapper - a `ast::UnaryOp` instance. + MutationWrapUnaryOperator(uint32_t expression_id, + uint32_t fresh_id, + ast::UnaryOp unary_op_wrapper); + + /// @copybrief Mutation::IsApplicable + /// + /// The mutation is applicable iff: + /// - `expression_id` must refer to a valid expression that can be wrapped + /// with unary operator. + /// - `fresh_id` must be fresh. + /// - `unary_op_wrapper` is a unary expression that is valid based on the + /// type of the given expression. + /// + /// @copydetails Mutation::IsApplicable + bool IsApplicable(const tint::Program& program, + const NodeIdMap& node_id_map) const override; + + /// @copybrief Mutation::Apply + /// + /// Wrap an expression in a unary operator that is valid based on + /// the type of the expression. + /// + /// @copydetails Mutation::Apply + void Apply(const NodeIdMap& node_id_map, + tint::CloneContext* clone_context, + NodeIdMap* new_node_id_map) const override; + + protobufs::Mutation ToMessage() const override; + + /// Return list of unary operator wrappers allowed for the given + /// expression. + /// @param expr - an `ast::Expression` instance from node id map. + /// @return a list of unary operators. + static std::vector<ast::UnaryOp> GetValidUnaryWrapper( + const sem::Expression& expr); + + private: + protobufs::MutationWrapUnaryOperator message_; +}; + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_WRAP_UNARY_OPERATOR_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator_test.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator_test.cc new file mode 100644 index 0000000..4b8435e --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator_test.cc
@@ -0,0 +1,548 @@ +// Copyright 2021 The Tint Authors. +// +// 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 <string> + +#include "gtest/gtest.h" + +#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/mutator.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/probability_context.h" +#include "src/tint/program_builder.h" +#include "src/tint/reader/wgsl/parser.h" +#include "src/tint/writer/wgsl/generator.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { +namespace { + +TEST(WrapUnaryOperatorTest, Applicable1) { + std::string content = R"( + fn main() { + var a = 5; + if (a < 5) { + a = 6; + } + } + )"; + Source::File file("test.wgsl", content); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + const auto& main_fn_statements = + program.AST().Functions()[0]->body->statements; + + auto expression_id = node_id_map.GetId( + main_fn_statements[1]->As<ast::IfStatement>()->condition); + ASSERT_NE(expression_id, 0); + + ASSERT_TRUE(MaybeApplyMutation( + program, + MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(), + ast::UnaryOp::kNot), + node_id_map, &program, &node_id_map, nullptr)); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + writer::wgsl::Options options; + auto result = writer::wgsl::Generate(&program, options); + ASSERT_TRUE(result.success) << result.error; + + std::string expected_shader = R"(fn main() { + var a = 5; + if (!((a < 5))) { + a = 6; + } +} +)"; + ASSERT_EQ(expected_shader, result.wgsl); +} + +TEST(WrapUnaryOperatorTest, Applicable2) { + std::string content = R"( + fn main() { + let a = vec3<bool>(true, false, true); + } + )"; + Source::File file("test.wgsl", content); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + const auto& main_fn_statements = + program.AST().Functions()[0]->body->statements; + + const auto* expr = main_fn_statements[0] + ->As<ast::VariableDeclStatement>() + ->variable->constructor->As<ast::Expression>(); + + const auto expression_id = node_id_map.GetId(expr); + ASSERT_NE(expression_id, 0); + + ASSERT_TRUE(MaybeApplyMutation( + program, + MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(), + ast::UnaryOp::kNot), + node_id_map, &program, &node_id_map, nullptr)); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + writer::wgsl::Options options; + auto result = writer::wgsl::Generate(&program, options); + ASSERT_TRUE(result.success) << result.error; + + std::string expected_shader = R"(fn main() { + let a = !(vec3<bool>(true, false, true)); +} +)"; + ASSERT_EQ(expected_shader, result.wgsl); +} + +TEST(WrapUnaryOperatorTest, Applicable3) { + std::string content = R"( + fn main() { + var a : u32; + a = 6u; + } + )"; + Source::File file("test.wgsl", content); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + const auto& main_fn_statements = + program.AST().Functions()[0]->body->statements; + + const auto* expr = main_fn_statements[1]->As<ast::AssignmentStatement>()->rhs; + + const auto expression_id = node_id_map.GetId(expr); + ASSERT_NE(expression_id, 0); + + ASSERT_TRUE(MaybeApplyMutation( + program, + MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(), + ast::UnaryOp::kComplement), + node_id_map, &program, &node_id_map, nullptr)); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + writer::wgsl::Options options; + auto result = writer::wgsl::Generate(&program, options); + ASSERT_TRUE(result.success) << result.error; + + std::string expected_shader = R"(fn main() { + var a : u32; + a = ~(6u); +} +)"; + ASSERT_EQ(expected_shader, result.wgsl); +} + +TEST(WrapUnaryOperatorTest, Applicable4) { + std::string content = R"( + fn main() -> vec2<bool> { + var a = (vec2<u32> (1u, 2u) == vec2<u32> (1u, 2u)); + return a; + } + )"; + Source::File file("test.wgsl", content); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + const auto& main_fn_statements = + program.AST().Functions()[0]->body->statements; + + const auto* expr = main_fn_statements[0] + ->As<ast::VariableDeclStatement>() + ->variable->constructor->As<ast::BinaryExpression>() + ->lhs; + + const auto expression_id = node_id_map.GetId(expr); + ASSERT_NE(expression_id, 0); + + ASSERT_TRUE(MaybeApplyMutation( + program, + MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(), + ast::UnaryOp::kComplement), + node_id_map, &program, &node_id_map, nullptr)); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + writer::wgsl::Options options; + auto result = writer::wgsl::Generate(&program, options); + ASSERT_TRUE(result.success) << result.error; + + std::string expected_shader = R"(fn main() -> vec2<bool> { + var a = (~(vec2<u32>(1u, 2u)) == vec2<u32>(1u, 2u)); + return a; +} +)"; + ASSERT_EQ(expected_shader, result.wgsl); +} + +TEST(WrapUnaryOperatorTest, Applicable5) { + std::string content = R"( + fn main() { + let a : f32 = -(1.0); + } + )"; + Source::File file("test.wgsl", content); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + const auto& main_fn_statements = + program.AST().Functions()[0]->body->statements; + + const auto* expr = main_fn_statements[0] + ->As<ast::VariableDeclStatement>() + ->variable->constructor->As<ast::UnaryOpExpression>() + ->expr; + + const auto expression_id = node_id_map.GetId(expr); + ASSERT_NE(expression_id, 0); + + ASSERT_TRUE(MaybeApplyMutation( + program, + MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(), + ast::UnaryOp::kNegation), + node_id_map, &program, &node_id_map, nullptr)); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + writer::wgsl::Options options; + auto result = writer::wgsl::Generate(&program, options); + ASSERT_TRUE(result.success) << result.error; + + std::string expected_shader = R"(fn main() { + let a : f32 = -(-(1.0)); +} +)"; + ASSERT_EQ(expected_shader, result.wgsl); +} + +TEST(WrapUnaryOperatorTest, Applicable6) { + std::string content = R"( + fn main() { + var a : vec4<f32> = vec4<f32>(-1.0, -1.0, -1.0, -1.0); + } + )"; + Source::File file("test.wgsl", content); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + const auto& main_fn_statements = + program.AST().Functions()[0]->body->statements; + + const auto* expr = main_fn_statements[0] + ->As<ast::VariableDeclStatement>() + ->variable->constructor->As<ast::Expression>(); + + const auto expression_id = node_id_map.GetId(expr); + ASSERT_NE(expression_id, 0); + + ASSERT_TRUE(MaybeApplyMutation( + program, + MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(), + ast::UnaryOp::kNegation), + node_id_map, &program, &node_id_map, nullptr)); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + writer::wgsl::Options options; + auto result = writer::wgsl::Generate(&program, options); + ASSERT_TRUE(result.success) << result.error; + + std::string expected_shader = R"(fn main() { + var a : vec4<f32> = -(vec4<f32>(-1.0, -1.0, -1.0, -1.0)); +} +)"; + ASSERT_EQ(expected_shader, result.wgsl); +} + +TEST(WrapUnaryOperatorTest, Applicable7) { + std::string content = R"( + fn main() { + var a = 1; + for(var i : i32 = 1; i < 5; i = i + 1) { + a = a + 1; + } + } + )"; + Source::File file("test.wgsl", content); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + const auto& main_fn_statements = + program.AST().Functions()[0]->body->statements; + + const auto* expr = main_fn_statements[1] + ->As<ast::ForLoopStatement>() + ->initializer->As<ast::VariableDeclStatement>() + ->variable->constructor->As<ast::Expression>(); + + const auto expression_id = node_id_map.GetId(expr); + ASSERT_NE(expression_id, 0); + + ASSERT_TRUE(MaybeApplyMutation( + program, + MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(), + ast::UnaryOp::kNegation), + node_id_map, &program, &node_id_map, nullptr)); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + writer::wgsl::Options options; + auto result = writer::wgsl::Generate(&program, options); + ASSERT_TRUE(result.success) << result.error; + + std::string expected_shader = R"(fn main() { + var a = 1; + for(var i : i32 = -(1); (i < 5); i = (i + 1)) { + a = (a + 1); + } +} +)"; + ASSERT_EQ(expected_shader, result.wgsl); +} + +TEST(WrapUnaryOperatorTest, Applicable8) { + std::string content = R"( + fn main() { + var a : vec4<i32> = vec4<i32>(1, 0, -1, 0); + } + )"; + Source::File file("test.wgsl", content); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + const auto& main_fn_statements = + program.AST().Functions()[0]->body->statements; + + const auto* expr = main_fn_statements[0] + ->As<ast::VariableDeclStatement>() + ->variable->constructor->As<ast::Expression>(); + + const auto expression_id = node_id_map.GetId(expr); + ASSERT_NE(expression_id, 0); + + ASSERT_TRUE(MaybeApplyMutation( + program, + MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(), + ast::UnaryOp::kComplement), + node_id_map, &program, &node_id_map, nullptr)); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + writer::wgsl::Options options; + auto result = writer::wgsl::Generate(&program, options); + ASSERT_TRUE(result.success) << result.error; + + std::string expected_shader = R"(fn main() { + var a : vec4<i32> = ~(vec4<i32>(1, 0, -1, 0)); +} +)"; + ASSERT_EQ(expected_shader, result.wgsl); +} + +TEST(WrapUnaryOperatorTest, NotApplicable1) { + std::string content = R"( + fn main() { + let a = mat2x3<f32>(vec3<f32>(1.,0.,1.), vec3<f32>(0.,1.,0.)); + } + )"; + Source::File file("test.wgsl", content); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + const auto& main_fn_statements = + program.AST().Functions()[0]->body->statements; + + const auto* expr = main_fn_statements[0] + ->As<ast::VariableDeclStatement>() + ->variable->constructor->As<ast::Expression>(); + + const auto expression_id = node_id_map.GetId(expr); + ASSERT_NE(expression_id, 0); + + // There is no unary operator that can be applied to matrix type. + ASSERT_FALSE(MaybeApplyMutation( + program, + MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(), + ast::UnaryOp::kNegation), + node_id_map, &program, &node_id_map, nullptr)); +} + +TEST(WrapUnaryOperatorTest, NotApplicable2) { + std::string content = R"( + fn main() { + let a = 1; + } + )"; + Source::File file("test.wgsl", content); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + const auto& main_fn_statements = + program.AST().Functions()[0]->body->statements; + + const auto* expr = main_fn_statements[0] + ->As<ast::VariableDeclStatement>() + ->variable->constructor->As<ast::Expression>(); + + const auto expression_id = node_id_map.GetId(expr); + ASSERT_NE(expression_id, 0); + + // Not cannot be applied to integer types. + ASSERT_FALSE(MaybeApplyMutation( + program, + MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(), + ast::UnaryOp::kNot), + node_id_map, &program, &node_id_map, nullptr)); +} + +TEST(WrapUnaryOperatorTest, NotApplicable3) { + std::string content = R"( + fn main() { + let a = vec2<u32>(1u, 2u); + } + )"; + Source::File file("test.wgsl", content); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + const auto& main_fn_statements = + program.AST().Functions()[0]->body->statements; + + const auto* expr = main_fn_statements[0] + ->As<ast::VariableDeclStatement>() + ->variable->constructor->As<ast::Expression>(); + + const auto expression_id = node_id_map.GetId(expr); + ASSERT_NE(expression_id, 0); + + // Negation cannot be applied to unsigned integer scalar or vectors. + ASSERT_FALSE(MaybeApplyMutation( + program, + MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(), + ast::UnaryOp::kNegation), + node_id_map, &program, &node_id_map, nullptr)); +} + +TEST(WrapUnaryOperatorTest, NotApplicable4) { + std::string content = R"( + fn main() { + let a = 1.5; + } + )"; + Source::File file("test.wgsl", content); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + const auto& main_fn_statements = + program.AST().Functions()[0]->body->statements; + + const auto* expr = main_fn_statements[0] + ->As<ast::VariableDeclStatement>() + ->variable->constructor->As<ast::Expression>(); + + const auto expression_id = node_id_map.GetId(expr); + ASSERT_NE(expression_id, 0); + + // Cannot wrap float types with complement operator. + ASSERT_FALSE(MaybeApplyMutation( + program, + MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(), + ast::UnaryOp::kComplement), + node_id_map, &program, &node_id_map, nullptr)); +} + +TEST(WrapUnaryOperatorTest, NotApplicable5) { + std::string content = R"( + fn main() { + let a = 1.5; + } + )"; + Source::File file("test.wgsl", content); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + const auto& main_fn_statements = + program.AST().Functions()[0]->body->statements; + + const auto* expr = main_fn_statements[0] + ->As<ast::VariableDeclStatement>() + ->variable->constructor->As<ast::Expression>(); + + const auto expression_id = node_id_map.GetId(expr); + ASSERT_NE(expression_id, 0); + + // Id for the replacement expression is not fresh. + ASSERT_FALSE( + MaybeApplyMutation(program, + MutationWrapUnaryOperator(expression_id, expression_id, + ast::UnaryOp::kNegation), + node_id_map, &program, &node_id_map, nullptr)); +} + +TEST(WrapUnaryOperatorTest, NotApplicable6) { + std::string content = R"( + fn main() { + let a = 1.5; + } + )"; + Source::File file("test.wgsl", content); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + const auto& main_fn_statements = + program.AST().Functions()[0]->body->statements; + + const auto* statement = + main_fn_statements[0]->As<ast::VariableDeclStatement>(); + + const auto statement_id = node_id_map.GetId(statement); + ASSERT_NE(statement_id, 0); + + // The id provided for the expression is not a valid expression type. + ASSERT_FALSE(MaybeApplyMutation( + program, + MutationWrapUnaryOperator(statement_id, node_id_map.TakeFreshId(), + ast::UnaryOp::kNegation), + node_id_map, &program, &node_id_map, nullptr)); +} + +} // namespace +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc new file mode 100644 index 0000000..ab73a3a --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc
@@ -0,0 +1,189 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_ast_fuzzer/mutator.h" + +#include <cassert> +#include <memory> +#include <unordered_map> +#include <utility> +#include <vector> + +#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h" +#include "src/tint/program_builder.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { +namespace { + +template <typename T, typename... Args> +void MaybeAddFinder(bool enable_all_mutations, + ProbabilityContext* probability_context, + MutationFinderList* finders, + Args&&... args) { + if (enable_all_mutations || probability_context->RandomBool()) { + finders->push_back(std::make_unique<T>(std::forward<Args>(args)...)); + } +} + +MutationFinderList CreateMutationFinders( + ProbabilityContext* probability_context, + bool enable_all_mutations) { + MutationFinderList result; + do { + MaybeAddFinder<MutationFinderChangeBinaryOperators>( + enable_all_mutations, probability_context, &result); + MaybeAddFinder<MutationFinderReplaceIdentifiers>( + enable_all_mutations, probability_context, &result); + MaybeAddFinder<MutationFinderWrapUnaryOperators>( + enable_all_mutations, probability_context, &result); + } while (result.empty()); + return result; +} + +} // namespace + +bool MaybeApplyMutation(const tint::Program& program, + const Mutation& mutation, + const NodeIdMap& node_id_map, + tint::Program* out_program, + NodeIdMap* out_node_id_map, + protobufs::MutationSequence* mutation_sequence) { + assert(out_program && "`out_program` may not be a nullptr"); + assert(out_node_id_map && "`out_node_id_map` may not be a nullptr"); + + if (!mutation.IsApplicable(program, node_id_map)) { + return false; + } + + // The mutated `program` will be copied into the `mutated` program builder. + tint::ProgramBuilder mutated; + tint::CloneContext clone_context(&mutated, &program); + NodeIdMap new_node_id_map; + clone_context.ReplaceAll( + [&node_id_map, &new_node_id_map, &clone_context](const ast::Node* node) { + // Make sure all `tint::ast::` nodes' ids are preserved. + auto* cloned = tint::As<ast::Node>(node->Clone(&clone_context)); + new_node_id_map.Add(cloned, node_id_map.GetId(node)); + return cloned; + }); + + mutation.Apply(node_id_map, &clone_context, &new_node_id_map); + if (mutation_sequence) { + *mutation_sequence->add_mutation() = mutation.ToMessage(); + } + + clone_context.Clone(); + *out_program = tint::Program(std::move(mutated)); + *out_node_id_map = std::move(new_node_id_map); + return true; +} + +tint::Program Replay(tint::Program program, + const protobufs::MutationSequence& mutation_sequence) { + assert(program.IsValid() && "Initial program is invalid"); + + NodeIdMap node_id_map(program); + for (const auto& mutation_message : mutation_sequence.mutation()) { + auto mutation = Mutation::FromMessage(mutation_message); + auto status = MaybeApplyMutation(program, *mutation, node_id_map, &program, + &node_id_map, nullptr); + (void)status; // `status` will be unused in release mode. + assert(status && "`mutation` is inapplicable - it's most likely a bug"); + if (!program.IsValid()) { + // `mutation` has a bug. + break; + } + } + + return program; +} + +tint::Program Mutate(tint::Program program, + ProbabilityContext* probability_context, + bool enable_all_mutations, + uint32_t max_applied_mutations, + protobufs::MutationSequence* mutation_sequence) { + assert(max_applied_mutations != 0 && + "Maximum number of mutations is invalid"); + assert(program.IsValid() && "Initial program is invalid"); + + // The number of allowed failed attempts to apply mutations. If this number is + // exceeded, the mutator is considered stuck and the mutation session is + // stopped. + const uint32_t kMaxFailureToApply = 10; + + auto finders = + CreateMutationFinders(probability_context, enable_all_mutations); + NodeIdMap node_id_map(program); + + // Total number of applied mutations during this call to `Mutate`. + uint32_t applied_mutations = 0; + + // The number of consecutively failed attempts to apply mutations. + uint32_t failure_to_apply = 0; + + // Apply mutations as long as the `program` is valid, the limit on the number + // of mutations is not reached and the mutator is not stuck (i.e. unable to + // apply any mutations for some time). + while (program.IsValid() && applied_mutations < max_applied_mutations && + failure_to_apply < kMaxFailureToApply) { + // Get all applicable mutations from some mutation finder. + const auto& mutation_finder = + finders[probability_context->GetRandomIndex(finders)]; + auto mutations = mutation_finder->FindMutations(program, &node_id_map, + probability_context); + + const auto old_applied_mutations = applied_mutations; + for (const auto& mutation : mutations) { + if (!probability_context->ChoosePercentage( + mutation_finder->GetChanceOfApplyingMutation( + probability_context))) { + // Skip this `mutation` probabilistically. + continue; + } + + if (!MaybeApplyMutation(program, *mutation, node_id_map, &program, + &node_id_map, mutation_sequence)) { + // This `mutation` is inapplicable. This may happen if some of the + // earlier mutations cancelled this one. + continue; + } + + applied_mutations++; + if (!program.IsValid()) { + // This `mutation` has a bug. + return program; + } + } + + if (old_applied_mutations == applied_mutations) { + // No mutation was applied. Increase the counter to prevent an infinite + // loop. + failure_to_apply++; + } else { + failure_to_apply = 0; + } + } + + return program; +} + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutator.h b/src/tint/fuzzers/tint_ast_fuzzer/mutator.h new file mode 100644 index 0000000..d700b70 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutator.h
@@ -0,0 +1,101 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATOR_H_ +#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATOR_H_ + +#include "src/tint/fuzzers/tint_ast_fuzzer/mutation.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/probability_context.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.h" + +#include "src/tint/program.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { +/// @file + +/// @brief Tries to apply a `mutation` to the `program`. +/// +/// If the `mutation` is inapplicable, this function will return `false` and +/// `out_program`, `out_node_id_map` and `mutation_sequence` won't be modified. +/// +/// The `mutation` is required to produce a valid program when the +/// `Mutation::Apply` method is called. This guarantees that this function +/// returns a valid program as well. +/// +/// @param program - the initial program (must be valid). +/// @param mutation - the mutation that will be applied. +/// @param node_id_map - a map from `tint::ast::` nodes in the `program` to +/// their unique ids. +/// @param out_program - the resulting mutated program will be written through +/// this pointer. It may *not* be a `nullptr`. It _may_ point to `program`, +/// so that a program can be updated in place. +/// @param out_node_id_map - will contain new ids for the AST nodes in the +/// mutated program. It may *not* be a `nullptr`. It _may_ point to +/// `node_id_map`, so that a map can be updated in place. +/// @param mutation_sequence - the message about this mutation will be recorded +/// here. It may be a `nullptr`, in which case it's ignored. +/// @return `true` if the `mutation` was applied. +/// @return `false` if the `mutation` is inapplicable. +bool MaybeApplyMutation(const tint::Program& program, + const Mutation& mutation, + const NodeIdMap& node_id_map, + tint::Program* out_program, + NodeIdMap* out_node_id_map, + protobufs::MutationSequence* mutation_sequence); + +/// @brief Applies mutations from `mutations_sequence` to the `program`. +/// +/// All mutations in `mutation_sequence` must be applicable. Additionally, all +/// mutations must produce a valid program when the `Mutation::Apply` method is +/// called. This guarantees that this function returns a valid program as well. +/// +/// @param program - the initial program - must be valid. +/// @param mutation_sequence - a sequence of mutations. +/// @return the mutated program. +tint::Program Replay(tint::Program program, + const protobufs::MutationSequence& mutation_sequence); + +/// @brief Applies up to `max_applied_mutations` mutations to the `program`. +/// +/// All applied mutations must produce valid programs. This guarantees that the +/// returned program is valid as well. The returned program may be identical to +/// the initial `program` if no mutation was applied. +/// +/// @param program - initial program - must be valid. +/// @param probability_context - contains information about various +/// probabilistic behaviour of the fuzzer. +/// @param enable_all_mutations - if `false`, only mutations from a +/// probabilistically selected set of mutation types are applied. If `true`, +/// all mutation types are considered. +/// @param max_applied_mutations - the maximum number of applied mutations. This +/// may not be 0. +/// @param mutation_sequence - applied mutations will be recorded into this +/// protobuf message. This argument may be `nullptr`, in which case it's +/// ignored. +/// @return the mutated program. +tint::Program Mutate(tint::Program program, + ProbabilityContext* probability_context, + bool enable_all_mutations, + uint32_t max_applied_mutations, + protobufs::MutationSequence* mutation_sequence); + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATOR_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.cc b/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.cc new file mode 100644 index 0000000..117fd2d --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.cc
@@ -0,0 +1,65 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h" + +#include <cassert> + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +NodeIdMap::NodeIdMap() = default; + +NodeIdMap::NodeIdMap(const Program& program) : NodeIdMap() { + for (const auto* node : program.ASTNodes().Objects()) { + Add(node, TakeFreshId()); + } +} + +NodeIdMap::IdType NodeIdMap::GetId(const ast::Node* node) const { + auto it = node_to_id_.find(node); + return it == node_to_id_.end() ? 0 : it->second; +} + +const ast::Node* NodeIdMap::GetNode(IdType id) const { + auto it = id_to_node_.find(id); + return it == id_to_node_.end() ? nullptr : it->second; +} + +void NodeIdMap::Add(const ast::Node* node, IdType id) { + assert(!node_to_id_.count(node) && "The node already exists in the map"); + assert(IdIsFreshAndValid(id) && "Id already exists in the map or Id is zero"); + assert(node && "`node` can't be a nullptr"); + + node_to_id_[node] = id; + id_to_node_[id] = node; + + if (id >= fresh_id_) { + fresh_id_ = id + 1; + } +} + +bool NodeIdMap::IdIsFreshAndValid(IdType id) const { + return id && !id_to_node_.count(id); +} + +NodeIdMap::IdType NodeIdMap::TakeFreshId() { + assert(fresh_id_ != 0 && "`NodeIdMap` id has overflowed"); + return fresh_id_++; +} + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h b/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h new file mode 100644 index 0000000..1aae93f --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h
@@ -0,0 +1,96 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_AST_FUZZER_NODE_ID_MAP_H_ +#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_NODE_ID_MAP_H_ + +#include <unordered_map> + +#include "src/tint/program.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +/// Contains a one-to-one mapping between the nodes in the AST of the program +/// and their ids. +/// +/// The motivation for having this mapping is: +/// - To be able to uniquely identify a node in the AST. This will be used +/// to record transformations in the protobuf messages. +/// - When the AST is being modified, only the mapping for the modified nodes +/// must be affected. That is, if some node is unchanged, it must have the +/// same id defined in this class. +/// +/// This class achieves these goals partially. Concretely, the only way to +/// change the AST is by cloning it since all instances of `tint::ast::` classes +/// are immutable. This will invalidate all the pointers to the AST nodes which +/// are used in this class. To overcome this, a new instance of this class is +/// created with all the cloned nodes and the old instance is discarded. +class NodeIdMap { + public: + /// Type of the id used by this map. + using IdType = uint32_t; + + /// Creates an empty map. + NodeIdMap(); + + /// @brief Initializes this instance with all the nodes in the `program`. + /// @param program - must be valid. + explicit NodeIdMap(const Program& program); + + /// @brief Returns a node for the given `id`. + /// @param id - any value is accepted. + /// @return a pointer to some node if `id` exists in this map. + /// @return `nullptr` otherwise. + const ast::Node* GetNode(IdType id) const; + + /// @brief Returns an id of the given `node`. + /// @param node - can be a `nullptr`. + /// @return not equal to 0 if `node` exists in this map. + /// @return 0 otherwise. + IdType GetId(const ast::Node* node) const; + + /// @brief Adds a mapping from `node` to `id` to this map. + /// @param node - may not be a `nullptr` and can't be present in this map. + /// @param id - may not be 0 and can't be present in this map. + void Add(const ast::Node* node, IdType id); + + /// @brief Returns whether the id is fresh by checking if it exists in + /// the id map and the id is not 0. + /// @param id - an id that is used to check in the map. + /// @return true the given id is fresh and valid (non-zero). + /// @return false otherwise. + bool IdIsFreshAndValid(IdType id) const; + + /// @brief Returns an id that is guaranteed to be unoccupied in this map. + /// + /// This will effectively increase the counter. This means that two + /// consecutive calls to this method will return different ids. + /// + /// @return an unoccupied id. + IdType TakeFreshId(); + + private: + IdType fresh_id_ = 1; + + std::unordered_map<const ast::Node*, IdType> node_to_id_; + std::unordered_map<IdType, const ast::Node*> id_to_node_; +}; + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_NODE_ID_MAP_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/override_cli_params.h b/src/tint/fuzzers/tint_ast_fuzzer/override_cli_params.h new file mode 100644 index 0000000..31b2946 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/override_cli_params.h
@@ -0,0 +1,36 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_AST_FUZZER_OVERRIDE_CLI_PARAMS_H_ +#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_OVERRIDE_CLI_PARAMS_H_ + +#include "src/tint/fuzzers/tint_ast_fuzzer/cli.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +/// @brief Allows CLI parameters to be overridden. +/// +/// This function allows fuzz targets to override particular CLI parameters, +/// for example forcing a particular back-end to be targeted. +/// +/// @param cli_params - the parsed CLI parameters to be updated. +void OverrideCliParams(CliParams& cli_params); + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_OVERRIDE_CLI_PARAMS_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc new file mode 100644 index 0000000..1d9461f --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc
@@ -0,0 +1,50 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_ast_fuzzer/probability_context.h" + +#include <cassert> + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { +namespace { + +const std::pair<uint32_t, uint32_t> kChanceOfChangingBinaryOperators = {30, 90}; +const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdentifiers = {30, 70}; +const std::pair<uint32_t, uint32_t> kChanceOfWrappingUnaryOperators = {30, 70}; + +} // namespace + +ProbabilityContext::ProbabilityContext(RandomGenerator* generator) + : generator_(generator), + chance_of_changing_binary_operators_( + RandomFromRange(kChanceOfChangingBinaryOperators)), + chance_of_replacing_identifiers_( + RandomFromRange(kChanceOfReplacingIdentifiers)), + chance_of_wrapping_unary_operators_( + RandomFromRange(kChanceOfWrappingUnaryOperators)) { + assert(generator != nullptr && "generator must not be nullptr"); +} + +uint32_t ProbabilityContext::RandomFromRange( + std::pair<uint32_t, uint32_t> range) { + assert(range.first <= range.second && "Range must be non-decreasing"); + return generator_->GetUInt32( + range.first, range.second + 1); // + 1 need since range is inclusive. +} + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h new file mode 100644 index 0000000..eacc1bd --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h
@@ -0,0 +1,89 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_AST_FUZZER_PROBABILITY_CONTEXT_H_ +#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_PROBABILITY_CONTEXT_H_ + +#include <utility> +#include <vector> + +#include "src/tint/fuzzers/random_generator.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +/// This class is intended to be used by the `MutationFinder`s to introduce some +/// variance to the mutation process. +class ProbabilityContext { + public: + /// Initializes this instance with a random number generator. + /// @param generator - must not be a `nullptr`. Must remain in scope as long + /// as this + /// instance exists. + explicit ProbabilityContext(RandomGenerator* generator); + + /// Get random bool with even odds + /// @returns true 50% of the time and false %50 of time. + bool RandomBool() { return generator_->GetBool(); } + + /// Get random bool with weighted odds + /// @param percentage - likelihood of true being returned + /// @returns true |percentage|% of the time, and false (100 - |percentage|)% + /// of the time. + bool ChoosePercentage(uint32_t percentage) { + return generator_->GetWeightedBool(percentage); + } + + /// Returns a random value in the range `[0; arr.size())`. + /// @tparam T - type of the elements in the vector. + /// @param arr - may not be empty. + /// @return the random index in the `arr`. + template <typename T> + size_t GetRandomIndex(const std::vector<T>& arr) { + return static_cast<size_t>(generator_->GetUInt64(arr.size())); + } + + /// @return the probability of replacing some binary operator with another. + uint32_t GetChanceOfChangingBinaryOperators() const { + return chance_of_changing_binary_operators_; + } + + /// @return the probability of replacing some identifier with some other one. + uint32_t GetChanceOfReplacingIdentifiers() const { + return chance_of_replacing_identifiers_; + } + + /// @return the probability of wrapping an expression in a unary operator. + uint32_t GetChanceOfWrappingUnaryOperators() const { + return chance_of_wrapping_unary_operators_; + } + + private: + /// @param range - a pair of integers `a` and `b` s.t. `a <= b`. + /// @return an random number in the range `[a; b]`. + uint32_t RandomFromRange(std::pair<uint32_t, uint32_t> range); + + RandomGenerator* generator_; + + uint32_t chance_of_changing_binary_operators_; + uint32_t chance_of_replacing_identifiers_; + uint32_t chance_of_wrapping_unary_operators_; +}; + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_PROBABILITY_CONTEXT_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.h b/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.h new file mode 100644 index 0000000..54586b1 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.h
@@ -0,0 +1,35 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_AST_FUZZER_PROTOBUFS_TINT_AST_FUZZER_H_ +#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_PROTOBUFS_TINT_AST_FUZZER_H_ + +// Compilation of the protobuf library and its autogenerated code can produce +// warnings. Ignore them since we can't control them. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" +#pragma clang diagnostic ignored "-Wreserved-id-macro" +#pragma clang diagnostic ignored "-Wsign-conversion" +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#pragma clang diagnostic ignored "-Wextra-semi-stmt" +#pragma clang diagnostic ignored "-Winconsistent-missing-destructor-override" +#pragma clang diagnostic ignored "-Wweak-vtables" +#pragma clang diagnostic ignored "-Wsuggest-destructor-override" +#pragma clang diagnostic ignored "-Wreserved-identifier" + +#include "src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.pb.h" + +#pragma clang diagnostic pop + +#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_PROTOBUFS_TINT_AST_FUZZER_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto b/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto new file mode 100644 index 0000000..0a5951f --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto
@@ -0,0 +1,76 @@ +// Copyright 2021 The Tint Authors. +// +// 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. + +syntax = "proto3"; + +package tint.fuzzers.ast_fuzzer.protobufs; + +message Mutation { + oneof mutation { + MutationReplaceIdentifier replace_identifier = 1; + MutationChangeBinaryOperator change_binary_operator = 2; + MutationWrapUnaryOperator wrap_unary_operator = 3; + }; +} + +message MutationSequence { + repeated Mutation mutation = 1; +} + +message MutatorState { + // Contains the state of the fuzzer. + + // The program that is being fuzzed. This can be either + // the original program (if mutation sequence is available) or + // the mutated version (if mutations are being recorded). + string program = 1; + + // The sequence of mutations that was applied to the `program`. + // This may not have any mutations if they are not being recorded. + MutationSequence mutation_sequence = 2; +} + +message MutationReplaceIdentifier { + // This transformation replaces a use of one variable with another. + + // The id of the use of a variable in the AST. + uint32 use_id = 1; + + // The id of a definition of a variable to replace the use with. + uint32 replacement_id = 2; +} + +message MutationChangeBinaryOperator { + // This transformation replaces one binary operator with another. + + // The id of a binary expression in the AST. + uint32 binary_expr_id = 1; + + // A BinaryOp representing the new binary operator. + uint32 new_operator = 2; +} + +message MutationWrapUnaryOperator { + // This transformation wraps an expression with a allowed unary + // expression operator. + + // The id of the expression. + uint32 expression_id = 1; + + // A fresh id for the created unary expression. + uint32 fresh_id = 2; + + // The unary operator to wrap the expression with. + uint32 unary_op_wrapper = 3; +}
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_fuzzer.cc b/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_fuzzer.cc new file mode 100644 index 0000000..77b00c4 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_fuzzer.cc
@@ -0,0 +1,28 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_ast_fuzzer/cli.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/override_cli_params.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +void OverrideCliParams(CliParams& /*unused*/) { + // Leave the CLI parameters unchanged. +} + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_hlsl_writer_fuzzer.cc b/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_hlsl_writer_fuzzer.cc new file mode 100644 index 0000000..a1d886e --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_hlsl_writer_fuzzer.cc
@@ -0,0 +1,33 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_ast_fuzzer/cli.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/override_cli_params.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +void OverrideCliParams(CliParams& cli_params) { + assert(cli_params.fuzzing_target == FuzzingTarget::kAll && + "The fuzzing target should not have been set by a CLI parameter: it " + "should have its default value."); + cli_params.fuzzing_target = FuzzingTarget::kHlsl; +} + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_msl_writer_fuzzer.cc b/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_msl_writer_fuzzer.cc new file mode 100644 index 0000000..8354ac3 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_msl_writer_fuzzer.cc
@@ -0,0 +1,33 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_ast_fuzzer/cli.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/override_cli_params.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +void OverrideCliParams(CliParams& cli_params) { + assert(cli_params.fuzzing_target == FuzzingTarget::kAll && + "The fuzzing target should not have been set by a CLI parameter: it " + "should have its default value."); + cli_params.fuzzing_target = FuzzingTarget::kMsl; +} + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_spv_writer_fuzzer.cc b/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_spv_writer_fuzzer.cc new file mode 100644 index 0000000..266ef6a --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_spv_writer_fuzzer.cc
@@ -0,0 +1,33 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_ast_fuzzer/cli.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/override_cli_params.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +void OverrideCliParams(CliParams& cli_params) { + assert(cli_params.fuzzing_target == FuzzingTarget::kAll && + "The fuzzing target should not have been set by a CLI parameter: it " + "should have its default value."); + cli_params.fuzzing_target = FuzzingTarget::kSpv; +} + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_wgsl_writer_fuzzer.cc b/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_wgsl_writer_fuzzer.cc new file mode 100644 index 0000000..ede740f --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/tint_ast_wgsl_writer_fuzzer.cc
@@ -0,0 +1,33 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_ast_fuzzer/cli.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/override_cli_params.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +void OverrideCliParams(CliParams& cli_params) { + assert(cli_params.fuzzing_target == FuzzingTarget::kAll && + "The fuzzing target should not have been set by a CLI parameter: it " + "should have its default value."); + cli_params.fuzzing_target = FuzzingTarget::kWgsl; +} + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/util.h b/src/tint/fuzzers/tint_ast_fuzzer/util.h new file mode 100644 index 0000000..019fdec --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/util.h
@@ -0,0 +1,109 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_AST_FUZZER_UTIL_H_ +#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_UTIL_H_ + +#include <vector> + +#include "src/tint/ast/module.h" +#include "src/tint/ast/variable_decl_statement.h" +#include "src/tint/castable.h" +#include "src/tint/program.h" +#include "src/tint/sem/block_statement.h" +#include "src/tint/sem/function.h" +#include "src/tint/sem/statement.h" +#include "src/tint/sem/variable.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { +namespace util { +/// @file + +/// @brief Returns all in-scope variables (including formal function parameters) +/// related to statement `curr_stmt`. +/// +/// These variables are additionally filtered by applying a predicate `pred`. +/// +/// @tparam Pred - a predicate that accepts a `const sem::Variable*` and returns +/// `bool`. +/// @param program - the program to look for variables in. +/// @param curr_stmt - the current statement. Everything below it is not in +/// scope. +/// @param pred - a predicate (e.g. a function pointer, functor, lambda etc) of +/// type `Pred`. +/// @return a vector of all variables that can be accessed from `curr_stmt`. +template <typename Pred> +std::vector<const sem::Variable*> GetAllVarsInScope( + const tint::Program& program, + const sem::Statement* curr_stmt, + Pred&& pred) { + std::vector<const sem::Variable*> result; + + // Walk up the hierarchy of blocks in which `curr_stmt` is contained. + for (const auto* block = curr_stmt->Block(); block; + block = tint::As<sem::BlockStatement>(block->Parent())) { + for (const auto* stmt : block->Declaration()->statements) { + if (stmt == curr_stmt->Declaration()) { + // `curr_stmt` was found. This is only possible if `block is the + // enclosing block of `curr_stmt` since the AST nodes are not shared. + // Because of all this, skip the iteration of the inner loop since + // the rest of the instructions in the `block` are not visible from the + // `curr_stmt`. + break; + } + + if (const auto* var_node = tint::As<ast::VariableDeclStatement>(stmt)) { + const auto* sem_var = program.Sem().Get(var_node->variable); + if (pred(sem_var)) { + result.push_back(sem_var); + } + } + } + } + + // Process function parameters. + for (const auto* param : curr_stmt->Function()->Parameters()) { + if (pred(param)) { + result.push_back(param); + } + } + + // Global variables do not belong to any ast::BlockStatement. + for (const auto* global_decl : program.AST().GlobalDeclarations()) { + if (global_decl == curr_stmt->Function()->Declaration()) { + // The same situation as in the previous loop. The current function has + // been reached. If there are any variables declared below, they won't be + // visible in this function. Thus, exit the loop. + break; + } + + if (const auto* global_var = tint::As<ast::Variable>(global_decl)) { + const auto* sem_node = program.Sem().Get(global_var); + if (pred(sem_node)) { + result.push_back(sem_node); + } + } + } + + return result; +} + +} // namespace util +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_UTIL_H_
diff --git a/src/tint/fuzzers/tint_binding_remapper_fuzzer.cc b/src/tint/fuzzers/tint_binding_remapper_fuzzer.cc new file mode 100644 index 0000000..f736cca --- /dev/null +++ b/src/tint/fuzzers/tint_binding_remapper_fuzzer.cc
@@ -0,0 +1,35 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/fuzzer_init.h" +#include "src/tint/fuzzers/tint_common_fuzzer.h" +#include "src/tint/fuzzers/transform_builder.h" + +namespace tint { +namespace fuzzers { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + TransformBuilder tb(data, size); + tb.AddTransform<transform::BindingRemapper>(); + + fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kWGSL); + fuzzer.SetTransformManager(tb.manager(), tb.data_map()); + fuzzer.SetDumpInput(GetCliParams().dump_input); + fuzzer.SetEnforceValidity(GetCliParams().enforce_validity); + + return fuzzer.Run(data, size); +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_black_box_fuzz_target.cc b/src/tint/fuzzers/tint_black_box_fuzz_target.cc new file mode 100644 index 0000000..426b58a --- /dev/null +++ b/src/tint/fuzzers/tint_black_box_fuzz_target.cc
@@ -0,0 +1,157 @@ +// Copyright 2021 The Tint Authors. +// +// 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 <fstream> +#include <iostream> +#include <string> +#include <vector> + +#include "src/tint/fuzzers/tint_common_fuzzer.h" + +namespace { + +/// Controls the target language in which code will be generated. +enum class TargetLanguage { + kHlsl, + kMsl, + kSpv, + kWgsl, + kTargetLanguageMax, +}; + +/// Copies the content from the file named `input_file` to `buffer`, +/// assuming each element in the file is of type `T`. If any error occurs, +/// writes error messages to the standard error stream and returns false. +/// Assumes the size of a `T` object is divisible by its required alignment. +/// @returns true if we successfully read the file. +template <typename T> +bool ReadFile(const std::string& input_file, std::vector<T>* buffer) { + if (!buffer) { + std::cerr << "The buffer pointer was null" << std::endl; + return false; + } + + FILE* file = nullptr; +#if defined(_MSC_VER) + fopen_s(&file, input_file.c_str(), "rb"); +#else + file = fopen(input_file.c_str(), "rb"); +#endif + if (!file) { + std::cerr << "Failed to open " << input_file << std::endl; + return false; + } + + fseek(file, 0, SEEK_END); + const auto file_size = static_cast<size_t>(ftell(file)); + if (0 != (file_size % sizeof(T))) { + std::cerr << "File " << input_file + << " does not contain an integral number of objects: " + << file_size << " bytes in the file, require " << sizeof(T) + << " bytes per object" << std::endl; + fclose(file); + return false; + } + fseek(file, 0, SEEK_SET); + + buffer->clear(); + buffer->resize(file_size / sizeof(T)); + + size_t bytes_read = fread(buffer->data(), 1, file_size, file); + fclose(file); + if (bytes_read != file_size) { + std::cerr << "Failed to read " << input_file << std::endl; + return false; + } + + return true; +} + +} // namespace + +int main(int argc, const char** argv) { + if (argc < 2 || argc > 3) { + std::cerr << "Usage: " << argv[0] << " <input file> [hlsl|msl|spv|wgsl]" + << std::endl; + return 1; + } + + std::string input_filename(argv[1]); + + std::vector<uint8_t> data; + if (!ReadFile<uint8_t>(input_filename, &data)) { + return 1; + } + + if (data.empty()) { + return 0; + } + + tint::fuzzers::DataBuilder builder(data.data(), data.size()); + + TargetLanguage target_language; + + if (argc == 3) { + std::string target_language_string = argv[2]; + if (target_language_string == "hlsl") { + target_language = TargetLanguage::kHlsl; + } else if (target_language_string == "msl") { + target_language = TargetLanguage::kMsl; + } else if (target_language_string == "spv") { + target_language = TargetLanguage::kSpv; + } else { + assert(target_language_string == "wgsl" && "Unknown target language."); + target_language = TargetLanguage::kWgsl; + } + } else { + target_language = builder.enum_class<TargetLanguage>( + static_cast<uint32_t>(TargetLanguage::kTargetLanguageMax)); + } + + switch (target_language) { + case TargetLanguage::kHlsl: { + tint::fuzzers::CommonFuzzer fuzzer(tint::fuzzers::InputFormat::kWGSL, + tint::fuzzers::OutputFormat::kHLSL); + return fuzzer.Run(data.data(), data.size()); + } + case TargetLanguage::kMsl: { + tint::writer::msl::Options options; + GenerateMslOptions(&builder, &options); + tint::fuzzers::CommonFuzzer fuzzer(tint::fuzzers::InputFormat::kWGSL, + tint::fuzzers::OutputFormat::kMSL); + fuzzer.SetOptionsMsl(options); + return fuzzer.Run(data.data(), data.size()); + } + case TargetLanguage::kSpv: { + tint::writer::spirv::Options options; + GenerateSpirvOptions(&builder, &options); + tint::fuzzers::CommonFuzzer fuzzer(tint::fuzzers::InputFormat::kWGSL, + tint::fuzzers::OutputFormat::kSpv); + fuzzer.SetOptionsSpirv(options); + return fuzzer.Run(data.data(), data.size()); + } + case TargetLanguage::kWgsl: { + tint::fuzzers::CommonFuzzer fuzzer(tint::fuzzers::InputFormat::kWGSL, + tint::fuzzers::OutputFormat::kWGSL); + return fuzzer.Run(data.data(), data.size()); + } + default: + std::cerr << "Aborting due to unknown target language; fuzzer must be " + "misconfigured." + << std::endl; + abort(); + } +}
diff --git a/src/tint/fuzzers/tint_common_fuzzer.cc b/src/tint/fuzzers/tint_common_fuzzer.cc new file mode 100644 index 0000000..b8bfe08 --- /dev/null +++ b/src/tint/fuzzers/tint_common_fuzzer.cc
@@ -0,0 +1,350 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_common_fuzzer.h" + +#include <cassert> +#include <cstring> +#include <fstream> +#include <memory> +#include <sstream> +#include <string> +#include <utility> +#include <vector> + +#if TINT_BUILD_SPV_READER +#include "spirv-tools/libspirv.hpp" +#endif // TINT_BUILD_SPV_READER + +#include "src/tint/ast/module.h" +#include "src/tint/diagnostic/formatter.h" +#include "src/tint/program.h" +#include "src/tint/utils/hash.h" + +namespace tint { +namespace fuzzers { + +namespace { + +// A macro is used to avoid FATAL_ERROR creating its own stack frame. This leads +// to better de-duplication of bug reports, because ClusterFuzz only uses the +// top few stack frames for de-duplication, and a FATAL_ERROR stack frame +// provides no useful information. +#define FATAL_ERROR(diags, msg_string) \ + do { \ + std::string msg = msg_string; \ + auto printer = tint::diag::Printer::create(stderr, true); \ + if (!msg.empty()) { \ + printer->write(msg + "\n", {diag::Color::kRed, true}); \ + } \ + tint::diag::Formatter().format(diags, printer.get()); \ + __builtin_trap(); \ + } while (false) + +[[noreturn]] void TintInternalCompilerErrorReporter( + const tint::diag::List& diagnostics) { + FATAL_ERROR(diagnostics, ""); +} + +// Wrapping in a macro, so it can be a one-liner in the code, but not +// introduce another level in the stack trace. This will help with de-duping +// ClusterFuzz issues. +#define CHECK_INSPECTOR(program, inspector) \ + do { \ + if ((inspector).has_error()) { \ + if (!enforce_validity) { \ + return; \ + } \ + FATAL_ERROR((program)->Diagnostics(), \ + "Inspector failed: " + (inspector).error()); \ + } \ + } while (false) + +// Wrapping in a macro to make code more readable and help with issue de-duping. +#define VALIDITY_ERROR(diags, msg_string) \ + do { \ + if (!enforce_validity) { \ + return 0; \ + } \ + FATAL_ERROR(diags, msg_string); \ + } while (false) + +bool SPIRVToolsValidationCheck(const tint::Program& program, + const std::vector<uint32_t>& spirv) { + spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); + const tint::diag::List& diags = program.Diagnostics(); + tools.SetMessageConsumer([diags](spv_message_level_t, const char*, + const spv_position_t& pos, const char* msg) { + std::stringstream out; + out << "Unexpected spirv-val error:\n" + << (pos.line + 1) << ":" << (pos.column + 1) << ": " << msg + << std::endl; + + auto printer = tint::diag::Printer::create(stderr, true); + printer->write(out.str(), {diag::Color::kYellow, false}); + tint::diag::Formatter().format(diags, printer.get()); + }); + + return tools.Validate(spirv.data(), spirv.size(), + spvtools::ValidatorOptions()); +} + +} // namespace + +void GenerateSpirvOptions(DataBuilder* b, writer::spirv::Options* options) { + *options = b->build<writer::spirv::Options>(); +} + +void GenerateWgslOptions(DataBuilder* b, writer::wgsl::Options* options) { + *options = b->build<writer::wgsl::Options>(); +} + +void GenerateHlslOptions(DataBuilder* b, writer::hlsl::Options* options) { + *options = b->build<writer::hlsl::Options>(); +} + +void GenerateMslOptions(DataBuilder* b, writer::msl::Options* options) { + *options = b->build<writer::msl::Options>(); +} + +CommonFuzzer::CommonFuzzer(InputFormat input, OutputFormat output) + : input_(input), output_(output) {} + +CommonFuzzer::~CommonFuzzer() = default; + +int CommonFuzzer::Run(const uint8_t* data, size_t size) { + tint::SetInternalCompilerErrorReporter(&TintInternalCompilerErrorReporter); + +#if TINT_BUILD_WGSL_WRITER + tint::Program::printer = [](const tint::Program* program) { + auto result = tint::writer::wgsl::Generate(program, {}); + if (!result.error.empty()) { + return "error: " + result.error; + } + return result.wgsl; + }; +#endif // TINT_BUILD_WGSL_WRITER + + Program program; + +#if TINT_BUILD_SPV_READER + std::vector<uint32_t> spirv_input(size / sizeof(uint32_t)); + +#endif // TINT_BUILD_SPV_READER + +#if TINT_BUILD_WGSL_READER || TINT_BUILD_SPV_READER + auto dump_input_data = [&](auto& content, const char* extension) { + size_t hash = utils::Hash(content); + auto filename = "fuzzer_input_" + std::to_string(hash) + extension; // + std::ofstream fout(filename, std::ios::binary); + fout.write(reinterpret_cast<const char*>(data), + static_cast<std::streamsize>(size)); + std::cout << "Dumped input data to " << filename << std::endl; + }; +#endif + + switch (input_) { +#if TINT_BUILD_WGSL_READER + case InputFormat::kWGSL: { + // Clear any existing diagnostics, as these will hold pointers to file_, + // which we are about to release. + diagnostics_ = {}; + std::string str(reinterpret_cast<const char*>(data), size); + file_ = std::make_unique<Source::File>("test.wgsl", str); + if (dump_input_) { + dump_input_data(str, ".wgsl"); + } + program = reader::wgsl::Parse(file_.get()); + break; + } +#endif // TINT_BUILD_WGSL_READER +#if TINT_BUILD_SPV_READER + case InputFormat::kSpv: { + // `spirv_input` has been initialized with the capacity to store `size / + // sizeof(uint32_t)` uint32_t values. If `size` is not a multiple of + // sizeof(uint32_t) then not all of `data` can be copied into + // `spirv_input`, and any trailing bytes are discarded. + std::memcpy(spirv_input.data(), data, + spirv_input.size() * sizeof(uint32_t)); + if (spirv_input.empty()) { + return 0; + } + if (dump_input_) { + dump_input_data(spirv_input, ".spv"); + } + program = reader::spirv::Parse(spirv_input); + break; + } +#endif // TINT_BUILD_SPV_READER + } + + if (!program.IsValid()) { + diagnostics_ = program.Diagnostics(); + return 0; + } + +#if TINT_BUILD_SPV_READER + if (input_ == InputFormat::kSpv && + !SPIRVToolsValidationCheck(program, spirv_input)) { + FATAL_ERROR( + program.Diagnostics(), + "Fuzzing detected invalid input spirv not being caught by Tint"); + } +#endif // TINT_BUILD_SPV_READER + + RunInspector(&program); + diagnostics_ = program.Diagnostics(); + + if (transform_manager_) { + auto out = transform_manager_->Run(&program, *transform_inputs_); + if (!out.program.IsValid()) { + // Transforms can produce error messages for bad input. + // Catch ICEs and errors from non transform systems. + for (const auto& diag : out.program.Diagnostics()) { + if (diag.severity > diag::Severity::Error || + diag.system != diag::System::Transform) { + VALIDITY_ERROR(program.Diagnostics(), + "Fuzzing detected valid input program being " + "transformed into an invalid output program"); + } + } + } + + program = std::move(out.program); + RunInspector(&program); + } + + switch (output_) { + case OutputFormat::kWGSL: { +#if TINT_BUILD_WGSL_WRITER + auto result = writer::wgsl::Generate(&program, options_wgsl_); + generated_wgsl_ = std::move(result.wgsl); + if (!result.success) { + VALIDITY_ERROR( + program.Diagnostics(), + "WGSL writer errored on validated input:\n" + result.error); + } +#endif // TINT_BUILD_WGSL_WRITER + break; + } + case OutputFormat::kSpv: { +#if TINT_BUILD_SPV_WRITER + auto result = writer::spirv::Generate(&program, options_spirv_); + generated_spirv_ = std::move(result.spirv); + if (!result.success) { + VALIDITY_ERROR( + program.Diagnostics(), + "SPIR-V writer errored on validated input:\n" + result.error); + } + + if (!SPIRVToolsValidationCheck(program, generated_spirv_)) { + VALIDITY_ERROR(program.Diagnostics(), + "Fuzzing detected invalid spirv being emitted by Tint"); + } + +#endif // TINT_BUILD_SPV_WRITER + break; + } + case OutputFormat::kHLSL: { +#if TINT_BUILD_HLSL_WRITER + auto result = writer::hlsl::Generate(&program, options_hlsl_); + generated_hlsl_ = std::move(result.hlsl); + if (!result.success) { + VALIDITY_ERROR( + program.Diagnostics(), + "HLSL writer errored on validated input:\n" + result.error); + } +#endif // TINT_BUILD_HLSL_WRITER + break; + } + case OutputFormat::kMSL: { +#if TINT_BUILD_MSL_WRITER + auto result = writer::msl::Generate(&program, options_msl_); + generated_msl_ = std::move(result.msl); + if (!result.success) { + VALIDITY_ERROR( + program.Diagnostics(), + "MSL writer errored on validated input:\n" + result.error); + } +#endif // TINT_BUILD_MSL_WRITER + break; + } + } + + return 0; +} + +void CommonFuzzer::RunInspector(Program* program) { + inspector::Inspector inspector(program); + diagnostics_ = program->Diagnostics(); + + auto entry_points = inspector.GetEntryPoints(); + CHECK_INSPECTOR(program, inspector); + + auto constant_ids = inspector.GetConstantIDs(); + CHECK_INSPECTOR(program, inspector); + + auto constant_name_to_id = inspector.GetConstantNameToIdMap(); + CHECK_INSPECTOR(program, inspector); + + for (auto& ep : entry_points) { + inspector.GetStorageSize(ep.name); + CHECK_INSPECTOR(program, inspector); + + inspector.GetResourceBindings(ep.name); + CHECK_INSPECTOR(program, inspector); + + inspector.GetUniformBufferResourceBindings(ep.name); + CHECK_INSPECTOR(program, inspector); + + inspector.GetStorageBufferResourceBindings(ep.name); + CHECK_INSPECTOR(program, inspector); + + inspector.GetReadOnlyStorageBufferResourceBindings(ep.name); + CHECK_INSPECTOR(program, inspector); + + inspector.GetSamplerResourceBindings(ep.name); + CHECK_INSPECTOR(program, inspector); + + inspector.GetComparisonSamplerResourceBindings(ep.name); + CHECK_INSPECTOR(program, inspector); + + inspector.GetSampledTextureResourceBindings(ep.name); + CHECK_INSPECTOR(program, inspector); + + inspector.GetMultisampledTextureResourceBindings(ep.name); + CHECK_INSPECTOR(program, inspector); + + inspector.GetWriteOnlyStorageTextureResourceBindings(ep.name); + CHECK_INSPECTOR(program, inspector); + + inspector.GetDepthTextureResourceBindings(ep.name); + CHECK_INSPECTOR(program, inspector); + + inspector.GetDepthMultisampledTextureResourceBindings(ep.name); + CHECK_INSPECTOR(program, inspector); + + inspector.GetExternalTextureResourceBindings(ep.name); + CHECK_INSPECTOR(program, inspector); + + inspector.GetSamplerTextureUses(ep.name); + CHECK_INSPECTOR(program, inspector); + + inspector.GetWorkgroupStorageSize(ep.name); + CHECK_INSPECTOR(program, inspector); + } +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_common_fuzzer.h b/src/tint/fuzzers/tint_common_fuzzer.h new file mode 100644 index 0000000..50bfb61 --- /dev/null +++ b/src/tint/fuzzers/tint_common_fuzzer.h
@@ -0,0 +1,162 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_COMMON_FUZZER_H_ +#define SRC_TINT_FUZZERS_TINT_COMMON_FUZZER_H_ + +#include <cassert> +#include <cstring> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "include/tint/tint.h" + +#include "src/tint/fuzzers/data_builder.h" + +namespace tint { +namespace fuzzers { + +// TODO(crbug.com/tint/1356): Add using shader reflection to generate options +// that are potentially valid for Generate*Options +// functions. +/// Generates random set of options for SPIRV generation +void GenerateSpirvOptions(DataBuilder* b, writer::spirv::Options* options); + +/// Generates random set of options for WGSL generation +void GenerateWgslOptions(DataBuilder* b, writer::wgsl::Options* options); + +/// Generates random set of options for HLSL generation +void GenerateHlslOptions(DataBuilder* b, writer::hlsl::Options* options); + +/// Generates random set of options for MSL generation +void GenerateMslOptions(DataBuilder* b, writer::msl::Options* options); + +/// Shader language the fuzzer is reading +enum class InputFormat { kWGSL, kSpv }; + +/// Shader language the fuzzer is emitting +enum class OutputFormat { kWGSL, kSpv, kHLSL, kMSL }; + +/// Generic runner for reading and emitting shaders using Tint, used by most +/// fuzzers to share common code. +class CommonFuzzer { + public: + /// Constructor + /// @param input shader language being read + /// @param output shader language being emitted + CommonFuzzer(InputFormat input, OutputFormat output); + + /// Destructor + ~CommonFuzzer(); + + /// @param tm manager for transforms to run + /// @param inputs data for transforms to run + void SetTransformManager(transform::Manager* tm, transform::DataMap* inputs) { + assert((!tm || inputs) && "DataMap must be !nullptr if Manager !nullptr"); + transform_manager_ = tm; + transform_inputs_ = inputs; + } + + /// @param enabled if the input shader for run should be outputted to the log + void SetDumpInput(bool enabled) { dump_input_ = enabled; } + + /// @param enabled if the shader being valid after parsing is being enforced. + /// If false, invalidation of the shader will cause an early exit, but not + /// throw an error. + /// If true invalidation will throw an error that is caught by libFuzzer and + /// will generate a crash report. + void SetEnforceValidity(bool enabled) { enforce_validity = enabled; } + + /// Convert given shader from input to output format. + /// Will also apply provided transforms and run the inspector over the result. + /// @param data buffer of data that will interpreted as a byte array or string + /// depending on the shader input format. + /// @param size number of elements in buffer + /// @returns 0, this is what libFuzzer expects + int Run(const uint8_t* data, size_t size); + + /// @returns diagnostic messages generated while Run() is executed. + const tint::diag::List& Diagnostics() const { return diagnostics_; } + + /// @returns if there are any errors in the diagnostic messages + bool HasErrors() const { return diagnostics_.contains_errors(); } + + /// @returns generated SPIR-V binary, if SPIR-V was emitted. + const std::vector<uint32_t>& GetGeneratedSpirv() const { + return generated_spirv_; + } + + /// @returns generated WGSL string, if WGSL was emitted. + const std::string& GetGeneratedWgsl() const { return generated_wgsl_; } + + /// @returns generated HLSL string, if HLSL was emitted. + const std::string& GetGeneratedHlsl() const { return generated_hlsl_; } + + /// @returns generated MSL string, if HLSL was emitted. + const std::string& GetGeneratedMsl() const { return generated_msl_; } + + /// @param options SPIR-V emission options + void SetOptionsSpirv(const writer::spirv::Options& options) { + options_spirv_ = options; + } + + /// @param options WGSL emission options + void SetOptionsWgsl(const writer::wgsl::Options& options) { + options_wgsl_ = options; + } + + /// @param options HLSL emission options + void SetOptionsHlsl(const writer::hlsl::Options& options) { + options_hlsl_ = options; + } + + /// @param options MSL emission options + void SetOptionsMsl(const writer::msl::Options& options) { + options_msl_ = options; + } + + private: + InputFormat input_; + OutputFormat output_; + transform::Manager* transform_manager_ = nullptr; + transform::DataMap* transform_inputs_ = nullptr; + bool dump_input_ = false; + tint::diag::List diagnostics_; + bool enforce_validity = false; + + std::vector<uint32_t> generated_spirv_; + std::string generated_wgsl_; + std::string generated_hlsl_; + std::string generated_msl_; + + writer::spirv::Options options_spirv_; + writer::wgsl::Options options_wgsl_; + writer::hlsl::Options options_hlsl_; + writer::msl::Options options_msl_; + +#if TINT_BUILD_WGSL_READER + /// The source file needs to live at least as long as #diagnostics_ + std::unique_ptr<Source::File> file_; +#endif // TINT_BUILD_WGSL_READER + + /// Runs a series of reflection operations to exercise the Inspector API. + void RunInspector(Program* program); +}; + +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_COMMON_FUZZER_H_
diff --git a/src/tint/fuzzers/tint_first_index_offset_fuzzer.cc b/src/tint/fuzzers/tint_first_index_offset_fuzzer.cc new file mode 100644 index 0000000..f8d437c --- /dev/null +++ b/src/tint/fuzzers/tint_first_index_offset_fuzzer.cc
@@ -0,0 +1,35 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/fuzzer_init.h" +#include "src/tint/fuzzers/tint_common_fuzzer.h" +#include "src/tint/fuzzers/transform_builder.h" + +namespace tint { +namespace fuzzers { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + TransformBuilder tb(data, size); + tb.AddTransform<transform::FirstIndexOffset>(); + + fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kWGSL); + fuzzer.SetTransformManager(tb.manager(), tb.data_map()); + fuzzer.SetDumpInput(GetCliParams().dump_input); + fuzzer.SetEnforceValidity(GetCliParams().enforce_validity); + + return fuzzer.Run(data, size); +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_reader_writer_fuzzer.h b/src/tint/fuzzers/tint_reader_writer_fuzzer.h new file mode 100644 index 0000000..e4a4e37 --- /dev/null +++ b/src/tint/fuzzers/tint_reader_writer_fuzzer.h
@@ -0,0 +1,72 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_READER_WRITER_FUZZER_H_ +#define SRC_TINT_FUZZERS_TINT_READER_WRITER_FUZZER_H_ + +#include <memory> + +#include "src/tint/fuzzers/tint_common_fuzzer.h" +#include "src/tint/fuzzers/transform_builder.h" + +namespace tint { +namespace fuzzers { + +/// Wrapper around the common fuzzing class for tint_*_reader_*_writter fuzzers +class ReaderWriterFuzzer : public CommonFuzzer { + public: + /// Constructor + /// Pass through to the CommonFuzzer constructor + /// @param input shader language being read + /// @param output shader language being emitted + ReaderWriterFuzzer(InputFormat input, OutputFormat output) + : CommonFuzzer(input, output) {} + + /// Destructor + ~ReaderWriterFuzzer() {} + + /// Pass through to the CommonFuzzer setter, but records if it has been + /// invoked. + /// @param tm manager for transforms to run + /// @param inputs data for transforms to run + void SetTransformManager(transform::Manager* tm, transform::DataMap* inputs) { + tm_set_ = true; + CommonFuzzer::SetTransformManager(tm, inputs); + } + + /// Pass through to the CommonFuzzer implementation, but will setup a + /// robustness transform, if no other transforms have been set. + /// @param data buffer of data that will interpreted as a byte array or string + /// depending on the shader input format. + /// @param size number of elements in buffer + /// @returns 0, this is what libFuzzer expects + int Run(const uint8_t* data, size_t size) { + if (!tm_set_) { + tb_ = std::make_unique<TransformBuilder>(data, size); + tb_->AddTransform<tint::transform::Robustness>(); + SetTransformManager(tb_->manager(), tb_->data_map()); + } + + return CommonFuzzer::Run(data, size); + } + + private: + bool tm_set_ = false; + std::unique_ptr<TransformBuilder> tb_; +}; + +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_READER_WRITER_FUZZER_H_
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/BUILD.gn b/src/tint/fuzzers/tint_regex_fuzzer/BUILD.gn new file mode 100644 index 0000000..e8d647d --- /dev/null +++ b/src/tint/fuzzers/tint_regex_fuzzer/BUILD.gn
@@ -0,0 +1,36 @@ +# Copyright 2021 The Tint Authors +# +# 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("//build_overrides/build.gni") +import("../../../../tint_overrides_with_defaults.gni") + +if (build_with_chromium) { + source_set("tint_regex_fuzzer") { + public_configs = [ + "${tint_root_dir}/src/tint:tint_config", + "${tint_root_dir}/src/tint:tint_common_config", + ] + + deps = [ "${tint_root_dir}/src/tint/fuzzers:tint_fuzzer_common_src" ] + + sources = [ + "cli.cc", + "cli.h", + "fuzzer.cc", + "override_cli_params.h", + "wgsl_mutator.cc", + "wgsl_mutator.h", + ] + } +}
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/CMakeLists.txt b/src/tint/fuzzers/tint_regex_fuzzer/CMakeLists.txt new file mode 100644 index 0000000..bcd0885 --- /dev/null +++ b/src/tint/fuzzers/tint_regex_fuzzer/CMakeLists.txt
@@ -0,0 +1,75 @@ +# Copyright 2021 The Tint Authors. +# +# 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. + +function(add_tint_regex_fuzzer NAME) + add_executable(${NAME} ${NAME}.cc ${REGEX_FUZZER_SOURCES}) + target_link_libraries(${NAME} libtint-fuzz libtint_regex_fuzzer) + tint_default_compile_options(${NAME}) + target_compile_definitions(${NAME} PRIVATE CUSTOM_MUTATOR) + target_include_directories(${NAME} PRIVATE ${CMAKE_BINARY_DIR}) +endfunction() + +set(LIBTINT_REGEX_FUZZER_SOURCES + ../mersenne_twister_engine.cc + ../mersenne_twister_engine.h + ../random_generator.cc + ../random_generator.h + ../random_generator_engine.cc + ../random_generator_engine.h + wgsl_mutator.cc + wgsl_mutator.h) + +# Add static library target. +add_library(libtint_regex_fuzzer STATIC ${LIBTINT_REGEX_FUZZER_SOURCES}) +tint_default_compile_options(libtint_regex_fuzzer) + +set(REGEX_FUZZER_SOURCES + cli.cc + cli.h + fuzzer.cc + override_cli_params.h + ../tint_common_fuzzer.cc + ../tint_common_fuzzer.h) + +set_source_files_properties(fuzzer.cc PROPERTIES COMPILE_FLAGS -Wno-missing-prototypes) + +# Add libfuzzer targets. +# Targets back-ends according to command line arguments. +add_tint_regex_fuzzer(tint_regex_fuzzer) +# Targets back-ends individually. +add_tint_regex_fuzzer(tint_regex_hlsl_writer_fuzzer) +add_tint_regex_fuzzer(tint_regex_msl_writer_fuzzer) +add_tint_regex_fuzzer(tint_regex_spv_writer_fuzzer) +add_tint_regex_fuzzer(tint_regex_wgsl_writer_fuzzer) + +# Add tests. +if (${TINT_BUILD_TESTS}) + set(TEST_SOURCES + regex_fuzzer_tests.cc) + + add_executable(tint_regex_fuzzer_unittests ${TEST_SOURCES}) + + target_include_directories( + tint_regex_fuzzer_unittests PRIVATE ${gmock_SOURCE_DIR}/include) + target_link_libraries(tint_regex_fuzzer_unittests gmock_main libtint_regex_fuzzer) + tint_default_compile_options(tint_regex_fuzzer_unittests) + target_compile_options(tint_regex_fuzzer_unittests PRIVATE + -Wno-global-constructors + -Wno-weak-vtables + -Wno-covered-switch-default) + + target_include_directories(tint_regex_fuzzer_unittests PRIVATE ${CMAKE_BINARY_DIR}) + + add_test(NAME tint_regex_fuzzer_unittests COMMAND tint_regex_fuzzer_unittests) +endif ()
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/CPPLINT.cfg b/src/tint/fuzzers/tint_regex_fuzzer/CPPLINT.cfg new file mode 100644 index 0000000..96988ad --- /dev/null +++ b/src/tint/fuzzers/tint_regex_fuzzer/CPPLINT.cfg
@@ -0,0 +1 @@ +filter=-build/c++11 \ No newline at end of file
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/cli.cc b/src/tint/fuzzers/tint_regex_fuzzer/cli.cc new file mode 100644 index 0000000..1fd3e10 --- /dev/null +++ b/src/tint/fuzzers/tint_regex_fuzzer/cli.cc
@@ -0,0 +1,126 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_regex_fuzzer/cli.h" + +#include <cstring> +#include <iostream> +#include <limits> +#include <sstream> +#include <string> +#include <utility> + +namespace tint { +namespace fuzzers { +namespace regex_fuzzer { +namespace { + +const char* const kHelpMessage = R"( +This is a fuzzer for the Tint compiler that works by mutating a WGSL shader. + +Below is a list of all supported parameters for this fuzzer. You may want to +run it with -help=1 to check out libfuzzer parameters. + + -tint_fuzzing_target= + Specifies the shading language to target during fuzzing. + This must be one or a combination of `wgsl`, `spv`, `hlsl`, + `msl` (without `) separated by commas. By default it's + `wgsl,msl,hlsl,spv`. + + -tint_help + Show this message. Note that there is also a -help=1 + parameter that will display libfuzzer's help message. +)"; + +bool HasPrefix(const char* str, const char* prefix) { + return strncmp(str, prefix, strlen(prefix)) == 0; +} + +[[noreturn]] void InvalidParam(const char* param) { + std::cout << "Invalid value for " << param << std::endl; + std::cout << kHelpMessage << std::endl; + exit(1); +} + +bool ParseFuzzingTarget(const char* value, FuzzingTarget* out) { + if (!strcmp(value, "wgsl")) { + *out = FuzzingTarget::kWgsl; + } else if (!strcmp(value, "spv")) { + *out = FuzzingTarget::kSpv; + } else if (!strcmp(value, "msl")) { + *out = FuzzingTarget::kMsl; + } else if (!strcmp(value, "hlsl")) { + *out = FuzzingTarget::kHlsl; + } else { + return false; + } + return true; +} + +} // namespace + +CliParams ParseCliParams(int* argc, char** argv) { + CliParams cli_params; + auto help = false; + + for (int i = *argc - 1; i > 0; --i) { + auto param = argv[i]; + auto recognized_parameter = true; + + if (HasPrefix(param, "-tint_fuzzing_target=")) { + auto result = FuzzingTarget::kNone; + + std::stringstream ss(param + sizeof("-tint_fuzzing_target=") - 1); + for (std::string value; std::getline(ss, value, ',');) { + auto tmp = FuzzingTarget::kNone; + if (!ParseFuzzingTarget(value.c_str(), &tmp)) { + InvalidParam(param); + } + result = result | tmp; + } + + if (result == FuzzingTarget::kNone) { + InvalidParam(param); + } + + cli_params.fuzzing_target = result; + } else if (!strcmp(param, "-tint_help")) { + help = true; + } else { + recognized_parameter = false; + } + + if (recognized_parameter) { + // Remove the recognized parameter from the list of all parameters by + // swapping it with the last one. This will suppress warnings in the + // libFuzzer about unrecognized parameters. By default, libFuzzer thinks + // that all user-defined parameters start with two dashes. However, we are + // forced to use a single one to make the fuzzer compatible with the + // ClusterFuzz. + std::swap(argv[i], argv[*argc - 1]); + *argc -= 1; + } + } + + if (help) { + std::cout << kHelpMessage << std::endl; + exit(0); + } + + return cli_params; +} + +} // namespace regex_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/cli.h b/src/tint/fuzzers/tint_regex_fuzzer/cli.h new file mode 100644 index 0000000..55048c5 --- /dev/null +++ b/src/tint/fuzzers/tint_regex_fuzzer/cli.h
@@ -0,0 +1,64 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_REGEX_FUZZER_CLI_H_ +#define SRC_TINT_FUZZERS_TINT_REGEX_FUZZER_CLI_H_ + +#include <cstdint> + +namespace tint { +namespace fuzzers { +namespace regex_fuzzer { + +/// The backend this fuzzer will test. +enum class FuzzingTarget { + kNone = 0, + kHlsl = 1 << 0, + kMsl = 1 << 1, + kSpv = 1 << 2, + kWgsl = 1 << 3, + kAll = kHlsl | kMsl | kSpv | kWgsl +}; + +inline FuzzingTarget operator|(FuzzingTarget a, FuzzingTarget b) { + return static_cast<FuzzingTarget>(static_cast<int>(a) | static_cast<int>(b)); +} + +inline FuzzingTarget operator&(FuzzingTarget a, FuzzingTarget b) { + return static_cast<FuzzingTarget>(static_cast<int>(a) & static_cast<int>(b)); +} + +/// CLI parameters accepted by the fuzzer. Type -tint_help in the CLI to see the +/// help message +struct CliParams { + /// Compiler backends we want to fuzz. + FuzzingTarget fuzzing_target = FuzzingTarget::kAll; +}; + +/// @brief Parses CLI parameters. +/// +/// This function will exit the process with non-zero return code if some +/// parameters are invalid. This function will remove recognized parameters from +/// `argv` and adjust `argc` accordingly. +/// +/// @param argc - the total number of parameters. +/// @param argv - array of all CLI parameters. +/// @return parsed parameters. +CliParams ParseCliParams(int* argc, char** argv); + +} // namespace regex_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_REGEX_FUZZER_CLI_H_
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/fuzzer.cc b/src/tint/fuzzers/tint_regex_fuzzer/fuzzer.cc new file mode 100644 index 0000000..bc8dc78 --- /dev/null +++ b/src/tint/fuzzers/tint_regex_fuzzer/fuzzer.cc
@@ -0,0 +1,156 @@ +// Copyright 2021 The Tint Authors. +// +// 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 <cstddef> +#include <cstdint> + +#include "src/tint/fuzzers/random_generator.h" +#include "src/tint/fuzzers/tint_common_fuzzer.h" +#include "src/tint/fuzzers/tint_regex_fuzzer/cli.h" +#include "src/tint/fuzzers/tint_regex_fuzzer/override_cli_params.h" +#include "src/tint/fuzzers/tint_regex_fuzzer/wgsl_mutator.h" +#include "src/tint/fuzzers/transform_builder.h" +#include "src/tint/reader/wgsl/parser.h" +#include "src/tint/writer/wgsl/generator.h" + +namespace tint { +namespace fuzzers { +namespace regex_fuzzer { +namespace { + +CliParams cli_params{}; + +enum class MutationKind { + kSwapIntervals, + kDeleteInterval, + kDuplicateInterval, + kReplaceIdentifier, + kReplaceLiteral, + kInsertReturnStatement, + kNumMutationKinds +}; + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) { + // Parse CLI parameters. `ParseCliParams` will call `exit` if some parameter + // is invalid. + cli_params = ParseCliParams(argc, *argv); + // For some fuzz targets it is desirable to force the values of certain CLI + // parameters after parsing. + OverrideCliParams(cli_params); + return 0; +} + +extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, + size_t size, + size_t max_size, + unsigned seed) { + std::string wgsl_code(data, data + size); + const std::vector<std::string> delimiters{";"}; + RandomGenerator generator(seed); + + std::string delimiter = + delimiters[generator.GetUInt32(static_cast<uint32_t>(delimiters.size()))]; + + MutationKind mutation_kind = static_cast<MutationKind>(generator.GetUInt32( + static_cast<uint32_t>(MutationKind::kNumMutationKinds))); + + switch (mutation_kind) { + case MutationKind::kSwapIntervals: + if (!SwapRandomIntervals(delimiter, wgsl_code, generator)) { + return 0; + } + break; + + case MutationKind::kDeleteInterval: + if (!DeleteRandomInterval(delimiter, wgsl_code, generator)) { + return 0; + } + break; + + case MutationKind::kDuplicateInterval: + if (!DuplicateRandomInterval(delimiter, wgsl_code, generator)) { + return 0; + } + break; + + case MutationKind::kReplaceIdentifier: + if (!ReplaceRandomIdentifier(wgsl_code, generator)) { + return 0; + } + break; + + case MutationKind::kReplaceLiteral: + if (!ReplaceRandomIntLiteral(wgsl_code, generator)) { + return 0; + } + break; + + case MutationKind::kInsertReturnStatement: + if (!InsertReturnStatement(wgsl_code, generator)) { + return 0; + } + break; + + default: + assert(false && "Unreachable"); + return 0; + } + + if (wgsl_code.size() > max_size) { + return 0; + } + + memcpy(data, wgsl_code.c_str(), wgsl_code.size()); + return wgsl_code.size(); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size == 0) { + return 0; + } + + struct Target { + FuzzingTarget fuzzing_target; + OutputFormat output_format; + const char* name; + }; + + Target targets[] = {{FuzzingTarget::kWgsl, OutputFormat::kWGSL, "WGSL"}, + {FuzzingTarget::kHlsl, OutputFormat::kHLSL, "HLSL"}, + {FuzzingTarget::kMsl, OutputFormat::kMSL, "MSL"}, + {FuzzingTarget::kSpv, OutputFormat::kSpv, "SPV"}}; + + for (auto target : targets) { + if ((target.fuzzing_target & cli_params.fuzzing_target) != + target.fuzzing_target) { + continue; + } + + TransformBuilder tb(data, size); + tb.AddTransform<tint::transform::Robustness>(); + + CommonFuzzer fuzzer(InputFormat::kWGSL, target.output_format); + fuzzer.SetTransformManager(tb.manager(), tb.data_map()); + + fuzzer.Run(data, size); + } + + return 0; +} + +} // namespace +} // namespace regex_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/override_cli_params.h b/src/tint/fuzzers/tint_regex_fuzzer/override_cli_params.h new file mode 100644 index 0000000..445f524 --- /dev/null +++ b/src/tint/fuzzers/tint_regex_fuzzer/override_cli_params.h
@@ -0,0 +1,36 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_REGEX_FUZZER_OVERRIDE_CLI_PARAMS_H_ +#define SRC_TINT_FUZZERS_TINT_REGEX_FUZZER_OVERRIDE_CLI_PARAMS_H_ + +#include "src/tint/fuzzers/tint_regex_fuzzer/cli.h" + +namespace tint { +namespace fuzzers { +namespace regex_fuzzer { + +/// @brief Allows CLI parameters to be overridden. +/// +/// This function allows fuzz targets to override particular CLI parameters, +/// for example forcing a particular back-end to be targeted. +/// +/// @param cli_params - the parsed CLI parameters to be updated. +void OverrideCliParams(CliParams& cli_params); + +} // namespace regex_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_REGEX_FUZZER_OVERRIDE_CLI_PARAMS_H_
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/regex_fuzzer_tests.cc b/src/tint/fuzzers/tint_regex_fuzzer/regex_fuzzer_tests.cc new file mode 100644 index 0000000..d58942d --- /dev/null +++ b/src/tint/fuzzers/tint_regex_fuzzer/regex_fuzzer_tests.cc
@@ -0,0 +1,521 @@ +// Copyright 2021 The Tint Authors. +// +// 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 <string> + +#include "gtest/gtest.h" + +#include "src/tint/fuzzers/tint_regex_fuzzer/wgsl_mutator.h" + +namespace tint { +namespace fuzzers { +namespace regex_fuzzer { +namespace { + +// Swaps two non-consecutive regions in the edge +TEST(SwapRegionsTest, SwapIntervalsEdgeNonConsecutive) { + std::string R1 = ";region1;", R2 = ";regionregion2;", + R3 = ";regionregionregion3;"; + std::string all_regions = R1 + R2 + R3; + + // this call should swap R1 with R3. + SwapIntervals(0, R1.length(), R1.length() + R2.length(), R3.length(), + all_regions); + + ASSERT_EQ(R3 + R2 + R1, all_regions); +} + +// Swaps two non-consecutive regions not in the edge +TEST(SwapRegionsTest, SwapIntervalsNonConsecutiveNonEdge) { + std::string R1 = ";region1;", R2 = ";regionregion2;", + R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;", + R5 = ";regionregionregionregionregion5;"; + std::string all_regions = R1 + R2 + R3 + R4 + R5; + + // this call should swap R2 with R4. + SwapIntervals(R1.length(), R2.length(), + R1.length() + R2.length() + R3.length(), R4.length(), + all_regions); + + ASSERT_EQ(R1 + R4 + R3 + R2 + R5, all_regions); +} + +// Swaps two consecutive regions not in the edge (sorrounded by other +// regions) +TEST(SwapRegionsTest, SwapIntervalsConsecutiveEdge) { + std::string R1 = ";region1;", R2 = ";regionregion2;", + R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;", + R5 = ";regionregionregionregionregion5;"; + std::string all_regions = R1 + R2 + R3 + R4; + + // this call should swap R2 with R3. + SwapIntervals(R1.length(), R2.length(), R1.length() + R2.length(), + R3.length(), all_regions); + + ASSERT_EQ(R1 + R3 + R2 + R4, all_regions); +} + +// Swaps two consecutive regions not in the edge (not sorrounded by other +// regions) +TEST(SwapRegionsTest, SwapIntervalsConsecutiveNonEdge) { + std::string R1 = ";region1;", R2 = ";regionregion2;", + R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;", + R5 = ";regionregionregionregionregion5;"; + std::string all_regions = R1 + R2 + R3 + R4 + R5; + + // this call should swap R4 with R5. + SwapIntervals(R1.length() + R2.length() + R3.length(), R4.length(), + R1.length() + R2.length() + R3.length() + R4.length(), + R5.length(), all_regions); + + ASSERT_EQ(R1 + R2 + R3 + R5 + R4, all_regions); +} + +// Deletes the first region. +TEST(DeleteRegionTest, DeleteFirstRegion) { + std::string R1 = ";region1;", R2 = ";regionregion2;", + R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;", + R5 = ";regionregionregionregionregion5;"; + std::string all_regions = R1 + R2 + R3 + R4 + R5; + + // This call should delete R1. + DeleteInterval(0, R1.length(), all_regions); + + ASSERT_EQ(";" + R2 + R3 + R4 + R5, all_regions); +} + +// Deletes the last region. +TEST(DeleteRegionTest, DeleteLastRegion) { + std::string R1 = ";region1;", R2 = ";regionregion2;", + R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;", + R5 = ";regionregionregionregionregion5;"; + std::string all_regions = R1 + R2 + R3 + R4 + R5; + + // This call should delete R5. + DeleteInterval(R1.length() + R2.length() + R3.length() + R4.length(), + R5.length(), all_regions); + + ASSERT_EQ(R1 + R2 + R3 + R4 + ";", all_regions); +} + +// Deletes the middle region. +TEST(DeleteRegionTest, DeleteMiddleRegion) { + std::string R1 = ";region1;", R2 = ";regionregion2;", + R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;", + R5 = ";regionregionregionregionregion5;"; + std::string all_regions = R1 + R2 + R3 + R4 + R5; + + // This call should delete R3. + DeleteInterval(R1.length() + R2.length(), R3.length(), all_regions); + + ASSERT_EQ(R1 + R2 + ";" + R4 + R5, all_regions); +} + +TEST(InsertRegionTest, InsertRegionTest1) { + std::string R1 = ";region1;", R2 = ";regionregion2;", + R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;", + R5 = ";regionregionregionregionregion5;"; + std::string all_regions = R1 + R2 + R3 + R4 + R5; + + // This call should insert R2 after R4. + DuplicateInterval(R1.length(), R2.length(), + R1.length() + R2.length() + R3.length() + R4.length() - 1, + all_regions); + + ASSERT_EQ(R1 + R2 + R3 + R4 + R2.substr(1, R2.size() - 1) + R5, all_regions); +} + +TEST(InsertRegionTest, InsertRegionTest2) { + std::string R1 = ";region1;", R2 = ";regionregion2;", + R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;", + R5 = ";regionregionregionregionregion5;"; + + std::string all_regions = R1 + R2 + R3 + R4 + R5; + + // This call should insert R3 after R1. + DuplicateInterval(R1.length() + R2.length(), R3.length(), R1.length() - 1, + all_regions); + + ASSERT_EQ(R1 + R3.substr(1, R3.length() - 1) + R2 + R3 + R4 + R5, + all_regions); +} + +TEST(InsertRegionTest, InsertRegionTest3) { + std::string R1 = ";region1;", R2 = ";regionregion2;", + R3 = ";regionregionregion3;", R4 = ";regionregionregionregion4;", + R5 = ";regionregionregionregionregion5;"; + + std::string all_regions = R1 + R2 + R3 + R4 + R5; + + // This call should insert R2 after R5. + DuplicateInterval(R1.length(), R2.length(), all_regions.length() - 1, + all_regions); + + ASSERT_EQ(R1 + R2 + R3 + R4 + R5 + R2.substr(1, R2.length() - 1), + all_regions); +} + +TEST(ReplaceIdentifierTest, ReplaceIdentifierTest1) { + std::string R1 = "|region1|", R2 = "; region2;", + R3 = "---------region3---------", R4 = "++region4++", + R5 = "***region5***"; + std::string all_regions = R1 + R2 + R3 + R4 + R5; + + // Replaces R3 with R1. + ReplaceRegion(0, R1.length(), R1.length() + R2.length(), R3.length(), + all_regions); + + ASSERT_EQ(R1 + R2 + R1 + R4 + R5, all_regions); +} + +TEST(ReplaceIdentifierTest, ReplaceIdentifierTest2) { + std::string R1 = "|region1|", R2 = "; region2;", + R3 = "---------region3---------", R4 = "++region4++", + R5 = "***region5***"; + std::string all_regions = R1 + R2 + R3 + R4 + R5; + + // Replaces R5 with R3. + ReplaceRegion(R1.length() + R2.length(), R3.length(), + R1.length() + R2.length() + R3.length() + R4.length(), + R5.length(), all_regions); + + ASSERT_EQ(R1 + R2 + R3 + R4 + R3, all_regions); +} + +TEST(GetIdentifierTest, GetIdentifierTest1) { + std::string wgsl_code = + R"(fn clamp_0acf8f() { + var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>()); + } + @stage(vertex) + fn vertex_main() -> @builtin(position) vec4<f32> { + clamp_0acf8f();" + return vec4<f32>(); + } + @stage(fragment) + fn fragment_main() { + clamp_0acf8f(); + } + @stage(compute) @workgroup_size(1) + fn compute_main() {" + var<private> foo: f32 = 0.0; + clamp_0acf8f(); + })"; + + std::vector<std::pair<size_t, size_t>> identifiers_pos = + GetIdentifiers(wgsl_code); + + std::vector<std::pair<size_t, size_t>> ground_truth = { + std::make_pair(3, 12), std::make_pair(28, 3), std::make_pair(37, 4), + std::make_pair(49, 5), std::make_pair(60, 3), std::make_pair(68, 4), + std::make_pair(81, 4), std::make_pair(110, 5), std::make_pair(130, 2), + std::make_pair(140, 4), std::make_pair(151, 7), std::make_pair(169, 4), + std::make_pair(190, 12), std::make_pair(216, 6), std::make_pair(228, 3), + std::make_pair(251, 5), std::make_pair(273, 2), std::make_pair(285, 4), + std::make_pair(302, 12), std::make_pair(333, 5), std::make_pair(349, 14), + std::make_pair(373, 2), std::make_pair(384, 4), std::make_pair(402, 3), + std::make_pair(415, 3), std::make_pair(420, 3), std::make_pair(439, 12)}; + + ASSERT_EQ(ground_truth, identifiers_pos); +} + +TEST(TestGetLiteralsValues, TestGetLiteralsValues1) { + std::string wgsl_code = + R"(fn clamp_0acf8f() { + var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>()); + } + @stage(vertex) + fn vertex_main() -> @builtin(position) vec4<f32> { + clamp_0acf8f(); + var foo_1: i32 = 3; + return vec4<f32>(); + } + @stage(fragment) + fn fragment_main() { + clamp_0acf8f(); + } + @stage(compute) @workgroup_size(1) + fn compute_main() { + var<private> foo: f32 = 0.0; + var foo_2: i32 = 10; + clamp_0acf8f(); + } + foo_1 = 5 + 7; + var foo_3 : i32 = -20;)"; + + std::vector<std::pair<size_t, size_t>> literals_pos = + GetIntLiterals(wgsl_code); + + std::vector<std::string> ground_truth = {"3", "10", "5", "7", "-20"}; + + std::vector<std::string> result; + + for (auto pos : literals_pos) { + result.push_back(wgsl_code.substr(pos.first, pos.second)); + } + + ASSERT_EQ(ground_truth, result); +} + +TEST(InsertReturnTest, FindClosingBrace) { + std::string wgsl_code = + R"(fn clamp_0acf8f() { + if(false){ + + } else{ + var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>()); + } + } + @stage(vertex) + fn vertex_main() -> @builtin(position) vec4<f32> { + clamp_0acf8f(); + var foo_1: i32 = 3; + return vec4<f32>(); + } + @stage(fragment) + fn fragment_main() { + clamp_0acf8f(); + } + @stage(compute) @workgroup_size(1) + fn compute_main() { + var<private> foo: f32 = 0.0; + var foo_2: i32 = 10; + clamp_0acf8f(); + } + foo_1 = 5 + 7; + var foo_3 : i32 = -20; + )"; + size_t opening_bracket_pos = 18; + size_t closing_bracket_pos = FindClosingBrace(opening_bracket_pos, wgsl_code); + + // The -1 is needed since the function body starts after the left bracket. + std::string function_body = wgsl_code.substr( + opening_bracket_pos + 1, closing_bracket_pos - opening_bracket_pos - 1); + std::string expected = + R"( + if(false){ + + } else{ + var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>()); + } + )"; + ASSERT_EQ(expected, function_body); +} + +TEST(InsertReturnTest, FindClosingBraceFailing) { + std::string wgsl_code = + R"(fn clamp_0acf8f() { + // This comment } causes the test to fail. + "if(false){ + + } else{ + var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>()); + } + } + @stage(vertex) + fn vertex_main() -> @builtin(position) vec4<f32> { + clamp_0acf8f(); + var foo_1: i32 = 3; + return vec4<f32>(); + } + @stage(fragment) + fn fragment_main() { + clamp_0acf8f(); + } + @stage(compute) @workgroup_size(1) + fn compute_main() { + var<private> foo: f32 = 0.0; + var foo_2: i32 = 10; + clamp_0acf8f(); + } + foo_1 = 5 + 7; + var foo_3 : i32 = -20;)"; + size_t opening_bracket_pos = 18; + size_t closing_bracket_pos = FindClosingBrace(opening_bracket_pos, wgsl_code); + + // The -1 is needed since the function body starts after the left bracket. + std::string function_body = wgsl_code.substr( + opening_bracket_pos + 1, closing_bracket_pos - opening_bracket_pos - 1); + std::string expected = + R"(// This comment } causes the test to fail. + "if(false){ + + } else{ + var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>()); + })"; + ASSERT_NE(expected, function_body); +} + +TEST(TestInsertReturn, TestInsertReturn1) { + std::string wgsl_code = + R"(fn clamp_0acf8f() { + var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>()); + } + @stage(vertex) + fn vertex_main() -> @builtin(position) vec4<f32> { + clamp_0acf8f(); + var foo_1: i32 = 3; + return vec4<f32>(); + } + @stage(fragment) + fn fragment_main() { + clamp_0acf8f(); + } + @stage(compute) @workgroup_size(1) + fn compute_main() { + var<private> foo: f32 = 0.0; + var foo_2: i32 = 10; + clamp_0acf8f(); + } + foo_1 = 5 + 7; + var foo_3 : i32 = -20;)"; + + std::vector<size_t> semicolon_pos; + for (size_t pos = wgsl_code.find(";", 0); pos != std::string::npos; + pos = wgsl_code.find(";", pos + 1)) { + semicolon_pos.push_back(pos); + } + + // should insert a return true statement after the first semicolon of the + // first function the the WGSL-like string above. + wgsl_code.insert(semicolon_pos[0] + 1, "return true;"); + + std::string expected_wgsl_code = + R"(fn clamp_0acf8f() { + var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>());return true; + } + @stage(vertex) + fn vertex_main() -> @builtin(position) vec4<f32> { + clamp_0acf8f(); + var foo_1: i32 = 3; + return vec4<f32>(); + } + @stage(fragment) + fn fragment_main() { + clamp_0acf8f(); + } + @stage(compute) @workgroup_size(1) + fn compute_main() { + var<private> foo: f32 = 0.0; + var foo_2: i32 = 10; + clamp_0acf8f(); + } + foo_1 = 5 + 7; + var foo_3 : i32 = -20;)"; + + ASSERT_EQ(expected_wgsl_code, wgsl_code); +} + +TEST(TestInsertReturn, TestFunctionPositions) { + std::string wgsl_code = + R"(fn clamp_0acf8f() { + var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>()); + } + @stage(vertex) + fn vertex_main() -> @builtin(position) vec4<f32> { + clamp_0acf8f(); + var foo_1: i32 = 3; + return vec4<f32>(); + } + @stage(fragment) + fn fragment_main() { + clamp_0acf8f(); + } + @stage(compute) @workgroup_size(1) + fn compute_main() { + var<private> foo: f32 = 0.0; + var foo_2: i32 = 10; + clamp_0acf8f(); + } + fn vert_main() -> @builtin(position) vec4<f32> { + clamp_0acf8f(); + var foo_1: i32 = 3; + return vec4<f32>(); + } + foo_1 = 5 + 7; + var foo_3 : i32 = -20;)"; + + std::vector<size_t> function_positions = GetFunctionBodyPositions(wgsl_code); + std::vector<size_t> expected_positions = {187, 607}; + ASSERT_EQ(expected_positions, function_positions); +} + +TEST(TestInsertReturn, TestMissingSemicolon) { + std::string wgsl_code = + R"(fn clamp_0acf8f() { + var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>()) + } + @stage(vertex) + fn vertex_main() -> @builtin(position) vec4<f32> { + clamp_0acf8f() + var foo_1: i32 = 3 + return vec4<f32>() + } + @stage(fragment) + fn fragment_main() { + clamp_0acf8f(); + } + @stage(compute) @workgroup_size(1) + fn compute_main() { + var<private> foo: f32 = 0.0; + var foo_2: i32 = 10; + clamp_0acf8f(); + } + fn vert_main() -> @builtin(position) vec4<f32> { + clamp_0acf8f() + var foo_1: i32 = 3 + return vec4<f32>() + } + foo_1 = 5 + 7; + var foo_3 : i32 = -20;)"; + + RandomGenerator generator(0); + InsertReturnStatement(wgsl_code, generator); + + // No semicolons found in the function's body, so wgsl_code + // should remain unchanged. + std::string expected_wgsl_code = + R"(fn clamp_0acf8f() { + var res: vec2<f32> = clamp(vec2<f32>(), vec2<f32>(), vec2<f32>()) + } + @stage(vertex) + fn vertex_main() -> @builtin(position) vec4<f32> { + clamp_0acf8f() + var foo_1: i32 = 3 + return vec4<f32>() + } + @stage(fragment) + fn fragment_main() { + clamp_0acf8f(); + } + @stage(compute) @workgroup_size(1) + fn compute_main() { + var<private> foo: f32 = 0.0; + var foo_2: i32 = 10; + clamp_0acf8f(); + } + fn vert_main() -> @builtin(position) vec4<f32> { + clamp_0acf8f() + var foo_1: i32 = 3 + return vec4<f32>() + } + foo_1 = 5 + 7; + var foo_3 : i32 = -20;)"; + ASSERT_EQ(expected_wgsl_code, wgsl_code); +} + +} // namespace +} // namespace regex_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_fuzzer.cc b/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_fuzzer.cc new file mode 100644 index 0000000..045ecfd --- /dev/null +++ b/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_fuzzer.cc
@@ -0,0 +1,28 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_regex_fuzzer/cli.h" +#include "src/tint/fuzzers/tint_regex_fuzzer/override_cli_params.h" + +namespace tint { +namespace fuzzers { +namespace regex_fuzzer { + +void OverrideCliParams(CliParams& /*unused*/) { + // Leave the CLI parameters unchanged. +} + +} // namespace regex_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_hlsl_writer_fuzzer.cc b/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_hlsl_writer_fuzzer.cc new file mode 100644 index 0000000..e89dd97 --- /dev/null +++ b/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_hlsl_writer_fuzzer.cc
@@ -0,0 +1,33 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_regex_fuzzer/cli.h" +#include "src/tint/fuzzers/tint_regex_fuzzer/override_cli_params.h" + +namespace tint { +namespace fuzzers { +namespace regex_fuzzer { + +void OverrideCliParams(CliParams& cli_params) { + assert(cli_params.fuzzing_target == FuzzingTarget::kAll && + "The fuzzing target should not have been set by a CLI parameter: it " + "should have its default value."); + cli_params.fuzzing_target = FuzzingTarget::kHlsl; +} + +} // namespace regex_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_msl_writer_fuzzer.cc b/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_msl_writer_fuzzer.cc new file mode 100644 index 0000000..23afc86 --- /dev/null +++ b/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_msl_writer_fuzzer.cc
@@ -0,0 +1,33 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_regex_fuzzer/cli.h" +#include "src/tint/fuzzers/tint_regex_fuzzer/override_cli_params.h" + +namespace tint { +namespace fuzzers { +namespace regex_fuzzer { + +void OverrideCliParams(CliParams& cli_params) { + assert(cli_params.fuzzing_target == FuzzingTarget::kAll && + "The fuzzing target should not have been set by a CLI parameter: it " + "should have its default value."); + cli_params.fuzzing_target = FuzzingTarget::kMsl; +} + +} // namespace regex_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_spv_writer_fuzzer.cc b/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_spv_writer_fuzzer.cc new file mode 100644 index 0000000..18a1a3e --- /dev/null +++ b/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_spv_writer_fuzzer.cc
@@ -0,0 +1,33 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_regex_fuzzer/cli.h" +#include "src/tint/fuzzers/tint_regex_fuzzer/override_cli_params.h" + +namespace tint { +namespace fuzzers { +namespace regex_fuzzer { + +void OverrideCliParams(CliParams& cli_params) { + assert(cli_params.fuzzing_target == FuzzingTarget::kAll && + "The fuzzing target should not have been set by a CLI parameter: it " + "should have its default value."); + cli_params.fuzzing_target = FuzzingTarget::kSpv; +} + +} // namespace regex_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_wgsl_writer_fuzzer.cc b/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_wgsl_writer_fuzzer.cc new file mode 100644 index 0000000..8fbd395 --- /dev/null +++ b/src/tint/fuzzers/tint_regex_fuzzer/tint_regex_wgsl_writer_fuzzer.cc
@@ -0,0 +1,33 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_regex_fuzzer/cli.h" +#include "src/tint/fuzzers/tint_regex_fuzzer/override_cli_params.h" + +namespace tint { +namespace fuzzers { +namespace regex_fuzzer { + +void OverrideCliParams(CliParams& cli_params) { + assert(cli_params.fuzzing_target == FuzzingTarget::kAll && + "The fuzzing target should not have been set by a CLI parameter: it " + "should have its default value."); + cli_params.fuzzing_target = FuzzingTarget::kWgsl; +} + +} // namespace regex_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/wgsl_mutator.cc b/src/tint/fuzzers/tint_regex_fuzzer/wgsl_mutator.cc new file mode 100644 index 0000000..b8d9160 --- /dev/null +++ b/src/tint/fuzzers/tint_regex_fuzzer/wgsl_mutator.cc
@@ -0,0 +1,358 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_regex_fuzzer/wgsl_mutator.h" + +#include <cassert> +#include <cstring> +#include <map> +#include <regex> +#include <string> +#include <utility> +#include <vector> + +#include "src/tint/fuzzers/random_generator.h" + +namespace tint { +namespace fuzzers { +namespace regex_fuzzer { + +std::vector<size_t> FindDelimiterIndices(const std::string& delimiter, + const std::string& wgsl_code) { + std::vector<size_t> result; + for (size_t pos = wgsl_code.find(delimiter, 0); pos != std::string::npos; + pos = wgsl_code.find(delimiter, pos + 1)) { + result.push_back(pos); + } + + return result; +} + +std::vector<std::pair<size_t, size_t>> GetIdentifiers( + const std::string& wgsl_code) { + std::vector<std::pair<size_t, size_t>> result; + + // This regular expression works by looking for a character that + // is not part of an identifier followed by a WGSL identifier, followed + // by a character which cannot be part of a WGSL identifer. The regex + // for the WGSL identifier is obtained from: + // https://www.w3.org/TR/WGSL/#identifiers. + std::regex wgsl_identifier_regex( + "[^a-zA-Z]([a-zA-Z][0-9a-zA-Z_]*)[^0-9a-zA-Z_]"); + + std::smatch match; + + std::string::const_iterator search_start(wgsl_code.cbegin()); + std::string prefix; + + while (regex_search(search_start, wgsl_code.cend(), match, + wgsl_identifier_regex) == true) { + prefix += match.prefix(); + result.push_back(std::make_pair(prefix.size() + 1, match.str(1).size())); + prefix += match.str(0); + search_start = match.suffix().first; + } + return result; +} + +std::vector<std::pair<size_t, size_t>> GetIntLiterals(const std::string& s) { + std::vector<std::pair<size_t, size_t>> result; + + // Looks for integer literals in decimal or hexadecimal form. + // Regex obtained here: https://www.w3.org/TR/WGSL/#literals + std::regex int_literal_regex("-?0x[0-9a-fA-F]+ | 0 | -?[1-9][0-9]*"); + std::regex uint_literal_regex("0x[0-9a-fA-F]+u | 0u | [1-9][0-9]*u"); + std::smatch match; + + std::string::const_iterator search_start(s.cbegin()); + std::string prefix = ""; + + while (regex_search(search_start, s.cend(), match, int_literal_regex) || + regex_search(search_start, s.cend(), match, uint_literal_regex)) { + prefix += match.prefix(); + result.push_back( + std::make_pair(prefix.size() + 1, match.str(0).size() - 1)); + prefix += match.str(0); + search_start = match.suffix().first; + } + return result; +} + +size_t FindClosingBrace(size_t opening_bracket_pos, + const std::string& wgsl_code) { + size_t open_bracket_count = 1; + size_t pos = opening_bracket_pos + 1; + while (open_bracket_count >= 1 && pos < wgsl_code.size()) { + if (wgsl_code[pos] == '{') { + ++open_bracket_count; + } else if (wgsl_code[pos] == '}') { + --open_bracket_count; + } + ++pos; + } + return (pos == wgsl_code.size() && open_bracket_count >= 1) ? 0 : pos - 1; +} + +std::vector<size_t> GetFunctionBodyPositions(const std::string& wgsl_code) { + // Finds all the functions with a non-void return value. + std::regex function_regex("fn.*?->.*?\\{"); + std::smatch match; + std::vector<size_t> result; + + auto search_start(wgsl_code.cbegin()); + std::string prefix = ""; + + while (std::regex_search(search_start, wgsl_code.cend(), match, + function_regex)) { + result.push_back( + static_cast<size_t>(match.suffix().first - wgsl_code.cbegin() - 1L)); + search_start = match.suffix().first; + } + return result; +} + +bool InsertReturnStatement(std::string& wgsl_code, RandomGenerator& generator) { + std::vector<size_t> function_body_positions = + GetFunctionBodyPositions(wgsl_code); + + // No function was found in wgsl_code. + if (function_body_positions.empty()) { + return false; + } + + // Pick a random function's opening bracket, find the corresponding closing + // bracket, and find a semi-colon within the function body. + size_t left_bracket_pos = generator.GetRandomElement(function_body_positions); + + size_t right_bracket_pos = FindClosingBrace(left_bracket_pos, wgsl_code); + + if (right_bracket_pos == 0) { + return false; + } + + std::vector<size_t> semicolon_positions; + for (size_t pos = wgsl_code.find(";", left_bracket_pos + 1); + pos < right_bracket_pos; pos = wgsl_code.find(";", pos + 1)) { + semicolon_positions.push_back(pos); + } + + if (semicolon_positions.empty()) { + return false; + } + + size_t semicolon_position = generator.GetRandomElement(semicolon_positions); + + // Get all identifiers and integer literals to use as potential return values. + std::vector<std::pair<size_t, size_t>> identifiers = + GetIdentifiers(wgsl_code); + auto return_values = identifiers; + std::vector<std::pair<size_t, size_t>> int_literals = + GetIntLiterals(wgsl_code); + return_values.insert(return_values.end(), int_literals.begin(), + int_literals.end()); + std::pair<size_t, size_t> return_value = + generator.GetRandomElement(return_values); + std::string return_statement = + "return " + wgsl_code.substr(return_value.first, return_value.second) + + ";"; + + // Insert the return statement immediately after the semicolon. + wgsl_code.insert(semicolon_position + 1, return_statement); + return true; +} + +void SwapIntervals(size_t idx1, + size_t reg1_len, + size_t idx2, + size_t reg2_len, + std::string& wgsl_code) { + std::string region_1 = wgsl_code.substr(idx1 + 1, reg1_len - 1); + + std::string region_2 = wgsl_code.substr(idx2 + 1, reg2_len - 1); + + // The second transformation is done first as it doesn't affect idx2. + wgsl_code.replace(idx2 + 1, region_2.size(), region_1); + + wgsl_code.replace(idx1 + 1, region_1.size(), region_2); +} + +void DeleteInterval(size_t idx1, size_t reg_len, std::string& wgsl_code) { + wgsl_code.erase(idx1 + 1, reg_len - 1); +} + +void DuplicateInterval(size_t idx1, + size_t reg1_len, + size_t idx2, + std::string& wgsl_code) { + std::string region = wgsl_code.substr(idx1 + 1, reg1_len - 1); + wgsl_code.insert(idx2 + 1, region); +} + +void ReplaceRegion(size_t idx1, + size_t id1_len, + size_t idx2, + size_t id2_len, + std::string& wgsl_code) { + std::string region_1 = wgsl_code.substr(idx1, id1_len); + std::string region_2 = wgsl_code.substr(idx2, id2_len); + wgsl_code.replace(idx2, region_2.size(), region_1); +} + +void ReplaceInterval(size_t start_index, + size_t length, + std::string replacement_text, + std::string& wgsl_code) { + std::string region_1 = wgsl_code.substr(start_index, length); + wgsl_code.replace(start_index, length, replacement_text); +} + +bool SwapRandomIntervals(const std::string& delimiter, + std::string& wgsl_code, + RandomGenerator& generator) { + std::vector<size_t> delimiter_positions = + FindDelimiterIndices(delimiter, wgsl_code); + + // Need to have at least 3 indices. + if (delimiter_positions.size() < 3) { + return false; + } + + // Choose indices: + // interval_1_start < interval_1_end <= interval_2_start < interval_2_end + uint32_t interval_1_start = generator.GetUInt32( + static_cast<uint32_t>(delimiter_positions.size()) - 2u); + uint32_t interval_1_end = generator.GetUInt32( + interval_1_start + 1u, + static_cast<uint32_t>(delimiter_positions.size()) - 1u); + uint32_t interval_2_start = generator.GetUInt32( + interval_1_end, static_cast<uint32_t>(delimiter_positions.size()) - 1u); + uint32_t interval_2_end = generator.GetUInt32( + interval_2_start + 1u, static_cast<uint32_t>(delimiter_positions.size())); + + SwapIntervals(delimiter_positions[interval_1_start], + delimiter_positions[interval_1_end] - + delimiter_positions[interval_1_start], + delimiter_positions[interval_2_start], + delimiter_positions[interval_2_end] - + delimiter_positions[interval_2_start], + wgsl_code); + + return true; +} + +bool DeleteRandomInterval(const std::string& delimiter, + std::string& wgsl_code, + RandomGenerator& generator) { + std::vector<size_t> delimiter_positions = + FindDelimiterIndices(delimiter, wgsl_code); + + // Need to have at least 2 indices. + if (delimiter_positions.size() < 2) { + return false; + } + + uint32_t interval_start = generator.GetUInt32( + static_cast<uint32_t>(delimiter_positions.size()) - 1u); + uint32_t interval_end = generator.GetUInt32( + interval_start + 1u, static_cast<uint32_t>(delimiter_positions.size())); + + DeleteInterval( + delimiter_positions[interval_start], + delimiter_positions[interval_end] - delimiter_positions[interval_start], + wgsl_code); + + return true; +} + +bool DuplicateRandomInterval(const std::string& delimiter, + std::string& wgsl_code, + RandomGenerator& generator) { + std::vector<size_t> delimiter_positions = + FindDelimiterIndices(delimiter, wgsl_code); + + // Need to have at least 2 indices + if (delimiter_positions.size() < 2) { + return false; + } + + uint32_t interval_start = generator.GetUInt32( + static_cast<uint32_t>(delimiter_positions.size()) - 1u); + uint32_t interval_end = generator.GetUInt32( + interval_start + 1u, static_cast<uint32_t>(delimiter_positions.size())); + uint32_t duplication_point = + generator.GetUInt32(static_cast<uint32_t>(delimiter_positions.size())); + + DuplicateInterval( + delimiter_positions[interval_start], + delimiter_positions[interval_end] - delimiter_positions[interval_start], + delimiter_positions[duplication_point], wgsl_code); + + return true; +} + +bool ReplaceRandomIdentifier(std::string& wgsl_code, + RandomGenerator& generator) { + std::vector<std::pair<size_t, size_t>> identifiers = + GetIdentifiers(wgsl_code); + + // Need at least 2 identifiers + if (identifiers.size() < 2) { + return false; + } + + uint32_t id1_index = + generator.GetUInt32(static_cast<uint32_t>(identifiers.size())); + uint32_t id2_index = + generator.GetUInt32(static_cast<uint32_t>(identifiers.size())); + + // The two identifiers must be different + while (id1_index == id2_index) { + id2_index = generator.GetUInt32(static_cast<uint32_t>(identifiers.size())); + } + + ReplaceRegion(identifiers[id1_index].first, identifiers[id1_index].second, + identifiers[id2_index].first, identifiers[id2_index].second, + wgsl_code); + + return true; +} + +bool ReplaceRandomIntLiteral(std::string& wgsl_code, + RandomGenerator& generator) { + std::vector<std::pair<size_t, size_t>> literals = GetIntLiterals(wgsl_code); + + // Need at least one integer literal + if (literals.size() < 1) { + return false; + } + + uint32_t literal_index = + generator.GetUInt32(static_cast<uint32_t>(literals.size())); + + // INT_MAX = 2147483647, INT_MIN = -2147483648 + std::vector<std::string> boundary_values = { + "2147483647", "-2147483648", "1", "-1", "0", "4294967295"}; + + uint32_t boundary_index = + generator.GetUInt32(static_cast<uint32_t>(boundary_values.size())); + + ReplaceInterval(literals[literal_index].first, literals[literal_index].second, + boundary_values[boundary_index], wgsl_code); + + return true; +} + +} // namespace regex_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_regex_fuzzer/wgsl_mutator.h b/src/tint/fuzzers/tint_regex_fuzzer/wgsl_mutator.h new file mode 100644 index 0000000..47e6adb --- /dev/null +++ b/src/tint/fuzzers/tint_regex_fuzzer/wgsl_mutator.h
@@ -0,0 +1,186 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_REGEX_FUZZER_WGSL_MUTATOR_H_ +#define SRC_TINT_FUZZERS_TINT_REGEX_FUZZER_WGSL_MUTATOR_H_ + +#include <string> +#include <utility> +#include <vector> + +#include "src/tint/fuzzers/random_generator.h" + +namespace tint { +namespace fuzzers { +namespace regex_fuzzer { + +/// A function that given a delimiter, returns a vector that contains +/// all the positions of the delimiter in the WGSL code. +/// @param delimiter - the delimiter of the enclosed region. +/// @param wgsl_code - the initial string (WGSL code) that will be mutated. +/// @return a vector with the positions of the delimiter in the WGSL code. +std::vector<size_t> FindDelimiterIndices(const std::string& delimiter, + const std::string& wgsl_code); + +/// A function that finds all the identifiers in a WGSL-like string. +/// @param wgsl_code - the WGSL-like string where the identifiers will be found. +/// @return a vector with the positions and the length of all the +/// identifiers in wgsl_code. +std::vector<std::pair<size_t, size_t>> GetIdentifiers( + const std::string& wgsl_code); + +/// A function that returns returns the starting position +/// and the length of all the integer literals in a WGSL-like string. +/// @param wgsl_code - the WGSL-like string where the int literals +/// will be found. +/// @return a vector with the starting positions and the length +/// of all the integer literals. +std::vector<std::pair<size_t, size_t>> GetIntLiterals( + const std::string& wgsl_code); + +/// Finds a possible closing brace corresponding to the opening +/// brace at position opening_bracket_pos. +/// @param opening_bracket_pos - the position of the opening brace. +/// @param wgsl_code - the WGSL-like string where the closing brace. +/// @return the position of the closing bracket or 0 if there is no closing +/// brace. +size_t FindClosingBrace(size_t opening_bracket_pos, + const std::string& wgsl_code); + +/// Returns the starting_position of the bodies of the functions +/// that follow the regular expression: fn.*?->.*?\\{, which searches for the +/// keyword fn followed by the function name, its return type and opening brace. +/// @param wgsl_code - the WGSL-like string where the functions will be +/// searched. +/// @return a vector with the starting position of the function bodies in +/// wgsl_code. +std::vector<size_t> GetFunctionBodyPositions(const std::string& wgsl_code); + +/// Given 4 indices, idx1, idx2, idx3 and idx4 it swaps the regions +/// in the interval (idx1, idx2] with the region in the interval (idx3, idx4] +/// in wgsl_text. +/// @param idx1 - starting index of the first region. +/// @param reg1_len - length of the first region. +/// @param idx2 - starting index of the second region. +/// @param reg2_len - length of the second region. +/// @param wgsl_code - the string where the swap will occur. +void SwapIntervals(size_t idx1, + size_t reg1_len, + size_t idx2, + size_t reg2_len, + std::string& wgsl_code); + +/// Given index idx1 it delets the region of length interval_len +/// starting at index idx1; +/// @param idx1 - starting index of the first region. +/// @param reg_len - terminating index of the second region. +/// @param wgsl_code - the string where the swap will occur. +void DeleteInterval(size_t idx1, size_t reg_len, std::string& wgsl_code); + +/// Given 2 indices, idx1, idx2, it inserts the region of length +/// reg1_len starting at idx1 after idx2. +/// @param idx1 - starting index of region. +/// @param reg1_len - length of the region. +/// @param idx2 - the position where the region will be inserted. +/// @param wgsl_code - the string where the swap will occur. +void DuplicateInterval(size_t idx1, + size_t reg1_len, + size_t idx2, + std::string& wgsl_code); + +/// Replaces a region of a WGSL-like string of length id2_len starting +/// at position idx2 with a region of length id1_len starting at +/// position idx1. +/// @param idx1 - starting position of the first region. +/// @param id1_len - length of the first region. +/// @param idx2 - starting position of the second region. +/// @param id2_len - length of the second region. +/// @param wgsl_code - the string where the replacement will occur. +void ReplaceRegion(size_t idx1, + size_t id1_len, + size_t idx2, + size_t id2_len, + std::string& wgsl_code); + +/// Replaces an interval of length `length` starting at start_index +/// with the `replacement_text`. +/// @param start_index - starting position of the interval to be replaced. +/// @param length - length of the interval to be replaced. +/// @param replacement_text - the interval that will be used as a replacement. +/// @param wgsl_code - the WGSL-like string where the replacement will occur. +void ReplaceInterval(size_t start_index, + size_t length, + std::string replacement_text, + std::string& wgsl_code); + +/// A function that, given WGSL-like string and a delimiter, +/// generates another WGSL-like string by picking two random regions +/// enclosed by the delimiter and swapping them. +/// @param delimiter - the delimiter that will be used to find enclosed regions. +/// @param wgsl_code - the initial string (WGSL code) that will be mutated. +/// @param generator - the random number generator. +/// @return true if a swap happened or false otherwise. +bool SwapRandomIntervals(const std::string& delimiter, + std::string& wgsl_code, + RandomGenerator& generator); + +/// A function that, given a WGSL-like string and a delimiter, +/// generates another WGSL-like string by deleting a random +/// region enclosed by the delimiter. +/// @param delimiter - the delimiter that will be used to find enclosed regions. +/// @param wgsl_code - the initial string (WGSL code) that will be mutated. +/// @param generator - the random number generator. +/// @return true if a deletion happened or false otherwise. +bool DeleteRandomInterval(const std::string& delimiter, + std::string& wgsl_code, + RandomGenerator& generator); + +/// A function that, given a WGSL-like string and a delimiter, +/// generates another WGSL-like string by duplicating a random +/// region enclosed by the delimiter. +/// @param delimiter - the delimiter that will be used to find enclosed regions. +/// @param wgsl_code - the initial string (WGSL code) that will be mutated. +/// @param generator - the random number generator. +/// @return true if a duplication happened or false otherwise. +bool DuplicateRandomInterval(const std::string& delimiter, + std::string& wgsl_code, + RandomGenerator& generator); + +/// Replaces a randomly-chosen identifier in wgsl_code. +/// @param wgsl_code - WGSL-like string where the replacement will occur. +/// @param generator - the random number generator. +/// @return true if a replacement happened or false otherwise. +bool ReplaceRandomIdentifier(std::string& wgsl_code, + RandomGenerator& generator); + +/// Replaces the value of a randomly-chosen integer with one of +/// the values in the set {INT_MAX, INT_MIN, 0, -1}. +/// @param wgsl_code - WGSL-like string where the replacement will occur. +/// @param generator - the random number generator. +/// @return true if a replacement happened or false otherwise. +bool ReplaceRandomIntLiteral(std::string& wgsl_code, + RandomGenerator& generator); + +/// Inserts a return statement in a randomly chosen function of a +/// WGSL-like string. The return value is a randomly-chosen identifier +/// or literal in the string. +/// @param wgsl_code - WGSL-like string that will be mutated. +/// @param generator - the random number generator. +/// @return true if the mutation was succesful or false otherwise. +bool InsertReturnStatement(std::string& wgsl_code, RandomGenerator& generator); +} // namespace regex_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_REGEX_FUZZER_WGSL_MUTATOR_H_
diff --git a/src/tint/fuzzers/tint_renamer_fuzzer.cc b/src/tint/fuzzers/tint_renamer_fuzzer.cc new file mode 100644 index 0000000..02e539c --- /dev/null +++ b/src/tint/fuzzers/tint_renamer_fuzzer.cc
@@ -0,0 +1,35 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/fuzzer_init.h" +#include "src/tint/fuzzers/tint_common_fuzzer.h" +#include "src/tint/fuzzers/transform_builder.h" + +namespace tint { +namespace fuzzers { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + TransformBuilder tb(data, size); + tb.AddTransform<transform::Renamer>(); + + fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kWGSL); + fuzzer.SetTransformManager(tb.manager(), tb.data_map()); + fuzzer.SetDumpInput(GetCliParams().dump_input); + fuzzer.SetEnforceValidity(GetCliParams().enforce_validity); + + return fuzzer.Run(data, size); +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_robustness_fuzzer.cc b/src/tint/fuzzers/tint_robustness_fuzzer.cc new file mode 100644 index 0000000..22f0ad1 --- /dev/null +++ b/src/tint/fuzzers/tint_robustness_fuzzer.cc
@@ -0,0 +1,35 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/fuzzer_init.h" +#include "src/tint/fuzzers/tint_common_fuzzer.h" +#include "src/tint/fuzzers/transform_builder.h" + +namespace tint { +namespace fuzzers { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + TransformBuilder tb(data, size); + tb.AddTransform<tint::transform::Robustness>(); + + tint::fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kWGSL); + fuzzer.SetTransformManager(tb.manager(), tb.data_map()); + fuzzer.SetDumpInput(GetCliParams().dump_input); + fuzzer.SetEnforceValidity(GetCliParams().enforce_validity); + + return fuzzer.Run(data, size); +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_single_entry_point_fuzzer.cc b/src/tint/fuzzers/tint_single_entry_point_fuzzer.cc new file mode 100644 index 0000000..84146d8 --- /dev/null +++ b/src/tint/fuzzers/tint_single_entry_point_fuzzer.cc
@@ -0,0 +1,35 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/fuzzer_init.h" +#include "src/tint/fuzzers/tint_common_fuzzer.h" +#include "src/tint/fuzzers/transform_builder.h" + +namespace tint { +namespace fuzzers { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + TransformBuilder tb(data, size); + tb.AddTransform<transform::SingleEntryPoint>(); + + fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kWGSL); + fuzzer.SetTransformManager(tb.manager(), tb.data_map()); + fuzzer.SetDumpInput(GetCliParams().dump_input); + fuzzer.SetEnforceValidity(GetCliParams().enforce_validity); + + return fuzzer.Run(data, size); +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/CMakeLists.txt b/src/tint/fuzzers/tint_spirv_tools_fuzzer/CMakeLists.txt new file mode 100644 index 0000000..c83333a --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/CMakeLists.txt
@@ -0,0 +1,106 @@ +# Copyright 2021 The Tint Authors. +# +# 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. + +set(FUZZER_SOURCES + ../mersenne_twister_engine.cc + ../random_generator.cc + ../random_generator_engine.cc + cli.cc + fuzzer.cc + mutator.cc + mutator_cache.cc + spirv_fuzz_mutator.cc + spirv_opt_mutator.cc + spirv_reduce_mutator.cc + util.cc) + +set(FUZZER_SOURCES ${FUZZER_SOURCES} + ../mersenne_twister_engine.h + ../random_generator.h + ../random_generator_engine.h + cli.h + mutator.h + mutator_cache.h + override_cli_params.h + spirv_fuzz_mutator.h + spirv_opt_mutator.h + spirv_reduce_mutator.h + util.h) + +set(FUZZER_SOURCES ${FUZZER_SOURCES} + ../tint_common_fuzzer.cc + ../tint_common_fuzzer.h) + +function(configure_spirv_tools_fuzzer_target NAME SOURCES) + add_executable(${NAME} ${SOURCES}) + target_link_libraries(${NAME} SPIRV-Tools SPIRV-Tools-opt SPIRV-Tools-fuzz SPIRV-Tools-reduce) + tint_default_compile_options(${NAME}) + target_compile_options(${NAME} PRIVATE + -Wno-missing-prototypes + -Wno-zero-as-null-pointer-constant + -Wno-reserved-id-macro + -Wno-sign-conversion + -Wno-extra-semi-stmt + -Wno-inconsistent-missing-destructor-override + -Wno-newline-eof + -Wno-old-style-cast + -Wno-weak-vtables + -Wno-undef) + target_include_directories(${NAME} PRIVATE + ${spirv-tools_SOURCE_DIR} + ${spirv-tools_BINARY_DIR}) +endfunction() + +function(add_tint_spirv_tools_fuzzer NAME) + set(FUZZER_TARGET_SOURCES ${NAME}.cc ${FUZZER_SOURCES}) + configure_spirv_tools_fuzzer_target(${NAME} "${FUZZER_TARGET_SOURCES}") + target_link_libraries(${NAME} libtint-fuzz) + target_compile_definitions(tint_spirv_tools_fuzzer PUBLIC CUSTOM_MUTATOR) + target_compile_definitions(tint_spirv_tools_fuzzer PRIVATE TARGET_FUZZER) +endfunction() + +# Add libfuzzer targets. +# Targets back-ends according to command line arguments. +add_tint_spirv_tools_fuzzer(tint_spirv_tools_fuzzer) +# Targets back-ends individually. +add_tint_spirv_tools_fuzzer(tint_spirv_tools_hlsl_writer_fuzzer) +add_tint_spirv_tools_fuzzer(tint_spirv_tools_msl_writer_fuzzer) +add_tint_spirv_tools_fuzzer(tint_spirv_tools_spv_writer_fuzzer) +add_tint_spirv_tools_fuzzer(tint_spirv_tools_wgsl_writer_fuzzer) + +set(DEBUGGER_SOURCES + ../mersenne_twister_engine.cc + ../random_generator.cc + ../random_generator_engine.cc + cli.cc + mutator.cc + mutator_debugger.cc + spirv_fuzz_mutator.cc + spirv_opt_mutator.cc + spirv_reduce_mutator.cc + util.cc) + +set(DEBUGGER_SOURCES ${DEBUGGER_SOURCES} + ../mersenne_twister_engine.h + ../random_generator.h + ../random_generator_engine.h + cli.h + mutator.h + spirv_fuzz_mutator.h + spirv_opt_mutator.h + spirv_reduce_mutator.h + util.h) + +configure_spirv_tools_fuzzer_target(tint_spirv_tools_mutator_debugger "${DEBUGGER_SOURCES}") +target_compile_definitions(tint_spirv_tools_mutator_debugger PRIVATE TARGET_DEBUGGER)
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.cc new file mode 100644 index 0000000..7659fcc --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.cc
@@ -0,0 +1,484 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h" + +#include <fstream> +#include <limits> +#include <sstream> +#include <string> +#include <utility> +#include <vector> + +#include "source/opt/build_module.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/util.h" + +namespace tint { +namespace fuzzers { +namespace spvtools_fuzzer { +namespace { + +const char* const kMutatorParameters = R"( +Mutators' parameters: + + -tint_donors= + A path to the text file with a list of paths to the + SPIR-V donor files. Check out the doc for the spirv-fuzz + to learn more about donor binaries. Donors are not used + by default. + + -tint_enable_all_fuzzer_passes= + Whether to use all fuzzer passes or a randomly selected subset + of them. This must be one of `true` or `false` (without `). + By default it's `false`. + + -tint_enable_all_reduce_passes= + Whether to use all reduction passes or a randomly selected subset + of them. This must be one of `true` or `false` (without `). + By default it's `false`. + + -tint_opt_batch_size= + The maximum number of spirv-opt optimizations that + will be applied in a single mutation session (i.e. + a call to LLVMFuzzerCustomMutator). This must fit in + uint32_t. By default it's 6. + + -tint_reduction_batch_size= + The maximum number of spirv-reduce reductions that + will be applied in a single mutation session (i.e. + a call to LLVMFuzzerCustomMutator). This must fit in + uint32_t. By default it's 3. + + -tint_repeated_pass_strategy= + The strategy that will be used to recommend the next fuzzer + pass. This must be one of `simple`, `looped` or `random` + (without `). By default it's `simple`. Check out the doc for + spirv-fuzz to learn more. + + -tint_transformation_batch_size= + The maximum number of spirv-fuzz transformations + that will be applied during a single mutation + session (i.e. a call to LLVMFuzzerCustomMutator). + This must fit in uint32_t. By default it's 3. + + -tint_validate_after_each_fuzzer_pass= + Whether to validate SPIR-V binary after each fuzzer pass. + This must be one of `true` or `false` (without `). + By default it's `true`. Switch this to `false` if you experience + bad performance. + + -tint_validate_after_each_opt_pass= + Whether to validate SPIR-V binary after each optimization pass. + This must be one of `true` or `false` (without `). + By default it's `true`. Switch this to `false` if you experience + bad performance. + + -tint_validate_after_each_reduce_pass= + Whether to validate SPIR-V binary after each reduction pass. + This must be one of `true` or `false` (without `). + By default it's `true`. Switch this to `false` if you experience + bad performance. +)"; + +const char* const kFuzzerHelpMessage = R"( +This fuzzer uses SPIR-V binaries to fuzz the Tint compiler. It uses SPIRV-Tools +to mutate those binaries. The fuzzer works on a corpus of SPIR-V shaders. +For each shader from the corpus it uses one of `spirv-fuzz`, `spirv-reduce` or +`spirv-opt` to mutate it and then runs the shader through the Tint compiler in +two steps: +- Converts the mutated shader to WGSL. +- Converts WGSL to some target language specified in the CLI arguments. + +Below is a list of all supported parameters for this fuzzer. You may want to +run it with -help=1 to check out libfuzzer parameters. + +Fuzzer parameters: + + -tint_error_dir + The directory that will be used to output invalid SPIR-V + binaries to. This is especially useful during debugging + mutators. The directory must have the following subdirectories: + - spv/ - will be used to output errors, produced during + the conversion from the SPIR-V to WGSL. + - wgsl/ - will be used to output errors, produced during + the conversion from the WGSL to `--fuzzing_target`. + - mutator/ - will be used to output errors, produced by + the mutators. + By default invalid files are not printed out. + + -tint_fuzzing_target + The type of backend to target during fuzzing. This must + be one or a combination of `wgsl`, `spv`, `msl` or `hlsl` + (without `) separated by commas. By default it's + `wgsl,spv,msl,hlsl`. + + -tint_help + Show this message. Note that there is also a -help=1 + parameter that will display libfuzzer's help message. + + -tint_mutator_cache_size= + The maximum size of the cache that stores + mutation sessions. This must fit in uint32_t. + By default it's 20. + + -tint_mutator_type= + Determines types of the mutators to run. This must be one or + a combination of `fuzz`, `opt`, `reduce` (without `) separated by + comma. If a combination is specified, each element in the + combination will have an equal chance of mutating a SPIR-V + binary during a mutation session (i.e. if no mutator exists + for that binary in the mutator cache). By default, the + parameter's value is `fuzz,opt,reduce`. +)"; + +const char* const kMutatorDebuggerHelpMessage = R"( +This tool is used to debug *mutators*. It uses CLI arguments similar to the +ones used by the fuzzer. To debug some mutator you just need to specify the +mutator type, the seed and the path to the SPIR-V binary that triggered the +error. This tool will run the mutator on the binary until the error is +produced or the mutator returns `kLimitReached`. + +Note that this is different from debugging the fuzzer by specifying input +files to test. The difference is that the latter will not execute any +mutator (it will only run the LLVMFuzzerTestOneInput function) whereas this +tool is useful when one of the SPIRV-Tools mutators crashes or produces an +invalid binary in LLVMFuzzerCustomMutator. + +Debugger parameters: + + --help + Show this message. + + --mutator_type= + Determines the type of the mutator to debug. This must be + one of `fuzz`, `reduce` or `opt` (without `). This parameter + is REQUIRED. + + --original_binary= + The path to the SPIR-V binary that the faulty mutator was + initialized with. This will be dumped on errors by the fuzzer + if `--error_dir` is specified. This parameter is REQUIRED. + + --seed= + The seed for the random number generator that was used to + initialize the mutator. This value is usually printed to + the console when the mutator produces an invalid binary. + It is also dumped into the log file if `--error_dir` is + specified. This must fit in uint32_t. This parameter is + REQUIRED. +)"; + +void PrintHelpMessage(const char* help_message) { + std::cout << help_message << std::endl << kMutatorParameters << std::endl; +} + +[[noreturn]] void InvalidParameter(const char* help_message, + const char* param) { + std::cout << "Invalid value for " << param << std::endl; + PrintHelpMessage(help_message); + exit(1); +} + +bool ParseUint32(const char* param, uint32_t* out) { + uint64_t value = static_cast<uint64_t>(strtoul(param, nullptr, 10)); + if (value > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) { + return false; + } + *out = static_cast<uint32_t>(value); + return true; +} + +std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> ParseDonors( + const char* file_name) { + std::ifstream fin(file_name); + if (!fin) { + std::cout << "Can't open donors list file: " << file_name << std::endl; + exit(1); + } + + std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> result; + for (std::string donor_file_name; fin >> donor_file_name;) { + if (!std::ifstream(donor_file_name)) { + std::cout << "Can't open donor file: " << donor_file_name << std::endl; + exit(1); + } + + result.emplace_back([donor_file_name] { + std::vector<uint32_t> binary; + if (!util::ReadBinary(donor_file_name, &binary)) { + std::cout << "Failed to read donor from: " << donor_file_name + << std::endl; + exit(1); + } + return spvtools::BuildModule( + kDefaultTargetEnv, spvtools::fuzz::fuzzerutil::kSilentMessageConsumer, + binary.data(), binary.size()); + }); + } + + return result; +} + +bool ParseRepeatedPassStrategy(const char* param, + spvtools::fuzz::RepeatedPassStrategy* out) { + if (!strcmp(param, "simple")) { + *out = spvtools::fuzz::RepeatedPassStrategy::kSimple; + } else if (!strcmp(param, "looped")) { + *out = spvtools::fuzz::RepeatedPassStrategy::kLoopedWithRecommendations; + } else if (!strcmp(param, "random")) { + *out = spvtools::fuzz::RepeatedPassStrategy::kRandomWithRecommendations; + } else { + return false; + } + return true; +} + +bool ParseBool(const char* param, bool* out) { + if (!strcmp(param, "true")) { + *out = true; + } else if (!strcmp(param, "false")) { + *out = false; + } else { + return false; + } + return true; +} + +bool ParseMutatorType(const char* param, MutatorType* out) { + if (!strcmp(param, "fuzz")) { + *out = MutatorType::kFuzz; + } else if (!strcmp(param, "opt")) { + *out = MutatorType::kOpt; + } else if (!strcmp(param, "reduce")) { + *out = MutatorType::kReduce; + } else { + return false; + } + return true; +} + +bool ParseFuzzingTarget(const char* param, FuzzingTarget* out) { + if (!strcmp(param, "wgsl")) { + *out = FuzzingTarget::kWgsl; + } else if (!strcmp(param, "spv")) { + *out = FuzzingTarget::kSpv; + } else if (!strcmp(param, "msl")) { + *out = FuzzingTarget::kMsl; + } else if (!strcmp(param, "hlsl")) { + *out = FuzzingTarget::kHlsl; + } else { + return false; + } + return true; +} + +bool HasPrefix(const char* str, const char* prefix) { + return strncmp(str, prefix, strlen(prefix)) == 0; +} + +bool ParseMutatorCliParam(const char* param, + const char* help_message, + MutatorCliParams* out) { + if (HasPrefix(param, "-tint_transformation_batch_size=")) { + if (!ParseUint32(param + sizeof("-tint_transformation_batch_size=") - 1, + &out->transformation_batch_size)) { + InvalidParameter(help_message, param); + } + } else if (HasPrefix(param, "-tint_reduction_batch_size=")) { + if (!ParseUint32(param + sizeof("-tint_reduction_batch_size=") - 1, + &out->reduction_batch_size)) { + InvalidParameter(help_message, param); + } + } else if (HasPrefix(param, "-tint_opt_batch_size=")) { + if (!ParseUint32(param + sizeof("-tint_opt_batch_size=") - 1, + &out->opt_batch_size)) { + InvalidParameter(help_message, param); + } + } else if (HasPrefix(param, "-tint_donors=")) { + out->donors = ParseDonors(param + sizeof("-tint_donors=") - 1); + } else if (HasPrefix(param, "-tint_repeated_pass_strategy=")) { + if (!ParseRepeatedPassStrategy( + param + sizeof("-tint_repeated_pass_strategy=") - 1, + &out->repeated_pass_strategy)) { + InvalidParameter(help_message, param); + } + } else if (HasPrefix(param, "-tint_enable_all_fuzzer_passes=")) { + if (!ParseBool(param + sizeof("-tint_enable_all_fuzzer_passes=") - 1, + &out->enable_all_fuzzer_passes)) { + InvalidParameter(help_message, param); + } + } else if (HasPrefix(param, "-tint_enable_all_reduce_passes=")) { + if (!ParseBool(param + sizeof("-tint_enable_all_reduce_passes=") - 1, + &out->enable_all_reduce_passes)) { + InvalidParameter(help_message, param); + } + } else if (HasPrefix(param, "-tint_validate_after_each_opt_pass=")) { + if (!ParseBool(param + sizeof("-tint_validate_after_each_opt_pass=") - 1, + &out->validate_after_each_opt_pass)) { + InvalidParameter(help_message, param); + } + } else if (HasPrefix(param, "-tint_validate_after_each_fuzzer_pass=")) { + if (!ParseBool(param + sizeof("-tint_validate_after_each_fuzzer_pass=") - 1, + &out->validate_after_each_fuzzer_pass)) { + InvalidParameter(help_message, param); + } + } else if (HasPrefix(param, "-tint_validate_after_each_reduce_pass=")) { + if (!ParseBool(param + sizeof("-tint_validate_after_each_reduce_pass=") - 1, + &out->validate_after_each_reduce_pass)) { + InvalidParameter(help_message, param); + } + } else { + return false; + } + return true; +} + +} // namespace + +FuzzerCliParams ParseFuzzerCliParams(int* argc, char** argv) { + FuzzerCliParams cli_params; + const auto* help_message = kFuzzerHelpMessage; + auto help = false; + + for (int i = *argc - 1; i > 0; --i) { + auto param = argv[i]; + auto recognized_param = true; + + if (HasPrefix(param, "-tint_mutator_cache_size=")) { + if (!ParseUint32(param + sizeof("-tint_mutator_cache_size=") - 1, + &cli_params.mutator_cache_size)) { + InvalidParameter(help_message, param); + } + } else if (HasPrefix(param, "-tint_mutator_type=")) { + auto result = MutatorType::kNone; + + std::stringstream ss(param + sizeof("-tint_mutator_type=") - 1); + for (std::string value; std::getline(ss, value, ',');) { + auto out = MutatorType::kNone; + if (!ParseMutatorType(value.c_str(), &out)) { + InvalidParameter(help_message, param); + } + result = result | out; + } + + if (result == MutatorType::kNone) { + InvalidParameter(help_message, param); + } + + cli_params.mutator_type = result; + } else if (HasPrefix(param, "-tint_fuzzing_target=")) { + auto result = FuzzingTarget::kNone; + + std::stringstream ss(param + sizeof("-tint_fuzzing_target=") - 1); + for (std::string value; std::getline(ss, value, ',');) { + auto tmp = FuzzingTarget::kNone; + if (!ParseFuzzingTarget(value.c_str(), &tmp)) { + InvalidParameter(help_message, param); + } + result = result | tmp; + } + + if (result == FuzzingTarget::kNone) { + InvalidParameter(help_message, param); + } + + cli_params.fuzzing_target = result; + } else if (HasPrefix(param, "-tint_error_dir=")) { + cli_params.error_dir = param + sizeof("-tint_error_dir=") - 1; + } else if (!strcmp(param, "-tint_help")) { + help = true; + } else { + recognized_param = + ParseMutatorCliParam(param, help_message, &cli_params.mutator_params); + } + + if (recognized_param) { + // Remove the recognized parameter from the list of all parameters by + // swapping it with the last one. This will suppress warnings in the + // libFuzzer about unrecognized parameters. By default, libFuzzer thinks + // that all user-defined parameters start with two dashes. However, we are + // forced to use a single one to make the fuzzer compatible with the + // ClusterFuzz. + std::swap(argv[i], argv[*argc - 1]); + *argc -= 1; + } + } + + if (help) { + PrintHelpMessage(help_message); + exit(0); + } + + return cli_params; +} + +MutatorDebuggerCliParams ParseMutatorDebuggerCliParams( + int argc, + const char* const* argv) { + MutatorDebuggerCliParams cli_params; + bool seed_param_present = false; + bool original_binary_param_present = false; + bool mutator_type_param_present = false; + const auto* help_message = kMutatorDebuggerHelpMessage; + auto help = false; + + for (int i = 0; i < argc; ++i) { + auto param = argv[i]; + ParseMutatorCliParam(param, help_message, &cli_params.mutator_params); + + if (HasPrefix(param, "--mutator_type=")) { + if (!ParseMutatorType(param + sizeof("--mutator_type=") - 1, + &cli_params.mutator_type)) { + InvalidParameter(help_message, param); + } + mutator_type_param_present = true; + } else if (HasPrefix(param, "--original_binary=")) { + if (!util::ReadBinary(param + sizeof("--original_binary=") - 1, + &cli_params.original_binary)) { + InvalidParameter(help_message, param); + } + original_binary_param_present = true; + } else if (HasPrefix(param, "--seed=")) { + if (!ParseUint32(param + sizeof("--seed=") - 1, &cli_params.seed)) { + InvalidParameter(help_message, param); + } + seed_param_present = true; + } else if (!strcmp(param, "--help")) { + help = true; + } + } + + if (help) { + PrintHelpMessage(help_message); + exit(0); + } + + std::pair<bool, const char*> required_params[] = { + {seed_param_present, "--seed"}, + {original_binary_param_present, "--original_binary"}, + {mutator_type_param_present, "--mutator_type"}}; + + for (auto required_param : required_params) { + if (!required_param.first) { + std::cout << required_param.second << " is missing" << std::endl; + exit(1); + } + } + + return cli_params; +} + +} // namespace spvtools_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h b/src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h new file mode 100644 index 0000000..6e6d60b --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h
@@ -0,0 +1,167 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_CLI_H_ +#define SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_CLI_H_ + +#include <string> +#include <vector> + +#include "source/fuzz/fuzzer.h" + +namespace tint { +namespace fuzzers { +namespace spvtools_fuzzer { + +/// Default SPIR-V environment that will be used during fuzzing. +const auto kDefaultTargetEnv = SPV_ENV_VULKAN_1_1; + +/// The type of the mutator to run. +enum class MutatorType { + kNone = 0, + kFuzz = 1 << 0, + kReduce = 1 << 1, + kOpt = 1 << 2, + kAll = kFuzz | kReduce | kOpt +}; + +inline MutatorType operator|(MutatorType a, MutatorType b) { + return static_cast<MutatorType>(static_cast<int>(a) | static_cast<int>(b)); +} + +inline MutatorType operator&(MutatorType a, MutatorType b) { + return static_cast<MutatorType>(static_cast<int>(a) & static_cast<int>(b)); +} + +/// Shading language to target during fuzzing. +enum class FuzzingTarget { + kNone = 0, + kHlsl = 1 << 0, + kMsl = 1 << 1, + kSpv = 1 << 2, + kWgsl = 1 << 3, + kAll = kHlsl | kMsl | kSpv | kWgsl +}; + +inline FuzzingTarget operator|(FuzzingTarget a, FuzzingTarget b) { + return static_cast<FuzzingTarget>(static_cast<int>(a) | static_cast<int>(b)); +} + +inline FuzzingTarget operator&(FuzzingTarget a, FuzzingTarget b) { + return static_cast<FuzzingTarget>(static_cast<int>(a) & static_cast<int>(b)); +} + +/// These parameters are accepted by various mutators and thus they are accepted +/// by both the fuzzer and the mutator debugger. +struct MutatorCliParams { + /// SPIR-V target environment for fuzzing. + spv_target_env target_env = kDefaultTargetEnv; + + /// The number of spirv-fuzz transformations to apply at a time. + uint32_t transformation_batch_size = 3; + + /// The number of spirv-reduce reductions to apply at a time. + uint32_t reduction_batch_size = 3; + + /// The number of spirv-opt optimizations to apply at a time. + uint32_t opt_batch_size = 6; + + /// The vector of donors to use in spirv-fuzz (see the doc for spirv-fuzz to + /// learn more). + std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> donors = {}; + + /// The strategy to use during fuzzing in spirv-fuzz (see the doc for + /// spirv-fuzz to learn more). + spvtools::fuzz::RepeatedPassStrategy repeated_pass_strategy = + spvtools::fuzz::RepeatedPassStrategy::kSimple; + + /// Whether to use all fuzzer passes or a randomly selected subset of them. + bool enable_all_fuzzer_passes = false; + + /// Whether to use all reduction passes or a randomly selected subset of them. + bool enable_all_reduce_passes = false; + + /// Whether to validate the SPIR-V binary after each optimization pass. + bool validate_after_each_opt_pass = true; + + /// Whether to validate the SPIR-V binary after each fuzzer pass. + bool validate_after_each_fuzzer_pass = true; + + /// Whether to validate the SPIR-V binary after each reduction pass. + bool validate_after_each_reduce_pass = true; +}; + +/// Parameters specific to the fuzzer. Type `-tint_help` in the CLI to learn +/// more. +struct FuzzerCliParams { + /// The size of the cache that records ongoing mutation sessions. + uint32_t mutator_cache_size = 20; + + /// The type of the mutator to run. + MutatorType mutator_type = MutatorType::kAll; + + /// Tint backend to fuzz. + FuzzingTarget fuzzing_target = FuzzingTarget::kAll; + + /// The path to the directory, that will be used to output buggy shaders. + std::string error_dir = ""; + + /// Parameters for various mutators. + MutatorCliParams mutator_params; +}; + +/// Parameters specific to the mutator debugger. Type `--help` in the CLI to +/// learn more. +struct MutatorDebuggerCliParams { + /// The type of the mutator to debug. + MutatorType mutator_type = MutatorType::kNone; + + /// The seed that was used to initialize the mutator. + uint32_t seed = 0; + + /// The binary that triggered a bug in the mutator. + std::vector<uint32_t> original_binary; + + /// Parameters for various mutators. + MutatorCliParams mutator_params; +}; + +/// Parses CLI parameters for the fuzzer. This function exits with an error code +/// and a message is printed to the console if some parameter has invalid +/// format. You can pass `-tint_help` to check out all available parameters. +/// This function will remove recognized parameters from the `argv` and adjust +/// the `argc` accordingly. +/// +/// @param argc - the number of parameters (identical to the `argc` in `main` +/// function). +/// @param argv - array of C strings of parameters. +/// @return the parsed parameters. +FuzzerCliParams ParseFuzzerCliParams(int* argc, char** argv); + +/// Parses CLI parameters for the mutator debugger. This function exits with an +/// error code and a message is printed to the console if some parameter has +/// invalid format. You can pass `--help` to check out all available parameters. +/// +/// @param argc - the number of parameters (identical to the `argc` in `main` +/// function). +/// @param argv - array of C strings of parameters. +/// @return the parsed parameters. +MutatorDebuggerCliParams ParseMutatorDebuggerCliParams(int argc, + const char* const* argv); + +} // namespace spvtools_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_CLI_H_
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/fuzzer.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/fuzzer.cc new file mode 100644 index 0000000..d17d743 --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/fuzzer.cc
@@ -0,0 +1,263 @@ +// Copyright 2021 The Tint Authors. +// +// 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 <memory> +#include <string> +#include <vector> + +#include "spirv-tools/libspirv.hpp" +#include "src/tint/fuzzers/random_generator.h" +#include "src/tint/fuzzers/tint_common_fuzzer.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/util.h" + +namespace tint { +namespace fuzzers { +namespace spvtools_fuzzer { +namespace { + +struct Context { + FuzzerCliParams params; + std::unique_ptr<MutatorCache> mutator_cache; +}; + +Context* context = nullptr; + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) { + auto params = ParseFuzzerCliParams(argc, *argv); + auto mutator_cache = + params.mutator_cache_size + ? std::make_unique<MutatorCache>(params.mutator_cache_size) + : nullptr; + context = new Context{std::move(params), std::move(mutator_cache)}; + OverrideCliParams(context->params); + return 0; +} + +std::unique_ptr<Mutator> CreateMutator(const std::vector<uint32_t>& binary, + unsigned seed) { + std::vector<MutatorType> types; + types.reserve(3); + + // Determine which mutator we will be using for `binary` at random. + auto cli_mutator_type = context->params.mutator_type; + if ((MutatorType::kFuzz & cli_mutator_type) == MutatorType::kFuzz) { + types.push_back(MutatorType::kFuzz); + } + if ((MutatorType::kReduce & cli_mutator_type) == MutatorType::kReduce) { + types.push_back(MutatorType::kReduce); + } + if ((MutatorType::kOpt & cli_mutator_type) == MutatorType::kOpt) { + types.push_back(MutatorType::kOpt); + } + + assert(!types.empty() && "At least one mutator type must be specified"); + RandomGenerator generator(seed); + auto mutator_type = + types[generator.GetUInt32(static_cast<uint32_t>(types.size()))]; + + const auto& mutator_params = context->params.mutator_params; + switch (mutator_type) { + case MutatorType::kFuzz: + return std::make_unique<SpirvFuzzMutator>( + mutator_params.target_env, binary, seed, mutator_params.donors, + mutator_params.enable_all_fuzzer_passes, + mutator_params.repeated_pass_strategy, + mutator_params.validate_after_each_fuzzer_pass, + mutator_params.transformation_batch_size); + case MutatorType::kReduce: + return std::make_unique<SpirvReduceMutator>( + mutator_params.target_env, binary, seed, + mutator_params.reduction_batch_size, + mutator_params.enable_all_reduce_passes, + mutator_params.validate_after_each_reduce_pass); + case MutatorType::kOpt: + return std::make_unique<SpirvOptMutator>( + mutator_params.target_env, seed, binary, + mutator_params.validate_after_each_opt_pass, + mutator_params.opt_batch_size); + default: + assert(false && "All mutator types must be handled above"); + return nullptr; + } +} + +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; + } +} + +bool IsValid(const std::vector<uint32_t>& binary) { + spvtools::SpirvTools tools(context->params.mutator_params.target_env); + tools.SetMessageConsumer(CLIMessageConsumer); + return tools.IsValid() && tools.Validate(binary.data(), binary.size(), + spvtools::ValidatorOptions()); +} + +extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, + size_t size, + size_t max_size, + unsigned seed) { + if ((size % sizeof(uint32_t)) != 0) { + // A valid SPIR-V binary's size must be a multiple of the size of a 32-bit + // word, and the SPIR-V Tools fuzzer is only designed to work with valid + // binaries. + return 0; + } + + std::vector<uint32_t> binary(size / sizeof(uint32_t)); + std::memcpy(binary.data(), data, size); + + MutatorCache placeholder_cache(1); + auto* mutator_cache = context->mutator_cache.get(); + if (!mutator_cache) { + // Use a placeholder cache if the user has decided not to use a real cache. + // The placeholder cache will be destroyed when we return from this function + // but it will save us from writing all the `if (mutator_cache)` below. + mutator_cache = &placeholder_cache; + } + + if (!mutator_cache->Get(binary)) { + // This is an unknown binary, so its validity must be checked before + // proceeding. + if (!IsValid(binary)) { + return 0; + } + // Assign a mutator to the binary if it doesn't have one yet. + mutator_cache->Put(binary, CreateMutator(binary, seed)); + } + + auto* mutator = mutator_cache->Get(binary); + assert(mutator && "Mutator must be present in the cache"); + + auto result = mutator->Mutate(); + + if (result.GetStatus() == Mutator::Status::kInvalid) { + // The binary is invalid - log the error and remove the mutator from the + // cache. + util::LogMutatorError(*mutator, context->params.error_dir); + mutator_cache->Remove(binary); + return 0; + } + + if (!result.IsChanged()) { + // The mutator didn't change the binary this time. This could be due to the + // fact that we've reached the number of mutations we can apply (e.g. the + // number of transformations in spirv-fuzz) or the mutator was just unlucky. + // Either way, there is no harm in destroying mutator and maybe trying again + // later (i.e. if libfuzzer decides to do so). + mutator_cache->Remove(binary); + return 0; + } + + // At this point the binary is valid and was changed by the mutator. + + auto mutated = mutator->GetBinary(); + auto mutated_bytes_size = mutated.size() * sizeof(uint32_t); + if (mutated_bytes_size > max_size) { + // The binary is too big. It's unlikely that we'll reduce its size by + // applying the mutator one more time. + mutator_cache->Remove(binary); + return 0; + } + + if (result.GetStatus() == Mutator::Status::kComplete) { + // Reassign the mutator to the mutated binary in the cache so that we can + // access later. + mutator_cache->Put(mutated, mutator_cache->Remove(binary)); + } else { + // If the binary is valid and was changed but is not `kComplete`, then the + // mutator has reached some limit on the number of mutations. + mutator_cache->Remove(binary); + } + + std::memcpy(data, mutated.data(), mutated_bytes_size); + return mutated_bytes_size; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size == 0) { + return 0; + } + + if ((size % sizeof(uint32_t)) != 0) { + // The SPIR-V Tools fuzzer has been designed to work with valid + // SPIR-V binaries, whose sizes should be multiples of the size of a 32-bit + // word. + return 0; + } + + CommonFuzzer spv_to_wgsl(InputFormat::kSpv, OutputFormat::kWGSL); + spv_to_wgsl.Run(data, size); + if (spv_to_wgsl.HasErrors()) { + auto error = spv_to_wgsl.Diagnostics().str(); + util::LogSpvError(error, data, size, + context ? context->params.error_dir : ""); + return 0; + } + + const auto& wgsl = spv_to_wgsl.GetGeneratedWgsl(); + + std::pair<FuzzingTarget, OutputFormat> targets[] = { + {FuzzingTarget::kHlsl, OutputFormat::kHLSL}, + {FuzzingTarget::kMsl, OutputFormat::kMSL}, + {FuzzingTarget::kSpv, OutputFormat::kSpv}, + {FuzzingTarget::kWgsl, OutputFormat::kWGSL}}; + + for (auto target : targets) { + if ((target.first & context->params.fuzzing_target) != target.first) { + continue; + } + + CommonFuzzer fuzzer(InputFormat::kWGSL, target.second); + fuzzer.Run(reinterpret_cast<const uint8_t*>(wgsl.data()), wgsl.size()); + if (fuzzer.HasErrors()) { + auto error = spv_to_wgsl.Diagnostics().str(); + util::LogWgslError(error, data, size, wgsl, target.second, + context->params.error_dir); + } + } + + return 0; +} + +} // namespace +} // namespace spvtools_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.cc new file mode 100644 index 0000000..05dcc41 --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.cc
@@ -0,0 +1,34 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.h" + +namespace tint { +namespace fuzzers { +namespace spvtools_fuzzer { + +// We need to define constructor here so that vtable is produced in this +// translation unit (see -Wweak-vtables clang flag). +Mutator::~Mutator() = default; + +Mutator::Result::Result(Status status, bool is_changed) + : status_(status), is_changed_(is_changed) { + assert((is_changed || status == Status::kStuck || + status == Status::kLimitReached) && + "Returning invalid result state"); +} + +} // namespace spvtools_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.h b/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.h new file mode 100644 index 0000000..9ff8fd9 --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.h
@@ -0,0 +1,108 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_H_ +#define SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_H_ + +#include <cassert> +#include <cstdint> +#include <string> +#include <vector> + +namespace tint { +namespace fuzzers { +namespace spvtools_fuzzer { + +/// This is an interface that is used to define custom mutators based on the +/// SPIR-V tools. +class Mutator { + public: + /// The status of the mutation. + enum class Status { + /// Binary is valid, the limit is not reached - can mutate further. + kComplete, + + /// The binary is valid, the limit of mutations has been reached - + /// can't mutate further. + kLimitReached, + + /// The binary is valid, the limit is not reached but the mutator has spent + /// too much time without mutating anything - better to restart to make sure + /// we can make any progress. + kStuck, + + /// The binary is invalid - this is likely a bug in the mutator - must + /// abort. + kInvalid + }; + + /// Represents the result of the mutation. The following states are possible: + /// - if `IsChanged() == false`, then `GetStatus()` can be either + /// `kLimitReached` or `kStuck`. + /// - otherwise, any value of `Status` is possible. + class Result { + public: + /// Constructor. + /// @param status - the status of the mutation. + /// @param is_changed - whether the module was changed during mutation. + Result(Status status, bool is_changed); + + /// @return the status of the mutation. + Status GetStatus() const { return status_; } + + /// @return whether the module was changed during mutation. + bool IsChanged() const { return is_changed_; } + + private: + Status status_; + bool is_changed_; + }; + + /// Virtual destructor. + virtual ~Mutator(); + + /// Causes the mutator to apply a mutation. This method can be called + /// multiple times as long as the previous call didn't return + /// `Status::kInvalid`. + /// + /// @return the status of the mutation (e.g. success, error etc) and whether + /// the binary was changed during mutation. + virtual Result Mutate() = 0; + + /// Returns the mutated binary. The returned binary is guaranteed to be valid + /// iff the previous call to the `Mutate` method returned didn't return + /// `Status::kInvalid`. + /// + /// @return the mutated SPIR-V binary. It might be identical to the original + /// binary if `Result::IsChanged` returns `false`. + virtual std::vector<uint32_t> GetBinary() const = 0; + + /// Returns errors, produced by the mutator. + /// + /// @param path - the directory to which the errors are printed to. No files + /// are created if the `path` is nullptr. + /// @param count - the number of the error. Files for this error will be + /// prefixed with `count`. + virtual void LogErrors(const std::string* path, uint32_t count) const = 0; + + /// @return errors encountered during the mutation. The returned string is + /// if there were no errors during mutation. + virtual std::string GetErrors() const = 0; +}; + +} // namespace spvtools_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_H_
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.cc new file mode 100644 index 0000000..4ce1ad2 --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.cc
@@ -0,0 +1,78 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h" + +namespace tint { +namespace fuzzers { +namespace spvtools_fuzzer { + +MutatorCache::MutatorCache(size_t max_size) + : map_(), entries_(), max_size_(max_size) { + assert(max_size && "`max_size` may not be 0"); +} + +MutatorCache::Value::pointer MutatorCache::Get(const Key& key) { + auto it = map_.find(key); + if (it == map_.end()) { + return nullptr; + } + UpdateUsage(it); + return entries_.front().second.get(); +} + +void MutatorCache::Put(const Key& key, Value value) { + assert(value && "Mutator cache can't have nullptr unique_ptr"); + auto it = map_.find(key); + if (it != map_.end()) { + it->second->second = std::move(value); + UpdateUsage(it); + } else { + if (map_.size() == max_size_) { + Remove(*entries_.back().first); + } + + entries_.emplace_front(nullptr, std::move(value)); + auto pair = map_.emplace(key, entries_.begin()); + assert(pair.second && "The key must be unique"); + entries_.front().first = &pair.first->first; + } +} + +MutatorCache::Value MutatorCache::Remove(const Key& key) { + auto it = map_.find(key); + if (it == map_.end()) { + return nullptr; + } + auto result = std::move(it->second->second); + entries_.erase(it->second); + map_.erase(it); + return result; +} + +size_t MutatorCache::KeyHash::operator()( + const std::vector<uint32_t>& vec) const { + return std::hash<std::u32string>()({vec.begin(), vec.end()}); +} + +void MutatorCache::UpdateUsage(Map::iterator it) { + auto entry = std::move(*it->second); + entries_.erase(it->second); + entries_.push_front(std::move(entry)); + it->second = entries_.begin(); +} + +} // namespace spvtools_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h b/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h new file mode 100644 index 0000000..7318b5c --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h
@@ -0,0 +1,99 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_CACHE_H_ +#define SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_CACHE_H_ + +#include <cassert> +#include <list> +#include <memory> +#include <unordered_map> +#include <utility> +#include <vector> + +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.h" + +namespace tint { +namespace fuzzers { +namespace spvtools_fuzzer { + +/// Implementation of a fixed size LRU cache. That is, when the number of +/// elements reaches a certain threshold, the element that wasn't used for the +/// longest period of time is removed from the cache when a new element is +/// inserted. All operations have amortized constant time complexity. +class MutatorCache { + public: + /// SPIR-V binary that is being mutated. + using Key = std::vector<uint32_t>; + + /// Mutator that is used to mutate the `Key`. + using Value = std::unique_ptr<Mutator>; + + /// Constructor. + /// @param max_size - the maximum number of elements the cache can store. May + /// not be equal to 0. + explicit MutatorCache(size_t max_size); + + /// Retrieves a pointer to a value, associated with a given `key`. + /// + /// If the key is present in the cache, its usage is updated and the + /// (non-null) pointer to the value is returned. Otherwise, `nullptr` is + /// returned. + /// + /// @param key - may not exist in this cache. + /// @return non-`nullptr` pointer to a value if `key` exists in the cache. + /// @return `nullptr` if `key` doesn't exist in this cache. + Value::pointer Get(const Key& key); + + /// Inserts a `key`-`value` pair into the cache. + /// + /// If the `key` is already present, the `value` replaces the old value and + /// the usage of `key` is updated. If the `key` is not present, then: + /// - if the number of elements in the cache is equal to `max_size`, the + /// key-value pair, where the usage of the key wasn't updated for the + /// longest period of time, is removed from the cache. + /// - a new `key`-`value` pair is inserted into the cache. + /// + /// @param key - a key. + /// @param value - may not be a `nullptr`. + void Put(const Key& key, Value value); + + /// Removes `key` and an associated value from the cache. + /// + /// @param key - a key. + /// @return a non-`nullptr` pointer to the removed value, associated with + /// `key`. + /// @return `nullptr` if `key` is not present in the cache. + Value Remove(const Key& key); + + private: + struct KeyHash { + size_t operator()(const std::vector<uint32_t>& vec) const; + }; + + using Entry = std::pair<const Key*, Value>; + using Map = std::unordered_map<Key, std::list<Entry>::iterator, KeyHash>; + + void UpdateUsage(Map::iterator it); + + Map map_; + std::list<Entry> entries_; + const size_t max_size_; +}; + +} // namespace spvtools_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_CACHE_H_
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator_debugger.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator_debugger.cc new file mode 100644 index 0000000..aaa85fa --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator_debugger.cc
@@ -0,0 +1,84 @@ +// Copyright 2021 The Tint Authors. +// +// 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 <memory> +#include <string> + +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/util.h" + +/// This tool is used to debug *mutators*. It uses CLI arguments similar to the +/// ones used by the fuzzer. To debug some mutator you just need to specify the +/// mutator type, the seed and the path to the SPIR-V binary that triggered the +/// error. This tool will run the mutator on the binary until the error is +/// produced or the mutator returns `kLimitReached`. +/// +/// Note that this is different from debugging the fuzzer by specifying input +/// files to test. The difference is that the latter will not execute any +/// mutator (it will only run the LLVMFuzzerTestOneInput function) whereas this +/// tool is useful when one of the spirv-tools mutators crashes or produces an +/// invalid binary in LLVMFuzzerCustomMutator. +int main(int argc, const char** argv) { + auto params = + tint::fuzzers::spvtools_fuzzer::ParseMutatorDebuggerCliParams(argc, argv); + + std::unique_ptr<tint::fuzzers::spvtools_fuzzer::Mutator> mutator; + const auto& mutator_params = params.mutator_params; + switch (params.mutator_type) { + case tint::fuzzers::spvtools_fuzzer::MutatorType::kFuzz: + mutator = + std::make_unique<tint::fuzzers::spvtools_fuzzer::SpirvFuzzMutator>( + mutator_params.target_env, params.original_binary, params.seed, + mutator_params.donors, mutator_params.enable_all_fuzzer_passes, + mutator_params.repeated_pass_strategy, + mutator_params.validate_after_each_fuzzer_pass, + mutator_params.transformation_batch_size); + break; + case tint::fuzzers::spvtools_fuzzer::MutatorType::kReduce: + mutator = + std::make_unique<tint::fuzzers::spvtools_fuzzer::SpirvReduceMutator>( + mutator_params.target_env, params.original_binary, params.seed, + mutator_params.reduction_batch_size, + mutator_params.enable_all_reduce_passes, + mutator_params.validate_after_each_reduce_pass); + break; + case tint::fuzzers::spvtools_fuzzer::MutatorType::kOpt: + mutator = + std::make_unique<tint::fuzzers::spvtools_fuzzer::SpirvOptMutator>( + mutator_params.target_env, params.seed, params.original_binary, + mutator_params.validate_after_each_opt_pass, + mutator_params.opt_batch_size); + break; + default: + assert(false && "All mutator types must've been handled"); + return 1; + } + + while (true) { + auto result = mutator->Mutate(); + if (result.GetStatus() == + tint::fuzzers::spvtools_fuzzer::Mutator::Status::kInvalid) { + std::cerr << mutator->GetErrors() << std::endl; + return 0; + } + if (result.GetStatus() == + tint::fuzzers::spvtools_fuzzer::Mutator::Status::kLimitReached) { + break; + } + } +}
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h b/src/tint/fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h new file mode 100644 index 0000000..2aa2086 --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h
@@ -0,0 +1,36 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_OVERRIDE_CLI_PARAMS_H_ +#define SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_OVERRIDE_CLI_PARAMS_H_ + +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h" + +namespace tint { +namespace fuzzers { +namespace spvtools_fuzzer { + +/// @brief Allows CLI parameters to be overridden. +/// +/// This function allows fuzz targets to override particular CLI parameters, +/// for example forcing a particular back-end to be targeted. +/// +/// @param cli_params - the parsed CLI parameters to be updated. +void OverrideCliParams(FuzzerCliParams& cli_params); + +} // namespace spvtools_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_OVERRIDE_CLI_PARAMS_H_
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.cc new file mode 100644 index 0000000..05fd2a3 --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.cc
@@ -0,0 +1,127 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h" + +#include <fstream> +#include <utility> + +#include "source/opt/build_module.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/util.h" + +namespace tint { +namespace fuzzers { +namespace spvtools_fuzzer { + +SpirvFuzzMutator::SpirvFuzzMutator( + spv_target_env target_env, + std::vector<uint32_t> binary, + unsigned seed, + const std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier>& donors, + bool enable_all_passes, + spvtools::fuzz::RepeatedPassStrategy repeated_pass_strategy, + bool validate_after_each_pass, + uint32_t transformation_batch_size) + : transformation_batch_size_(transformation_batch_size), + errors_(std::make_unique<std::stringstream>()), + fuzzer_(nullptr), + validator_options_(), + original_binary_(std::move(binary)), + seed_(seed) { + auto ir_context = spvtools::BuildModule( + target_env, spvtools::fuzz::fuzzerutil::kSilentMessageConsumer, + original_binary_.data(), original_binary_.size()); + assert(ir_context && "|binary| is invalid"); + + auto transformation_context = + std::make_unique<spvtools::fuzz::TransformationContext>( + std::make_unique<spvtools::fuzz::FactManager>(ir_context.get()), + validator_options_); + + auto fuzzer_context = std::make_unique<spvtools::fuzz::FuzzerContext>( + std::make_unique<spvtools::fuzz::PseudoRandomGenerator>(seed), + spvtools::fuzz::FuzzerContext::GetMinFreshId(ir_context.get()), false); + fuzzer_ = std::make_unique<spvtools::fuzz::Fuzzer>( + std::move(ir_context), std::move(transformation_context), + std::move(fuzzer_context), util::GetBufferMessageConsumer(errors_.get()), + donors, enable_all_passes, repeated_pass_strategy, + validate_after_each_pass, validator_options_); +} + +Mutator::Result SpirvFuzzMutator::Mutate() { + // The assertion will fail in |fuzzer_->Run| if the previous fuzzing led to + // invalid module. + auto result = fuzzer_->Run(transformation_batch_size_); + switch (result.status) { + case spvtools::fuzz::Fuzzer::Status::kComplete: + return {Mutator::Status::kComplete, result.is_changed}; + case spvtools::fuzz::Fuzzer::Status::kModuleTooBig: + case spvtools::fuzz::Fuzzer::Status::kTransformationLimitReached: + return {Mutator::Status::kLimitReached, result.is_changed}; + case spvtools::fuzz::Fuzzer::Status::kFuzzerStuck: + return {Mutator::Status::kStuck, result.is_changed}; + case spvtools::fuzz::Fuzzer::Status::kFuzzerPassLedToInvalidModule: + return {Mutator::Status::kInvalid, result.is_changed}; + } +} + +std::vector<uint32_t> SpirvFuzzMutator::GetBinary() const { + std::vector<uint32_t> result; + fuzzer_->GetIRContext()->module()->ToBinary(&result, true); + return result; +} + +std::string SpirvFuzzMutator::GetErrors() const { + return errors_->str(); +} + +void SpirvFuzzMutator::LogErrors(const std::string* path, + uint32_t count) const { + auto message = GetErrors(); + std::cout << count << " | SpirvFuzzMutator (seed: " << seed_ << ")" + << std::endl; + std::cout << message << std::endl; + + if (path) { + auto prefix = *path + std::to_string(count); + + // Write errors to file. + std::ofstream(prefix + ".fuzzer.log") << "seed: " << seed_ << std::endl + << message << std::endl; + + // Write the invalid SPIR-V binary. + util::WriteBinary(prefix + ".fuzzer.invalid.spv", GetBinary()); + + // Write the original SPIR-V binary. + util::WriteBinary(prefix + ".fuzzer.original.spv", original_binary_); + + // Write transformations. + google::protobuf::util::JsonOptions options; + options.add_whitespace = true; + std::string json; + google::protobuf::util::MessageToJsonString( + fuzzer_->GetTransformationSequence(), &json, options); + std::ofstream(prefix + ".fuzzer.transformations.json") << json << std::endl; + + std::ofstream binary_transformations( + prefix + ".fuzzer.transformations.binary", + std::ios::binary | std::ios::out); + fuzzer_->GetTransformationSequence().SerializeToOstream( + &binary_transformations); + } +} + +} // namespace spvtools_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h new file mode 100644 index 0000000..073662e --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h
@@ -0,0 +1,95 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_FUZZ_MUTATOR_H_ +#define SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_FUZZ_MUTATOR_H_ + +#include <memory> +#include <sstream> +#include <string> +#include <vector> + +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.h" + +#include "source/fuzz/fuzzer.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/pseudo_random_generator.h" + +namespace tint { +namespace fuzzers { +namespace spvtools_fuzzer { + +/// The mutator that uses spirv-fuzz to mutate SPIR-V. +/// +/// The initial `binary` must be valid according to `target_env`. All other +/// parameters (except for the `seed` which just initializes the RNG) are from +/// the `spvtools::fuzz::Fuzzer` class. +class SpirvFuzzMutator : public Mutator { + public: + /// Constructor. + /// @param target_env - the target environment for the `binary`. + /// @param binary - the SPIR-V binary. Must be valid. + /// @param seed - seed for the RNG. + /// @param donors - vector of donor suppliers. + /// @param enable_all_passes - whether to use all fuzzer passes. + /// @param repeated_pass_strategy - the strategy to use when selecting the + /// next fuzzer pass. + /// @param validate_after_each_pass - whether to validate the binary after + /// each fuzzer pass. + /// @param transformation_batch_size - the maximum number of transformations + /// that will be applied during a single call to `Mutate`. It it's equal + /// to 0 then we apply as much transformations as we can until the + /// threshold in the spvtools::fuzz::Fuzzer is reached (see the doc for + /// that class for more info). + SpirvFuzzMutator( + spv_target_env target_env, + std::vector<uint32_t> binary, + uint32_t seed, + const std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier>& donors, + bool enable_all_passes, + spvtools::fuzz::RepeatedPassStrategy repeated_pass_strategy, + bool validate_after_each_pass, + uint32_t transformation_batch_size); + + Result Mutate() override; + std::vector<uint32_t> GetBinary() const override; + void LogErrors(const std::string* path, uint32_t count) const override; + std::string GetErrors() const override; + + private: + // The number of transformations that will be applied during a single call to + // the `Mutate` method. Is this only a lower bound since transformations are + // applied in batches by fuzzer passes (see docs for the + // `spvtools::fuzz::Fuzzer` for more info). + const uint32_t transformation_batch_size_; + + // The errors produced by the `spvtools::fuzz::Fuzzer`. + std::unique_ptr<std::stringstream> errors_; + std::unique_ptr<spvtools::fuzz::Fuzzer> fuzzer_; + spvtools::ValidatorOptions validator_options_; + + // The following fields are useful for debugging. + + // The binary that the mutator is constructed with. + const std::vector<uint32_t> original_binary_; + + // The seed that the mutator is constructed with. + const uint32_t seed_; +}; + +} // namespace spvtools_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_FUZZ_MUTATOR_H_
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.cc new file mode 100644 index 0000000..4e17ad0 --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.cc
@@ -0,0 +1,159 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h" + +#include <fstream> +#include <iostream> +#include <unordered_set> +#include <utility> + +#include "spirv-tools/optimizer.hpp" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/util.h" + +namespace tint { +namespace fuzzers { +namespace spvtools_fuzzer { + +SpirvOptMutator::SpirvOptMutator(spv_target_env target_env, + uint32_t seed, + std::vector<uint32_t> binary, + bool validate_after_each_opt, + uint32_t opt_batch_size) + : num_executions_(0), + is_valid_(true), + target_env_(target_env), + original_binary_(std::move(binary)), + seed_(seed), + opt_passes_({"--combine-access-chains", + "--loop-unroll", + "--merge-blocks", + "--cfg-cleanup", + "--eliminate-dead-functions", + "--merge-return", + "--wrap-opkill", + "--eliminate-dead-code-aggressive", + "--if-conversion", + "--eliminate-local-single-store", + "--eliminate-local-single-block", + "--eliminate-dead-branches", + "--scalar-replacement=0", + "--eliminate-dead-inserts", + "--eliminate-dead-members", + "--simplify-instructions", + "--private-to-local", + "--ssa-rewrite", + "--ccp", + "--reduce-load-size", + "--vector-dce", + "--scalar-replacement=100", + "--inline-entry-points-exhaustive", + "--redundancy-elimination", + "--convert-local-access-chains", + "--copy-propagate-arrays", + "--fix-storage-class"}), + optimized_binary_(), + validate_after_each_opt_(validate_after_each_opt), + opt_batch_size_(opt_batch_size), + generator_(seed) { + assert(spvtools::SpirvTools(target_env).Validate(original_binary_) && + "Initial binary is invalid"); + assert(!opt_passes_.empty() && "Must be at least one pass"); +} + +SpirvOptMutator::Result SpirvOptMutator::Mutate() { + assert(is_valid_ && "The optimizer is not longer valid"); + + const uint32_t kMaxNumExecutions = 100; + const uint32_t kMaxNumStuck = 10; + + if (num_executions_ == kMaxNumExecutions) { + // We've applied this mutator many times already. Indicate to the user that + // it might be better to try a different mutator. + return {Status::kLimitReached, false}; + } + + num_executions_++; + + // Get the input binary. If this is the first time we run this mutator, use + // the `original_binary_`. Otherwise, one of the following will be true: + // - the `optimized_binary_` is not empty. + // - the previous call to the `Mutate` method returned `kStuck`. + auto binary = num_executions_ == 1 ? original_binary_ : optimized_binary_; + optimized_binary_.clear(); + + assert(!binary.empty() && "Can't run the optimizer on an empty binary"); + + // Number of times spirv-opt wasn't able to produce any new result. + uint32_t num_stuck = 0; + do { + // Randomly select `opt_batch_size` optimization passes. If `opt_batch_size` + // is equal to 0, we will use the number of passes equal to the number of + // all available passes. + auto num_of_passes = opt_batch_size_ ? opt_batch_size_ : opt_passes_.size(); + std::vector<std::string> passes; + + while (passes.size() < num_of_passes) { + auto idx = + generator_.GetUInt32(static_cast<uint32_t>(opt_passes_.size())); + passes.push_back(opt_passes_[idx]); + } + + // Run the `binary` into the `optimized_binary_`. + spvtools::Optimizer optimizer(target_env_); + optimizer.SetMessageConsumer(util::GetBufferMessageConsumer(&errors_)); + optimizer.SetValidateAfterAll(validate_after_each_opt_); + optimizer.RegisterPassesFromFlags(passes); + if (!optimizer.Run(binary.data(), binary.size(), &optimized_binary_)) { + is_valid_ = false; + return {Status::kInvalid, true}; + } + } while (optimized_binary_.empty() && ++num_stuck < kMaxNumStuck); + + return {optimized_binary_.empty() ? Status::kStuck : Status::kComplete, + !optimized_binary_.empty()}; +} + +std::vector<uint32_t> SpirvOptMutator::GetBinary() const { + return optimized_binary_; +} + +std::string SpirvOptMutator::GetErrors() const { + return errors_.str(); +} + +void SpirvOptMutator::LogErrors(const std::string* path, uint32_t count) const { + auto message = GetErrors(); + std::cout << count << " | SpirvOptMutator (seed: " << seed_ << ")" + << std::endl; + std::cout << message << std::endl; + + if (path) { + auto prefix = *path + std::to_string(count); + + // Write errors to file. + std::ofstream(prefix + ".opt.log") << "seed: " << seed_ << std::endl + << message << std::endl; + + // Write the invalid SPIR-V binary. + util::WriteBinary(prefix + ".opt.invalid.spv", optimized_binary_); + + // Write the original SPIR-V binary. + util::WriteBinary(prefix + ".opt.original.spv", original_binary_); + } +} + +} // namespace spvtools_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h new file mode 100644 index 0000000..6c209cd --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h
@@ -0,0 +1,96 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_OPT_MUTATOR_H_ +#define SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_OPT_MUTATOR_H_ + +#include <sstream> +#include <string> +#include <vector> + +#include "spirv-tools/libspirv.h" +#include "src/tint/fuzzers/random_generator.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.h" + +namespace tint { +namespace fuzzers { +namespace spvtools_fuzzer { + +/// Mutates the SPIR-V module using the spirv-opt tool. +/// +/// The initial `binary` must be valid according to `target_env`. On each call +/// to the `Mutate` method the mutator selects `opt_batch_size` random +/// optimization passes (with substitutions) and applies them to the binary. +class SpirvOptMutator : public Mutator { + public: + /// Constructor. + /// @param target_env - target environment for the `binary`. + /// @param seed - seed for the RNG. + /// @param binary - SPIR-V binary. Must be valid. + /// @param validate_after_each_opt - whether to validate the binary after each + /// optimization pass. + /// @param opt_batch_size - the maximum number of optimization passes that + /// will be applied in a single call to `Mutate`. If it's equal to 0 then + /// all available optimization passes are applied. + SpirvOptMutator(spv_target_env target_env, + uint32_t seed, + std::vector<uint32_t> binary, + bool validate_after_each_opt, + uint32_t opt_batch_size); + + Result Mutate() override; + std::vector<uint32_t> GetBinary() const override; + void LogErrors(const std::string* path, uint32_t count) const override; + std::string GetErrors() const override; + + private: + // Number of times this mutator was executed. + uint32_t num_executions_; + + // Whether the last execution left it in a valid state. + bool is_valid_; + + // Target environment for the SPIR-V binary. + const spv_target_env target_env_; + + // The original SPIR-V binary. Useful for debugging. + const std::vector<uint32_t> original_binary_; + + // The seed for the RNG. Useful for debugging. + const uint32_t seed_; + + // All the optimization passes available. + const std::vector<std::string> opt_passes_; + + // The result of the optimization. + std::vector<uint32_t> optimized_binary_; + + // Whether we need to validate the binary after each optimization pass. + const bool validate_after_each_opt_; + + // The number of optimization passes to apply at once. + const uint32_t opt_batch_size_; + + // All the errors produced by the optimizer. + std::stringstream errors_; + + // The random number generator initialized with `seed_`. + RandomGenerator generator_; +}; + +} // namespace spvtools_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_OPT_MUTATOR_H_
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.cc new file mode 100644 index 0000000..93d2e8b --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.cc
@@ -0,0 +1,190 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h" + +#include <fstream> + +#include "source/fuzz/fuzzer_util.h" +#include "source/opt/build_module.h" +#include "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h" +#include "source/reduce/merge_blocks_reduction_opportunity_finder.h" +#include "source/reduce/operand_to_const_reduction_opportunity_finder.h" +#include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h" +#include "source/reduce/operand_to_undef_reduction_opportunity_finder.h" +#include "source/reduce/remove_block_reduction_opportunity_finder.h" +#include "source/reduce/remove_function_reduction_opportunity_finder.h" +#include "source/reduce/remove_selection_reduction_opportunity_finder.h" +#include "source/reduce/remove_unused_instruction_reduction_opportunity_finder.h" +#include "source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h" +#include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h" +#include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/util.h" + +namespace tint { +namespace fuzzers { +namespace spvtools_fuzzer { + +SpirvReduceMutator::SpirvReduceMutator(spv_target_env target_env, + std::vector<uint32_t> binary, + uint32_t seed, + uint32_t reductions_batch_size, + bool enable_all_reductions, + bool validate_after_each_reduction) + : ir_context_(nullptr), + finders_(), + generator_(seed), + errors_(), + is_valid_(true), + reductions_batch_size_(reductions_batch_size), + total_applied_reductions_(0), + enable_all_reductions_(enable_all_reductions), + validate_after_each_reduction_(validate_after_each_reduction), + original_binary_(std::move(binary)), + seed_(seed) { + ir_context_ = spvtools::BuildModule( + target_env, spvtools::fuzz::fuzzerutil::kSilentMessageConsumer, + original_binary_.data(), original_binary_.size()); + assert(ir_context_ && "|binary| is invalid"); + + do { + MaybeAddFinder< + spvtools::reduce:: + ConditionalBranchToSimpleConditionalBranchOpportunityFinder>(); + MaybeAddFinder<spvtools::reduce::MergeBlocksReductionOpportunityFinder>(); + MaybeAddFinder< + spvtools::reduce::OperandToConstReductionOpportunityFinder>(); + MaybeAddFinder< + spvtools::reduce::OperandToDominatingIdReductionOpportunityFinder>(); + MaybeAddFinder< + spvtools::reduce::OperandToUndefReductionOpportunityFinder>(); + MaybeAddFinder<spvtools::reduce::RemoveBlockReductionOpportunityFinder>(); + MaybeAddFinder< + spvtools::reduce::RemoveFunctionReductionOpportunityFinder>(); + MaybeAddFinder< + spvtools::reduce::RemoveSelectionReductionOpportunityFinder>(); + MaybeAddFinder< + spvtools::reduce::RemoveUnusedInstructionReductionOpportunityFinder>( + true); + MaybeAddFinder< + spvtools::reduce::RemoveUnusedStructMemberReductionOpportunityFinder>(); + MaybeAddFinder< + spvtools::reduce::SimpleConditionalBranchToBranchOpportunityFinder>(); + MaybeAddFinder<spvtools::reduce:: + StructuredLoopToSelectionReductionOpportunityFinder>(); + } while (finders_.empty()); +} + +Mutator::Result SpirvReduceMutator::Mutate() { + assert(is_valid_ && "Can't mutate invalid module"); + + // The upper limit on the number of applied reduction passes. + const uint32_t kMaxAppliedReductions = 500; + const auto old_applied_reductions = total_applied_reductions_; + + // The upper limit on the number of failed attempts to apply reductions (i.e. + // when no reduction was returned by the reduction finder). + const uint32_t kMaxConsecutiveFailures = 10; + uint32_t num_consecutive_failures = 0; + + // Iterate while we haven't exceeded the limit on the total number of applied + // reductions, the limit on the number of reductions applied at once and limit + // on the number of consecutive failed attempts. + while (total_applied_reductions_ < kMaxAppliedReductions && + (reductions_batch_size_ == 0 || + total_applied_reductions_ - old_applied_reductions < + reductions_batch_size_) && + num_consecutive_failures < kMaxConsecutiveFailures) { + // Select an opportunity finder and get some reduction opportunities from + // it. + auto finder = GetRandomElement(&finders_); + auto reduction_opportunities = + finder->GetAvailableOpportunities(ir_context_.get(), 0); + + if (reduction_opportunities.empty()) { + // There is nothing to reduce. We increase the counter to make sure we + // don't stuck in this situation. + num_consecutive_failures++; + } else { + // Apply a random reduction opportunity. The latter should be applicable. + auto opportunity = GetRandomElement(&reduction_opportunities); + assert(opportunity->PreconditionHolds() && "Preconditions should hold"); + total_applied_reductions_++; + num_consecutive_failures = 0; + if (!ApplyReduction(opportunity)) { + // The module became invalid as a result of the applied reduction. + is_valid_ = false; + return {Mutator::Status::kInvalid, + total_applied_reductions_ != old_applied_reductions}; + } + } + } + + auto is_changed = total_applied_reductions_ != old_applied_reductions; + if (total_applied_reductions_ == kMaxAppliedReductions) { + return {Mutator::Status::kLimitReached, is_changed}; + } + + if (num_consecutive_failures == kMaxConsecutiveFailures) { + return {Mutator::Status::kStuck, is_changed}; + } + + assert(is_changed && "This is the only way left to break the loop"); + return {Mutator::Status::kComplete, is_changed}; +} + +bool SpirvReduceMutator::ApplyReduction( + spvtools::reduce::ReductionOpportunity* reduction_opportunity) { + reduction_opportunity->TryToApply(); + return !validate_after_each_reduction_ || + spvtools::fuzz::fuzzerutil::IsValidAndWellFormed( + ir_context_.get(), spvtools::ValidatorOptions(), + util::GetBufferMessageConsumer(&errors_)); +} + +std::vector<uint32_t> SpirvReduceMutator::GetBinary() const { + std::vector<uint32_t> result; + ir_context_->module()->ToBinary(&result, true); + return result; +} + +std::string SpirvReduceMutator::GetErrors() const { + return errors_.str(); +} + +void SpirvReduceMutator::LogErrors(const std::string* path, + uint32_t count) const { + auto message = GetErrors(); + std::cout << count << " | SpirvReduceMutator (seed: " << seed_ << ")" + << std::endl; + std::cout << message << std::endl; + + if (path) { + auto prefix = *path + std::to_string(count); + + // Write errors to file. + std::ofstream(prefix + ".reducer.log") << "seed: " << seed_ << std::endl + << message << std::endl; + + // Write the invalid SPIR-V binary. + util::WriteBinary(prefix + ".reducer.invalid.spv", GetBinary()); + + // Write the original SPIR-V binary. + util::WriteBinary(prefix + ".reducer.original.spv", original_binary_); + } +} + +} // namespace spvtools_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h new file mode 100644 index 0000000..9699b01 --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h
@@ -0,0 +1,132 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_REDUCE_MUTATOR_H_ +#define SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_REDUCE_MUTATOR_H_ + +#include <memory> +#include <sstream> +#include <string> +#include <utility> +#include <vector> + +#include "src/tint/fuzzers/random_generator.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.h" + +#include "source/reduce/reduction_opportunity_finder.h" + +namespace tint { +namespace fuzzers { +namespace spvtools_fuzzer { + +/// Mutates SPIR-V binary by running spirv-reduce tool. +/// +/// The initial `binary` must be valid according to `target_env`. Applies at +/// most `reductions_batch_size` reductions at a time. This parameter is ignored +/// if its value is 0. Uses a random subset of reduction opportunity finders by +/// default. This can be overridden with the `enable_all_reductions` parameter. +class SpirvReduceMutator : public Mutator { + public: + /// Constructor. + /// @param target_env - the target environment for the `binary`. + /// @param binary - SPIR-V binary. Must be valid. + /// @param seed - the seed for the RNG. + /// @param reductions_batch_size - the number of reduction passes that will be + /// applied during a single call to `Mutate`. If it's equal to 0 then we + /// apply the passes until we reach the threshold for the total number of + /// applied passes. + /// @param enable_all_reductions - whether to use all reduction passes or only + /// a randomly selected subset of them. + /// @param validate_after_each_reduction - whether to validate after each + /// applied reduction. + SpirvReduceMutator(spv_target_env target_env, + std::vector<uint32_t> binary, + uint32_t seed, + uint32_t reductions_batch_size, + bool enable_all_reductions, + bool validate_after_each_reduction); + + Result Mutate() override; + std::vector<uint32_t> GetBinary() const override; + void LogErrors(const std::string* path, uint32_t count) const override; + std::string GetErrors() const override; + + private: + template <typename T, typename... Args> + void MaybeAddFinder(Args&&... args) { + if (enable_all_reductions_ || generator_.GetBool()) { + finders_.push_back(std::make_unique<T>(std::forward<Args>(args)...)); + } + } + + template <typename T> + T* GetRandomElement(std::vector<T>* arr) { + assert(!arr->empty() && "Can't get random element from an empty vector"); + auto index = generator_.GetUInt32(static_cast<uint32_t>(arr->size())); + return &(*arr)[index]; + } + + template <typename T> + T* GetRandomElement(std::vector<std::unique_ptr<T>>* arr) { + assert(!arr->empty() && "Can't get random element from an empty vector"); + auto index = generator_.GetUInt32(static_cast<uint32_t>(arr->size())); + return (*arr)[index].get(); + } + + bool ApplyReduction( + spvtools::reduce::ReductionOpportunity* reduction_opportunity); + + // The SPIR-V binary that is being reduced. + std::unique_ptr<spvtools::opt::IRContext> ir_context_; + + // The selected subset of reduction opportunity finders. + std::vector<std::unique_ptr<spvtools::reduce::ReductionOpportunityFinder>> + finders_; + + // Random number generator initialized with `seed_`. + RandomGenerator generator_; + + // All the errors produced by the reducer. + std::stringstream errors_; + + // Whether the last call to the `Mutate` method produced the valid binary. + bool is_valid_; + + // The number of reductions to apply on a single call to `Mutate`. + const uint32_t reductions_batch_size_; + + // The total number of applied reductions. + uint32_t total_applied_reductions_; + + // Whether we want to use all the reduction opportunity finders and not just a + // subset of them. + const bool enable_all_reductions_; + + // Whether we want to validate all the binary after each reduction. + const bool validate_after_each_reduction_; + + // The original binary that was used to initialize this mutator. + // Useful for debugging. + const std::vector<uint32_t> original_binary_; + + // The seed that was used to initialize the random number generator. + // Useful for debugging. + const uint32_t seed_; +}; + +} // namespace spvtools_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_REDUCE_MUTATOR_H_
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_fuzzer.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_fuzzer.cc new file mode 100644 index 0000000..8c14547 --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_fuzzer.cc
@@ -0,0 +1,30 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h" + +namespace tint { +namespace fuzzers { +namespace spvtools_fuzzer { + +void OverrideCliParams(FuzzerCliParams& /*unused*/) { + // Leave the CLI parameters unchanged. +} + +} // namespace spvtools_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_hlsl_writer_fuzzer.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_hlsl_writer_fuzzer.cc new file mode 100644 index 0000000..08111d9 --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_hlsl_writer_fuzzer.cc
@@ -0,0 +1,33 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h" + +namespace tint { +namespace fuzzers { +namespace spvtools_fuzzer { + +void OverrideCliParams(FuzzerCliParams& cli_params) { + assert(cli_params.fuzzing_target == FuzzingTarget::kAll && + "The fuzzing target should not have been set by a CLI parameter: it " + "should have its default value."); + cli_params.fuzzing_target = FuzzingTarget::kHlsl; +} + +} // namespace spvtools_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_msl_writer_fuzzer.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_msl_writer_fuzzer.cc new file mode 100644 index 0000000..86a493e --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_msl_writer_fuzzer.cc
@@ -0,0 +1,33 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h" + +namespace tint { +namespace fuzzers { +namespace spvtools_fuzzer { + +void OverrideCliParams(FuzzerCliParams& cli_params) { + assert(cli_params.fuzzing_target == FuzzingTarget::kAll && + "The fuzzing target should not have been set by a CLI parameter: it " + "should have its default value."); + cli_params.fuzzing_target = FuzzingTarget::kMsl; +} + +} // namespace spvtools_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_spv_writer_fuzzer.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_spv_writer_fuzzer.cc new file mode 100644 index 0000000..2cbfef7 --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_spv_writer_fuzzer.cc
@@ -0,0 +1,33 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h" + +namespace tint { +namespace fuzzers { +namespace spvtools_fuzzer { + +void OverrideCliParams(FuzzerCliParams& cli_params) { + assert(cli_params.fuzzing_target == FuzzingTarget::kAll && + "The fuzzing target should not have been set by a CLI parameter: it " + "should have its default value."); + cli_params.fuzzing_target = FuzzingTarget::kSpv; +} + +} // namespace spvtools_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_wgsl_writer_fuzzer.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_wgsl_writer_fuzzer.cc new file mode 100644 index 0000000..4538973 --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/tint_spirv_tools_wgsl_writer_fuzzer.cc
@@ -0,0 +1,33 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/tint_spirv_tools_fuzzer/cli.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h" + +namespace tint { +namespace fuzzers { +namespace spvtools_fuzzer { + +void OverrideCliParams(FuzzerCliParams& cli_params) { + assert(cli_params.fuzzing_target == FuzzingTarget::kAll && + "The fuzzing target should not have been set by a CLI parameter: it " + "should have its default value."); + cli_params.fuzzing_target = FuzzingTarget::kWgsl; +} + +} // namespace spvtools_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/util.cc b/src/tint/fuzzers/tint_spirv_tools_fuzzer/util.cc new file mode 100644 index 0000000..a20a1f4 --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/util.cc
@@ -0,0 +1,157 @@ +// Copyright 2021 The Tint Authors. +// +// 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 <fstream> +#include <iostream> + +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/util.h" + +namespace tint { +namespace fuzzers { +namespace spvtools_fuzzer { +namespace util { +namespace { + +bool WriteBinary(const std::string& path, const uint8_t* data, size_t size) { + std::ofstream spv(path, std::ios::binary); + return spv && spv.write(reinterpret_cast<const char*>(data), + static_cast<std::streamsize>(size)); +} + +void LogError(uint32_t index, + const std::string& type, + const std::string& message, + const std::string* path, + const uint8_t* data, + size_t size, + const std::string* wgsl) { + std::cout << index << " | " << type << ": " << message << std::endl; + + if (path) { + auto prefix = *path + std::to_string(index); + std::ofstream(prefix + ".log") << message << std::endl; + + WriteBinary(prefix + ".spv", data, size); + + if (wgsl) { + std::ofstream(prefix + ".wgsl") << *wgsl << std::endl; + } + } +} + +} // namespace + +spvtools::MessageConsumer GetBufferMessageConsumer(std::stringstream* buffer) { + return [buffer](spv_message_level_t level, const char*, + const spv_position_t& position, const char* message) { + std::string status; + switch (level) { + case SPV_MSG_FATAL: + case SPV_MSG_INTERNAL_ERROR: + case SPV_MSG_ERROR: + status = "ERROR"; + break; + case SPV_MSG_WARNING: + case SPV_MSG_INFO: + case SPV_MSG_DEBUG: + status = "INFO"; + break; + } + *buffer << status << " " << position.line << ":" << position.column << ":" + << position.index << ": " << message << std::endl; + }; +} + +void LogMutatorError(const Mutator& mutator, const std::string& error_dir) { + static uint32_t mutator_count = 0; + auto error_path = error_dir.empty() ? error_dir : error_dir + "/mutator/"; + mutator.LogErrors(error_dir.empty() ? nullptr : &error_path, mutator_count++); +} + +void LogWgslError(const std::string& message, + const uint8_t* data, + size_t size, + const std::string& wgsl, + OutputFormat output_format, + const std::string& error_dir) { + static uint32_t wgsl_count = 0; + std::string error_type; + switch (output_format) { + case OutputFormat::kSpv: + error_type = "WGSL -> SPV"; + break; + case OutputFormat::kMSL: + error_type = "WGSL -> MSL"; + break; + case OutputFormat::kHLSL: + error_type = "WGSL -> HLSL"; + break; + case OutputFormat::kWGSL: + error_type = "WGSL -> WGSL"; + break; + } + auto error_path = error_dir.empty() ? error_dir : error_dir + "/wgsl/"; + LogError(wgsl_count++, error_type, message, + error_dir.empty() ? nullptr : &error_path, data, size, &wgsl); +} + +void LogSpvError(const std::string& message, + const uint8_t* data, + size_t size, + const std::string& error_dir) { + static uint32_t spv_count = 0; + auto error_path = error_dir.empty() ? error_dir : error_dir + "/spv/"; + LogError(spv_count++, "SPV -> WGSL", message, + error_dir.empty() ? nullptr : &error_path, data, size, nullptr); +} + +bool ReadBinary(const std::string& path, std::vector<uint32_t>* out) { + if (!out) { + return false; + } + + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file) { + return false; + } + + size_t size = static_cast<size_t>(file.tellg()); + if (!file) { + return false; + } + + file.seekg(0); + if (!file) { + return false; + } + + std::vector<char> binary(size); + if (!file.read(binary.data(), size)) { + return false; + } + + out->resize(binary.size() / sizeof(uint32_t)); + std::memcpy(out->data(), binary.data(), binary.size()); + return true; +} + +bool WriteBinary(const std::string& path, const std::vector<uint32_t>& binary) { + return WriteBinary(path, reinterpret_cast<const uint8_t*>(binary.data()), + binary.size() * sizeof(uint32_t)); +} + +} // namespace util +} // namespace spvtools_fuzzer +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_spirv_tools_fuzzer/util.h b/src/tint/fuzzers/tint_spirv_tools_fuzzer/util.h new file mode 100644 index 0000000..301e3ba --- /dev/null +++ b/src/tint/fuzzers/tint_spirv_tools_fuzzer/util.h
@@ -0,0 +1,96 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_UTIL_H_ +#define SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_UTIL_H_ + +#include <sstream> +#include <string> +#include <vector> + +#include "src/tint/fuzzers/tint_common_fuzzer.h" +#include "src/tint/fuzzers/tint_spirv_tools_fuzzer/mutator.h" + +#include "spirv-tools/libspirv.hpp" + +namespace tint { +namespace fuzzers { +namespace spvtools_fuzzer { +namespace util { + +/// @param buffer will be used to output errors by the returned message +/// consumer. Must remain in scope as long as the returned consumer is in +/// scope. +/// @return the message consumer that will print errors to the `buffer`. +spvtools::MessageConsumer GetBufferMessageConsumer(std::stringstream* buffer); + +/// Output errors from the SPV -> WGSL conversion. +/// +/// @param message - the error message. +/// @param data - invalid SPIR-V binary. +/// @param size - the size of `data`. +/// @param error_dir - the directory, to which the binary will be printed to. +/// If it's empty, the invalid binary and supplemental files will not be +/// printed. Otherwise, it must have a `spv/` subdirectory. +void LogSpvError(const std::string& message, + const uint8_t* data, + size_t size, + const std::string& error_dir); + +/// Output errors from the WGSL -> `output_format` conversion. +/// +/// @param message - the error message. +/// @param data - the SPIR-V binary that generated the WGSL binary. +/// @param size - the size of `data`. +/// @param wgsl - the invalid WGSL binary. +/// @param output_format - the format which we attempted to convert `wgsl` to. +/// @param error_dir - the directory, to which the binary will be printed out. +/// If it's empty, the invalid binary and supplemental files will not be +/// printed. Otherwise, it must have a `wgsl/` subdirectory. +void LogWgslError(const std::string& message, + const uint8_t* data, + size_t size, + const std::string& wgsl, + OutputFormat output_format, + const std::string& error_dir); + +/// Output errors produced by the mutator. +/// +/// @param mutator - the mutator with invalid state. +/// @param error_dir - the directory, to which invalid files will be printed to. +/// If it's empty, the invalid binary and supplemental files will not be +/// printed. Otherwise, it must have a `mutator/` subdirectory. +void LogMutatorError(const Mutator& mutator, const std::string& error_dir); + +/// Reads SPIR-V binary from `path` into `out`. Returns `true` if successful and +/// `false` otherwise (in this case, `out` is unchanged). +/// +/// @param path - the path to the SPIR-V binary. +/// @param out - may be a `nullptr`. In this case, `false` is returned. +/// @return `true` if successful and `false` otherwise. +bool ReadBinary(const std::string& path, std::vector<uint32_t>* out); + +/// Writes `binary` into `path`. +/// +/// @param path - the path to write `binary` to. +/// @param binary - SPIR-V binary. +/// @return whether the operation was successful. +bool WriteBinary(const std::string& path, const std::vector<uint32_t>& binary); + +} // namespace util +} // namespace spvtools_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_SPIRV_TOOLS_FUZZER_UTIL_H_
diff --git a/src/tint/fuzzers/tint_spv_reader_fuzzer.cc b/src/tint/fuzzers/tint_spv_reader_fuzzer.cc new file mode 100644 index 0000000..0daab5d --- /dev/null +++ b/src/tint/fuzzers/tint_spv_reader_fuzzer.cc
@@ -0,0 +1,30 @@ +// Copyright 2020 The Tint Authors. +// +// 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 <vector> + +#include "src/tint/fuzzers/fuzzer_init.h" +#include "src/tint/fuzzers/tint_common_fuzzer.h" + +namespace tint { +namespace fuzzers { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + tint::fuzzers::CommonFuzzer fuzzer(InputFormat::kSpv, OutputFormat::kNone); + fuzzer.SetDumpInput(GetCliParams().dump_input); + return fuzzer.Run(data, size); +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_spv_reader_hlsl_writer_fuzzer.cc b/src/tint/fuzzers/tint_spv_reader_hlsl_writer_fuzzer.cc new file mode 100644 index 0000000..5f2f2c7 --- /dev/null +++ b/src/tint/fuzzers/tint_spv_reader_hlsl_writer_fuzzer.cc
@@ -0,0 +1,33 @@ +// Copyright 2021 The Tint Authors. +// +// 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 <vector> + +#include "src/tint/fuzzers/fuzzer_init.h" +#include "src/tint/fuzzers/tint_reader_writer_fuzzer.h" + +namespace tint { +namespace fuzzers { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kSpv, + OutputFormat::kHLSL); + fuzzer.SetDumpInput(GetCliParams().dump_input); + fuzzer.SetEnforceValidity(GetCliParams().enforce_validity); + + return fuzzer.Run(data, size); +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_spv_reader_msl_writer_fuzzer.cc b/src/tint/fuzzers/tint_spv_reader_msl_writer_fuzzer.cc new file mode 100644 index 0000000..0b1446b --- /dev/null +++ b/src/tint/fuzzers/tint_spv_reader_msl_writer_fuzzer.cc
@@ -0,0 +1,37 @@ +// Copyright 2021 The Tint Authors. +// +// 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 <vector> + +#include "src/tint/fuzzers/fuzzer_init.h" +#include "src/tint/fuzzers/tint_reader_writer_fuzzer.h" + +namespace tint { +namespace fuzzers { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + DataBuilder db(data, size); + writer::msl::Options options; + GenerateMslOptions(&db, &options); + tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kSpv, + OutputFormat::kMSL); + fuzzer.SetOptionsMsl(options); + fuzzer.SetDumpInput(GetCliParams().dump_input); + fuzzer.SetEnforceValidity(GetCliParams().enforce_validity); + + return fuzzer.Run(data, size); +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_spv_reader_spv_writer_fuzzer.cc b/src/tint/fuzzers/tint_spv_reader_spv_writer_fuzzer.cc new file mode 100644 index 0000000..b3cc6b0 --- /dev/null +++ b/src/tint/fuzzers/tint_spv_reader_spv_writer_fuzzer.cc
@@ -0,0 +1,37 @@ +// Copyright 2021 The Tint Authors. +// +// 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 <vector> + +#include "src/tint/fuzzers/fuzzer_init.h" +#include "src/tint/fuzzers/tint_reader_writer_fuzzer.h" + +namespace tint { +namespace fuzzers { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + DataBuilder db(data, size); + writer::spirv::Options options; + GenerateSpirvOptions(&db, &options); + tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kSpv, + OutputFormat::kSpv); + fuzzer.SetOptionsSpirv(options); + fuzzer.SetDumpInput(GetCliParams().dump_input); + fuzzer.SetEnforceValidity(GetCliParams().enforce_validity); + + return fuzzer.Run(data, size); +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_spv_reader_wgsl_writer_fuzzer.cc b/src/tint/fuzzers/tint_spv_reader_wgsl_writer_fuzzer.cc new file mode 100644 index 0000000..eff349d --- /dev/null +++ b/src/tint/fuzzers/tint_spv_reader_wgsl_writer_fuzzer.cc
@@ -0,0 +1,33 @@ +// Copyright 2021 The Tint Authors. +// +// 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 <vector> + +#include "src/tint/fuzzers/fuzzer_init.h" +#include "src/tint/fuzzers/tint_reader_writer_fuzzer.h" + +namespace tint { +namespace fuzzers { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kSpv, + OutputFormat::kWGSL); + fuzzer.SetDumpInput(GetCliParams().dump_input); + fuzzer.SetEnforceValidity(GetCliParams().enforce_validity); + + return fuzzer.Run(data, size); +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_vertex_pulling_fuzzer.cc b/src/tint/fuzzers/tint_vertex_pulling_fuzzer.cc new file mode 100644 index 0000000..e1ad99e --- /dev/null +++ b/src/tint/fuzzers/tint_vertex_pulling_fuzzer.cc
@@ -0,0 +1,35 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/fuzzers/fuzzer_init.h" +#include "src/tint/fuzzers/tint_common_fuzzer.h" +#include "src/tint/fuzzers/transform_builder.h" + +namespace tint { +namespace fuzzers { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + TransformBuilder tb(data, size); + tb.AddTransform<transform::VertexPulling>(); + + tint::fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kWGSL); + fuzzer.SetTransformManager(tb.manager(), tb.data_map()); + fuzzer.SetDumpInput(GetCliParams().dump_input); + fuzzer.SetEnforceValidity(GetCliParams().enforce_validity); + + return fuzzer.Run(data, size); +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_wgsl_reader_fuzzer.cc b/src/tint/fuzzers/tint_wgsl_reader_fuzzer.cc new file mode 100644 index 0000000..d1b1f27 --- /dev/null +++ b/src/tint/fuzzers/tint_wgsl_reader_fuzzer.cc
@@ -0,0 +1,30 @@ +// Copyright 2020 The Tint Authors. +// +// 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 <string> + +#include "src/tint/fuzzers/fuzzer_init.h" +#include "src/tint/fuzzers/tint_common_fuzzer.h" + +namespace tint { +namespace fuzzers { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + tint::fuzzers::CommonFuzzer fuzzer(InputFormat::kWGSL, OutputFormat::kNone); + fuzzer.SetDumpInput(GetCliParams().dump_input); + return fuzzer.Run(data, size); +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_wgsl_reader_hlsl_writer_fuzzer.cc b/src/tint/fuzzers/tint_wgsl_reader_hlsl_writer_fuzzer.cc new file mode 100644 index 0000000..d5a15a7 --- /dev/null +++ b/src/tint/fuzzers/tint_wgsl_reader_hlsl_writer_fuzzer.cc
@@ -0,0 +1,33 @@ +// Copyright 2021 The Tint Authors. +// +// 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 <string> + +#include "src/tint/fuzzers/fuzzer_init.h" +#include "src/tint/fuzzers/tint_reader_writer_fuzzer.h" + +namespace tint { +namespace fuzzers { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kWGSL, + OutputFormat::kHLSL); + fuzzer.SetDumpInput(GetCliParams().dump_input); + fuzzer.SetEnforceValidity(GetCliParams().enforce_validity); + + return fuzzer.Run(data, size); +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_wgsl_reader_msl_writer_fuzzer.cc b/src/tint/fuzzers/tint_wgsl_reader_msl_writer_fuzzer.cc new file mode 100644 index 0000000..58fdf8b --- /dev/null +++ b/src/tint/fuzzers/tint_wgsl_reader_msl_writer_fuzzer.cc
@@ -0,0 +1,37 @@ +// Copyright 2021 The Tint Authors. +// +// 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 <string> + +#include "src/tint/fuzzers/fuzzer_init.h" +#include "src/tint/fuzzers/tint_reader_writer_fuzzer.h" + +namespace tint { +namespace fuzzers { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + DataBuilder db(data, size); + writer::msl::Options options; + GenerateMslOptions(&db, &options); + tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kWGSL, + OutputFormat::kMSL); + fuzzer.SetOptionsMsl(options); + fuzzer.SetDumpInput(GetCliParams().dump_input); + fuzzer.SetEnforceValidity(GetCliParams().enforce_validity); + + return fuzzer.Run(data, size); +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_wgsl_reader_spv_writer_fuzzer.cc b/src/tint/fuzzers/tint_wgsl_reader_spv_writer_fuzzer.cc new file mode 100644 index 0000000..f4932dd --- /dev/null +++ b/src/tint/fuzzers/tint_wgsl_reader_spv_writer_fuzzer.cc
@@ -0,0 +1,37 @@ +// Copyright 2021 The Tint Authors. +// +// 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 <string> + +#include "src/tint/fuzzers/fuzzer_init.h" +#include "src/tint/fuzzers/tint_reader_writer_fuzzer.h" + +namespace tint { +namespace fuzzers { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + DataBuilder db(data, size); + writer::spirv::Options options; + GenerateSpirvOptions(&db, &options); + tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kWGSL, + OutputFormat::kSpv); + fuzzer.SetOptionsSpirv(options); + fuzzer.SetDumpInput(GetCliParams().dump_input); + fuzzer.SetEnforceValidity(GetCliParams().enforce_validity); + + return fuzzer.Run(data, size); +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/tint_wgsl_reader_wgsl_writer_fuzzer.cc b/src/tint/fuzzers/tint_wgsl_reader_wgsl_writer_fuzzer.cc new file mode 100644 index 0000000..8cb7eaa --- /dev/null +++ b/src/tint/fuzzers/tint_wgsl_reader_wgsl_writer_fuzzer.cc
@@ -0,0 +1,33 @@ +// Copyright 2021 The Tint Authors. +// +// 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 <string> + +#include "src/tint/fuzzers/fuzzer_init.h" +#include "src/tint/fuzzers/tint_reader_writer_fuzzer.h" + +namespace tint { +namespace fuzzers { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + tint::fuzzers::ReaderWriterFuzzer fuzzer(InputFormat::kWGSL, + OutputFormat::kWGSL); + fuzzer.SetDumpInput(GetCliParams().dump_input); + fuzzer.SetEnforceValidity(GetCliParams().enforce_validity); + + return fuzzer.Run(data, size); +} + +} // namespace fuzzers +} // namespace tint
diff --git a/src/tint/fuzzers/transform_builder.h b/src/tint/fuzzers/transform_builder.h new file mode 100644 index 0000000..7c5073d --- /dev/null +++ b/src/tint/fuzzers/transform_builder.h
@@ -0,0 +1,228 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_FUZZERS_TRANSFORM_BUILDER_H_ +#define SRC_TINT_FUZZERS_TRANSFORM_BUILDER_H_ + +#include <string> +#include <vector> + +#include "include/tint/tint.h" + +#include "src/tint/fuzzers/data_builder.h" +#include "src/tint/fuzzers/shuffle_transform.h" + +namespace tint { +namespace fuzzers { + +/// Fuzzer utility class to build inputs for transforms and setup the transform +/// manager. +class TransformBuilder { + public: + /// @brief Initializes the internal builder using a seed value + /// @param seed - seed value passed to engine + explicit TransformBuilder(uint64_t seed) : builder_(seed) {} + + /// @brief Initializes the internal builder using seed data + /// @param data - data fuzzer to calculate seed from + /// @param size - size of data buffer + explicit TransformBuilder(const uint8_t* data, size_t size) + : builder_(data, size) { + assert(data != nullptr && "|data| must be !nullptr"); + } + + ~TransformBuilder() = default; + + /// @returns manager for transforms + transform::Manager* manager() { return &manager_; } + + /// @returns data for transforms + transform::DataMap* data_map() { return &data_map_; } + + /// Adds a transform and needed data to |manager_| and |data_map_|. + /// @tparam T - A class that inherits from transform::Transform and has an + /// explicit specialization in AddTransformImpl. + template <typename T> + void AddTransform() { + static_assert(std::is_base_of<transform::Transform, T>::value, + "T is not a transform::Transform"); + AddTransformImpl<T>::impl(this); + } + + /// Helper that invokes Add*Transform for all of the platform independent + /// passes. + void AddPlatformIndependentPasses() { + AddTransform<transform::Robustness>(); + AddTransform<transform::FirstIndexOffset>(); + AddTransform<transform::BindingRemapper>(); + AddTransform<transform::Renamer>(); + AddTransform<transform::SingleEntryPoint>(); + AddTransform<transform::VertexPulling>(); + } + + private: + DataBuilder builder_; + transform::Manager manager_; + transform::DataMap data_map_; + + DataBuilder* builder() { return &builder_; } + + /// Implementation of AddTransform, specialized for each transform that is + /// implemented. Default implementation intentionally deleted to cause compile + /// error if unimplemented type passed in. + /// @tparam T - A fuzzer transform + template <typename T> + struct AddTransformImpl; + + /// Implementation of AddTransform for ShuffleTransform + template <> + struct AddTransformImpl<ShuffleTransform> { + /// Add instance of ShuffleTransform to TransformBuilder + /// @param tb - TransformBuilder to add transform to + static void impl(TransformBuilder* tb) { + tb->manager()->Add<ShuffleTransform>(tb->builder_.build<size_t>()); + } + }; + + /// Implementation of AddTransform for transform::Robustness + template <> + struct AddTransformImpl<transform::Robustness> { + /// Add instance of transform::Robustness to TransformBuilder + /// @param tb - TransformBuilder to add transform to + static void impl(TransformBuilder* tb) { + tb->manager()->Add<transform::Robustness>(); + } + }; + + /// Implementation of AddTransform for transform::FirstIndexOffset + template <> + struct AddTransformImpl<transform::FirstIndexOffset> { + /// Add instance of transform::FirstIndexOffset to TransformBuilder + /// @param tb - TransformBuilder to add transform to + static void impl(TransformBuilder* tb) { + struct Config { + uint32_t group; + uint32_t binding; + }; + + Config config = tb->builder()->build<Config>(); + + tb->data_map()->Add<tint::transform::FirstIndexOffset::BindingPoint>( + config.binding, config.group); + tb->manager()->Add<transform::FirstIndexOffset>(); + } + }; + + /// Implementation of AddTransform for transform::BindingRemapper + template <> + struct AddTransformImpl<transform::BindingRemapper> { + /// Add instance of transform::BindingRemapper to TransformBuilder + /// @param tb - TransformBuilder to add transform to + static void impl(TransformBuilder* tb) { + struct Config { + uint8_t old_group; + uint8_t old_binding; + uint8_t new_group; + uint8_t new_binding; + ast::Access new_access; + }; + + std::vector<Config> configs = tb->builder()->vector<Config>(); + transform::BindingRemapper::BindingPoints binding_points; + transform::BindingRemapper::AccessControls accesses; + for (const auto& config : configs) { + binding_points[{config.old_binding, config.old_group}] = { + config.new_binding, config.new_group}; + accesses[{config.old_binding, config.old_group}] = config.new_access; + } + + tb->data_map()->Add<transform::BindingRemapper::Remappings>( + binding_points, accesses, tb->builder()->build<bool>()); + tb->manager()->Add<transform::BindingRemapper>(); + } + }; + + /// Implementation of AddTransform for transform::Renamer + template <> + struct AddTransformImpl<transform::Renamer> { + /// Add instance of transform::Renamer to TransformBuilder + /// @param tb - TransformBuilder to add transform to + static void impl(TransformBuilder* tb) { + tb->manager()->Add<transform::Renamer>(); + } + }; + + /// Implementation of AddTransform for transform::SingleEntryPoint + template <> + struct AddTransformImpl<transform::SingleEntryPoint> { + /// Add instance of transform::SingleEntryPoint to TransformBuilder + /// @param tb - TransformBuilder to add transform to + static void impl(TransformBuilder* tb) { + auto input = tb->builder()->build<std::string>(); + transform::SingleEntryPoint::Config cfg(input); + + tb->data_map()->Add<transform::SingleEntryPoint::Config>(cfg); + tb->manager()->Add<transform::SingleEntryPoint>(); + } + }; // struct AddTransformImpl<transform::SingleEntryPoint> + + /// Implementation of AddTransform for transform::VertexPulling + template <> + struct AddTransformImpl<transform::VertexPulling> { + /// Add instance of transform::VertexPulling to TransformBuilder + /// @param tb - TransformBuilder to add transform to + static void impl(TransformBuilder* tb) { + transform::VertexPulling::Config cfg; + cfg.entry_point_name = tb->builder()->build<std::string>(); + cfg.vertex_state = + tb->builder()->vector<transform::VertexBufferLayoutDescriptor>( + GenerateVertexBufferLayoutDescriptor); + cfg.pulling_group = tb->builder()->build<uint32_t>(); + + tb->data_map()->Add<transform::VertexPulling::Config>(cfg); + tb->manager()->Add<transform::VertexPulling>(); + } + + private: + /// Generate an instance of transform::VertexAttributeDescriptor + /// @param b - DataBuilder to use + static transform::VertexAttributeDescriptor + GenerateVertexAttributeDescriptor(DataBuilder* b) { + transform::VertexAttributeDescriptor desc{}; + desc.format = b->enum_class<transform::VertexFormat>( + static_cast<uint8_t>(transform::VertexFormat::kLastEntry) + 1); + desc.offset = b->build<uint32_t>(); + desc.shader_location = b->build<uint32_t>(); + return desc; + } + + /// Generate an instance of VertexBufferLayoutDescriptor + /// @param b - DataBuilder to use + static transform::VertexBufferLayoutDescriptor + GenerateVertexBufferLayoutDescriptor(DataBuilder* b) { + transform::VertexBufferLayoutDescriptor desc; + desc.array_stride = b->build<uint32_t>(); + desc.step_mode = b->enum_class<transform::VertexStepMode>( + static_cast<uint8_t>(transform::VertexStepMode::kLastEntry) + 1); + desc.attributes = b->vector<transform::VertexAttributeDescriptor>( + GenerateVertexAttributeDescriptor); + return desc; + } + }; +}; // class TransformBuilder + +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TRANSFORM_BUILDER_H_
diff --git a/src/tint/inspector/entry_point.cc b/src/tint/inspector/entry_point.cc new file mode 100644 index 0000000..9b0b46f --- /dev/null +++ b/src/tint/inspector/entry_point.cc
@@ -0,0 +1,70 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/inspector/entry_point.h" + +namespace tint { +namespace inspector { + +StageVariable::StageVariable() = default; +StageVariable::StageVariable(const StageVariable& other) + : name(other.name), + has_location_attribute(other.has_location_attribute), + location_attribute(other.location_attribute), + has_location_decoration(has_location_attribute), + location_decoration(location_attribute), + component_type(other.component_type), + composition_type(other.composition_type), + interpolation_type(other.interpolation_type), + interpolation_sampling(other.interpolation_sampling) {} + +StageVariable::~StageVariable() = default; + +EntryPoint::EntryPoint() = default; +EntryPoint::EntryPoint(EntryPoint&) = default; +EntryPoint::EntryPoint(EntryPoint&&) = default; +EntryPoint::~EntryPoint() = default; + +InterpolationType ASTToInspectorInterpolationType( + ast::InterpolationType ast_type) { + switch (ast_type) { + case ast::InterpolationType::kPerspective: + return InterpolationType::kPerspective; + case ast::InterpolationType::kLinear: + return InterpolationType::kLinear; + case ast::InterpolationType::kFlat: + return InterpolationType::kFlat; + } + + return InterpolationType::kUnknown; +} + +InterpolationSampling ASTToInspectorInterpolationSampling( + ast::InterpolationSampling sampling) { + switch (sampling) { + case ast::InterpolationSampling::kNone: + return InterpolationSampling::kNone; + case ast::InterpolationSampling::kCenter: + return InterpolationSampling::kCenter; + case ast::InterpolationSampling::kCentroid: + return InterpolationSampling::kCentroid; + case ast::InterpolationSampling::kSample: + return InterpolationSampling::kSample; + } + + return InterpolationSampling::kUnknown; +} + +} // namespace inspector +} // namespace tint
diff --git a/src/tint/inspector/entry_point.h b/src/tint/inspector/entry_point.h new file mode 100644 index 0000000..46b87dd --- /dev/null +++ b/src/tint/inspector/entry_point.h
@@ -0,0 +1,187 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_INSPECTOR_ENTRY_POINT_H_ +#define SRC_TINT_INSPECTOR_ENTRY_POINT_H_ + +#include <string> +#include <tuple> +#include <vector> + +#include "src/tint/ast/interpolate_attribute.h" +#include "src/tint/ast/pipeline_stage.h" + +namespace tint { +namespace inspector { + +/// Base component type of a stage variable. +enum class ComponentType { + kUnknown = -1, + kFloat, + kUInt, + kSInt, +}; + +/// Composition of components of a stage variable. +enum class CompositionType { + kUnknown = -1, + kScalar, + kVec2, + kVec3, + kVec4, +}; + +/// Type of interpolation of a stage variable. +enum class InterpolationType { kUnknown = -1, kPerspective, kLinear, kFlat }; + +/// Type of interpolation sampling of a stage variable. +enum class InterpolationSampling { + kUnknown = -1, + kNone, + kCenter, + kCentroid, + kSample +}; + +/// Reflection data about an entry point input or output. +struct StageVariable { + /// Constructor + StageVariable(); + /// Copy constructor + /// @param other the StageVariable to copy + StageVariable(const StageVariable& other); + /// Destructor + ~StageVariable(); + + /// Name of the variable in the shader. + std::string name; + /// Is location attribute present + bool has_location_attribute = false; + /// Value of the location attribute, only valid if #has_location_attribute is + /// true. + uint32_t location_attribute; + /// Is Location attribute present + /// [DEPRECATED]: Use #has_location_attribute + bool& has_location_decoration = has_location_attribute; + /// Value of Location Decoration, only valid if #has_location_decoration is + /// true. + /// [DEPRECATED]: Use #location_attribute + uint32_t& location_decoration = location_attribute; + /// Scalar type that the variable is composed of. + ComponentType component_type = ComponentType::kUnknown; + /// How the scalars are composed for the variable. + CompositionType composition_type = CompositionType::kUnknown; + /// Interpolation type of the variable. + InterpolationType interpolation_type = InterpolationType::kUnknown; + /// Interpolation sampling of the variable. + InterpolationSampling interpolation_sampling = + InterpolationSampling::kUnknown; +}; + +/// Convert from internal ast::InterpolationType to public ::InterpolationType. +/// @param ast_type internal value to convert from +/// @returns the publicly visible equivalent +InterpolationType ASTToInspectorInterpolationType( + ast::InterpolationType ast_type); + +/// Convert from internal ast::InterpolationSampling to public +/// ::InterpolationSampling +/// @param sampling internal value to convert from +/// @returns the publicly visible equivalent +InterpolationSampling ASTToInspectorInterpolationSampling( + ast::InterpolationSampling sampling); + +/// Reflection data about a pipeline overridable constant referenced by an entry +/// point +struct OverridableConstant { + /// Name of the constant + std::string name; + + /// ID of the constant + uint16_t numeric_id; + + /// Type of the scalar + enum class Type { + kBool, + kFloat32, + kUint32, + kInt32, + }; + + /// Type of the scalar + Type type; + + /// Does this pipeline overridable constant have an initializer? + bool is_initialized = false; + + /// Does this pipeline overridable constant have a numeric ID specified + /// explicitly? + bool is_numeric_id_specified = false; +}; + +/// Reflection data for an entry point in the shader. +struct EntryPoint { + /// Constructors + EntryPoint(); + /// Copy Constructor + EntryPoint(EntryPoint&); + /// Move Constructor + EntryPoint(EntryPoint&&); + ~EntryPoint(); + + /// The entry point name + std::string name; + /// Remapped entry point name in the backend + std::string remapped_name; + /// The entry point stage + ast::PipelineStage stage = ast::PipelineStage::kNone; + /// The workgroup x size + uint32_t workgroup_size_x = 0; + /// The workgroup y size + uint32_t workgroup_size_y = 0; + /// The workgroup z size + uint32_t workgroup_size_z = 0; + /// List of the input variable accessed via this entry point. + std::vector<StageVariable> input_variables; + /// List of the output variable accessed via this entry point. + std::vector<StageVariable> output_variables; + /// List of the pipeline overridable constants accessed via this entry point. + std::vector<OverridableConstant> overridable_constants; + /// Does the entry point use the sample_mask builtin as an input builtin + /// variable. + bool input_sample_mask_used = false; + /// Does the entry point use the sample_mask builtin as an output builtin + /// variable. + bool output_sample_mask_used = false; + /// Does the entry point use the position builtin as an input builtin + /// variable. + bool input_position_used = false; + /// Does the entry point use the front_facing builtin + bool front_facing_used = false; + /// Does the entry point use the sample_index builtin + bool sample_index_used = false; + /// Does the entry point use the num_workgroups builtin + bool num_workgroups_used = false; + + /// @returns the size of the workgroup in {x,y,z} format + std::tuple<uint32_t, uint32_t, uint32_t> workgroup_size() { + return std::tuple<uint32_t, uint32_t, uint32_t>( + workgroup_size_x, workgroup_size_y, workgroup_size_z); + } +}; + +} // namespace inspector +} // namespace tint + +#endif // SRC_TINT_INSPECTOR_ENTRY_POINT_H_
diff --git a/src/tint/inspector/inspector.cc b/src/tint/inspector/inspector.cc new file mode 100644 index 0000000..d4f9b5e --- /dev/null +++ b/src/tint/inspector/inspector.cc
@@ -0,0 +1,956 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/inspector/inspector.h" + +#include <limits> +#include <utility> + +#include "src/tint/ast/bool_literal_expression.h" +#include "src/tint/ast/call_expression.h" +#include "src/tint/ast/float_literal_expression.h" +#include "src/tint/ast/id_attribute.h" +#include "src/tint/ast/interpolate_attribute.h" +#include "src/tint/ast/location_attribute.h" +#include "src/tint/ast/module.h" +#include "src/tint/ast/sint_literal_expression.h" +#include "src/tint/ast/uint_literal_expression.h" +#include "src/tint/sem/array.h" +#include "src/tint/sem/call.h" +#include "src/tint/sem/depth_multisampled_texture_type.h" +#include "src/tint/sem/f32_type.h" +#include "src/tint/sem/function.h" +#include "src/tint/sem/i32_type.h" +#include "src/tint/sem/matrix_type.h" +#include "src/tint/sem/multisampled_texture_type.h" +#include "src/tint/sem/sampled_texture_type.h" +#include "src/tint/sem/statement.h" +#include "src/tint/sem/storage_texture_type.h" +#include "src/tint/sem/struct.h" +#include "src/tint/sem/u32_type.h" +#include "src/tint/sem/variable.h" +#include "src/tint/sem/vector_type.h" +#include "src/tint/sem/void_type.h" +#include "src/tint/utils/math.h" +#include "src/tint/utils/unique_vector.h" + +namespace tint { +namespace inspector { + +namespace { + +void AppendResourceBindings(std::vector<ResourceBinding>* dest, + const std::vector<ResourceBinding>& orig) { + TINT_ASSERT(Inspector, dest); + if (!dest) { + return; + } + + dest->reserve(dest->size() + orig.size()); + dest->insert(dest->end(), orig.begin(), orig.end()); +} + +std::tuple<ComponentType, CompositionType> CalculateComponentAndComposition( + const sem::Type* type) { + if (type->is_float_scalar()) { + return {ComponentType::kFloat, CompositionType::kScalar}; + } else if (type->is_float_vector()) { + auto* vec = type->As<sem::Vector>(); + if (vec->Width() == 2) { + return {ComponentType::kFloat, CompositionType::kVec2}; + } else if (vec->Width() == 3) { + return {ComponentType::kFloat, CompositionType::kVec3}; + } else if (vec->Width() == 4) { + return {ComponentType::kFloat, CompositionType::kVec4}; + } + } else if (type->is_unsigned_integer_scalar()) { + return {ComponentType::kUInt, CompositionType::kScalar}; + } else if (type->is_unsigned_integer_vector()) { + auto* vec = type->As<sem::Vector>(); + if (vec->Width() == 2) { + return {ComponentType::kUInt, CompositionType::kVec2}; + } else if (vec->Width() == 3) { + return {ComponentType::kUInt, CompositionType::kVec3}; + } else if (vec->Width() == 4) { + return {ComponentType::kUInt, CompositionType::kVec4}; + } + } else if (type->is_signed_integer_scalar()) { + return {ComponentType::kSInt, CompositionType::kScalar}; + } else if (type->is_signed_integer_vector()) { + auto* vec = type->As<sem::Vector>(); + if (vec->Width() == 2) { + return {ComponentType::kSInt, CompositionType::kVec2}; + } else if (vec->Width() == 3) { + return {ComponentType::kSInt, CompositionType::kVec3}; + } else if (vec->Width() == 4) { + return {ComponentType::kSInt, CompositionType::kVec4}; + } + } + return {ComponentType::kUnknown, CompositionType::kUnknown}; +} + +std::tuple<InterpolationType, InterpolationSampling> CalculateInterpolationData( + const sem::Type* type, + const ast::AttributeList& attributes) { + auto* interpolation_attribute = + ast::GetAttribute<ast::InterpolateAttribute>(attributes); + if (type->is_integer_scalar_or_vector()) { + return {InterpolationType::kFlat, InterpolationSampling::kNone}; + } + + if (!interpolation_attribute) { + return {InterpolationType::kPerspective, InterpolationSampling::kCenter}; + } + + auto interpolation_type = interpolation_attribute->type; + auto sampling = interpolation_attribute->sampling; + if (interpolation_type != ast::InterpolationType::kFlat && + sampling == ast::InterpolationSampling::kNone) { + sampling = ast::InterpolationSampling::kCenter; + } + return {ASTToInspectorInterpolationType(interpolation_type), + ASTToInspectorInterpolationSampling(sampling)}; +} + +} // namespace + +Inspector::Inspector(const Program* program) : program_(program) {} + +Inspector::~Inspector() = default; + +std::vector<EntryPoint> Inspector::GetEntryPoints() { + std::vector<EntryPoint> result; + + for (auto* func : program_->AST().Functions()) { + if (!func->IsEntryPoint()) { + continue; + } + + auto* sem = program_->Sem().Get(func); + + EntryPoint entry_point; + entry_point.name = program_->Symbols().NameFor(func->symbol); + entry_point.remapped_name = program_->Symbols().NameFor(func->symbol); + entry_point.stage = func->PipelineStage(); + + auto wgsize = sem->WorkgroupSize(); + entry_point.workgroup_size_x = wgsize[0].value; + entry_point.workgroup_size_y = wgsize[1].value; + entry_point.workgroup_size_z = wgsize[2].value; + if (wgsize[0].overridable_const || wgsize[1].overridable_const || + wgsize[2].overridable_const) { + // TODO(crbug.com/tint/713): Handle overridable constants. + TINT_ASSERT(Inspector, false); + } + + for (auto* param : sem->Parameters()) { + AddEntryPointInOutVariables( + program_->Symbols().NameFor(param->Declaration()->symbol), + param->Type(), param->Declaration()->attributes, + entry_point.input_variables); + + entry_point.input_position_used |= + ContainsBuiltin(ast::Builtin::kPosition, param->Type(), + param->Declaration()->attributes); + entry_point.front_facing_used |= + ContainsBuiltin(ast::Builtin::kFrontFacing, param->Type(), + param->Declaration()->attributes); + entry_point.sample_index_used |= + ContainsBuiltin(ast::Builtin::kSampleIndex, param->Type(), + param->Declaration()->attributes); + entry_point.input_sample_mask_used |= + ContainsBuiltin(ast::Builtin::kSampleMask, param->Type(), + param->Declaration()->attributes); + entry_point.num_workgroups_used |= + ContainsBuiltin(ast::Builtin::kNumWorkgroups, param->Type(), + param->Declaration()->attributes); + } + + if (!sem->ReturnType()->Is<sem::Void>()) { + AddEntryPointInOutVariables("<retval>", sem->ReturnType(), + func->return_type_attributes, + entry_point.output_variables); + + entry_point.output_sample_mask_used = + ContainsBuiltin(ast::Builtin::kSampleMask, sem->ReturnType(), + func->return_type_attributes); + } + + for (auto* var : sem->TransitivelyReferencedGlobals()) { + auto* decl = var->Declaration(); + + auto name = program_->Symbols().NameFor(decl->symbol); + + auto* global = var->As<sem::GlobalVariable>(); + if (global && global->IsOverridable()) { + OverridableConstant overridable_constant; + overridable_constant.name = name; + overridable_constant.numeric_id = global->ConstantId(); + auto* type = var->Type(); + TINT_ASSERT(Inspector, type->is_scalar()); + if (type->is_bool_scalar_or_vector()) { + overridable_constant.type = OverridableConstant::Type::kBool; + } else if (type->is_float_scalar()) { + overridable_constant.type = OverridableConstant::Type::kFloat32; + } else if (type->is_signed_integer_scalar()) { + overridable_constant.type = OverridableConstant::Type::kInt32; + } else if (type->is_unsigned_integer_scalar()) { + overridable_constant.type = OverridableConstant::Type::kUint32; + } else { + TINT_UNREACHABLE(Inspector, diagnostics_); + } + + overridable_constant.is_initialized = + global->Declaration()->constructor; + overridable_constant.is_numeric_id_specified = + ast::HasAttribute<ast::IdAttribute>( + global->Declaration()->attributes); + + entry_point.overridable_constants.push_back(overridable_constant); + } + } + + result.push_back(std::move(entry_point)); + } + + return result; +} + +std::map<uint32_t, Scalar> Inspector::GetConstantIDs() { + std::map<uint32_t, Scalar> result; + for (auto* var : program_->AST().GlobalVariables()) { + auto* global = program_->Sem().Get<sem::GlobalVariable>(var); + if (!global || !global->IsOverridable()) { + continue; + } + + // If there are conflicting defintions for a constant id, that is invalid + // WGSL, so the resolver should catch it. Thus here the inspector just + // assumes all definitions of the constant id are the same, so only needs + // to find the first reference to constant id. + uint32_t constant_id = global->ConstantId(); + if (result.find(constant_id) != result.end()) { + continue; + } + + if (!var->constructor) { + result[constant_id] = Scalar(); + continue; + } + + auto* literal = var->constructor->As<ast::LiteralExpression>(); + if (!literal) { + // This is invalid WGSL, but handling gracefully. + result[constant_id] = Scalar(); + continue; + } + + if (auto* l = literal->As<ast::BoolLiteralExpression>()) { + result[constant_id] = Scalar(l->value); + continue; + } + + if (auto* l = literal->As<ast::UintLiteralExpression>()) { + result[constant_id] = Scalar(l->value); + continue; + } + + if (auto* l = literal->As<ast::SintLiteralExpression>()) { + result[constant_id] = Scalar(l->value); + continue; + } + + if (auto* l = literal->As<ast::FloatLiteralExpression>()) { + result[constant_id] = Scalar(l->value); + continue; + } + + result[constant_id] = Scalar(); + } + + return result; +} + +std::map<std::string, uint32_t> Inspector::GetConstantNameToIdMap() { + std::map<std::string, uint32_t> result; + for (auto* var : program_->AST().GlobalVariables()) { + auto* global = program_->Sem().Get<sem::GlobalVariable>(var); + if (global && global->IsOverridable()) { + auto name = program_->Symbols().NameFor(var->symbol); + result[name] = global->ConstantId(); + } + } + return result; +} + +uint32_t Inspector::GetStorageSize(const std::string& entry_point) { + auto* func = FindEntryPointByName(entry_point); + if (!func) { + return 0; + } + + size_t size = 0; + auto* func_sem = program_->Sem().Get(func); + for (auto& ruv : func_sem->TransitivelyReferencedUniformVariables()) { + size += ruv.first->Type()->UnwrapRef()->Size(); + } + for (auto& rsv : func_sem->TransitivelyReferencedStorageBufferVariables()) { + size += rsv.first->Type()->UnwrapRef()->Size(); + } + + if (static_cast<uint64_t>(size) > + static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) { + return std::numeric_limits<uint32_t>::max(); + } + return static_cast<uint32_t>(size); +} + +std::vector<ResourceBinding> Inspector::GetResourceBindings( + const std::string& entry_point) { + auto* func = FindEntryPointByName(entry_point); + if (!func) { + return {}; + } + + std::vector<ResourceBinding> result; + for (auto fn : { + &Inspector::GetUniformBufferResourceBindings, + &Inspector::GetStorageBufferResourceBindings, + &Inspector::GetReadOnlyStorageBufferResourceBindings, + &Inspector::GetSamplerResourceBindings, + &Inspector::GetComparisonSamplerResourceBindings, + &Inspector::GetSampledTextureResourceBindings, + &Inspector::GetMultisampledTextureResourceBindings, + &Inspector::GetWriteOnlyStorageTextureResourceBindings, + &Inspector::GetDepthTextureResourceBindings, + &Inspector::GetDepthMultisampledTextureResourceBindings, + &Inspector::GetExternalTextureResourceBindings, + }) { + AppendResourceBindings(&result, (this->*fn)(entry_point)); + } + return result; +} + +std::vector<ResourceBinding> Inspector::GetUniformBufferResourceBindings( + const std::string& entry_point) { + auto* func = FindEntryPointByName(entry_point); + if (!func) { + return {}; + } + + std::vector<ResourceBinding> result; + + auto* func_sem = program_->Sem().Get(func); + for (auto& ruv : func_sem->TransitivelyReferencedUniformVariables()) { + auto* var = ruv.first; + auto binding_info = ruv.second; + + auto* unwrapped_type = var->Type()->UnwrapRef(); + + ResourceBinding entry; + entry.resource_type = ResourceBinding::ResourceType::kUniformBuffer; + entry.bind_group = binding_info.group->value; + entry.binding = binding_info.binding->value; + entry.size = unwrapped_type->Size(); + entry.size_no_padding = entry.size; + if (auto* str = unwrapped_type->As<sem::Struct>()) { + entry.size_no_padding = str->SizeNoPadding(); + } else { + entry.size_no_padding = entry.size; + } + + result.push_back(entry); + } + + return result; +} + +std::vector<ResourceBinding> Inspector::GetStorageBufferResourceBindings( + const std::string& entry_point) { + return GetStorageBufferResourceBindingsImpl(entry_point, false); +} + +std::vector<ResourceBinding> +Inspector::GetReadOnlyStorageBufferResourceBindings( + const std::string& entry_point) { + return GetStorageBufferResourceBindingsImpl(entry_point, true); +} + +std::vector<ResourceBinding> Inspector::GetSamplerResourceBindings( + const std::string& entry_point) { + auto* func = FindEntryPointByName(entry_point); + if (!func) { + return {}; + } + + std::vector<ResourceBinding> result; + + auto* func_sem = program_->Sem().Get(func); + for (auto& rs : func_sem->TransitivelyReferencedSamplerVariables()) { + auto binding_info = rs.second; + + ResourceBinding entry; + entry.resource_type = ResourceBinding::ResourceType::kSampler; + entry.bind_group = binding_info.group->value; + entry.binding = binding_info.binding->value; + + result.push_back(entry); + } + + return result; +} + +std::vector<ResourceBinding> Inspector::GetComparisonSamplerResourceBindings( + const std::string& entry_point) { + auto* func = FindEntryPointByName(entry_point); + if (!func) { + return {}; + } + + std::vector<ResourceBinding> result; + + auto* func_sem = program_->Sem().Get(func); + for (auto& rcs : + func_sem->TransitivelyReferencedComparisonSamplerVariables()) { + auto binding_info = rcs.second; + + ResourceBinding entry; + entry.resource_type = ResourceBinding::ResourceType::kComparisonSampler; + entry.bind_group = binding_info.group->value; + entry.binding = binding_info.binding->value; + + result.push_back(entry); + } + + return result; +} + +std::vector<ResourceBinding> Inspector::GetSampledTextureResourceBindings( + const std::string& entry_point) { + return GetSampledTextureResourceBindingsImpl(entry_point, false); +} + +std::vector<ResourceBinding> Inspector::GetMultisampledTextureResourceBindings( + const std::string& entry_point) { + return GetSampledTextureResourceBindingsImpl(entry_point, true); +} + +std::vector<ResourceBinding> +Inspector::GetWriteOnlyStorageTextureResourceBindings( + const std::string& entry_point) { + return GetStorageTextureResourceBindingsImpl(entry_point); +} + +std::vector<ResourceBinding> Inspector::GetTextureResourceBindings( + const std::string& entry_point, + const tint::TypeInfo* texture_type, + ResourceBinding::ResourceType resource_type) { + auto* func = FindEntryPointByName(entry_point); + if (!func) { + return {}; + } + + std::vector<ResourceBinding> result; + auto* func_sem = program_->Sem().Get(func); + for (auto& ref : + func_sem->TransitivelyReferencedVariablesOfType(texture_type)) { + auto* var = ref.first; + auto binding_info = ref.second; + + ResourceBinding entry; + entry.resource_type = resource_type; + entry.bind_group = binding_info.group->value; + entry.binding = binding_info.binding->value; + + auto* tex = var->Type()->UnwrapRef()->As<sem::Texture>(); + entry.dim = + TypeTextureDimensionToResourceBindingTextureDimension(tex->dim()); + + result.push_back(entry); + } + + return result; +} + +std::vector<ResourceBinding> Inspector::GetDepthTextureResourceBindings( + const std::string& entry_point) { + return GetTextureResourceBindings( + entry_point, &TypeInfo::Of<sem::DepthTexture>(), + ResourceBinding::ResourceType::kDepthTexture); +} + +std::vector<ResourceBinding> +Inspector::GetDepthMultisampledTextureResourceBindings( + const std::string& entry_point) { + return GetTextureResourceBindings( + entry_point, &TypeInfo::Of<sem::DepthMultisampledTexture>(), + ResourceBinding::ResourceType::kDepthMultisampledTexture); +} + +std::vector<ResourceBinding> Inspector::GetExternalTextureResourceBindings( + const std::string& entry_point) { + return GetTextureResourceBindings( + entry_point, &TypeInfo::Of<sem::ExternalTexture>(), + ResourceBinding::ResourceType::kExternalTexture); +} + +std::vector<sem::SamplerTexturePair> Inspector::GetSamplerTextureUses( + const std::string& entry_point) { + auto* func = FindEntryPointByName(entry_point); + if (!func) { + return {}; + } + + GenerateSamplerTargets(); + + auto it = sampler_targets_->find(entry_point); + if (it == sampler_targets_->end()) { + return {}; + } + return it->second; +} + +std::vector<sem::SamplerTexturePair> Inspector::GetSamplerTextureUses( + const std::string& entry_point, + const sem::BindingPoint& placeholder) { + auto* func = FindEntryPointByName(entry_point); + if (!func) { + return {}; + } + auto* func_sem = program_->Sem().Get(func); + + std::vector<sem::SamplerTexturePair> new_pairs; + for (auto pair : func_sem->TextureSamplerPairs()) { + auto* texture = pair.first->As<sem::GlobalVariable>(); + auto* sampler = + pair.second ? pair.second->As<sem::GlobalVariable>() : nullptr; + SamplerTexturePair new_pair; + new_pair.sampler_binding_point = + sampler ? sampler->BindingPoint() : placeholder; + new_pair.texture_binding_point = texture->BindingPoint(); + new_pairs.push_back(new_pair); + } + return new_pairs; +} + +uint32_t Inspector::GetWorkgroupStorageSize(const std::string& entry_point) { + auto* func = FindEntryPointByName(entry_point); + if (!func) { + return 0; + } + + uint32_t total_size = 0; + auto* func_sem = program_->Sem().Get(func); + for (const sem::Variable* var : func_sem->TransitivelyReferencedGlobals()) { + if (var->StorageClass() == ast::StorageClass::kWorkgroup) { + auto* ty = var->Type()->UnwrapRef(); + uint32_t align = ty->Align(); + uint32_t size = ty->Size(); + + // This essentially matches std430 layout rules from GLSL, which are in + // turn specified as an upper bound for Vulkan layout sizing. Since D3D + // and Metal are even less specific, we assume Vulkan behavior as a + // good-enough approximation everywhere. + total_size += utils::RoundUp(align, size); + } + } + + return total_size; +} + +const ast::Function* Inspector::FindEntryPointByName(const std::string& name) { + auto* func = program_->AST().Functions().Find(program_->Symbols().Get(name)); + if (!func) { + diagnostics_.add_error(diag::System::Inspector, name + " was not found!"); + return nullptr; + } + + if (!func->IsEntryPoint()) { + diagnostics_.add_error(diag::System::Inspector, + name + " is not an entry point!"); + return nullptr; + } + + return func; +} + +void Inspector::AddEntryPointInOutVariables( + std::string name, + const sem::Type* type, + const ast::AttributeList& attributes, + std::vector<StageVariable>& variables) const { + // Skip builtins. + if (ast::HasAttribute<ast::BuiltinAttribute>(attributes)) { + return; + } + + auto* unwrapped_type = type->UnwrapRef(); + + if (auto* struct_ty = unwrapped_type->As<sem::Struct>()) { + // Recurse into members. + for (auto* member : struct_ty->Members()) { + AddEntryPointInOutVariables( + name + "." + + program_->Symbols().NameFor(member->Declaration()->symbol), + member->Type(), member->Declaration()->attributes, variables); + } + return; + } + + // Base case: add the variable. + + StageVariable stage_variable; + stage_variable.name = name; + std::tie(stage_variable.component_type, stage_variable.composition_type) = + CalculateComponentAndComposition(type); + + auto* location = ast::GetAttribute<ast::LocationAttribute>(attributes); + TINT_ASSERT(Inspector, location != nullptr); + stage_variable.has_location_attribute = true; + stage_variable.location_attribute = location->value; + + std::tie(stage_variable.interpolation_type, + stage_variable.interpolation_sampling) = + CalculateInterpolationData(type, attributes); + + variables.push_back(stage_variable); +} + +bool Inspector::ContainsBuiltin(ast::Builtin builtin, + const sem::Type* type, + const ast::AttributeList& attributes) const { + auto* unwrapped_type = type->UnwrapRef(); + + if (auto* struct_ty = unwrapped_type->As<sem::Struct>()) { + // Recurse into members. + for (auto* member : struct_ty->Members()) { + if (ContainsBuiltin(builtin, member->Type(), + member->Declaration()->attributes)) { + return true; + } + } + return false; + } + + // Base case: check for builtin + auto* builtin_declaration = + ast::GetAttribute<ast::BuiltinAttribute>(attributes); + if (!builtin_declaration || builtin_declaration->builtin != builtin) { + return false; + } + + return true; +} + +std::vector<ResourceBinding> Inspector::GetStorageBufferResourceBindingsImpl( + const std::string& entry_point, + bool read_only) { + auto* func = FindEntryPointByName(entry_point); + if (!func) { + return {}; + } + + auto* func_sem = program_->Sem().Get(func); + std::vector<ResourceBinding> result; + for (auto& rsv : func_sem->TransitivelyReferencedStorageBufferVariables()) { + auto* var = rsv.first; + auto binding_info = rsv.second; + + if (read_only != (var->Access() == ast::Access::kRead)) { + continue; + } + + auto* unwrapped_type = var->Type()->UnwrapRef(); + + ResourceBinding entry; + entry.resource_type = + read_only ? ResourceBinding::ResourceType::kReadOnlyStorageBuffer + : ResourceBinding::ResourceType::kStorageBuffer; + entry.bind_group = binding_info.group->value; + entry.binding = binding_info.binding->value; + entry.size = unwrapped_type->Size(); + if (auto* str = unwrapped_type->As<sem::Struct>()) { + entry.size_no_padding = str->SizeNoPadding(); + } else { + entry.size_no_padding = entry.size; + } + + result.push_back(entry); + } + + return result; +} + +std::vector<ResourceBinding> Inspector::GetSampledTextureResourceBindingsImpl( + const std::string& entry_point, + bool multisampled_only) { + auto* func = FindEntryPointByName(entry_point); + if (!func) { + return {}; + } + + std::vector<ResourceBinding> result; + auto* func_sem = program_->Sem().Get(func); + auto referenced_variables = + multisampled_only + ? func_sem->TransitivelyReferencedMultisampledTextureVariables() + : func_sem->TransitivelyReferencedSampledTextureVariables(); + for (auto& ref : referenced_variables) { + auto* var = ref.first; + auto binding_info = ref.second; + + ResourceBinding entry; + entry.resource_type = + multisampled_only ? ResourceBinding::ResourceType::kMultisampledTexture + : ResourceBinding::ResourceType::kSampledTexture; + entry.bind_group = binding_info.group->value; + entry.binding = binding_info.binding->value; + + auto* texture_type = var->Type()->UnwrapRef()->As<sem::Texture>(); + entry.dim = TypeTextureDimensionToResourceBindingTextureDimension( + texture_type->dim()); + + const sem::Type* base_type = nullptr; + if (multisampled_only) { + base_type = texture_type->As<sem::MultisampledTexture>()->type(); + } else { + base_type = texture_type->As<sem::SampledTexture>()->type(); + } + entry.sampled_kind = BaseTypeToSampledKind(base_type); + + result.push_back(entry); + } + + return result; +} + +std::vector<ResourceBinding> Inspector::GetStorageTextureResourceBindingsImpl( + const std::string& entry_point) { + auto* func = FindEntryPointByName(entry_point); + if (!func) { + return {}; + } + + auto* func_sem = program_->Sem().Get(func); + std::vector<ResourceBinding> result; + for (auto& ref : + func_sem->TransitivelyReferencedVariablesOfType<sem::StorageTexture>()) { + auto* var = ref.first; + auto binding_info = ref.second; + + auto* texture_type = var->Type()->UnwrapRef()->As<sem::StorageTexture>(); + + ResourceBinding entry; + entry.resource_type = + ResourceBinding::ResourceType::kWriteOnlyStorageTexture; + entry.bind_group = binding_info.group->value; + entry.binding = binding_info.binding->value; + + entry.dim = TypeTextureDimensionToResourceBindingTextureDimension( + texture_type->dim()); + + auto* base_type = texture_type->type(); + entry.sampled_kind = BaseTypeToSampledKind(base_type); + entry.image_format = TypeTexelFormatToResourceBindingTexelFormat( + texture_type->texel_format()); + + result.push_back(entry); + } + + return result; +} + +void Inspector::GenerateSamplerTargets() { + // Do not re-generate, since |program_| should not change during the lifetime + // of the inspector. + if (sampler_targets_ != nullptr) { + return; + } + + sampler_targets_ = std::make_unique<std::unordered_map< + std::string, utils::UniqueVector<sem::SamplerTexturePair>>>(); + + auto& sem = program_->Sem(); + + for (auto* node : program_->ASTNodes().Objects()) { + auto* c = node->As<ast::CallExpression>(); + if (!c) { + continue; + } + + auto* call = sem.Get(c); + if (!call) { + continue; + } + + auto* i = call->Target()->As<sem::Builtin>(); + if (!i) { + continue; + } + + const auto& signature = i->Signature(); + int sampler_index = signature.IndexOf(sem::ParameterUsage::kSampler); + if (sampler_index == -1) { + continue; + } + + int texture_index = signature.IndexOf(sem::ParameterUsage::kTexture); + if (texture_index == -1) { + continue; + } + + auto* call_func = call->Stmt()->Function(); + std::vector<const sem::Function*> entry_points; + if (call_func->Declaration()->IsEntryPoint()) { + entry_points = {call_func}; + } else { + entry_points = call_func->AncestorEntryPoints(); + } + + if (entry_points.empty()) { + continue; + } + + auto* t = c->args[texture_index]; + auto* s = c->args[sampler_index]; + + GetOriginatingResources( + std::array<const ast::Expression*, 2>{t, s}, + [&](std::array<const sem::GlobalVariable*, 2> globals) { + auto* texture = globals[0]; + sem::BindingPoint texture_binding_point = { + texture->Declaration()->BindingPoint().group->value, + texture->Declaration()->BindingPoint().binding->value}; + + auto* sampler = globals[1]; + sem::BindingPoint sampler_binding_point = { + sampler->Declaration()->BindingPoint().group->value, + sampler->Declaration()->BindingPoint().binding->value}; + + for (auto* entry_point : entry_points) { + const auto& ep_name = + program_->Symbols().NameFor(entry_point->Declaration()->symbol); + (*sampler_targets_)[ep_name].add( + {sampler_binding_point, texture_binding_point}); + } + }); + } +} + +template <size_t N, typename F> +void Inspector::GetOriginatingResources( + std::array<const ast::Expression*, N> exprs, + F&& callback) { + if (!program_->IsValid()) { + TINT_ICE(Inspector, diagnostics_) + << "attempting to get originating resources in invalid program"; + return; + } + + auto& sem = program_->Sem(); + + std::array<const sem::GlobalVariable*, N> globals{}; + std::array<const sem::Parameter*, N> parameters{}; + utils::UniqueVector<const ast::CallExpression*> callsites; + + for (size_t i = 0; i < N; i++) { + auto*& expr = exprs[i]; + // Resolve each of the expressions + while (true) { + if (auto* user = sem.Get<sem::VariableUser>(expr)) { + auto* var = user->Variable(); + + if (auto* global = tint::As<sem::GlobalVariable>(var)) { + // Found the global resource declaration. + globals[i] = global; + break; // Done with this expression. + } + + if (auto* local = tint::As<sem::LocalVariable>(var)) { + // Chase the variable + expr = local->Declaration()->constructor; + if (!expr) { + TINT_ICE(Inspector, diagnostics_) + << "resource variable had no initializer"; + return; + } + continue; // Continue chasing the expression in this function + } + + if (auto* param = tint::As<sem::Parameter>(var)) { + // Gather each of the callers of this function + auto* func = tint::As<sem::Function>(param->Owner()); + if (func->CallSites().empty()) { + // One or more of the expressions is a parameter, but this function + // is not called. Ignore. + return; + } + for (auto* call : func->CallSites()) { + callsites.add(call->Declaration()); + } + // Need to evaluate each function call with the group of + // expressions, so move on to the next expression. + parameters[i] = param; + break; + } + + TINT_ICE(Inspector, diagnostics_) + << "unexpected variable type " << var->TypeInfo().name; + } + + if (auto* unary = tint::As<ast::UnaryOpExpression>(expr)) { + switch (unary->op) { + case ast::UnaryOp::kAddressOf: + case ast::UnaryOp::kIndirection: + // `*` and `&` are the only valid unary ops for a resource type, + // and must be balanced in order for the program to have passed + // validation. Just skip past these. + expr = unary->expr; + continue; + default: { + TINT_ICE(Inspector, diagnostics_) + << "unexpected unary op on resource: " << unary->op; + return; + } + } + } + + TINT_ICE(Inspector, diagnostics_) + << "cannot resolve originating resource with expression type " + << expr->TypeInfo().name; + return; + } + } + + if (callsites.size()) { + for (auto* call_expr : callsites) { + // Make a copy of the expressions for this callsite + std::array<const ast::Expression*, N> call_exprs = exprs; + // Patch all the parameter expressions with their argument + for (size_t i = 0; i < N; i++) { + if (auto* param = parameters[i]) { + call_exprs[i] = call_expr->args[param->Index()]; + } + } + // Now call GetOriginatingResources() with from the callsite + GetOriginatingResources(call_exprs, callback); + } + } else { + // All the expressions resolved to globals + callback(globals); + } +} + +} // namespace inspector +} // namespace tint
diff --git a/src/tint/inspector/inspector.h b/src/tint/inspector/inspector.h new file mode 100644 index 0000000..df0ccab --- /dev/null +++ b/src/tint/inspector/inspector.h
@@ -0,0 +1,234 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_INSPECTOR_INSPECTOR_H_ +#define SRC_TINT_INSPECTOR_INSPECTOR_H_ + +#include <map> +#include <memory> +#include <string> +#include <tuple> +#include <unordered_map> +#include <vector> + +#include "src/tint/inspector/entry_point.h" +#include "src/tint/inspector/resource_binding.h" +#include "src/tint/inspector/scalar.h" +#include "src/tint/program.h" +#include "src/tint/sem/sampler_texture_pair.h" +#include "src/tint/utils/unique_vector.h" + +namespace tint { +namespace inspector { + +/// A temporary alias to sem::SamplerTexturePair. [DEPRECATED] +using SamplerTexturePair = sem::SamplerTexturePair; + +/// Extracts information from a program +class Inspector { + public: + /// Constructor + /// @param program Shader program to extract information from. + explicit Inspector(const Program* program); + + /// Destructor + ~Inspector(); + + /// @returns error messages from the Inspector + std::string error() { return diagnostics_.str(); } + /// @returns true if an error was encountered + bool has_error() const { return diagnostics_.contains_errors(); } + + /// @returns vector of entry point information + std::vector<EntryPoint> GetEntryPoints(); + + /// @returns map of const_id to initial value + std::map<uint32_t, Scalar> GetConstantIDs(); + + /// @returns map of module-constant name to pipeline constant ID + std::map<std::string, uint32_t> GetConstantNameToIdMap(); + + /// @param entry_point name of the entry point to get information about. + /// @returns the total size of shared storage required by an entry point, + /// including all uniform storage buffers. + uint32_t GetStorageSize(const std::string& entry_point); + + /// @param entry_point name of the entry point to get information about. + /// @returns vector of all of the resource bindings. + std::vector<ResourceBinding> GetResourceBindings( + const std::string& entry_point); + + /// @param entry_point name of the entry point to get information about. + /// @returns vector of all of the bindings for uniform buffers. + std::vector<ResourceBinding> GetUniformBufferResourceBindings( + const std::string& entry_point); + + /// @param entry_point name of the entry point to get information about. + /// @returns vector of all of the bindings for storage buffers. + std::vector<ResourceBinding> GetStorageBufferResourceBindings( + const std::string& entry_point); + + /// @param entry_point name of the entry point to get information about. + /// @returns vector of all of the bindings for read-only storage buffers. + std::vector<ResourceBinding> GetReadOnlyStorageBufferResourceBindings( + const std::string& entry_point); + + /// @param entry_point name of the entry point to get information about. + /// @returns vector of all of the bindings for regular samplers. + std::vector<ResourceBinding> GetSamplerResourceBindings( + const std::string& entry_point); + + /// @param entry_point name of the entry point to get information about. + /// @returns vector of all of the bindings for comparison samplers. + std::vector<ResourceBinding> GetComparisonSamplerResourceBindings( + const std::string& entry_point); + + /// @param entry_point name of the entry point to get information about. + /// @returns vector of all of the bindings for sampled textures. + std::vector<ResourceBinding> GetSampledTextureResourceBindings( + const std::string& entry_point); + + /// @param entry_point name of the entry point to get information about. + /// @returns vector of all of the bindings for multisampled textures. + std::vector<ResourceBinding> GetMultisampledTextureResourceBindings( + const std::string& entry_point); + + /// @param entry_point name of the entry point to get information about. + /// @returns vector of all of the bindings for write-only storage textures. + std::vector<ResourceBinding> GetWriteOnlyStorageTextureResourceBindings( + const std::string& entry_point); + + /// @param entry_point name of the entry point to get information about. + /// @returns vector of all of the bindings for depth textures. + std::vector<ResourceBinding> GetDepthTextureResourceBindings( + const std::string& entry_point); + + /// @param entry_point name of the entry point to get information about. + /// @returns vector of all of the bindings for depth textures. + std::vector<ResourceBinding> GetDepthMultisampledTextureResourceBindings( + const std::string& entry_point); + + /// @param entry_point name of the entry point to get information about. + /// @returns vector of all of the bindings for external textures. + std::vector<ResourceBinding> GetExternalTextureResourceBindings( + const std::string& entry_point); + + /// @param entry_point name of the entry point to get information about. + /// @returns vector of all of the sampler/texture sampling pairs that are used + /// by that entry point. + std::vector<sem::SamplerTexturePair> GetSamplerTextureUses( + const std::string& entry_point); + + /// @param entry_point name of the entry point to get information about. + /// @param placeholder the sampler binding point to use for texture-only + /// access (e.g., textureLoad) + /// @returns vector of all of the sampler/texture sampling pairs that are used + /// by that entry point. + std::vector<sem::SamplerTexturePair> GetSamplerTextureUses( + const std::string& entry_point, + const sem::BindingPoint& placeholder); + + /// @param entry_point name of the entry point to get information about. + /// @returns the total size in bytes of all Workgroup storage-class storage + /// referenced transitively by the entry point. + uint32_t GetWorkgroupStorageSize(const std::string& entry_point); + + private: + const Program* program_; + diag::List diagnostics_; + std::unique_ptr< + std::unordered_map<std::string, + utils::UniqueVector<sem::SamplerTexturePair>>> + sampler_targets_; + + /// @param name name of the entry point to find + /// @returns a pointer to the entry point if it exists, otherwise returns + /// nullptr and sets the error string. + const ast::Function* FindEntryPointByName(const std::string& name); + + /// Recursively add entry point IO variables. + /// If `type` is a struct, recurse into members, appending the member name. + /// Otherwise, add the variable unless it is a builtin. + /// @param name the name of the variable being added + /// @param type the type of the variable + /// @param attributes the variable attributes + /// @param variables the list to add the variables to + void AddEntryPointInOutVariables(std::string name, + const sem::Type* type, + const ast::AttributeList& attributes, + std::vector<StageVariable>& variables) const; + + /// Recursively determine if the type contains builtin. + /// If `type` is a struct, recurse into members to check for the attribute. + /// Otherwise, check `attributes` for the attribute. + bool ContainsBuiltin(ast::Builtin builtin, + const sem::Type* type, + const ast::AttributeList& attributes) const; + + /// Gathers all the texture resource bindings of the given type for the given + /// entry point. + /// @param entry_point name of the entry point to get information about. + /// @param texture_type the type of the textures to gather. + /// @param resource_type the ResourceBinding::ResourceType for the given + /// texture type. + /// @returns vector of all of the bindings for depth textures. + std::vector<ResourceBinding> GetTextureResourceBindings( + const std::string& entry_point, + const tint::TypeInfo* texture_type, + ResourceBinding::ResourceType resource_type); + + /// @param entry_point name of the entry point to get information about. + /// @param read_only if true get only read-only bindings, if false get + /// write-only bindings. + /// @returns vector of all of the bindings for the requested storage buffers. + std::vector<ResourceBinding> GetStorageBufferResourceBindingsImpl( + const std::string& entry_point, + bool read_only); + + /// @param entry_point name of the entry point to get information about. + /// @param multisampled_only only get multisampled textures if true, otherwise + /// only get sampled textures. + /// @returns vector of all of the bindings for the request storage buffers. + std::vector<ResourceBinding> GetSampledTextureResourceBindingsImpl( + const std::string& entry_point, + bool multisampled_only); + + /// @param entry_point name of the entry point to get information about. + /// @returns vector of all of the bindings for the requested storage textures. + std::vector<ResourceBinding> GetStorageTextureResourceBindingsImpl( + const std::string& entry_point); + + /// Constructs |sampler_targets_| if it hasn't already been instantiated. + void GenerateSamplerTargets(); + + /// For a N-uple of expressions, resolve to the appropriate global resources + /// and call 'cb'. + /// 'cb' may be called multiple times. + /// Assumes that not being able to resolve the resources is an error, so will + /// invoke TINT_ICE when that occurs. + /// @tparam N number of expressions in the n-uple + /// @tparam F type of the callback provided. + /// @param exprs N-uple of expressions to resolve. + /// @param cb is a callback function with the signature: + /// `void(std::array<const sem::GlobalVariable*, N>)`, which is invoked + /// whenever a set of expressions are resolved to globals. + template <size_t N, typename F> + void GetOriginatingResources(std::array<const ast::Expression*, N> exprs, + F&& cb); +}; + +} // namespace inspector +} // namespace tint + +#endif // SRC_TINT_INSPECTOR_INSPECTOR_H_
diff --git a/src/tint/inspector/inspector_test.cc b/src/tint/inspector/inspector_test.cc new file mode 100644 index 0000000..dc8ba8f --- /dev/null +++ b/src/tint/inspector/inspector_test.cc
@@ -0,0 +1,3031 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "gtest/gtest.h" +#include "src/tint/ast/call_statement.h" +#include "src/tint/ast/disable_validation_attribute.h" +#include "src/tint/ast/id_attribute.h" +#include "src/tint/ast/stage_attribute.h" +#include "src/tint/ast/workgroup_attribute.h" +#include "src/tint/inspector/test_inspector_builder.h" +#include "src/tint/inspector/test_inspector_runner.h" +#include "src/tint/program_builder.h" +#include "src/tint/sem/depth_texture_type.h" +#include "src/tint/sem/external_texture_type.h" +#include "src/tint/sem/multisampled_texture_type.h" +#include "src/tint/sem/sampled_texture_type.h" +#include "src/tint/sem/variable.h" +#include "tint/tint.h" + +namespace tint { +namespace inspector { +namespace { + +// All the tests that descend from InspectorBuilder are expected to define their +// test state via building up the AST through InspectorBuilder and then generate +// the program with ::Build. +// The returned Inspector from ::Build can then be used to test expecations. +// +// All the tests that descend from InspectorRunner are expected to define their +// test state via a WGSL shader, which will be parsed to generate a Program and +// Inspector in ::Initialize. +// The returned Inspector from ::Initialize can then be used to test +// expecations. + +class InspectorGetEntryPointTest : public InspectorBuilder, + public testing::Test {}; + +typedef std::tuple<inspector::ComponentType, inspector::CompositionType> + InspectorGetEntryPointComponentAndCompositionTestParams; +class InspectorGetEntryPointComponentAndCompositionTest + : public InspectorBuilder, + public testing::TestWithParam< + InspectorGetEntryPointComponentAndCompositionTestParams> {}; +struct InspectorGetEntryPointInterpolateTestParams { + ast::InterpolationType in_type; + ast::InterpolationSampling in_sampling; + inspector::InterpolationType out_type; + inspector::InterpolationSampling out_sampling; +}; +class InspectorGetEntryPointInterpolateTest + : public InspectorBuilder, + public testing::TestWithParam< + InspectorGetEntryPointInterpolateTestParams> {}; +class InspectorGetConstantIDsTest : public InspectorBuilder, + public testing::Test {}; +class InspectorGetConstantNameToIdMapTest : public InspectorBuilder, + public testing::Test {}; +class InspectorGetStorageSizeTest : public InspectorBuilder, + public testing::Test {}; +class InspectorGetResourceBindingsTest : public InspectorBuilder, + public testing::Test {}; +class InspectorGetUniformBufferResourceBindingsTest : public InspectorBuilder, + public testing::Test {}; +class InspectorGetStorageBufferResourceBindingsTest : public InspectorBuilder, + public testing::Test {}; +class InspectorGetReadOnlyStorageBufferResourceBindingsTest + : public InspectorBuilder, + public testing::Test {}; +class InspectorGetSamplerResourceBindingsTest : public InspectorBuilder, + public testing::Test {}; +class InspectorGetComparisonSamplerResourceBindingsTest + : public InspectorBuilder, + public testing::Test {}; +class InspectorGetSampledTextureResourceBindingsTest : public InspectorBuilder, + public testing::Test {}; +class InspectorGetSampledArrayTextureResourceBindingsTest + : public InspectorBuilder, + public testing::Test {}; +struct GetSampledTextureTestParams { + ast::TextureDimension type_dim; + inspector::ResourceBinding::TextureDimension inspector_dim; + inspector::ResourceBinding::SampledKind sampled_kind; +}; +class InspectorGetSampledTextureResourceBindingsTestWithParam + : public InspectorBuilder, + public testing::TestWithParam<GetSampledTextureTestParams> {}; +class InspectorGetSampledArrayTextureResourceBindingsTestWithParam + : public InspectorBuilder, + public testing::TestWithParam<GetSampledTextureTestParams> {}; +class InspectorGetMultisampledTextureResourceBindingsTest + : public InspectorBuilder, + public testing::Test {}; +class InspectorGetMultisampledArrayTextureResourceBindingsTest + : public InspectorBuilder, + public testing::Test {}; +typedef GetSampledTextureTestParams GetMultisampledTextureTestParams; +class InspectorGetMultisampledArrayTextureResourceBindingsTestWithParam + : public InspectorBuilder, + public testing::TestWithParam<GetMultisampledTextureTestParams> {}; +class InspectorGetMultisampledTextureResourceBindingsTestWithParam + : public InspectorBuilder, + public testing::TestWithParam<GetMultisampledTextureTestParams> {}; +class InspectorGetStorageTextureResourceBindingsTest : public InspectorBuilder, + public testing::Test {}; +struct GetDepthTextureTestParams { + ast::TextureDimension type_dim; + inspector::ResourceBinding::TextureDimension inspector_dim; +}; +class InspectorGetDepthTextureResourceBindingsTestWithParam + : public InspectorBuilder, + public testing::TestWithParam<GetDepthTextureTestParams> {}; + +class InspectorGetDepthMultisampledTextureResourceBindingsTest + : public InspectorBuilder, + public testing::Test {}; + +typedef std::tuple<ast::TextureDimension, ResourceBinding::TextureDimension> + DimensionParams; +typedef std::tuple<ast::TexelFormat, + ResourceBinding::TexelFormat, + ResourceBinding::SampledKind> + TexelFormatParams; +typedef std::tuple<DimensionParams, TexelFormatParams> + GetStorageTextureTestParams; +class InspectorGetStorageTextureResourceBindingsTestWithParam + : public InspectorBuilder, + public testing::TestWithParam<GetStorageTextureTestParams> {}; + +class InspectorGetExternalTextureResourceBindingsTest : public InspectorBuilder, + public testing::Test {}; + +class InspectorGetSamplerTextureUsesTest : public InspectorRunner, + public testing::Test {}; + +class InspectorGetWorkgroupStorageSizeTest : public InspectorBuilder, + public testing::Test {}; + +// This is a catch all for shaders that have demonstrated regressions/crashes in +// the wild. +class InspectorRegressionTest : public InspectorRunner, public testing::Test {}; + +TEST_F(InspectorGetEntryPointTest, NoFunctions) { + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + EXPECT_EQ(0u, result.size()); +} + +TEST_F(InspectorGetEntryPointTest, NoEntryPoints) { + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + EXPECT_EQ(0u, result.size()); +} + +TEST_F(InspectorGetEntryPointTest, OneEntryPoint) { + MakeEmptyBodyFunction("foo", ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + // TODO(dsinclair): Update to run the namer transform when available. + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + EXPECT_EQ("foo", result[0].name); + EXPECT_EQ("foo", result[0].remapped_name); + EXPECT_EQ(ast::PipelineStage::kFragment, result[0].stage); +} + +TEST_F(InspectorGetEntryPointTest, MultipleEntryPoints) { + MakeEmptyBodyFunction("foo", ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + MakeEmptyBodyFunction("bar", + ast::AttributeList{Stage(ast::PipelineStage::kCompute), + WorkgroupSize(1)}); + + // TODO(dsinclair): Update to run the namer transform when available. + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(2u, result.size()); + EXPECT_EQ("foo", result[0].name); + EXPECT_EQ("foo", result[0].remapped_name); + EXPECT_EQ(ast::PipelineStage::kFragment, result[0].stage); + EXPECT_EQ("bar", result[1].name); + EXPECT_EQ("bar", result[1].remapped_name); + EXPECT_EQ(ast::PipelineStage::kCompute, result[1].stage); +} + +TEST_F(InspectorGetEntryPointTest, MixFunctionsAndEntryPoints) { + MakeEmptyBodyFunction("func", {}); + + MakeCallerBodyFunction("foo", {"func"}, + ast::AttributeList{Stage(ast::PipelineStage::kCompute), + WorkgroupSize(1)}); + + MakeCallerBodyFunction("bar", {"func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + // TODO(dsinclair): Update to run the namer transform when available. + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + EXPECT_FALSE(inspector.has_error()); + + ASSERT_EQ(2u, result.size()); + EXPECT_EQ("foo", result[0].name); + EXPECT_EQ("foo", result[0].remapped_name); + EXPECT_EQ(ast::PipelineStage::kCompute, result[0].stage); + EXPECT_EQ("bar", result[1].name); + EXPECT_EQ("bar", result[1].remapped_name); + EXPECT_EQ(ast::PipelineStage::kFragment, result[1].stage); +} + +TEST_F(InspectorGetEntryPointTest, DefaultWorkgroupSize) { + MakeEmptyBodyFunction("foo", + ast::AttributeList{Stage(ast::PipelineStage::kCompute), + WorkgroupSize(8, 2, 1)}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + uint32_t x, y, z; + std::tie(x, y, z) = result[0].workgroup_size(); + EXPECT_EQ(8u, x); + EXPECT_EQ(2u, y); + EXPECT_EQ(1u, z); +} + +TEST_F(InspectorGetEntryPointTest, NonDefaultWorkgroupSize) { + MakeEmptyBodyFunction( + "foo", {Stage(ast::PipelineStage::kCompute), WorkgroupSize(8, 2, 1)}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + uint32_t x, y, z; + std::tie(x, y, z) = result[0].workgroup_size(); + EXPECT_EQ(8u, x); + EXPECT_EQ(2u, y); + EXPECT_EQ(1u, z); +} + +TEST_F(InspectorGetEntryPointTest, NoInOutVariables) { + MakeEmptyBodyFunction("func", {}); + + MakeCallerBodyFunction("foo", {"func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + EXPECT_EQ(0u, result[0].input_variables.size()); + EXPECT_EQ(0u, result[0].output_variables.size()); +} + +TEST_P(InspectorGetEntryPointComponentAndCompositionTest, Test) { + ComponentType component; + CompositionType composition; + std::tie(component, composition) = GetParam(); + std::function<const ast::Type*()> tint_type = + GetTypeFunction(component, composition); + + auto* in_var = Param("in_var", tint_type(), {Location(0u), Flat()}); + Func("foo", {in_var}, tint_type(), {Return("in_var")}, + {Stage(ast::PipelineStage::kFragment)}, {Location(0u)}); + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + + ASSERT_EQ(1u, result[0].input_variables.size()); + EXPECT_EQ("in_var", result[0].input_variables[0].name); + EXPECT_TRUE(result[0].input_variables[0].has_location_attribute); + EXPECT_EQ(0u, result[0].input_variables[0].location_attribute); + EXPECT_EQ(component, result[0].input_variables[0].component_type); + + ASSERT_EQ(1u, result[0].output_variables.size()); + EXPECT_EQ("<retval>", result[0].output_variables[0].name); + EXPECT_TRUE(result[0].output_variables[0].has_location_attribute); + EXPECT_EQ(0u, result[0].output_variables[0].location_attribute); + EXPECT_EQ(component, result[0].output_variables[0].component_type); +} +INSTANTIATE_TEST_SUITE_P( + InspectorGetEntryPointTest, + InspectorGetEntryPointComponentAndCompositionTest, + testing::Combine(testing::Values(ComponentType::kFloat, + ComponentType::kSInt, + ComponentType::kUInt), + testing::Values(CompositionType::kScalar, + CompositionType::kVec2, + CompositionType::kVec3, + CompositionType::kVec4))); + +TEST_F(InspectorGetEntryPointTest, MultipleInOutVariables) { + auto* in_var0 = Param("in_var0", ty.u32(), {Location(0u), Flat()}); + auto* in_var1 = Param("in_var1", ty.u32(), {Location(1u), Flat()}); + auto* in_var4 = Param("in_var4", ty.u32(), {Location(4u), Flat()}); + Func("foo", {in_var0, in_var1, in_var4}, ty.u32(), {Return("in_var0")}, + {Stage(ast::PipelineStage::kFragment)}, {Location(0u)}); + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + + ASSERT_EQ(3u, result[0].input_variables.size()); + EXPECT_EQ("in_var0", result[0].input_variables[0].name); + EXPECT_TRUE(result[0].input_variables[0].has_location_attribute); + EXPECT_EQ(0u, result[0].input_variables[0].location_attribute); + EXPECT_EQ(InterpolationType::kFlat, + result[0].input_variables[0].interpolation_type); + EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[0].component_type); + EXPECT_EQ("in_var1", result[0].input_variables[1].name); + EXPECT_TRUE(result[0].input_variables[1].has_location_attribute); + EXPECT_EQ(1u, result[0].input_variables[1].location_attribute); + EXPECT_EQ(InterpolationType::kFlat, + result[0].input_variables[1].interpolation_type); + EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[1].component_type); + EXPECT_EQ("in_var4", result[0].input_variables[2].name); + EXPECT_TRUE(result[0].input_variables[2].has_location_attribute); + EXPECT_EQ(4u, result[0].input_variables[2].location_attribute); + EXPECT_EQ(InterpolationType::kFlat, + result[0].input_variables[2].interpolation_type); + EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[2].component_type); + + ASSERT_EQ(1u, result[0].output_variables.size()); + EXPECT_EQ("<retval>", result[0].output_variables[0].name); + EXPECT_TRUE(result[0].output_variables[0].has_location_attribute); + EXPECT_EQ(0u, result[0].output_variables[0].location_attribute); + EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[0].component_type); +} + +TEST_F(InspectorGetEntryPointTest, MultipleEntryPointsInOutVariables) { + auto* in_var_foo = Param("in_var_foo", ty.u32(), {Location(0u), Flat()}); + Func("foo", {in_var_foo}, ty.u32(), {Return("in_var_foo")}, + {Stage(ast::PipelineStage::kFragment)}, {Location(0u)}); + + auto* in_var_bar = Param("in_var_bar", ty.u32(), {Location(0u), Flat()}); + Func("bar", {in_var_bar}, ty.u32(), {Return("in_var_bar")}, + {Stage(ast::PipelineStage::kFragment)}, {Location(1u)}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(2u, result.size()); + + ASSERT_EQ(1u, result[0].input_variables.size()); + EXPECT_EQ("in_var_foo", result[0].input_variables[0].name); + EXPECT_TRUE(result[0].input_variables[0].has_location_attribute); + EXPECT_EQ(0u, result[0].input_variables[0].location_attribute); + EXPECT_EQ(InterpolationType::kFlat, + result[0].input_variables[0].interpolation_type); + EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[0].component_type); + + ASSERT_EQ(1u, result[0].output_variables.size()); + EXPECT_EQ("<retval>", result[0].output_variables[0].name); + EXPECT_TRUE(result[0].output_variables[0].has_location_attribute); + EXPECT_EQ(0u, result[0].output_variables[0].location_attribute); + EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[0].component_type); + + ASSERT_EQ(1u, result[1].input_variables.size()); + EXPECT_EQ("in_var_bar", result[1].input_variables[0].name); + EXPECT_TRUE(result[1].input_variables[0].has_location_attribute); + EXPECT_EQ(0u, result[1].input_variables[0].location_attribute); + EXPECT_EQ(InterpolationType::kFlat, + result[1].input_variables[0].interpolation_type); + EXPECT_EQ(ComponentType::kUInt, result[1].input_variables[0].component_type); + + ASSERT_EQ(1u, result[1].output_variables.size()); + EXPECT_EQ("<retval>", result[1].output_variables[0].name); + EXPECT_TRUE(result[1].output_variables[0].has_location_attribute); + EXPECT_EQ(1u, result[1].output_variables[0].location_attribute); + EXPECT_EQ(ComponentType::kUInt, result[1].output_variables[0].component_type); +} + +TEST_F(InspectorGetEntryPointTest, BuiltInsNotStageVariables) { + auto* in_var0 = + Param("in_var0", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)}); + auto* in_var1 = Param("in_var1", ty.f32(), {Location(0u)}); + Func("foo", {in_var0, in_var1}, ty.f32(), {Return("in_var1")}, + {Stage(ast::PipelineStage::kFragment)}, + {Builtin(ast::Builtin::kFragDepth)}); + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + + ASSERT_EQ(1u, result[0].input_variables.size()); + EXPECT_EQ("in_var1", result[0].input_variables[0].name); + EXPECT_TRUE(result[0].input_variables[0].has_location_attribute); + EXPECT_EQ(0u, result[0].input_variables[0].location_attribute); + EXPECT_EQ(ComponentType::kFloat, result[0].input_variables[0].component_type); + + ASSERT_EQ(0u, result[0].output_variables.size()); +} + +TEST_F(InspectorGetEntryPointTest, InOutStruct) { + auto* interface = MakeInOutStruct("interface", {{"a", 0u}, {"b", 1u}}); + Func("foo", {Param("param", ty.Of(interface))}, ty.Of(interface), + {Return("param")}, {Stage(ast::PipelineStage::kFragment)}); + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + + ASSERT_EQ(2u, result[0].input_variables.size()); + EXPECT_EQ("param.a", result[0].input_variables[0].name); + EXPECT_TRUE(result[0].input_variables[0].has_location_attribute); + EXPECT_EQ(0u, result[0].input_variables[0].location_attribute); + EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[0].component_type); + EXPECT_EQ("param.b", result[0].input_variables[1].name); + EXPECT_TRUE(result[0].input_variables[1].has_location_attribute); + EXPECT_EQ(1u, result[0].input_variables[1].location_attribute); + EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[1].component_type); + + ASSERT_EQ(2u, result[0].output_variables.size()); + EXPECT_EQ("<retval>.a", result[0].output_variables[0].name); + EXPECT_TRUE(result[0].output_variables[0].has_location_attribute); + EXPECT_EQ(0u, result[0].output_variables[0].location_attribute); + EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[0].component_type); + EXPECT_EQ("<retval>.b", result[0].output_variables[1].name); + EXPECT_TRUE(result[0].output_variables[1].has_location_attribute); + EXPECT_EQ(1u, result[0].output_variables[1].location_attribute); + EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[1].component_type); +} + +TEST_F(InspectorGetEntryPointTest, MultipleEntryPointsInOutSharedStruct) { + auto* interface = MakeInOutStruct("interface", {{"a", 0u}, {"b", 1u}}); + Func("foo", {}, ty.Of(interface), {Return(Construct(ty.Of(interface)))}, + {Stage(ast::PipelineStage::kFragment)}); + Func("bar", {Param("param", ty.Of(interface))}, ty.void_(), {}, + {Stage(ast::PipelineStage::kFragment)}); + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(2u, result.size()); + + ASSERT_EQ(0u, result[0].input_variables.size()); + + ASSERT_EQ(2u, result[0].output_variables.size()); + EXPECT_EQ("<retval>.a", result[0].output_variables[0].name); + EXPECT_TRUE(result[0].output_variables[0].has_location_attribute); + EXPECT_EQ(0u, result[0].output_variables[0].location_attribute); + EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[0].component_type); + EXPECT_EQ("<retval>.b", result[0].output_variables[1].name); + EXPECT_TRUE(result[0].output_variables[1].has_location_attribute); + EXPECT_EQ(1u, result[0].output_variables[1].location_attribute); + EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[1].component_type); + + ASSERT_EQ(2u, result[1].input_variables.size()); + EXPECT_EQ("param.a", result[1].input_variables[0].name); + EXPECT_TRUE(result[1].input_variables[0].has_location_attribute); + EXPECT_EQ(0u, result[1].input_variables[0].location_attribute); + EXPECT_EQ(ComponentType::kUInt, result[1].input_variables[0].component_type); + EXPECT_EQ("param.b", result[1].input_variables[1].name); + EXPECT_TRUE(result[1].input_variables[1].has_location_attribute); + EXPECT_EQ(1u, result[1].input_variables[1].location_attribute); + EXPECT_EQ(ComponentType::kUInt, result[1].input_variables[1].component_type); + + ASSERT_EQ(0u, result[1].output_variables.size()); +} + +TEST_F(InspectorGetEntryPointTest, MixInOutVariablesAndStruct) { + auto* struct_a = MakeInOutStruct("struct_a", {{"a", 0u}, {"b", 1u}}); + auto* struct_b = MakeInOutStruct("struct_b", {{"a", 2u}}); + Func("foo", + {Param("param_a", ty.Of(struct_a)), Param("param_b", ty.Of(struct_b)), + Param("param_c", ty.f32(), {Location(3u)}), + Param("param_d", ty.f32(), {Location(4u)})}, + ty.Of(struct_a), {Return("param_a")}, + {Stage(ast::PipelineStage::kFragment)}); + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + + ASSERT_EQ(5u, result[0].input_variables.size()); + EXPECT_EQ("param_a.a", result[0].input_variables[0].name); + EXPECT_TRUE(result[0].input_variables[0].has_location_attribute); + EXPECT_EQ(0u, result[0].input_variables[0].location_attribute); + EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[0].component_type); + EXPECT_EQ("param_a.b", result[0].input_variables[1].name); + EXPECT_TRUE(result[0].input_variables[1].has_location_attribute); + EXPECT_EQ(1u, result[0].input_variables[1].location_attribute); + EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[1].component_type); + EXPECT_EQ("param_b.a", result[0].input_variables[2].name); + EXPECT_TRUE(result[0].input_variables[2].has_location_attribute); + EXPECT_EQ(2u, result[0].input_variables[2].location_attribute); + EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[2].component_type); + EXPECT_EQ("param_c", result[0].input_variables[3].name); + EXPECT_TRUE(result[0].input_variables[3].has_location_attribute); + EXPECT_EQ(3u, result[0].input_variables[3].location_attribute); + EXPECT_EQ(ComponentType::kFloat, result[0].input_variables[3].component_type); + EXPECT_EQ("param_d", result[0].input_variables[4].name); + EXPECT_TRUE(result[0].input_variables[4].has_location_attribute); + EXPECT_EQ(4u, result[0].input_variables[4].location_attribute); + EXPECT_EQ(ComponentType::kFloat, result[0].input_variables[4].component_type); + + ASSERT_EQ(2u, result[0].output_variables.size()); + EXPECT_EQ("<retval>.a", result[0].output_variables[0].name); + EXPECT_TRUE(result[0].output_variables[0].has_location_attribute); + EXPECT_EQ(0u, result[0].output_variables[0].location_attribute); + EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[0].component_type); + EXPECT_EQ("<retval>.b", result[0].output_variables[1].name); + EXPECT_TRUE(result[0].output_variables[1].has_location_attribute); + EXPECT_EQ(1u, result[0].output_variables[1].location_attribute); + EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[1].component_type); +} + +TEST_F(InspectorGetEntryPointTest, OverridableConstantUnreferenced) { + AddOverridableConstantWithoutID("foo", ty.f32(), nullptr); + MakeEmptyBodyFunction( + "ep_func", {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + EXPECT_EQ(0u, result[0].overridable_constants.size()); +} + +TEST_F(InspectorGetEntryPointTest, OverridableConstantReferencedByEntryPoint) { + AddOverridableConstantWithoutID("foo", ty.f32(), nullptr); + MakePlainGlobalReferenceBodyFunction( + "ep_func", "foo", ty.f32(), + {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + ASSERT_EQ(1u, result[0].overridable_constants.size()); + EXPECT_EQ("foo", result[0].overridable_constants[0].name); +} + +TEST_F(InspectorGetEntryPointTest, OverridableConstantReferencedByCallee) { + AddOverridableConstantWithoutID("foo", ty.f32(), nullptr); + MakePlainGlobalReferenceBodyFunction("callee_func", "foo", ty.f32(), {}); + MakeCallerBodyFunction( + "ep_func", {"callee_func"}, + {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + ASSERT_EQ(1u, result[0].overridable_constants.size()); + EXPECT_EQ("foo", result[0].overridable_constants[0].name); +} + +TEST_F(InspectorGetEntryPointTest, OverridableConstantSomeReferenced) { + AddOverridableConstantWithID("foo", 1, ty.f32(), nullptr); + AddOverridableConstantWithID("bar", 2, ty.f32(), nullptr); + MakePlainGlobalReferenceBodyFunction("callee_func", "foo", ty.f32(), {}); + MakeCallerBodyFunction( + "ep_func", {"callee_func"}, + {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + ASSERT_EQ(1u, result[0].overridable_constants.size()); + EXPECT_EQ("foo", result[0].overridable_constants[0].name); + EXPECT_EQ(1, result[0].overridable_constants[0].numeric_id); +} + +TEST_F(InspectorGetEntryPointTest, OverridableConstantTypes) { + AddOverridableConstantWithoutID("bool_var", ty.bool_(), nullptr); + AddOverridableConstantWithoutID("float_var", ty.f32(), nullptr); + AddOverridableConstantWithoutID("u32_var", ty.u32(), nullptr); + AddOverridableConstantWithoutID("i32_var", ty.i32(), nullptr); + + MakePlainGlobalReferenceBodyFunction("bool_func", "bool_var", ty.bool_(), {}); + MakePlainGlobalReferenceBodyFunction("float_func", "float_var", ty.f32(), {}); + MakePlainGlobalReferenceBodyFunction("u32_func", "u32_var", ty.u32(), {}); + MakePlainGlobalReferenceBodyFunction("i32_func", "i32_var", ty.i32(), {}); + + MakeCallerBodyFunction( + "ep_func", {"bool_func", "float_func", "u32_func", "i32_func"}, + {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + ASSERT_EQ(4u, result[0].overridable_constants.size()); + EXPECT_EQ("bool_var", result[0].overridable_constants[0].name); + EXPECT_EQ(inspector::OverridableConstant::Type::kBool, + result[0].overridable_constants[0].type); + EXPECT_EQ("float_var", result[0].overridable_constants[1].name); + EXPECT_EQ(inspector::OverridableConstant::Type::kFloat32, + result[0].overridable_constants[1].type); + EXPECT_EQ("u32_var", result[0].overridable_constants[2].name); + EXPECT_EQ(inspector::OverridableConstant::Type::kUint32, + result[0].overridable_constants[2].type); + EXPECT_EQ("i32_var", result[0].overridable_constants[3].name); + EXPECT_EQ(inspector::OverridableConstant::Type::kInt32, + result[0].overridable_constants[3].type); +} + +TEST_F(InspectorGetEntryPointTest, OverridableConstantInitialized) { + AddOverridableConstantWithoutID("foo", ty.f32(), Expr(0.0f)); + MakePlainGlobalReferenceBodyFunction( + "ep_func", "foo", ty.f32(), + {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + ASSERT_EQ(1u, result[0].overridable_constants.size()); + EXPECT_EQ("foo", result[0].overridable_constants[0].name); + EXPECT_TRUE(result[0].overridable_constants[0].is_initialized); +} + +TEST_F(InspectorGetEntryPointTest, OverridableConstantUninitialized) { + AddOverridableConstantWithoutID("foo", ty.f32(), nullptr); + MakePlainGlobalReferenceBodyFunction( + "ep_func", "foo", ty.f32(), + {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + ASSERT_EQ(1u, result[0].overridable_constants.size()); + EXPECT_EQ("foo", result[0].overridable_constants[0].name); + + EXPECT_FALSE(result[0].overridable_constants[0].is_initialized); +} + +TEST_F(InspectorGetEntryPointTest, OverridableConstantNumericIDSpecified) { + AddOverridableConstantWithoutID("foo_no_id", ty.f32(), nullptr); + AddOverridableConstantWithID("foo_id", 1234, ty.f32(), nullptr); + + MakePlainGlobalReferenceBodyFunction("no_id_func", "foo_no_id", ty.f32(), {}); + MakePlainGlobalReferenceBodyFunction("id_func", "foo_id", ty.f32(), {}); + + MakeCallerBodyFunction( + "ep_func", {"no_id_func", "id_func"}, + {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + ASSERT_EQ(2u, result[0].overridable_constants.size()); + EXPECT_EQ("foo_no_id", result[0].overridable_constants[0].name); + EXPECT_EQ("foo_id", result[0].overridable_constants[1].name); + EXPECT_EQ(1234, result[0].overridable_constants[1].numeric_id); + + EXPECT_FALSE(result[0].overridable_constants[0].is_numeric_id_specified); + EXPECT_TRUE(result[0].overridable_constants[1].is_numeric_id_specified); +} + +TEST_F(InspectorGetEntryPointTest, NonOverridableConstantSkipped) { + auto* foo_struct_type = MakeUniformBufferType("foo_type", {ty.i32()}); + AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0); + MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}}); + MakeCallerBodyFunction("ep_func", {"ub_func"}, + {Stage(ast::PipelineStage::kFragment)}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + EXPECT_EQ(0u, result[0].overridable_constants.size()); +} + +TEST_F(InspectorGetEntryPointTest, BuiltinNotReferenced) { + MakeEmptyBodyFunction("ep_func", {Stage(ast::PipelineStage::kFragment)}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + EXPECT_FALSE(result[0].input_sample_mask_used); + EXPECT_FALSE(result[0].output_sample_mask_used); + EXPECT_FALSE(result[0].input_position_used); + EXPECT_FALSE(result[0].front_facing_used); + EXPECT_FALSE(result[0].sample_index_used); + EXPECT_FALSE(result[0].num_workgroups_used); +} + +TEST_F(InspectorGetEntryPointTest, InputSampleMaskSimpleReferenced) { + auto* in_var = + Param("in_var", ty.u32(), {Builtin(ast::Builtin::kSampleMask)}); + Func("ep_func", {in_var}, ty.void_(), {Return()}, + {Stage(ast::PipelineStage::kFragment)}, {}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + EXPECT_TRUE(result[0].input_sample_mask_used); +} + +TEST_F(InspectorGetEntryPointTest, InputSampleMaskStructReferenced) { + ast::StructMemberList members; + members.push_back( + Member("inner_position", ty.u32(), {Builtin(ast::Builtin::kSampleMask)})); + Structure("in_struct", members); + auto* in_var = Param("in_var", ty.type_name("in_struct"), {}); + + Func("ep_func", {in_var}, ty.void_(), {Return()}, + {Stage(ast::PipelineStage::kFragment)}, {}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + EXPECT_TRUE(result[0].input_sample_mask_used); +} + +TEST_F(InspectorGetEntryPointTest, OutputSampleMaskSimpleReferenced) { + auto* in_var = + Param("in_var", ty.u32(), {Builtin(ast::Builtin::kSampleMask)}); + Func("ep_func", {in_var}, ty.u32(), {Return("in_var")}, + {Stage(ast::PipelineStage::kFragment)}, + {Builtin(ast::Builtin::kSampleMask)}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + EXPECT_TRUE(result[0].output_sample_mask_used); +} + +TEST_F(InspectorGetEntryPointTest, OutputSampleMaskStructReferenced) { + ast::StructMemberList members; + members.push_back(Member("inner_sample_mask", ty.u32(), + {Builtin(ast::Builtin::kSampleMask)})); + Structure("out_struct", members); + + Func("ep_func", {}, ty.type_name("out_struct"), + {Decl(Var("out_var", ty.type_name("out_struct"))), Return("out_var")}, + {Stage(ast::PipelineStage::kFragment)}, {}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + EXPECT_TRUE(result[0].output_sample_mask_used); +} + +TEST_F(InspectorGetEntryPointTest, InputPositionSimpleReferenced) { + auto* in_var = + Param("in_var", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)}); + Func("ep_func", {in_var}, ty.void_(), {Return()}, + {Stage(ast::PipelineStage::kFragment)}, {}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + EXPECT_TRUE(result[0].input_position_used); +} + +TEST_F(InspectorGetEntryPointTest, InputPositionStructReferenced) { + ast::StructMemberList members; + members.push_back(Member("inner_position", ty.vec4<f32>(), + {Builtin(ast::Builtin::kPosition)})); + Structure("in_struct", members); + auto* in_var = Param("in_var", ty.type_name("in_struct"), {}); + + Func("ep_func", {in_var}, ty.void_(), {Return()}, + {Stage(ast::PipelineStage::kFragment)}, {}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + EXPECT_TRUE(result[0].input_position_used); +} + +TEST_F(InspectorGetEntryPointTest, FrontFacingSimpleReferenced) { + auto* in_var = + Param("in_var", ty.bool_(), {Builtin(ast::Builtin::kFrontFacing)}); + Func("ep_func", {in_var}, ty.void_(), {Return()}, + {Stage(ast::PipelineStage::kFragment)}, {}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + EXPECT_TRUE(result[0].front_facing_used); +} + +TEST_F(InspectorGetEntryPointTest, FrontFacingStructReferenced) { + ast::StructMemberList members; + members.push_back(Member("inner_position", ty.bool_(), + {Builtin(ast::Builtin::kFrontFacing)})); + Structure("in_struct", members); + auto* in_var = Param("in_var", ty.type_name("in_struct"), {}); + + Func("ep_func", {in_var}, ty.void_(), {Return()}, + {Stage(ast::PipelineStage::kFragment)}, {}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + EXPECT_TRUE(result[0].front_facing_used); +} + +TEST_F(InspectorGetEntryPointTest, SampleIndexSimpleReferenced) { + auto* in_var = + Param("in_var", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)}); + Func("ep_func", {in_var}, ty.void_(), {Return()}, + {Stage(ast::PipelineStage::kFragment)}, {}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + EXPECT_TRUE(result[0].sample_index_used); +} + +TEST_F(InspectorGetEntryPointTest, SampleIndexStructReferenced) { + ast::StructMemberList members; + members.push_back(Member("inner_position", ty.u32(), + {Builtin(ast::Builtin::kSampleIndex)})); + Structure("in_struct", members); + auto* in_var = Param("in_var", ty.type_name("in_struct"), {}); + + Func("ep_func", {in_var}, ty.void_(), {Return()}, + {Stage(ast::PipelineStage::kFragment)}, {}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + EXPECT_TRUE(result[0].sample_index_used); +} + +TEST_F(InspectorGetEntryPointTest, NumWorkgroupsSimpleReferenced) { + auto* in_var = + Param("in_var", ty.vec3<u32>(), {Builtin(ast::Builtin::kNumWorkgroups)}); + Func("ep_func", {in_var}, ty.void_(), {Return()}, + {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}, {}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + EXPECT_TRUE(result[0].num_workgroups_used); +} + +TEST_F(InspectorGetEntryPointTest, NumWorkgroupsStructReferenced) { + ast::StructMemberList members; + members.push_back(Member("inner_position", ty.vec3<u32>(), + {Builtin(ast::Builtin::kNumWorkgroups)})); + Structure("in_struct", members); + auto* in_var = Param("in_var", ty.type_name("in_struct"), {}); + + Func("ep_func", {in_var}, ty.void_(), {Return()}, + {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}, {}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + EXPECT_TRUE(result[0].num_workgroups_used); +} + +TEST_F(InspectorGetEntryPointTest, ImplicitInterpolate) { + ast::StructMemberList members; + members.push_back(Member("struct_inner", ty.f32(), {Location(0)})); + Structure("in_struct", members); + auto* in_var = Param("in_var", ty.type_name("in_struct"), {}); + + Func("ep_func", {in_var}, ty.void_(), {Return()}, + {Stage(ast::PipelineStage::kFragment)}, {}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + ASSERT_EQ(1u, result[0].input_variables.size()); + EXPECT_EQ(InterpolationType::kPerspective, + result[0].input_variables[0].interpolation_type); + EXPECT_EQ(InterpolationSampling::kCenter, + result[0].input_variables[0].interpolation_sampling); +} + +TEST_P(InspectorGetEntryPointInterpolateTest, Test) { + auto& params = GetParam(); + ast::StructMemberList members; + members.push_back( + Member("struct_inner", ty.f32(), + {Interpolate(params.in_type, params.in_sampling), Location(0)})); + Structure("in_struct", members); + auto* in_var = Param("in_var", ty.type_name("in_struct"), {}); + + Func("ep_func", {in_var}, ty.void_(), {Return()}, + {Stage(ast::PipelineStage::kFragment)}, {}); + + Inspector& inspector = Build(); + + auto result = inspector.GetEntryPoints(); + + ASSERT_EQ(1u, result.size()); + ASSERT_EQ(1u, result[0].input_variables.size()); + EXPECT_EQ(params.out_type, result[0].input_variables[0].interpolation_type); + EXPECT_EQ(params.out_sampling, + result[0].input_variables[0].interpolation_sampling); +} + +INSTANTIATE_TEST_SUITE_P( + InspectorGetEntryPointTest, + InspectorGetEntryPointInterpolateTest, + testing::Values( + InspectorGetEntryPointInterpolateTestParams{ + ast::InterpolationType::kPerspective, + ast::InterpolationSampling::kCenter, + InterpolationType::kPerspective, InterpolationSampling::kCenter}, + InspectorGetEntryPointInterpolateTestParams{ + ast::InterpolationType::kPerspective, + ast::InterpolationSampling::kCentroid, + InterpolationType::kPerspective, InterpolationSampling::kCentroid}, + InspectorGetEntryPointInterpolateTestParams{ + ast::InterpolationType::kPerspective, + ast::InterpolationSampling::kSample, + InterpolationType::kPerspective, InterpolationSampling::kSample}, + InspectorGetEntryPointInterpolateTestParams{ + ast::InterpolationType::kPerspective, + ast::InterpolationSampling::kNone, InterpolationType::kPerspective, + InterpolationSampling::kCenter}, + InspectorGetEntryPointInterpolateTestParams{ + ast::InterpolationType::kLinear, + ast::InterpolationSampling::kCenter, InterpolationType::kLinear, + InterpolationSampling::kCenter}, + InspectorGetEntryPointInterpolateTestParams{ + ast::InterpolationType::kLinear, + ast::InterpolationSampling::kCentroid, InterpolationType::kLinear, + InterpolationSampling::kCentroid}, + InspectorGetEntryPointInterpolateTestParams{ + ast::InterpolationType::kLinear, + ast::InterpolationSampling::kSample, InterpolationType::kLinear, + InterpolationSampling::kSample}, + InspectorGetEntryPointInterpolateTestParams{ + ast::InterpolationType::kLinear, ast::InterpolationSampling::kNone, + InterpolationType::kLinear, InterpolationSampling::kCenter}, + InspectorGetEntryPointInterpolateTestParams{ + ast::InterpolationType::kFlat, ast::InterpolationSampling::kNone, + InterpolationType::kFlat, InterpolationSampling::kNone})); + +TEST_F(InspectorGetConstantIDsTest, Bool) { + AddOverridableConstantWithID("foo", 1, ty.bool_(), nullptr); + AddOverridableConstantWithID("bar", 20, ty.bool_(), Expr(true)); + AddOverridableConstantWithID("baz", 300, ty.bool_(), Expr(false)); + + Inspector& inspector = Build(); + + auto result = inspector.GetConstantIDs(); + ASSERT_EQ(3u, result.size()); + + ASSERT_TRUE(result.find(1) != result.end()); + EXPECT_TRUE(result[1].IsNull()); + + ASSERT_TRUE(result.find(20) != result.end()); + EXPECT_TRUE(result[20].IsBool()); + EXPECT_TRUE(result[20].AsBool()); + + ASSERT_TRUE(result.find(300) != result.end()); + EXPECT_TRUE(result[300].IsBool()); + EXPECT_FALSE(result[300].AsBool()); +} + +TEST_F(InspectorGetConstantIDsTest, U32) { + AddOverridableConstantWithID("foo", 1, ty.u32(), nullptr); + AddOverridableConstantWithID("bar", 20, ty.u32(), Expr(42u)); + + Inspector& inspector = Build(); + + auto result = inspector.GetConstantIDs(); + ASSERT_EQ(2u, result.size()); + + ASSERT_TRUE(result.find(1) != result.end()); + EXPECT_TRUE(result[1].IsNull()); + + ASSERT_TRUE(result.find(20) != result.end()); + EXPECT_TRUE(result[20].IsU32()); + EXPECT_EQ(42u, result[20].AsU32()); +} + +TEST_F(InspectorGetConstantIDsTest, I32) { + AddOverridableConstantWithID("foo", 1, ty.i32(), nullptr); + AddOverridableConstantWithID("bar", 20, ty.i32(), Expr(-42)); + AddOverridableConstantWithID("baz", 300, ty.i32(), Expr(42)); + + Inspector& inspector = Build(); + + auto result = inspector.GetConstantIDs(); + ASSERT_EQ(3u, result.size()); + + ASSERT_TRUE(result.find(1) != result.end()); + EXPECT_TRUE(result[1].IsNull()); + + ASSERT_TRUE(result.find(20) != result.end()); + EXPECT_TRUE(result[20].IsI32()); + EXPECT_EQ(-42, result[20].AsI32()); + + ASSERT_TRUE(result.find(300) != result.end()); + EXPECT_TRUE(result[300].IsI32()); + EXPECT_EQ(42, result[300].AsI32()); +} + +TEST_F(InspectorGetConstantIDsTest, Float) { + AddOverridableConstantWithID("foo", 1, ty.f32(), nullptr); + AddOverridableConstantWithID("bar", 20, ty.f32(), Expr(0.0f)); + AddOverridableConstantWithID("baz", 300, ty.f32(), Expr(-10.0f)); + AddOverridableConstantWithID("x", 4000, ty.f32(), Expr(15.0f)); + + Inspector& inspector = Build(); + + auto result = inspector.GetConstantIDs(); + ASSERT_EQ(4u, result.size()); + + ASSERT_TRUE(result.find(1) != result.end()); + EXPECT_TRUE(result[1].IsNull()); + + ASSERT_TRUE(result.find(20) != result.end()); + EXPECT_TRUE(result[20].IsFloat()); + EXPECT_FLOAT_EQ(0.0, result[20].AsFloat()); + + ASSERT_TRUE(result.find(300) != result.end()); + EXPECT_TRUE(result[300].IsFloat()); + EXPECT_FLOAT_EQ(-10.0, result[300].AsFloat()); + + ASSERT_TRUE(result.find(4000) != result.end()); + EXPECT_TRUE(result[4000].IsFloat()); + EXPECT_FLOAT_EQ(15.0, result[4000].AsFloat()); +} + +TEST_F(InspectorGetConstantNameToIdMapTest, WithAndWithoutIds) { + AddOverridableConstantWithID("v1", 1, ty.f32(), nullptr); + AddOverridableConstantWithID("v20", 20, ty.f32(), nullptr); + AddOverridableConstantWithID("v300", 300, ty.f32(), nullptr); + auto* a = AddOverridableConstantWithoutID("a", ty.f32(), nullptr); + auto* b = AddOverridableConstantWithoutID("b", ty.f32(), nullptr); + auto* c = AddOverridableConstantWithoutID("c", ty.f32(), nullptr); + + Inspector& inspector = Build(); + + auto result = inspector.GetConstantNameToIdMap(); + ASSERT_EQ(6u, result.size()); + + ASSERT_TRUE(result.count("v1")); + EXPECT_EQ(result["v1"], 1u); + + ASSERT_TRUE(result.count("v20")); + EXPECT_EQ(result["v20"], 20u); + + ASSERT_TRUE(result.count("v300")); + EXPECT_EQ(result["v300"], 300u); + + ASSERT_TRUE(result.count("a")); + ASSERT_TRUE(program_->Sem().Get<sem::GlobalVariable>(a)); + EXPECT_EQ(result["a"], + program_->Sem().Get<sem::GlobalVariable>(a)->ConstantId()); + + ASSERT_TRUE(result.count("b")); + ASSERT_TRUE(program_->Sem().Get<sem::GlobalVariable>(b)); + EXPECT_EQ(result["b"], + program_->Sem().Get<sem::GlobalVariable>(b)->ConstantId()); + + ASSERT_TRUE(result.count("c")); + ASSERT_TRUE(program_->Sem().Get<sem::GlobalVariable>(c)); + EXPECT_EQ(result["c"], + program_->Sem().Get<sem::GlobalVariable>(c)->ConstantId()); +} + +TEST_F(InspectorGetStorageSizeTest, Empty) { + MakeEmptyBodyFunction("ep_func", + ast::AttributeList{Stage(ast::PipelineStage::kCompute), + WorkgroupSize(1)}); + Inspector& inspector = Build(); + EXPECT_EQ(0u, inspector.GetStorageSize("ep_func")); +} + +TEST_F(InspectorGetStorageSizeTest, Simple_NonStruct) { + AddUniformBuffer("ub_var", ty.i32(), 0, 0); + AddStorageBuffer("sb_var", ty.i32(), ast::Access::kReadWrite, 1, 0); + AddStorageBuffer("rosb_var", ty.i32(), ast::Access::kRead, 1, 1); + Func("ep_func", {}, ty.void_(), + { + Decl(Const("ub", nullptr, Expr("ub_var"))), + Decl(Const("sb", nullptr, Expr("sb_var"))), + Decl(Const("rosb", nullptr, Expr("rosb_var"))), + }, + {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}); + + Inspector& inspector = Build(); + + EXPECT_EQ(12u, inspector.GetStorageSize("ep_func")); +} + +TEST_F(InspectorGetStorageSizeTest, Simple_Struct) { + auto* ub_struct_type = MakeUniformBufferType("ub_type", {ty.i32(), ty.i32()}); + AddUniformBuffer("ub_var", ty.Of(ub_struct_type), 0, 0); + MakeStructVariableReferenceBodyFunction("ub_func", "ub_var", {{0, ty.i32()}}); + + auto sb = MakeStorageBufferTypes("sb_type", {ty.i32()}); + AddStorageBuffer("sb_var", sb(), ast::Access::kReadWrite, 1, 0); + MakeStructVariableReferenceBodyFunction("sb_func", "sb_var", {{0, ty.i32()}}); + + auto ro_sb = MakeStorageBufferTypes("rosb_type", {ty.i32()}); + AddStorageBuffer("rosb_var", ro_sb(), ast::Access::kRead, 1, 1); + MakeStructVariableReferenceBodyFunction("rosb_func", "rosb_var", + {{0, ty.i32()}}); + + MakeCallerBodyFunction("ep_func", {"ub_func", "sb_func", "rosb_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kCompute), + WorkgroupSize(1), + }); + + Inspector& inspector = Build(); + + EXPECT_EQ(16u, inspector.GetStorageSize("ep_func")); +} + +TEST_F(InspectorGetStorageSizeTest, NonStructVec3) { + AddUniformBuffer("ub_var", ty.vec3<f32>(), 0, 0); + Func("ep_func", {}, ty.void_(), + { + Decl(Const("ub", nullptr, Expr("ub_var"))), + }, + {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}); + + Inspector& inspector = Build(); + + EXPECT_EQ(12u, inspector.GetStorageSize("ep_func")); +} + +TEST_F(InspectorGetStorageSizeTest, StructVec3) { + auto* ub_struct_type = MakeUniformBufferType("ub_type", {ty.vec3<f32>()}); + AddUniformBuffer("ub_var", ty.Of(ub_struct_type), 0, 0); + Func("ep_func", {}, ty.void_(), + { + Decl(Const("ub", nullptr, Expr("ub_var"))), + }, + {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}); + + Inspector& inspector = Build(); + + EXPECT_EQ(16u, inspector.GetStorageSize("ep_func")); +} + +TEST_F(InspectorGetResourceBindingsTest, Empty) { + MakeCallerBodyFunction("ep_func", {}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(0u, result.size()); +} + +TEST_F(InspectorGetResourceBindingsTest, Simple) { + auto* ub_struct_type = MakeUniformBufferType("ub_type", {ty.i32()}); + AddUniformBuffer("ub_var", ty.Of(ub_struct_type), 0, 0); + MakeStructVariableReferenceBodyFunction("ub_func", "ub_var", {{0, ty.i32()}}); + + auto sb = MakeStorageBufferTypes("sb_type", {ty.i32()}); + AddStorageBuffer("sb_var", sb(), ast::Access::kReadWrite, 1, 0); + MakeStructVariableReferenceBodyFunction("sb_func", "sb_var", {{0, ty.i32()}}); + + auto ro_sb = MakeStorageBufferTypes("rosb_type", {ty.i32()}); + AddStorageBuffer("rosb_var", ro_sb(), ast::Access::kRead, 1, 1); + MakeStructVariableReferenceBodyFunction("rosb_func", "rosb_var", + {{0, ty.i32()}}); + + auto* s_texture_type = + ty.sampled_texture(ast::TextureDimension::k1d, ty.f32()); + AddResource("s_texture", s_texture_type, 2, 0); + AddSampler("s_var", 3, 0); + AddGlobalVariable("s_coords", ty.f32()); + MakeSamplerReferenceBodyFunction("s_func", "s_texture", "s_var", "s_coords", + ty.f32(), {}); + + auto* cs_depth_texture_type = ty.depth_texture(ast::TextureDimension::k2d); + AddResource("cs_texture", cs_depth_texture_type, 3, 1); + AddComparisonSampler("cs_var", 3, 2); + AddGlobalVariable("cs_coords", ty.vec2<f32>()); + AddGlobalVariable("cs_depth", ty.f32()); + MakeComparisonSamplerReferenceBodyFunction( + "cs_func", "cs_texture", "cs_var", "cs_coords", "cs_depth", ty.f32(), {}); + + auto* depth_ms_texture_type = + ty.depth_multisampled_texture(ast::TextureDimension::k2d); + AddResource("depth_ms_texture", depth_ms_texture_type, 3, 3); + Func("depth_ms_func", {}, ty.void_(), {Ignore("depth_ms_texture")}); + + auto* st_type = MakeStorageTextureTypes(ast::TextureDimension::k2d, + ast::TexelFormat::kR32Uint); + AddStorageTexture("st_var", st_type, 4, 0); + MakeStorageTextureBodyFunction("st_func", "st_var", ty.vec2<i32>(), {}); + + MakeCallerBodyFunction("ep_func", + {"ub_func", "sb_func", "rosb_func", "s_func", + "cs_func", "depth_ms_func", "st_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(9u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + + EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, + result[1].resource_type); + EXPECT_EQ(1u, result[1].bind_group); + EXPECT_EQ(0u, result[1].binding); + + EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer, + result[2].resource_type); + EXPECT_EQ(1u, result[2].bind_group); + EXPECT_EQ(1u, result[2].binding); + + EXPECT_EQ(ResourceBinding::ResourceType::kSampler, result[3].resource_type); + EXPECT_EQ(3u, result[3].bind_group); + EXPECT_EQ(0u, result[3].binding); + + EXPECT_EQ(ResourceBinding::ResourceType::kComparisonSampler, + result[4].resource_type); + EXPECT_EQ(3u, result[4].bind_group); + EXPECT_EQ(2u, result[4].binding); + + EXPECT_EQ(ResourceBinding::ResourceType::kSampledTexture, + result[5].resource_type); + EXPECT_EQ(2u, result[5].bind_group); + EXPECT_EQ(0u, result[5].binding); + + EXPECT_EQ(ResourceBinding::ResourceType::kWriteOnlyStorageTexture, + result[6].resource_type); + EXPECT_EQ(4u, result[6].bind_group); + EXPECT_EQ(0u, result[6].binding); + + EXPECT_EQ(ResourceBinding::ResourceType::kDepthTexture, + result[7].resource_type); + EXPECT_EQ(3u, result[7].bind_group); + EXPECT_EQ(1u, result[7].binding); + + EXPECT_EQ(ResourceBinding::ResourceType::kDepthMultisampledTexture, + result[8].resource_type); + EXPECT_EQ(3u, result[8].bind_group); + EXPECT_EQ(3u, result[8].binding); +} + +TEST_F(InspectorGetUniformBufferResourceBindingsTest, MissingEntryPoint) { + Inspector& inspector = Build(); + + auto result = inspector.GetUniformBufferResourceBindings("ep_func"); + ASSERT_TRUE(inspector.has_error()); + std::string error = inspector.error(); + EXPECT_TRUE(error.find("not found") != std::string::npos); +} + +TEST_F(InspectorGetUniformBufferResourceBindingsTest, NonEntryPointFunc) { + auto* foo_struct_type = MakeUniformBufferType("foo_type", {ty.i32()}); + AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0); + + MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}}); + + MakeCallerBodyFunction("ep_func", {"ub_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetUniformBufferResourceBindings("ub_func"); + std::string error = inspector.error(); + EXPECT_TRUE(error.find("not an entry point") != std::string::npos); +} + +TEST_F(InspectorGetUniformBufferResourceBindingsTest, Simple_NonStruct) { + AddUniformBuffer("foo_ub", ty.i32(), 0, 0); + MakePlainGlobalReferenceBodyFunction("ub_func", "foo_ub", ty.i32(), {}); + + MakeCallerBodyFunction("ep_func", {"ub_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetUniformBufferResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(4u, result[0].size); + EXPECT_EQ(4u, result[0].size_no_padding); +} + +TEST_F(InspectorGetUniformBufferResourceBindingsTest, Simple_Struct) { + auto* foo_struct_type = MakeUniformBufferType("foo_type", {ty.i32()}); + AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0); + + MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}}); + + MakeCallerBodyFunction("ep_func", {"ub_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetUniformBufferResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(4u, result[0].size); + EXPECT_EQ(4u, result[0].size_no_padding); +} + +TEST_F(InspectorGetUniformBufferResourceBindingsTest, MultipleMembers) { + auto* foo_struct_type = + MakeUniformBufferType("foo_type", {ty.i32(), ty.u32(), ty.f32()}); + AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0); + + MakeStructVariableReferenceBodyFunction( + "ub_func", "foo_ub", {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}}); + + MakeCallerBodyFunction("ep_func", {"ub_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetUniformBufferResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(12u, result[0].size); + EXPECT_EQ(12u, result[0].size_no_padding); +} + +TEST_F(InspectorGetUniformBufferResourceBindingsTest, ContainingPadding) { + auto* foo_struct_type = MakeUniformBufferType("foo_type", {ty.vec3<f32>()}); + AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0); + + MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", + {{0, ty.vec3<f32>()}}); + + MakeCallerBodyFunction("ep_func", {"ub_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetUniformBufferResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(16u, result[0].size); + EXPECT_EQ(12u, result[0].size_no_padding); +} + +TEST_F(InspectorGetUniformBufferResourceBindingsTest, NonStructVec3) { + AddUniformBuffer("foo_ub", ty.vec3<f32>(), 0, 0); + MakePlainGlobalReferenceBodyFunction("ub_func", "foo_ub", ty.vec3<f32>(), {}); + + MakeCallerBodyFunction("ep_func", {"ub_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetUniformBufferResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(12u, result[0].size); + EXPECT_EQ(12u, result[0].size_no_padding); +} + +TEST_F(InspectorGetUniformBufferResourceBindingsTest, MultipleUniformBuffers) { + auto* ub_struct_type = + MakeUniformBufferType("ub_type", {ty.i32(), ty.u32(), ty.f32()}); + AddUniformBuffer("ub_foo", ty.Of(ub_struct_type), 0, 0); + AddUniformBuffer("ub_bar", ty.Of(ub_struct_type), 0, 1); + AddUniformBuffer("ub_baz", ty.Of(ub_struct_type), 2, 0); + + auto AddReferenceFunc = [this](const std::string& func_name, + const std::string& var_name) { + MakeStructVariableReferenceBodyFunction( + func_name, var_name, {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}}); + }; + AddReferenceFunc("ub_foo_func", "ub_foo"); + AddReferenceFunc("ub_bar_func", "ub_bar"); + AddReferenceFunc("ub_baz_func", "ub_baz"); + + auto FuncCall = [&](const std::string& callee) { + return create<ast::CallStatement>(Call(callee)); + }; + + Func("ep_func", ast::VariableList(), ty.void_(), + ast::StatementList{FuncCall("ub_foo_func"), FuncCall("ub_bar_func"), + FuncCall("ub_baz_func"), Return()}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetUniformBufferResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(3u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(12u, result[0].size); + EXPECT_EQ(12u, result[0].size_no_padding); + + EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer, + result[1].resource_type); + EXPECT_EQ(0u, result[1].bind_group); + EXPECT_EQ(1u, result[1].binding); + EXPECT_EQ(12u, result[1].size); + EXPECT_EQ(12u, result[1].size_no_padding); + + EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer, + result[2].resource_type); + EXPECT_EQ(2u, result[2].bind_group); + EXPECT_EQ(0u, result[2].binding); + EXPECT_EQ(12u, result[2].size); + EXPECT_EQ(12u, result[2].size_no_padding); +} + +TEST_F(InspectorGetUniformBufferResourceBindingsTest, ContainingArray) { + // Manually create uniform buffer to make sure it had a valid layout (array + // with elem stride of 16, and that is 16-byte aligned within the struct) + auto* foo_struct_type = Structure( + "foo_type", + {Member("0i32", ty.i32()), + Member("b", ty.array(ty.u32(), 4, /*stride*/ 16), {MemberAlign(16)})}); + + AddUniformBuffer("foo_ub", ty.Of(foo_struct_type), 0, 0); + + MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", {{0, ty.i32()}}); + + MakeCallerBodyFunction("ep_func", {"ub_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetUniformBufferResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(80u, result[0].size); + EXPECT_EQ(80u, result[0].size_no_padding); +} + +TEST_F(InspectorGetStorageBufferResourceBindingsTest, Simple_NonStruct) { + AddStorageBuffer("foo_sb", ty.i32(), ast::Access::kReadWrite, 0, 0); + MakePlainGlobalReferenceBodyFunction("sb_func", "foo_sb", ty.i32(), {}); + + MakeCallerBodyFunction("ep_func", {"sb_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetStorageBufferResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(4u, result[0].size); + EXPECT_EQ(4u, result[0].size_no_padding); +} + +TEST_F(InspectorGetStorageBufferResourceBindingsTest, Simple_Struct) { + auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.i32()}); + AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0); + + MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}}); + + MakeCallerBodyFunction("ep_func", {"sb_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetStorageBufferResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(4u, result[0].size); + EXPECT_EQ(4u, result[0].size_no_padding); +} + +TEST_F(InspectorGetStorageBufferResourceBindingsTest, MultipleMembers) { + auto foo_struct_type = MakeStorageBufferTypes("foo_type", { + ty.i32(), + ty.u32(), + ty.f32(), + }); + AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0); + + MakeStructVariableReferenceBodyFunction( + "sb_func", "foo_sb", {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}}); + + MakeCallerBodyFunction("ep_func", {"sb_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetStorageBufferResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(12u, result[0].size); + EXPECT_EQ(12u, result[0].size_no_padding); +} + +TEST_F(InspectorGetStorageBufferResourceBindingsTest, MultipleStorageBuffers) { + auto sb_struct_type = MakeStorageBufferTypes("sb_type", { + ty.i32(), + ty.u32(), + ty.f32(), + }); + AddStorageBuffer("sb_foo", sb_struct_type(), ast::Access::kReadWrite, 0, 0); + AddStorageBuffer("sb_bar", sb_struct_type(), ast::Access::kReadWrite, 0, 1); + AddStorageBuffer("sb_baz", sb_struct_type(), ast::Access::kReadWrite, 2, 0); + + auto AddReferenceFunc = [this](const std::string& func_name, + const std::string& var_name) { + MakeStructVariableReferenceBodyFunction( + func_name, var_name, {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}}); + }; + AddReferenceFunc("sb_foo_func", "sb_foo"); + AddReferenceFunc("sb_bar_func", "sb_bar"); + AddReferenceFunc("sb_baz_func", "sb_baz"); + + auto FuncCall = [&](const std::string& callee) { + return create<ast::CallStatement>(Call(callee)); + }; + + Func("ep_func", ast::VariableList(), ty.void_(), + ast::StatementList{ + FuncCall("sb_foo_func"), + FuncCall("sb_bar_func"), + FuncCall("sb_baz_func"), + Return(), + }, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetStorageBufferResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(3u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(12u, result[0].size); + EXPECT_EQ(12u, result[0].size_no_padding); + + EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, + result[1].resource_type); + EXPECT_EQ(0u, result[1].bind_group); + EXPECT_EQ(1u, result[1].binding); + EXPECT_EQ(12u, result[1].size); + EXPECT_EQ(12u, result[1].size_no_padding); + + EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, + result[2].resource_type); + EXPECT_EQ(2u, result[2].bind_group); + EXPECT_EQ(0u, result[2].binding); + EXPECT_EQ(12u, result[2].size); + EXPECT_EQ(12u, result[2].size_no_padding); +} + +TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingArray) { + auto foo_struct_type = + MakeStorageBufferTypes("foo_type", {ty.i32(), ty.array<u32, 4>()}); + AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0); + + MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}}); + + MakeCallerBodyFunction("ep_func", {"sb_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetStorageBufferResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(20u, result[0].size); + EXPECT_EQ(20u, result[0].size_no_padding); +} + +TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingRuntimeArray) { + auto foo_struct_type = MakeStorageBufferTypes("foo_type", { + ty.i32(), + ty.array<u32>(), + }); + AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0); + + MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}}); + + MakeCallerBodyFunction("ep_func", {"sb_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetStorageBufferResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(8u, result[0].size); + EXPECT_EQ(8u, result[0].size_no_padding); +} + +TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingPadding) { + auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.vec3<f32>()}); + AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0); + + MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", + {{0, ty.vec3<f32>()}}); + + MakeCallerBodyFunction("ep_func", {"sb_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetStorageBufferResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(16u, result[0].size); + EXPECT_EQ(12u, result[0].size_no_padding); +} + +TEST_F(InspectorGetStorageBufferResourceBindingsTest, NonStructVec3) { + AddStorageBuffer("foo_ub", ty.vec3<f32>(), ast::Access::kReadWrite, 0, 0); + MakePlainGlobalReferenceBodyFunction("ub_func", "foo_ub", ty.vec3<f32>(), {}); + + MakeCallerBodyFunction("ep_func", {"ub_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetStorageBufferResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(12u, result[0].size); + EXPECT_EQ(12u, result[0].size_no_padding); +} + +TEST_F(InspectorGetStorageBufferResourceBindingsTest, SkipReadOnly) { + auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.i32()}); + AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kRead, 0, 0); + + MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}}); + + MakeCallerBodyFunction("ep_func", {"sb_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetStorageBufferResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(0u, result.size()); +} + +TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, Simple) { + auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.i32()}); + AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kRead, 0, 0); + + MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}}); + + MakeCallerBodyFunction("ep_func", {"sb_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetReadOnlyStorageBufferResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(4u, result[0].size); + EXPECT_EQ(4u, result[0].size_no_padding); +} + +TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, + MultipleStorageBuffers) { + auto sb_struct_type = MakeStorageBufferTypes("sb_type", { + ty.i32(), + ty.u32(), + ty.f32(), + }); + AddStorageBuffer("sb_foo", sb_struct_type(), ast::Access::kRead, 0, 0); + AddStorageBuffer("sb_bar", sb_struct_type(), ast::Access::kRead, 0, 1); + AddStorageBuffer("sb_baz", sb_struct_type(), ast::Access::kRead, 2, 0); + + auto AddReferenceFunc = [this](const std::string& func_name, + const std::string& var_name) { + MakeStructVariableReferenceBodyFunction( + func_name, var_name, {{0, ty.i32()}, {1, ty.u32()}, {2, ty.f32()}}); + }; + AddReferenceFunc("sb_foo_func", "sb_foo"); + AddReferenceFunc("sb_bar_func", "sb_bar"); + AddReferenceFunc("sb_baz_func", "sb_baz"); + + auto FuncCall = [&](const std::string& callee) { + return create<ast::CallStatement>(Call(callee)); + }; + + Func("ep_func", ast::VariableList(), ty.void_(), + ast::StatementList{ + FuncCall("sb_foo_func"), + FuncCall("sb_bar_func"), + FuncCall("sb_baz_func"), + Return(), + }, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetReadOnlyStorageBufferResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(3u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(12u, result[0].size); + EXPECT_EQ(12u, result[0].size_no_padding); + + EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer, + result[1].resource_type); + EXPECT_EQ(0u, result[1].bind_group); + EXPECT_EQ(1u, result[1].binding); + EXPECT_EQ(12u, result[1].size); + EXPECT_EQ(12u, result[1].size_no_padding); + + EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer, + result[2].resource_type); + EXPECT_EQ(2u, result[2].bind_group); + EXPECT_EQ(0u, result[2].binding); + EXPECT_EQ(12u, result[2].size); + EXPECT_EQ(12u, result[2].size_no_padding); +} + +TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, ContainingArray) { + auto foo_struct_type = + MakeStorageBufferTypes("foo_type", { + ty.i32(), + ty.array<u32, 4>(), + }); + AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kRead, 0, 0); + + MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}}); + + MakeCallerBodyFunction("ep_func", {"sb_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetReadOnlyStorageBufferResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(20u, result[0].size); + EXPECT_EQ(20u, result[0].size_no_padding); +} + +TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, + ContainingRuntimeArray) { + auto foo_struct_type = MakeStorageBufferTypes("foo_type", { + ty.i32(), + ty.array<u32>(), + }); + AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kRead, 0, 0); + + MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}}); + + MakeCallerBodyFunction("ep_func", {"sb_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetReadOnlyStorageBufferResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(8u, result[0].size); + EXPECT_EQ(8u, result[0].size_no_padding); +} + +TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, SkipNonReadOnly) { + auto foo_struct_type = MakeStorageBufferTypes("foo_type", {ty.i32()}); + AddStorageBuffer("foo_sb", foo_struct_type(), ast::Access::kReadWrite, 0, 0); + + MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", {{0, ty.i32()}}); + + MakeCallerBodyFunction("ep_func", {"sb_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetReadOnlyStorageBufferResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(0u, result.size()); +} + +TEST_F(InspectorGetSamplerResourceBindingsTest, Simple) { + auto* sampled_texture_type = + ty.sampled_texture(ast::TextureDimension::k1d, ty.f32()); + AddResource("foo_texture", sampled_texture_type, 0, 0); + AddSampler("foo_sampler", 0, 1); + AddGlobalVariable("foo_coords", ty.f32()); + + MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler", + "foo_coords", ty.f32(), + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetSamplerResourceBindings("ep"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + EXPECT_EQ(ResourceBinding::ResourceType::kSampler, result[0].resource_type); + ASSERT_EQ(1u, result.size()); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(1u, result[0].binding); +} + +TEST_F(InspectorGetSamplerResourceBindingsTest, NoSampler) { + MakeEmptyBodyFunction("ep_func", ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetSamplerResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(0u, result.size()); +} + +TEST_F(InspectorGetSamplerResourceBindingsTest, InFunction) { + auto* sampled_texture_type = + ty.sampled_texture(ast::TextureDimension::k1d, ty.f32()); + AddResource("foo_texture", sampled_texture_type, 0, 0); + AddSampler("foo_sampler", 0, 1); + AddGlobalVariable("foo_coords", ty.f32()); + + MakeSamplerReferenceBodyFunction("foo_func", "foo_texture", "foo_sampler", + "foo_coords", ty.f32(), {}); + + MakeCallerBodyFunction("ep_func", {"foo_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetSamplerResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + EXPECT_EQ(ResourceBinding::ResourceType::kSampler, result[0].resource_type); + ASSERT_EQ(1u, result.size()); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(1u, result[0].binding); +} + +TEST_F(InspectorGetSamplerResourceBindingsTest, UnknownEntryPoint) { + auto* sampled_texture_type = + ty.sampled_texture(ast::TextureDimension::k1d, ty.f32()); + AddResource("foo_texture", sampled_texture_type, 0, 0); + AddSampler("foo_sampler", 0, 1); + AddGlobalVariable("foo_coords", ty.f32()); + + MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler", + "foo_coords", ty.f32(), + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetSamplerResourceBindings("foo"); + ASSERT_TRUE(inspector.has_error()) << inspector.error(); +} + +TEST_F(InspectorGetSamplerResourceBindingsTest, SkipsComparisonSamplers) { + auto* depth_texture_type = ty.depth_texture(ast::TextureDimension::k2d); + AddResource("foo_texture", depth_texture_type, 0, 0); + AddComparisonSampler("foo_sampler", 0, 1); + AddGlobalVariable("foo_coords", ty.vec2<f32>()); + AddGlobalVariable("foo_depth", ty.f32()); + + MakeComparisonSamplerReferenceBodyFunction( + "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_depth", ty.f32(), + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetSamplerResourceBindings("ep"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(0u, result.size()); +} + +TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, Simple) { + auto* depth_texture_type = ty.depth_texture(ast::TextureDimension::k2d); + AddResource("foo_texture", depth_texture_type, 0, 0); + AddComparisonSampler("foo_sampler", 0, 1); + AddGlobalVariable("foo_coords", ty.vec2<f32>()); + AddGlobalVariable("foo_depth", ty.f32()); + + MakeComparisonSamplerReferenceBodyFunction( + "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_depth", ty.f32(), + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetComparisonSamplerResourceBindings("ep"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + EXPECT_EQ(ResourceBinding::ResourceType::kComparisonSampler, + result[0].resource_type); + ASSERT_EQ(1u, result.size()); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(1u, result[0].binding); +} + +TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, NoSampler) { + MakeEmptyBodyFunction("ep_func", ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetComparisonSamplerResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(0u, result.size()); +} + +TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, InFunction) { + auto* depth_texture_type = ty.depth_texture(ast::TextureDimension::k2d); + AddResource("foo_texture", depth_texture_type, 0, 0); + AddComparisonSampler("foo_sampler", 0, 1); + AddGlobalVariable("foo_coords", ty.vec2<f32>()); + AddGlobalVariable("foo_depth", ty.f32()); + + MakeComparisonSamplerReferenceBodyFunction("foo_func", "foo_texture", + "foo_sampler", "foo_coords", + "foo_depth", ty.f32(), {}); + + MakeCallerBodyFunction("ep_func", {"foo_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetComparisonSamplerResourceBindings("ep_func"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + EXPECT_EQ(ResourceBinding::ResourceType::kComparisonSampler, + result[0].resource_type); + ASSERT_EQ(1u, result.size()); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(1u, result[0].binding); +} + +TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, UnknownEntryPoint) { + auto* depth_texture_type = ty.depth_texture(ast::TextureDimension::k2d); + AddResource("foo_texture", depth_texture_type, 0, 0); + AddComparisonSampler("foo_sampler", 0, 1); + AddGlobalVariable("foo_coords", ty.vec2<f32>()); + AddGlobalVariable("foo_depth", ty.f32()); + + MakeComparisonSamplerReferenceBodyFunction( + "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_depth", ty.f32(), + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetSamplerResourceBindings("foo"); + ASSERT_TRUE(inspector.has_error()) << inspector.error(); +} + +TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, SkipsSamplers) { + auto* sampled_texture_type = + ty.sampled_texture(ast::TextureDimension::k1d, ty.f32()); + AddResource("foo_texture", sampled_texture_type, 0, 0); + AddSampler("foo_sampler", 0, 1); + AddGlobalVariable("foo_coords", ty.f32()); + + MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler", + "foo_coords", ty.f32(), + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetComparisonSamplerResourceBindings("ep"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(0u, result.size()); +} + +TEST_F(InspectorGetSampledTextureResourceBindingsTest, Empty) { + MakeEmptyBodyFunction("foo", ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetSampledTextureResourceBindings("foo"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + EXPECT_EQ(0u, result.size()); +} + +TEST_P(InspectorGetSampledTextureResourceBindingsTestWithParam, textureSample) { + auto* sampled_texture_type = ty.sampled_texture( + GetParam().type_dim, GetBaseType(GetParam().sampled_kind)); + AddResource("foo_texture", sampled_texture_type, 0, 0); + AddSampler("foo_sampler", 0, 1); + auto* coord_type = GetCoordsType(GetParam().type_dim, ty.f32()); + AddGlobalVariable("foo_coords", coord_type); + + MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler", + "foo_coords", + GetBaseType(GetParam().sampled_kind), + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetSampledTextureResourceBindings("ep"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + EXPECT_EQ(ResourceBinding::ResourceType::kSampledTexture, + result[0].resource_type); + ASSERT_EQ(1u, result.size()); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(GetParam().inspector_dim, result[0].dim); + EXPECT_EQ(GetParam().sampled_kind, result[0].sampled_kind); + + // Prove that sampled and multi-sampled bindings are accounted + // for separately. + auto multisampled_result = + inspector.GetMultisampledTextureResourceBindings("ep"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_TRUE(multisampled_result.empty()); +} + +INSTANTIATE_TEST_SUITE_P( + InspectorGetSampledTextureResourceBindingsTest, + InspectorGetSampledTextureResourceBindingsTestWithParam, + testing::Values( + GetSampledTextureTestParams{ + ast::TextureDimension::k1d, + inspector::ResourceBinding::TextureDimension::k1d, + inspector::ResourceBinding::SampledKind::kFloat}, + GetSampledTextureTestParams{ + ast::TextureDimension::k2d, + inspector::ResourceBinding::TextureDimension::k2d, + inspector::ResourceBinding::SampledKind::kFloat}, + GetSampledTextureTestParams{ + ast::TextureDimension::k3d, + inspector::ResourceBinding::TextureDimension::k3d, + inspector::ResourceBinding::SampledKind::kFloat}, + GetSampledTextureTestParams{ + ast::TextureDimension::kCube, + inspector::ResourceBinding::TextureDimension::kCube, + inspector::ResourceBinding::SampledKind::kFloat})); + +TEST_P(InspectorGetSampledArrayTextureResourceBindingsTestWithParam, + textureSample) { + auto* sampled_texture_type = ty.sampled_texture( + GetParam().type_dim, GetBaseType(GetParam().sampled_kind)); + AddResource("foo_texture", sampled_texture_type, 0, 0); + AddSampler("foo_sampler", 0, 1); + auto* coord_type = GetCoordsType(GetParam().type_dim, ty.f32()); + AddGlobalVariable("foo_coords", coord_type); + AddGlobalVariable("foo_array_index", ty.i32()); + + MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler", + "foo_coords", "foo_array_index", + GetBaseType(GetParam().sampled_kind), + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetSampledTextureResourceBindings("ep"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kSampledTexture, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(GetParam().inspector_dim, result[0].dim); + EXPECT_EQ(GetParam().sampled_kind, result[0].sampled_kind); +} + +INSTANTIATE_TEST_SUITE_P( + InspectorGetSampledArrayTextureResourceBindingsTest, + InspectorGetSampledArrayTextureResourceBindingsTestWithParam, + testing::Values( + GetSampledTextureTestParams{ + ast::TextureDimension::k2dArray, + inspector::ResourceBinding::TextureDimension::k2dArray, + inspector::ResourceBinding::SampledKind::kFloat}, + GetSampledTextureTestParams{ + ast::TextureDimension::kCubeArray, + inspector::ResourceBinding::TextureDimension::kCubeArray, + inspector::ResourceBinding::SampledKind::kFloat})); + +TEST_P(InspectorGetMultisampledTextureResourceBindingsTestWithParam, + textureLoad) { + auto* multisampled_texture_type = ty.multisampled_texture( + GetParam().type_dim, GetBaseType(GetParam().sampled_kind)); + AddResource("foo_texture", multisampled_texture_type, 0, 0); + auto* coord_type = GetCoordsType(GetParam().type_dim, ty.i32()); + AddGlobalVariable("foo_coords", coord_type); + AddGlobalVariable("foo_sample_index", ty.i32()); + + Func("ep", ast::VariableList(), ty.void_(), + ast::StatementList{ + CallStmt(Call("textureLoad", "foo_texture", "foo_coords", + "foo_sample_index")), + }, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetMultisampledTextureResourceBindings("ep"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + EXPECT_EQ(ResourceBinding::ResourceType::kMultisampledTexture, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(GetParam().inspector_dim, result[0].dim); + EXPECT_EQ(GetParam().sampled_kind, result[0].sampled_kind); + + // Prove that sampled and multi-sampled bindings are accounted + // for separately. + auto single_sampled_result = + inspector.GetSampledTextureResourceBindings("ep"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_TRUE(single_sampled_result.empty()); +} + +INSTANTIATE_TEST_SUITE_P( + InspectorGetMultisampledTextureResourceBindingsTest, + InspectorGetMultisampledTextureResourceBindingsTestWithParam, + testing::Values( + GetMultisampledTextureTestParams{ + ast::TextureDimension::k2d, + inspector::ResourceBinding::TextureDimension::k2d, + inspector::ResourceBinding::SampledKind::kFloat}, + GetMultisampledTextureTestParams{ + ast::TextureDimension::k2d, + inspector::ResourceBinding::TextureDimension::k2d, + inspector::ResourceBinding::SampledKind::kSInt}, + GetMultisampledTextureTestParams{ + ast::TextureDimension::k2d, + inspector::ResourceBinding::TextureDimension::k2d, + inspector::ResourceBinding::SampledKind::kUInt})); + +TEST_F(InspectorGetMultisampledArrayTextureResourceBindingsTest, Empty) { + MakeEmptyBodyFunction("foo", ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetSampledTextureResourceBindings("foo"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + EXPECT_EQ(0u, result.size()); +} + +TEST_P(InspectorGetMultisampledArrayTextureResourceBindingsTestWithParam, + DISABLED_textureSample) { + auto* multisampled_texture_type = ty.multisampled_texture( + GetParam().type_dim, GetBaseType(GetParam().sampled_kind)); + AddResource("foo_texture", multisampled_texture_type, 0, 0); + AddSampler("foo_sampler", 0, 1); + auto* coord_type = GetCoordsType(GetParam().type_dim, ty.f32()); + AddGlobalVariable("foo_coords", coord_type); + AddGlobalVariable("foo_array_index", ty.i32()); + + MakeSamplerReferenceBodyFunction("ep", "foo_texture", "foo_sampler", + "foo_coords", "foo_array_index", + GetBaseType(GetParam().sampled_kind), + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetMultisampledTextureResourceBindings("ep"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kMultisampledTexture, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(GetParam().inspector_dim, result[0].dim); + EXPECT_EQ(GetParam().sampled_kind, result[0].sampled_kind); +} + +INSTANTIATE_TEST_SUITE_P( + InspectorGetMultisampledArrayTextureResourceBindingsTest, + InspectorGetMultisampledArrayTextureResourceBindingsTestWithParam, + testing::Values( + GetMultisampledTextureTestParams{ + ast::TextureDimension::k2dArray, + inspector::ResourceBinding::TextureDimension::k2dArray, + inspector::ResourceBinding::SampledKind::kFloat}, + GetMultisampledTextureTestParams{ + ast::TextureDimension::k2dArray, + inspector::ResourceBinding::TextureDimension::k2dArray, + inspector::ResourceBinding::SampledKind::kSInt}, + GetMultisampledTextureTestParams{ + ast::TextureDimension::k2dArray, + inspector::ResourceBinding::TextureDimension::k2dArray, + inspector::ResourceBinding::SampledKind::kUInt})); + +TEST_F(InspectorGetStorageTextureResourceBindingsTest, Empty) { + MakeEmptyBodyFunction("ep", ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetWriteOnlyStorageTextureResourceBindings("ep"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + EXPECT_EQ(0u, result.size()); +} + +TEST_P(InspectorGetStorageTextureResourceBindingsTestWithParam, Simple) { + DimensionParams dim_params; + TexelFormatParams format_params; + std::tie(dim_params, format_params) = GetParam(); + + ast::TextureDimension dim; + ResourceBinding::TextureDimension expected_dim; + std::tie(dim, expected_dim) = dim_params; + + ast::TexelFormat format; + ResourceBinding::TexelFormat expected_format; + ResourceBinding::SampledKind expected_kind; + std::tie(format, expected_format, expected_kind) = format_params; + + auto* st_type = MakeStorageTextureTypes(dim, format); + AddStorageTexture("st_var", st_type, 0, 0); + + const ast::Type* dim_type = nullptr; + switch (dim) { + case ast::TextureDimension::k1d: + dim_type = ty.i32(); + break; + case ast::TextureDimension::k2d: + case ast::TextureDimension::k2dArray: + dim_type = ty.vec2<i32>(); + break; + case ast::TextureDimension::k3d: + dim_type = ty.vec3<i32>(); + break; + default: + break; + } + + ASSERT_FALSE(dim_type == nullptr); + + MakeStorageTextureBodyFunction( + "ep", "st_var", dim_type, + ast::AttributeList{Stage(ast::PipelineStage::kFragment)}); + + Inspector& inspector = Build(); + + auto result = inspector.GetWriteOnlyStorageTextureResourceBindings("ep"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(ResourceBinding::ResourceType::kWriteOnlyStorageTexture, + result[0].resource_type); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(expected_dim, result[0].dim); + EXPECT_EQ(expected_format, result[0].image_format); + EXPECT_EQ(expected_kind, result[0].sampled_kind); +} + +INSTANTIATE_TEST_SUITE_P( + InspectorGetStorageTextureResourceBindingsTest, + InspectorGetStorageTextureResourceBindingsTestWithParam, + testing::Combine( + testing::Values( + std::make_tuple(ast::TextureDimension::k1d, + ResourceBinding::TextureDimension::k1d), + std::make_tuple(ast::TextureDimension::k2d, + ResourceBinding::TextureDimension::k2d), + std::make_tuple(ast::TextureDimension::k2dArray, + ResourceBinding::TextureDimension::k2dArray), + std::make_tuple(ast::TextureDimension::k3d, + ResourceBinding::TextureDimension::k3d)), + testing::Values( + std::make_tuple(ast::TexelFormat::kR32Float, + ResourceBinding::TexelFormat::kR32Float, + ResourceBinding::SampledKind::kFloat), + std::make_tuple(ast::TexelFormat::kR32Sint, + ResourceBinding::TexelFormat::kR32Sint, + ResourceBinding::SampledKind::kSInt), + std::make_tuple(ast::TexelFormat::kR32Uint, + ResourceBinding::TexelFormat::kR32Uint, + ResourceBinding::SampledKind::kUInt), + std::make_tuple(ast::TexelFormat::kRg32Float, + ResourceBinding::TexelFormat::kRg32Float, + ResourceBinding::SampledKind::kFloat), + std::make_tuple(ast::TexelFormat::kRg32Sint, + ResourceBinding::TexelFormat::kRg32Sint, + ResourceBinding::SampledKind::kSInt), + std::make_tuple(ast::TexelFormat::kRg32Uint, + ResourceBinding::TexelFormat::kRg32Uint, + ResourceBinding::SampledKind::kUInt), + std::make_tuple(ast::TexelFormat::kRgba16Float, + ResourceBinding::TexelFormat::kRgba16Float, + ResourceBinding::SampledKind::kFloat), + std::make_tuple(ast::TexelFormat::kRgba16Sint, + ResourceBinding::TexelFormat::kRgba16Sint, + ResourceBinding::SampledKind::kSInt), + std::make_tuple(ast::TexelFormat::kRgba16Uint, + ResourceBinding::TexelFormat::kRgba16Uint, + ResourceBinding::SampledKind::kUInt), + std::make_tuple(ast::TexelFormat::kRgba32Float, + ResourceBinding::TexelFormat::kRgba32Float, + ResourceBinding::SampledKind::kFloat), + std::make_tuple(ast::TexelFormat::kRgba32Sint, + ResourceBinding::TexelFormat::kRgba32Sint, + ResourceBinding::SampledKind::kSInt), + std::make_tuple(ast::TexelFormat::kRgba32Uint, + ResourceBinding::TexelFormat::kRgba32Uint, + ResourceBinding::SampledKind::kUInt), + std::make_tuple(ast::TexelFormat::kRgba8Sint, + ResourceBinding::TexelFormat::kRgba8Sint, + ResourceBinding::SampledKind::kSInt), + std::make_tuple(ast::TexelFormat::kRgba8Snorm, + ResourceBinding::TexelFormat::kRgba8Snorm, + ResourceBinding::SampledKind::kFloat), + std::make_tuple(ast::TexelFormat::kRgba8Uint, + ResourceBinding::TexelFormat::kRgba8Uint, + ResourceBinding::SampledKind::kUInt), + std::make_tuple(ast::TexelFormat::kRgba8Unorm, + ResourceBinding::TexelFormat::kRgba8Unorm, + ResourceBinding::SampledKind::kFloat)))); + +TEST_P(InspectorGetDepthTextureResourceBindingsTestWithParam, + textureDimensions) { + auto* depth_texture_type = ty.depth_texture(GetParam().type_dim); + AddResource("dt", depth_texture_type, 0, 0); + + Func("ep", ast::VariableList(), ty.void_(), + ast::StatementList{ + CallStmt(Call("textureDimensions", "dt")), + }, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetDepthTextureResourceBindings("ep"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + EXPECT_EQ(ResourceBinding::ResourceType::kDepthTexture, + result[0].resource_type); + ASSERT_EQ(1u, result.size()); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(GetParam().inspector_dim, result[0].dim); +} + +INSTANTIATE_TEST_SUITE_P( + InspectorGetDepthTextureResourceBindingsTest, + InspectorGetDepthTextureResourceBindingsTestWithParam, + testing::Values( + GetDepthTextureTestParams{ + ast::TextureDimension::k2d, + inspector::ResourceBinding::TextureDimension::k2d}, + GetDepthTextureTestParams{ + ast::TextureDimension::k2dArray, + inspector::ResourceBinding::TextureDimension::k2dArray}, + GetDepthTextureTestParams{ + ast::TextureDimension::kCube, + inspector::ResourceBinding::TextureDimension::kCube}, + GetDepthTextureTestParams{ + ast::TextureDimension::kCubeArray, + inspector::ResourceBinding::TextureDimension::kCubeArray})); + +TEST_F(InspectorGetDepthMultisampledTextureResourceBindingsTest, + textureDimensions) { + auto* depth_ms_texture_type = + ty.depth_multisampled_texture(ast::TextureDimension::k2d); + AddResource("tex", depth_ms_texture_type, 0, 0); + + Func("ep", ast::VariableList(), ty.void_(), + ast::StatementList{ + CallStmt(Call("textureDimensions", "tex")), + }, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetDepthMultisampledTextureResourceBindings("ep"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + EXPECT_EQ(ResourceBinding::ResourceType::kDepthMultisampledTexture, + result[0].resource_type); + ASSERT_EQ(1u, result.size()); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); + EXPECT_EQ(ResourceBinding::TextureDimension::k2d, result[0].dim); +} + +TEST_F(InspectorGetExternalTextureResourceBindingsTest, Simple) { + auto* external_texture_type = ty.external_texture(); + AddResource("et", external_texture_type, 0, 0); + + Func("ep", ast::VariableList(), ty.void_(), + ast::StatementList{ + CallStmt(Call("textureDimensions", "et")), + }, + ast::AttributeList{ + Stage(ast::PipelineStage::kFragment), + }); + + Inspector& inspector = Build(); + + auto result = inspector.GetExternalTextureResourceBindings("ep"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + EXPECT_EQ(ResourceBinding::ResourceType::kExternalTexture, + result[0].resource_type); + + ASSERT_EQ(1u, result.size()); + EXPECT_EQ(0u, result[0].bind_group); + EXPECT_EQ(0u, result[0].binding); +} + +TEST_F(InspectorGetSamplerTextureUsesTest, None) { + std::string shader = R"( +@stage(fragment) +fn main() { +})"; + + Inspector& inspector = Initialize(shader); + auto result = inspector.GetSamplerTextureUses("main"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(0u, result.size()); +} + +TEST_F(InspectorGetSamplerTextureUsesTest, Simple) { + std::string shader = R"( +@group(0) @binding(1) var mySampler: sampler; +@group(0) @binding(2) var myTexture: texture_2d<f32>; + +@stage(fragment) +fn main(@location(0) fragUV: vec2<f32>, + @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> { + return textureSample(myTexture, mySampler, fragUV) * fragPosition; +})"; + + Inspector& inspector = Initialize(shader); + auto result = inspector.GetSamplerTextureUses("main"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(0u, result[0].sampler_binding_point.group); + EXPECT_EQ(1u, result[0].sampler_binding_point.binding); + EXPECT_EQ(0u, result[0].texture_binding_point.group); + EXPECT_EQ(2u, result[0].texture_binding_point.binding); +} + +TEST_F(InspectorGetSamplerTextureUsesTest, UnknownEntryPoint) { + std::string shader = R"( +@group(0) @binding(1) var mySampler: sampler; +@group(0) @binding(2) var myTexture: texture_2d<f32>; + +@stage(fragment) +fn main(@location(0) fragUV: vec2<f32>, + @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> { + return textureSample(myTexture, mySampler, fragUV) * fragPosition; +})"; + + Inspector& inspector = Initialize(shader); + auto result = inspector.GetSamplerTextureUses("foo"); + ASSERT_TRUE(inspector.has_error()) << inspector.error(); +} + +TEST_F(InspectorGetSamplerTextureUsesTest, MultipleCalls) { + std::string shader = R"( +@group(0) @binding(1) var mySampler: sampler; +@group(0) @binding(2) var myTexture: texture_2d<f32>; + +@stage(fragment) +fn main(@location(0) fragUV: vec2<f32>, + @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> { + return textureSample(myTexture, mySampler, fragUV) * fragPosition; +})"; + + Inspector& inspector = Initialize(shader); + auto result_0 = inspector.GetSamplerTextureUses("main"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + auto result_1 = inspector.GetSamplerTextureUses("main"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + EXPECT_EQ(result_0, result_1); +} + +TEST_F(InspectorGetSamplerTextureUsesTest, BothIndirect) { + std::string shader = R"( +@group(0) @binding(1) var mySampler: sampler; +@group(0) @binding(2) var myTexture: texture_2d<f32>; + +fn doSample(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> { + return textureSample(t, s, uv); +} + +@stage(fragment) +fn main(@location(0) fragUV: vec2<f32>, + @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> { + return doSample(myTexture, mySampler, fragUV) * fragPosition; +})"; + + Inspector& inspector = Initialize(shader); + auto result = inspector.GetSamplerTextureUses("main"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(0u, result[0].sampler_binding_point.group); + EXPECT_EQ(1u, result[0].sampler_binding_point.binding); + EXPECT_EQ(0u, result[0].texture_binding_point.group); + EXPECT_EQ(2u, result[0].texture_binding_point.binding); +} + +TEST_F(InspectorGetSamplerTextureUsesTest, SamplerIndirect) { + std::string shader = R"( +@group(0) @binding(1) var mySampler: sampler; +@group(0) @binding(2) var myTexture: texture_2d<f32>; + +fn doSample(s: sampler, uv: vec2<f32>) -> vec4<f32> { + return textureSample(myTexture, s, uv); +} + +@stage(fragment) +fn main(@location(0) fragUV: vec2<f32>, + @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> { + return doSample(mySampler, fragUV) * fragPosition; +})"; + + Inspector& inspector = Initialize(shader); + auto result = inspector.GetSamplerTextureUses("main"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(0u, result[0].sampler_binding_point.group); + EXPECT_EQ(1u, result[0].sampler_binding_point.binding); + EXPECT_EQ(0u, result[0].texture_binding_point.group); + EXPECT_EQ(2u, result[0].texture_binding_point.binding); +} + +TEST_F(InspectorGetSamplerTextureUsesTest, TextureIndirect) { + std::string shader = R"( +@group(0) @binding(1) var mySampler: sampler; +@group(0) @binding(2) var myTexture: texture_2d<f32>; + +fn doSample(t: texture_2d<f32>, uv: vec2<f32>) -> vec4<f32> { + return textureSample(t, mySampler, uv); +} + +@stage(fragment) +fn main(@location(0) fragUV: vec2<f32>, + @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> { + return doSample(myTexture, fragUV) * fragPosition; +})"; + + Inspector& inspector = Initialize(shader); + auto result = inspector.GetSamplerTextureUses("main"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(0u, result[0].sampler_binding_point.group); + EXPECT_EQ(1u, result[0].sampler_binding_point.binding); + EXPECT_EQ(0u, result[0].texture_binding_point.group); + EXPECT_EQ(2u, result[0].texture_binding_point.binding); +} + +TEST_F(InspectorGetSamplerTextureUsesTest, NeitherIndirect) { + std::string shader = R"( +@group(0) @binding(1) var mySampler: sampler; +@group(0) @binding(2) var myTexture: texture_2d<f32>; + +fn doSample(uv: vec2<f32>) -> vec4<f32> { + return textureSample(myTexture, mySampler, uv); +} + +@stage(fragment) +fn main(@location(0) fragUV: vec2<f32>, + @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> { + return doSample(fragUV) * fragPosition; +})"; + + Inspector& inspector = Initialize(shader); + auto result = inspector.GetSamplerTextureUses("main"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(0u, result[0].sampler_binding_point.group); + EXPECT_EQ(1u, result[0].sampler_binding_point.binding); + EXPECT_EQ(0u, result[0].texture_binding_point.group); + EXPECT_EQ(2u, result[0].texture_binding_point.binding); +} + +TEST_F(InspectorGetSamplerTextureUsesTest, Complex) { + std::string shader = R"( +@group(0) @binding(1) var mySampler: sampler; +@group(0) @binding(2) var myTexture: texture_2d<f32>; + + +fn doSample(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> { + return textureSample(t, s, uv); +} + +fn X(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> { + return doSample(t, s, uv); +} + +fn Y(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> { + return doSample(t, s, uv); +} + +fn Z(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> { + return X(t, s, uv) + Y(t, s, uv); +} + +@stage(fragment) +fn via_call(@location(0) fragUV: vec2<f32>, + @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> { + return Z(myTexture, mySampler, fragUV) * fragPosition; +} + +@stage(fragment) +fn via_ptr(@location(0) fragUV: vec2<f32>, + @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> { + return textureSample(myTexture, mySampler, fragUV) + fragPosition; +} + +@stage(fragment) +fn direct(@location(0) fragUV: vec2<f32>, + @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> { + return textureSample(myTexture, mySampler, fragUV) + fragPosition; +})"; + + Inspector& inspector = Initialize(shader); + + { + auto result = inspector.GetSamplerTextureUses("via_call"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(0u, result[0].sampler_binding_point.group); + EXPECT_EQ(1u, result[0].sampler_binding_point.binding); + EXPECT_EQ(0u, result[0].texture_binding_point.group); + EXPECT_EQ(2u, result[0].texture_binding_point.binding); + } + + { + auto result = inspector.GetSamplerTextureUses("via_ptr"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(0u, result[0].sampler_binding_point.group); + EXPECT_EQ(1u, result[0].sampler_binding_point.binding); + EXPECT_EQ(0u, result[0].texture_binding_point.group); + EXPECT_EQ(2u, result[0].texture_binding_point.binding); + } + + { + auto result = inspector.GetSamplerTextureUses("direct"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(0u, result[0].sampler_binding_point.group); + EXPECT_EQ(1u, result[0].sampler_binding_point.binding); + EXPECT_EQ(0u, result[0].texture_binding_point.group); + EXPECT_EQ(2u, result[0].texture_binding_point.binding); + } +} + +TEST_F(InspectorGetWorkgroupStorageSizeTest, Empty) { + MakeEmptyBodyFunction("ep_func", + ast::AttributeList{Stage(ast::PipelineStage::kCompute), + WorkgroupSize(1)}); + Inspector& inspector = Build(); + EXPECT_EQ(0u, inspector.GetWorkgroupStorageSize("ep_func")); +} + +TEST_F(InspectorGetWorkgroupStorageSizeTest, Simple) { + AddWorkgroupStorage("wg_f32", ty.f32()); + MakePlainGlobalReferenceBodyFunction("f32_func", "wg_f32", ty.f32(), {}); + + MakeCallerBodyFunction("ep_func", {"f32_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kCompute), + WorkgroupSize(1), + }); + + Inspector& inspector = Build(); + EXPECT_EQ(4u, inspector.GetWorkgroupStorageSize("ep_func")); +} + +TEST_F(InspectorGetWorkgroupStorageSizeTest, CompoundTypes) { + // This struct should occupy 68 bytes. 4 from the i32 field, and another 64 + // from the 4-element array with 16-byte stride. + auto* wg_struct_type = MakeStructType( + "WgStruct", {ty.i32(), ty.array(ty.i32(), 4, /*stride=*/16)}); + AddWorkgroupStorage("wg_struct_var", ty.Of(wg_struct_type)); + MakeStructVariableReferenceBodyFunction("wg_struct_func", "wg_struct_var", + {{0, ty.i32()}}); + + // Plus another 4 bytes from this other workgroup-class f32. + AddWorkgroupStorage("wg_f32", ty.f32()); + MakePlainGlobalReferenceBodyFunction("f32_func", "wg_f32", ty.f32(), {}); + + MakeCallerBodyFunction("ep_func", {"wg_struct_func", "f32_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kCompute), + WorkgroupSize(1), + }); + + Inspector& inspector = Build(); + EXPECT_EQ(72u, inspector.GetWorkgroupStorageSize("ep_func")); +} + +TEST_F(InspectorGetWorkgroupStorageSizeTest, AlignmentPadding) { + // vec3<f32> has an alignment of 16 but a size of 12. We leverage this to test + // that our padded size calculation for workgroup storage is accurate. + AddWorkgroupStorage("wg_vec3", ty.vec3<f32>()); + MakePlainGlobalReferenceBodyFunction("wg_func", "wg_vec3", ty.vec3<f32>(), + {}); + + MakeCallerBodyFunction("ep_func", {"wg_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kCompute), + WorkgroupSize(1), + }); + + Inspector& inspector = Build(); + EXPECT_EQ(16u, inspector.GetWorkgroupStorageSize("ep_func")); +} + +TEST_F(InspectorGetWorkgroupStorageSizeTest, StructAlignment) { + // Per WGSL spec, a struct's size is the offset its last member plus the size + // of its last member, rounded up to the alignment of its largest member. So + // here the struct is expected to occupy 1024 bytes of workgroup storage. + const auto* wg_struct_type = MakeStructTypeFromMembers( + "WgStruct", + {MakeStructMember(0, ty.f32(), + {create<ast::StructMemberAlignAttribute>(1024)})}); + + AddWorkgroupStorage("wg_struct_var", ty.Of(wg_struct_type)); + MakeStructVariableReferenceBodyFunction("wg_struct_func", "wg_struct_var", + {{0, ty.f32()}}); + + MakeCallerBodyFunction("ep_func", {"wg_struct_func"}, + ast::AttributeList{ + Stage(ast::PipelineStage::kCompute), + WorkgroupSize(1), + }); + + Inspector& inspector = Build(); + EXPECT_EQ(1024u, inspector.GetWorkgroupStorageSize("ep_func")); +} + +// Crash was occuring in ::GenerateSamplerTargets, when +// ::GetSamplerTextureUses was called. +TEST_F(InspectorRegressionTest, tint967) { + std::string shader = R"( +@group(0) @binding(1) var mySampler: sampler; +@group(0) @binding(2) var myTexture: texture_2d<f32>; + +fn doSample(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> { + return textureSample(t, s, uv); +} + +@stage(fragment) +fn main(@location(0) fragUV: vec2<f32>, + @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> { + return doSample(myTexture, mySampler, fragUV) * fragPosition; +})"; + + Inspector& inspector = Initialize(shader); + auto result = inspector.GetSamplerTextureUses("main"); +} + +} // namespace +} // namespace inspector +} // namespace tint
diff --git a/src/tint/inspector/resource_binding.cc b/src/tint/inspector/resource_binding.cc new file mode 100644 index 0000000..a4a0793 --- /dev/null +++ b/src/tint/inspector/resource_binding.cc
@@ -0,0 +1,116 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/inspector/resource_binding.h" + +#include "src/tint/sem/array.h" +#include "src/tint/sem/f32_type.h" +#include "src/tint/sem/i32_type.h" +#include "src/tint/sem/matrix_type.h" +#include "src/tint/sem/type.h" +#include "src/tint/sem/u32_type.h" +#include "src/tint/sem/vector_type.h" + +namespace tint { +namespace inspector { + +ResourceBinding::TextureDimension +TypeTextureDimensionToResourceBindingTextureDimension( + const ast::TextureDimension& type_dim) { + switch (type_dim) { + case ast::TextureDimension::k1d: + return ResourceBinding::TextureDimension::k1d; + case ast::TextureDimension::k2d: + return ResourceBinding::TextureDimension::k2d; + case ast::TextureDimension::k2dArray: + return ResourceBinding::TextureDimension::k2dArray; + case ast::TextureDimension::k3d: + return ResourceBinding::TextureDimension::k3d; + case ast::TextureDimension::kCube: + return ResourceBinding::TextureDimension::kCube; + case ast::TextureDimension::kCubeArray: + return ResourceBinding::TextureDimension::kCubeArray; + case ast::TextureDimension::kNone: + return ResourceBinding::TextureDimension::kNone; + } + return ResourceBinding::TextureDimension::kNone; +} + +ResourceBinding::SampledKind BaseTypeToSampledKind(const sem::Type* base_type) { + if (!base_type) { + return ResourceBinding::SampledKind::kUnknown; + } + + if (auto* at = base_type->As<sem::Array>()) { + base_type = at->ElemType(); + } else if (auto* mt = base_type->As<sem::Matrix>()) { + base_type = mt->type(); + } else if (auto* vt = base_type->As<sem::Vector>()) { + base_type = vt->type(); + } + + if (base_type->Is<sem::F32>()) { + return ResourceBinding::SampledKind::kFloat; + } else if (base_type->Is<sem::U32>()) { + return ResourceBinding::SampledKind::kUInt; + } else if (base_type->Is<sem::I32>()) { + return ResourceBinding::SampledKind::kSInt; + } else { + return ResourceBinding::SampledKind::kUnknown; + } +} + +ResourceBinding::TexelFormat TypeTexelFormatToResourceBindingTexelFormat( + const ast::TexelFormat& image_format) { + switch (image_format) { + case ast::TexelFormat::kR32Uint: + return ResourceBinding::TexelFormat::kR32Uint; + case ast::TexelFormat::kR32Sint: + return ResourceBinding::TexelFormat::kR32Sint; + case ast::TexelFormat::kR32Float: + return ResourceBinding::TexelFormat::kR32Float; + case ast::TexelFormat::kRgba8Unorm: + return ResourceBinding::TexelFormat::kRgba8Unorm; + case ast::TexelFormat::kRgba8Snorm: + return ResourceBinding::TexelFormat::kRgba8Snorm; + case ast::TexelFormat::kRgba8Uint: + return ResourceBinding::TexelFormat::kRgba8Uint; + case ast::TexelFormat::kRgba8Sint: + return ResourceBinding::TexelFormat::kRgba8Sint; + case ast::TexelFormat::kRg32Uint: + return ResourceBinding::TexelFormat::kRg32Uint; + case ast::TexelFormat::kRg32Sint: + return ResourceBinding::TexelFormat::kRg32Sint; + case ast::TexelFormat::kRg32Float: + return ResourceBinding::TexelFormat::kRg32Float; + case ast::TexelFormat::kRgba16Uint: + return ResourceBinding::TexelFormat::kRgba16Uint; + case ast::TexelFormat::kRgba16Sint: + return ResourceBinding::TexelFormat::kRgba16Sint; + case ast::TexelFormat::kRgba16Float: + return ResourceBinding::TexelFormat::kRgba16Float; + case ast::TexelFormat::kRgba32Uint: + return ResourceBinding::TexelFormat::kRgba32Uint; + case ast::TexelFormat::kRgba32Sint: + return ResourceBinding::TexelFormat::kRgba32Sint; + case ast::TexelFormat::kRgba32Float: + return ResourceBinding::TexelFormat::kRgba32Float; + case ast::TexelFormat::kNone: + return ResourceBinding::TexelFormat::kNone; + } + return ResourceBinding::TexelFormat::kNone; +} + +} // namespace inspector +} // namespace tint
diff --git a/src/tint/inspector/resource_binding.h b/src/tint/inspector/resource_binding.h new file mode 100644 index 0000000..f2c74d7 --- /dev/null +++ b/src/tint/inspector/resource_binding.h
@@ -0,0 +1,129 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_INSPECTOR_RESOURCE_BINDING_H_ +#define SRC_TINT_INSPECTOR_RESOURCE_BINDING_H_ + +#include <cstdint> + +#include "src/tint/ast/storage_texture.h" +#include "src/tint/ast/texture.h" + +namespace tint { +namespace inspector { + +/// Container for information about how a resource is bound +struct ResourceBinding { + /// The dimensionality of a texture + enum class TextureDimension { + /// Invalid texture + kNone = -1, + /// 1 dimensional texture + k1d, + /// 2 dimensional texture + k2d, + /// 2 dimensional array texture + k2dArray, + /// 3 dimensional texture + k3d, + /// cube texture + kCube, + /// cube array texture + kCubeArray, + }; + + /// Component type of the texture's data. Same as the Sampled Type parameter + /// in SPIR-V OpTypeImage. + enum class SampledKind { kUnknown = -1, kFloat, kUInt, kSInt }; + + /// Enumerator of texel image formats + enum class TexelFormat { + kNone = -1, + + kRgba8Unorm, + kRgba8Snorm, + kRgba8Uint, + kRgba8Sint, + kRgba16Uint, + kRgba16Sint, + kRgba16Float, + kR32Uint, + kR32Sint, + kR32Float, + kRg32Uint, + kRg32Sint, + kRg32Float, + kRgba32Uint, + kRgba32Sint, + kRgba32Float, + }; + + /// kXXX maps to entries returned by GetXXXResourceBindings call. + enum class ResourceType { + kUniformBuffer, + kStorageBuffer, + kReadOnlyStorageBuffer, + kSampler, + kComparisonSampler, + kSampledTexture, + kMultisampledTexture, + kWriteOnlyStorageTexture, + kDepthTexture, + kDepthMultisampledTexture, + kExternalTexture + }; + + /// Type of resource that is bound. + ResourceType resource_type; + /// Bind group the binding belongs + uint32_t bind_group; + /// Identifier to identify this binding within the bind group + uint32_t binding; + /// Size for this binding, in bytes, if defined. + uint64_t size; + /// Size for this binding without trailing structure padding, in bytes, if + /// defined. + uint64_t size_no_padding; + /// Dimensionality of this binding, if defined. + TextureDimension dim; + /// Kind of data being sampled, if defined. + SampledKind sampled_kind; + /// Format of data, if defined. + TexelFormat image_format; +}; + +/// Convert from internal ast::TextureDimension to public +/// ResourceBinding::TextureDimension +/// @param type_dim internal value to convert from +/// @returns the publicly visible equivalent +ResourceBinding::TextureDimension +TypeTextureDimensionToResourceBindingTextureDimension( + const ast::TextureDimension& type_dim); + +/// Infer ResourceBinding::SampledKind for a given sem::Type +/// @param base_type internal type to infer from +/// @returns the publicly visible equivalent +ResourceBinding::SampledKind BaseTypeToSampledKind(const sem::Type* base_type); + +/// Convert from internal ast::TexelFormat to public +/// ResourceBinding::TexelFormat +/// @param image_format internal value to convert from +/// @returns the publicly visible equivalent +ResourceBinding::TexelFormat TypeTexelFormatToResourceBindingTexelFormat( + const ast::TexelFormat& image_format); + +} // namespace inspector +} // namespace tint + +#endif // SRC_TINT_INSPECTOR_RESOURCE_BINDING_H_
diff --git a/src/tint/inspector/scalar.cc b/src/tint/inspector/scalar.cc new file mode 100644 index 0000000..fa276f3 --- /dev/null +++ b/src/tint/inspector/scalar.cc
@@ -0,0 +1,75 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/inspector/scalar.h" + +namespace tint { +namespace inspector { + +Scalar::Scalar() : type_(kNull) {} + +Scalar::Scalar(bool val) : type_(kBool) { + value_.b = val; +} + +Scalar::Scalar(uint32_t val) : type_(kU32) { + value_.u = val; +} + +Scalar::Scalar(int32_t val) : type_(kI32) { + value_.i = val; +} + +Scalar::Scalar(float val) : type_(kFloat) { + value_.f = val; +} + +bool Scalar::IsNull() const { + return type_ == kNull; +} + +bool Scalar::IsBool() const { + return type_ == kBool; +} + +bool Scalar::IsU32() const { + return type_ == kU32; +} + +bool Scalar::IsI32() const { + return type_ == kI32; +} + +bool Scalar::IsFloat() const { + return type_ == kFloat; +} + +bool Scalar::AsBool() const { + return value_.b; +} + +uint32_t Scalar::AsU32() const { + return value_.u; +} + +int32_t Scalar::AsI32() const { + return value_.i; +} + +float Scalar::AsFloat() const { + return value_.f; +} + +} // namespace inspector +} // namespace tint
diff --git a/src/tint/inspector/scalar.h b/src/tint/inspector/scalar.h new file mode 100644 index 0000000..d4d61a0 --- /dev/null +++ b/src/tint/inspector/scalar.h
@@ -0,0 +1,80 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_INSPECTOR_SCALAR_H_ +#define SRC_TINT_INSPECTOR_SCALAR_H_ + +#include <cstdint> + +namespace tint { +namespace inspector { + +/// Contains a literal scalar value +class Scalar { + public: + /// Null Constructor + Scalar(); + /// @param val literal scalar value to contain + explicit Scalar(bool val); + /// @param val literal scalar value to contain + explicit Scalar(uint32_t val); + /// @param val literal scalar value to contain + explicit Scalar(int32_t val); + /// @param val literal scalar value to contain + explicit Scalar(float val); + + /// @returns true if this is a null + bool IsNull() const; + /// @returns true if this is a bool + bool IsBool() const; + /// @returns true if this is a unsigned integer. + bool IsU32() const; + /// @returns true if this is a signed integer. + bool IsI32() const; + /// @returns true if this is a float. + bool IsFloat() const; + + /// @returns scalar value if bool, otherwise undefined behaviour. + bool AsBool() const; + /// @returns scalar value if unsigned integer, otherwise undefined behaviour. + uint32_t AsU32() const; + /// @returns scalar value if signed integer, otherwise undefined behaviour. + int32_t AsI32() const; + /// @returns scalar value if float, otherwise undefined behaviour. + float AsFloat() const; + + private: + typedef enum { + kNull, + kBool, + kU32, + kI32, + kFloat, + } Type; + + typedef union { + bool b; + uint32_t u; + int32_t i; + float f; + } Value; + + Type type_; + Value value_; +}; + +} // namespace inspector +} // namespace tint + +#endif // SRC_TINT_INSPECTOR_SCALAR_H_
diff --git a/src/tint/inspector/test_inspector_builder.cc b/src/tint/inspector/test_inspector_builder.cc new file mode 100644 index 0000000..183dbeb --- /dev/null +++ b/src/tint/inspector/test_inspector_builder.cc
@@ -0,0 +1,399 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/inspector/test_inspector_builder.h" + +#include <memory> +#include <string> +#include <tuple> +#include <utility> +#include <vector> + +#include "gtest/gtest.h" + +namespace tint { +namespace inspector { + +InspectorBuilder::InspectorBuilder() = default; +InspectorBuilder::~InspectorBuilder() = default; + +void InspectorBuilder::MakeEmptyBodyFunction(std::string name, + ast::AttributeList attributes) { + Func(name, ast::VariableList(), ty.void_(), ast::StatementList{Return()}, + attributes); +} + +void InspectorBuilder::MakeCallerBodyFunction(std::string caller, + std::vector<std::string> callees, + ast::AttributeList attributes) { + ast::StatementList body; + body.reserve(callees.size() + 1); + for (auto callee : callees) { + body.push_back(CallStmt(Call(callee))); + } + body.push_back(Return()); + + Func(caller, ast::VariableList(), ty.void_(), body, attributes); +} + +const ast::Struct* InspectorBuilder::MakeInOutStruct( + std::string name, + std::vector<std::tuple<std::string, uint32_t>> inout_vars) { + ast::StructMemberList members; + for (auto var : inout_vars) { + std::string member_name; + uint32_t location; + std::tie(member_name, location) = var; + members.push_back( + Member(member_name, ty.u32(), {Location(location), Flat()})); + } + return Structure(name, members); +} + +const ast::Function* InspectorBuilder::MakePlainGlobalReferenceBodyFunction( + std::string func, + std::string var, + const ast::Type* type, + ast::AttributeList attributes) { + ast::StatementList stmts; + stmts.emplace_back(Decl(Var("local_" + var, type))); + stmts.emplace_back(Assign("local_" + var, var)); + stmts.emplace_back(Return()); + + return Func(func, ast::VariableList(), ty.void_(), stmts, attributes); +} + +bool InspectorBuilder::ContainsName(const std::vector<StageVariable>& vec, + const std::string& name) { + for (auto& s : vec) { + if (s.name == name) { + return true; + } + } + return false; +} + +std::string InspectorBuilder::StructMemberName(size_t idx, + const ast::Type* type) { + return std::to_string(idx) + type->FriendlyName(Symbols()); +} + +const ast::Struct* InspectorBuilder::MakeStructType( + const std::string& name, + std::vector<const ast::Type*> member_types) { + ast::StructMemberList members; + for (auto* type : member_types) { + members.push_back(MakeStructMember(members.size(), type, {})); + } + return MakeStructTypeFromMembers(name, std::move(members)); +} + +const ast::Struct* InspectorBuilder::MakeStructTypeFromMembers( + const std::string& name, + ast::StructMemberList members) { + return Structure(name, std::move(members)); +} + +const ast::StructMember* InspectorBuilder::MakeStructMember( + size_t index, + const ast::Type* type, + ast::AttributeList attributes) { + return Member(StructMemberName(index, type), type, std::move(attributes)); +} + +const ast::Struct* InspectorBuilder::MakeUniformBufferType( + const std::string& name, + std::vector<const ast::Type*> member_types) { + return MakeStructType(name, member_types); +} + +std::function<const ast::TypeName*()> InspectorBuilder::MakeStorageBufferTypes( + const std::string& name, + std::vector<const ast::Type*> member_types) { + MakeStructType(name, member_types); + return [this, name] { return ty.type_name(name); }; +} + +void InspectorBuilder::AddUniformBuffer(const std::string& name, + const ast::Type* type, + uint32_t group, + uint32_t binding) { + Global(name, type, ast::StorageClass::kUniform, + ast::AttributeList{ + create<ast::BindingAttribute>(binding), + create<ast::GroupAttribute>(group), + }); +} + +void InspectorBuilder::AddWorkgroupStorage(const std::string& name, + const ast::Type* type) { + Global(name, type, ast::StorageClass::kWorkgroup); +} + +void InspectorBuilder::AddStorageBuffer(const std::string& name, + const ast::Type* type, + ast::Access access, + uint32_t group, + uint32_t binding) { + Global(name, type, ast::StorageClass::kStorage, access, + ast::AttributeList{ + create<ast::BindingAttribute>(binding), + create<ast::GroupAttribute>(group), + }); +} + +void InspectorBuilder::MakeStructVariableReferenceBodyFunction( + std::string func_name, + std::string struct_name, + std::vector<std::tuple<size_t, const ast::Type*>> members) { + ast::StatementList stmts; + for (auto member : members) { + size_t member_idx; + const ast::Type* member_type; + std::tie(member_idx, member_type) = member; + std::string member_name = StructMemberName(member_idx, member_type); + + stmts.emplace_back(Decl(Var("local" + member_name, member_type))); + } + + for (auto member : members) { + size_t member_idx; + const ast::Type* member_type; + std::tie(member_idx, member_type) = member; + std::string member_name = StructMemberName(member_idx, member_type); + + stmts.emplace_back(Assign("local" + member_name, + MemberAccessor(struct_name, member_name))); + } + + stmts.emplace_back(Return()); + + Func(func_name, ast::VariableList(), ty.void_(), stmts, ast::AttributeList{}); +} + +void InspectorBuilder::AddSampler(const std::string& name, + uint32_t group, + uint32_t binding) { + Global(name, sampler_type(), + ast::AttributeList{ + create<ast::BindingAttribute>(binding), + create<ast::GroupAttribute>(group), + }); +} + +void InspectorBuilder::AddComparisonSampler(const std::string& name, + uint32_t group, + uint32_t binding) { + Global(name, comparison_sampler_type(), + ast::AttributeList{ + create<ast::BindingAttribute>(binding), + create<ast::GroupAttribute>(group), + }); +} + +void InspectorBuilder::AddResource(const std::string& name, + const ast::Type* type, + uint32_t group, + uint32_t binding) { + Global(name, type, + ast::AttributeList{ + create<ast::BindingAttribute>(binding), + create<ast::GroupAttribute>(group), + }); +} + +void InspectorBuilder::AddGlobalVariable(const std::string& name, + const ast::Type* type) { + Global(name, type, ast::StorageClass::kPrivate); +} + +const ast::Function* InspectorBuilder::MakeSamplerReferenceBodyFunction( + const std::string& func_name, + const std::string& texture_name, + const std::string& sampler_name, + const std::string& coords_name, + const ast::Type* base_type, + ast::AttributeList attributes) { + std::string result_name = "sampler_result"; + + ast::StatementList stmts; + stmts.emplace_back(Decl(Var(result_name, ty.vec(base_type, 4)))); + + stmts.emplace_back(Assign(result_name, Call("textureSample", texture_name, + sampler_name, coords_name))); + stmts.emplace_back(Return()); + + return Func(func_name, ast::VariableList(), ty.void_(), stmts, attributes); +} + +const ast::Function* InspectorBuilder::MakeSamplerReferenceBodyFunction( + const std::string& func_name, + const std::string& texture_name, + const std::string& sampler_name, + const std::string& coords_name, + const std::string& array_index, + const ast::Type* base_type, + ast::AttributeList attributes) { + std::string result_name = "sampler_result"; + + ast::StatementList stmts; + + stmts.emplace_back(Decl(Var("sampler_result", ty.vec(base_type, 4)))); + + stmts.emplace_back( + Assign("sampler_result", Call("textureSample", texture_name, sampler_name, + coords_name, array_index))); + stmts.emplace_back(Return()); + + return Func(func_name, ast::VariableList(), ty.void_(), stmts, attributes); +} + +const ast::Function* +InspectorBuilder::MakeComparisonSamplerReferenceBodyFunction( + const std::string& func_name, + const std::string& texture_name, + const std::string& sampler_name, + const std::string& coords_name, + const std::string& depth_name, + const ast::Type* base_type, + ast::AttributeList attributes) { + std::string result_name = "sampler_result"; + + ast::StatementList stmts; + + stmts.emplace_back(Decl(Var("sampler_result", base_type))); + stmts.emplace_back( + Assign("sampler_result", Call("textureSampleCompare", texture_name, + sampler_name, coords_name, depth_name))); + stmts.emplace_back(Return()); + + return Func(func_name, ast::VariableList(), ty.void_(), stmts, attributes); +} + +const ast::Type* InspectorBuilder::GetBaseType( + ResourceBinding::SampledKind sampled_kind) { + switch (sampled_kind) { + case ResourceBinding::SampledKind::kFloat: + return ty.f32(); + case ResourceBinding::SampledKind::kSInt: + return ty.i32(); + case ResourceBinding::SampledKind::kUInt: + return ty.u32(); + default: + return nullptr; + } +} + +const ast::Type* InspectorBuilder::GetCoordsType(ast::TextureDimension dim, + const ast::Type* scalar) { + switch (dim) { + case ast::TextureDimension::k1d: + return scalar; + case ast::TextureDimension::k2d: + case ast::TextureDimension::k2dArray: + return create<ast::Vector>(scalar, 2); + case ast::TextureDimension::k3d: + case ast::TextureDimension::kCube: + case ast::TextureDimension::kCubeArray: + return create<ast::Vector>(scalar, 3); + default: + [=]() { FAIL() << "Unsupported texture dimension: " << dim; }(); + } + return nullptr; +} + +const ast::Type* InspectorBuilder::MakeStorageTextureTypes( + ast::TextureDimension dim, + ast::TexelFormat format) { + return ty.storage_texture(dim, format, ast::Access::kWrite); +} + +void InspectorBuilder::AddStorageTexture(const std::string& name, + const ast::Type* type, + uint32_t group, + uint32_t binding) { + Global(name, type, + ast::AttributeList{ + create<ast::BindingAttribute>(binding), + create<ast::GroupAttribute>(group), + }); +} + +const ast::Function* InspectorBuilder::MakeStorageTextureBodyFunction( + const std::string& func_name, + const std::string& st_name, + const ast::Type* dim_type, + ast::AttributeList attributes) { + ast::StatementList stmts; + + stmts.emplace_back(Decl(Var("dim", dim_type))); + stmts.emplace_back(Assign("dim", Call("textureDimensions", st_name))); + stmts.emplace_back(Return()); + + return Func(func_name, ast::VariableList(), ty.void_(), stmts, attributes); +} + +std::function<const ast::Type*()> InspectorBuilder::GetTypeFunction( + ComponentType component, + CompositionType composition) { + std::function<const ast::Type*()> func; + switch (component) { + case ComponentType::kFloat: + func = [this]() -> const ast::Type* { return ty.f32(); }; + break; + case ComponentType::kSInt: + func = [this]() -> const ast::Type* { return ty.i32(); }; + break; + case ComponentType::kUInt: + func = [this]() -> const ast::Type* { return ty.u32(); }; + break; + case ComponentType::kUnknown: + return []() -> const ast::Type* { return nullptr; }; + } + + uint32_t n; + switch (composition) { + case CompositionType::kScalar: + return func; + case CompositionType::kVec2: + n = 2; + break; + case CompositionType::kVec3: + n = 3; + break; + case CompositionType::kVec4: + n = 4; + break; + default: + return []() -> ast::Type* { return nullptr; }; + } + + return [this, func, n]() -> const ast::Type* { return ty.vec(func(), n); }; +} + +Inspector& InspectorBuilder::Build() { + if (inspector_) { + return *inspector_; + } + program_ = std::make_unique<Program>(std::move(*this)); + [&]() { + ASSERT_TRUE(program_->IsValid()) + << diag::Formatter().format(program_->Diagnostics()); + }(); + inspector_ = std::make_unique<Inspector>(program_.get()); + return *inspector_; +} + +} // namespace inspector +} // namespace tint
diff --git a/src/tint/inspector/test_inspector_builder.h b/src/tint/inspector/test_inspector_builder.h new file mode 100644 index 0000000..b19066d --- /dev/null +++ b/src/tint/inspector/test_inspector_builder.h
@@ -0,0 +1,386 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_INSPECTOR_TEST_INSPECTOR_BUILDER_H_ +#define SRC_TINT_INSPECTOR_TEST_INSPECTOR_BUILDER_H_ + +#include <memory> +#include <string> +#include <tuple> +#include <vector> + +#include "src/tint/ast/call_statement.h" +#include "src/tint/ast/disable_validation_attribute.h" +#include "src/tint/ast/id_attribute.h" +#include "src/tint/ast/stage_attribute.h" +#include "src/tint/ast/workgroup_attribute.h" +#include "src/tint/program_builder.h" +#include "src/tint/sem/depth_texture_type.h" +#include "src/tint/sem/external_texture_type.h" +#include "src/tint/sem/multisampled_texture_type.h" +#include "src/tint/sem/sampled_texture_type.h" +#include "src/tint/sem/variable.h" +#include "tint/tint.h" + +namespace tint { +namespace inspector { + +/// Utility class for building programs in inspector tests +class InspectorBuilder : public ProgramBuilder { + public: + InspectorBuilder(); + ~InspectorBuilder() override; + + /// Generates an empty function + /// @param name name of the function created + /// @param attributes the function attributes + void MakeEmptyBodyFunction(std::string name, ast::AttributeList attributes); + + /// Generates a function that calls other functions + /// @param caller name of the function created + /// @param callees names of the functions to be called + /// @param attributes the function attributes + void MakeCallerBodyFunction(std::string caller, + std::vector<std::string> callees, + ast::AttributeList attributes); + + /// Generates a struct that contains user-defined IO members + /// @param name the name of the generated struct + /// @param inout_vars tuples of {name, loc} that will be the struct members + /// @returns a structure object + const ast::Struct* MakeInOutStruct( + std::string name, + std::vector<std::tuple<std::string, uint32_t>> inout_vars); + + // TODO(crbug.com/tint/697): Remove this. + /// Add In/Out variables to the global variables + /// @param inout_vars tuples of {in, out} that will be added as entries to the + /// global variables + void AddInOutVariables( + std::vector<std::tuple<std::string, std::string>> inout_vars); + + // TODO(crbug.com/tint/697): Remove this. + /// Generates a function that references in/out variables + /// @param name name of the function created + /// @param inout_vars tuples of {in, out} that will be converted into out = in + /// calls in the function body + /// @param attributes the function attributes + void MakeInOutVariableBodyFunction( + std::string name, + std::vector<std::tuple<std::string, std::string>> inout_vars, + ast::AttributeList attributes); + + // TODO(crbug.com/tint/697): Remove this. + /// Generates a function that references in/out variables and calls another + /// function. + /// @param caller name of the function created + /// @param callee name of the function to be called + /// @param inout_vars tuples of {in, out} that will be converted into out = in + /// calls in the function body + /// @param attributes the function attributes + /// @returns a function object + const ast::Function* MakeInOutVariableCallerBodyFunction( + std::string caller, + std::string callee, + std::vector<std::tuple<std::string, std::string>> inout_vars, + ast::AttributeList attributes); + + /// Add a pipeline constant to the global variables, with a specific ID. + /// @param name name of the variable to add + /// @param id id number for the constant id + /// @param type type of the variable + /// @param constructor val to initialize the constant with, if NULL no + /// constructor will be added. + /// @returns the constant that was created + const ast::Variable* AddOverridableConstantWithID( + std::string name, + uint32_t id, + const ast::Type* type, + const ast::Expression* constructor) { + return Override(name, type, constructor, {Id(id)}); + } + + /// Add a pipeline constant to the global variables, without a specific ID. + /// @param name name of the variable to add + /// @param type type of the variable + /// @param constructor val to initialize the constant with, if NULL no + /// constructor will be added. + /// @returns the constant that was created + const ast::Variable* AddOverridableConstantWithoutID( + std::string name, + const ast::Type* type, + const ast::Expression* constructor) { + return Override(name, type, constructor); + } + + /// Generates a function that references module-scoped, plain-typed constant + /// or variable. + /// @param func name of the function created + /// @param var name of the constant to be reference + /// @param type type of the const being referenced + /// @param attributes the function attributes + /// @returns a function object + const ast::Function* MakePlainGlobalReferenceBodyFunction( + std::string func, + std::string var, + const ast::Type* type, + ast::AttributeList attributes); + + /// @param vec Vector of StageVariable to be searched + /// @param name Name to be searching for + /// @returns true if name is in vec, otherwise false + bool ContainsName(const std::vector<StageVariable>& vec, + const std::string& name); + + /// Builds a string for accessing a member in a generated struct + /// @param idx index of member + /// @param type type of member + /// @returns a string for the member + std::string StructMemberName(size_t idx, const ast::Type* type); + + /// Generates a struct type + /// @param name name for the type + /// @param member_types a vector of member types + /// @returns a struct type + const ast::Struct* MakeStructType(const std::string& name, + std::vector<const ast::Type*> member_types); + + /// Generates a struct type from a list of member nodes. + /// @param name name for the struct type + /// @param members a vector of members + /// @returns a struct type + const ast::Struct* MakeStructTypeFromMembers(const std::string& name, + ast::StructMemberList members); + + /// Generates a struct member with a specified index and type. + /// @param index index of the field within the struct + /// @param type the type of the member field + /// @param attributes a list of attributes to apply to the member field + /// @returns a struct member + const ast::StructMember* MakeStructMember(size_t index, + const ast::Type* type, + ast::AttributeList attributes); + + /// Generates types appropriate for using in an uniform buffer + /// @param name name for the type + /// @param member_types a vector of member types + /// @returns a struct type that has the layout for an uniform buffer. + const ast::Struct* MakeUniformBufferType( + const std::string& name, + std::vector<const ast::Type*> member_types); + + /// Generates types appropriate for using in a storage buffer + /// @param name name for the type + /// @param member_types a vector of member types + /// @returns a function that returns the created structure. + std::function<const ast::TypeName*()> MakeStorageBufferTypes( + const std::string& name, + std::vector<const ast::Type*> member_types); + + /// Adds an uniform buffer variable to the program + /// @param name the name of the variable + /// @param type the type to use + /// @param group the binding/group/ to use for the uniform buffer + /// @param binding the binding number to use for the uniform buffer + void AddUniformBuffer(const std::string& name, + const ast::Type* type, + uint32_t group, + uint32_t binding); + + /// Adds a workgroup storage variable to the program + /// @param name the name of the variable + /// @param type the type of the variable + void AddWorkgroupStorage(const std::string& name, const ast::Type* type); + + /// Adds a storage buffer variable to the program + /// @param name the name of the variable + /// @param type the type to use + /// @param access the storage buffer access control + /// @param group the binding/group to use for the storage buffer + /// @param binding the binding number to use for the storage buffer + void AddStorageBuffer(const std::string& name, + const ast::Type* type, + ast::Access access, + uint32_t group, + uint32_t binding); + + /// Generates a function that references a specific struct variable + /// @param func_name name of the function created + /// @param struct_name name of the struct variabler to be accessed + /// @param members list of members to access, by index and type + void MakeStructVariableReferenceBodyFunction( + std::string func_name, + std::string struct_name, + std::vector<std::tuple<size_t, const ast::Type*>> members); + + /// Adds a regular sampler variable to the program + /// @param name the name of the variable + /// @param group the binding/group to use for the storage buffer + /// @param binding the binding number to use for the storage buffer + void AddSampler(const std::string& name, uint32_t group, uint32_t binding); + + /// Adds a comparison sampler variable to the program + /// @param name the name of the variable + /// @param group the binding/group to use for the storage buffer + /// @param binding the binding number to use for the storage buffer + void AddComparisonSampler(const std::string& name, + uint32_t group, + uint32_t binding); + + /// Adds a sampler or texture variable to the program + /// @param name the name of the variable + /// @param type the type to use + /// @param group the binding/group to use for the resource + /// @param binding the binding number to use for the resource + void AddResource(const std::string& name, + const ast::Type* type, + uint32_t group, + uint32_t binding); + + /// Add a module scope private variable to the progames + /// @param name the name of the variable + /// @param type the type to use + void AddGlobalVariable(const std::string& name, const ast::Type* type); + + /// Generates a function that references a specific sampler variable + /// @param func_name name of the function created + /// @param texture_name name of the texture to be sampled + /// @param sampler_name name of the sampler to use + /// @param coords_name name of the coords variable to use + /// @param base_type sampler base type + /// @param attributes the function attributes + /// @returns a function that references all of the values specified + const ast::Function* MakeSamplerReferenceBodyFunction( + const std::string& func_name, + const std::string& texture_name, + const std::string& sampler_name, + const std::string& coords_name, + const ast::Type* base_type, + ast::AttributeList attributes); + + /// Generates a function that references a specific sampler variable + /// @param func_name name of the function created + /// @param texture_name name of the texture to be sampled + /// @param sampler_name name of the sampler to use + /// @param coords_name name of the coords variable to use + /// @param array_index name of the array index variable to use + /// @param base_type sampler base type + /// @param attributes the function attributes + /// @returns a function that references all of the values specified + const ast::Function* MakeSamplerReferenceBodyFunction( + const std::string& func_name, + const std::string& texture_name, + const std::string& sampler_name, + const std::string& coords_name, + const std::string& array_index, + const ast::Type* base_type, + ast::AttributeList attributes); + + /// Generates a function that references a specific comparison sampler + /// variable. + /// @param func_name name of the function created + /// @param texture_name name of the depth texture to use + /// @param sampler_name name of the sampler to use + /// @param coords_name name of the coords variable to use + /// @param depth_name name of the depth reference to use + /// @param base_type sampler base type + /// @param attributes the function attributes + /// @returns a function that references all of the values specified + const ast::Function* MakeComparisonSamplerReferenceBodyFunction( + const std::string& func_name, + const std::string& texture_name, + const std::string& sampler_name, + const std::string& coords_name, + const std::string& depth_name, + const ast::Type* base_type, + ast::AttributeList attributes); + + /// Gets an appropriate type for the data in a given texture type. + /// @param sampled_kind type of in the texture + /// @returns a pointer to a type appropriate for the coord param + const ast::Type* GetBaseType(ResourceBinding::SampledKind sampled_kind); + + /// Gets an appropriate type for the coords parameter depending the the + /// dimensionality of the texture being sampled. + /// @param dim dimensionality of the texture being sampled + /// @param scalar the scalar type + /// @returns a pointer to a type appropriate for the coord param + const ast::Type* GetCoordsType(ast::TextureDimension dim, + const ast::Type* scalar); + + /// Generates appropriate types for a Read-Only StorageTexture + /// @param dim the texture dimension of the storage texture + /// @param format the texel format of the storage texture + /// @returns the storage texture type + const ast::Type* MakeStorageTextureTypes(ast::TextureDimension dim, + ast::TexelFormat format); + + /// Adds a storage texture variable to the program + /// @param name the name of the variable + /// @param type the type to use + /// @param group the binding/group to use for the sampled texture + /// @param binding the binding57 number to use for the sampled texture + void AddStorageTexture(const std::string& name, + const ast::Type* type, + uint32_t group, + uint32_t binding); + + /// Generates a function that references a storage texture variable. + /// @param func_name name of the function created + /// @param st_name name of the storage texture to use + /// @param dim_type type expected by textureDimensons to return + /// @param attributes the function attributes + /// @returns a function that references all of the values specified + const ast::Function* MakeStorageTextureBodyFunction( + const std::string& func_name, + const std::string& st_name, + const ast::Type* dim_type, + ast::AttributeList attributes); + + /// Get a generator function that returns a type appropriate for a stage + /// variable with the given combination of component and composition type. + /// @param component component type of the stage variable + /// @param composition composition type of the stage variable + /// @returns a generator function for the stage variable's type. + std::function<const ast::Type*()> GetTypeFunction( + ComponentType component, + CompositionType composition); + + /// Build the Program given all of the previous methods called and return an + /// Inspector for it. + /// Should only be called once per test. + /// @returns a reference to the Inspector for the built Program. + Inspector& Build(); + + /// @returns the type for a SamplerKind::kSampler + const ast::Sampler* sampler_type() { + return ty.sampler(ast::SamplerKind::kSampler); + } + + /// @returns the type for a SamplerKind::kComparison + const ast::Sampler* comparison_sampler_type() { + return ty.sampler(ast::SamplerKind::kComparisonSampler); + } + + protected: + /// Program built by this builder. + std::unique_ptr<Program> program_; + /// Inspector for |program_| + std::unique_ptr<Inspector> inspector_; +}; + +} // namespace inspector +} // namespace tint + +#endif // SRC_TINT_INSPECTOR_TEST_INSPECTOR_BUILDER_H_
diff --git a/src/tint/inspector/test_inspector_runner.cc b/src/tint/inspector/test_inspector_runner.cc new file mode 100644 index 0000000..5b937eb --- /dev/null +++ b/src/tint/inspector/test_inspector_runner.cc
@@ -0,0 +1,39 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/inspector/test_inspector_runner.h" + +namespace tint { +namespace inspector { + +InspectorRunner::InspectorRunner() = default; +InspectorRunner::~InspectorRunner() = default; + +Inspector& InspectorRunner::Initialize(std::string shader) { + if (inspector_) { + return *inspector_; + } + + file_ = std::make_unique<Source::File>("test", shader); + program_ = std::make_unique<Program>(reader::wgsl::Parse(file_.get())); + [&]() { + ASSERT_TRUE(program_->IsValid()) + << diag::Formatter().format(program_->Diagnostics()); + }(); + inspector_ = std::make_unique<Inspector>(program_.get()); + return *inspector_; +} + +} // namespace inspector +} // namespace tint
diff --git a/src/tint/inspector/test_inspector_runner.h b/src/tint/inspector/test_inspector_runner.h new file mode 100644 index 0000000..0d435d1 --- /dev/null +++ b/src/tint/inspector/test_inspector_runner.h
@@ -0,0 +1,51 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_INSPECTOR_TEST_INSPECTOR_RUNNER_H_ +#define SRC_TINT_INSPECTOR_TEST_INSPECTOR_RUNNER_H_ + +#include <memory> +#include <string> + +#include "gtest/gtest.h" +#include "tint/tint.h" + +namespace tint { +namespace inspector { + +/// Utility class for running shaders in inspector tests +class InspectorRunner { + public: + InspectorRunner(); + virtual ~InspectorRunner(); + + /// Create a Program with Inspector from the provided WGSL shader. + /// Should only be called once per test. + /// @param shader a WGSL shader + /// @returns a reference to the Inspector for the built Program. + Inspector& Initialize(std::string shader); + + protected: + /// File created from input shader and used to create Program. + std::unique_ptr<Source::File> file_; + /// Program created by this runner. + std::unique_ptr<Program> program_; + /// Inspector for |program_| + std::unique_ptr<Inspector> inspector_; +}; + +} // namespace inspector +} // namespace tint + +#endif // SRC_TINT_INSPECTOR_TEST_INSPECTOR_RUNNER_H_
diff --git a/src/tint/program.cc b/src/tint/program.cc new file mode 100644 index 0000000..a6a6ab7 --- /dev/null +++ b/src/tint/program.cc
@@ -0,0 +1,131 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/program.h" + +#include <utility> + +#include "src/tint/demangler.h" +#include "src/tint/resolver/resolver.h" +#include "src/tint/sem/expression.h" + +namespace tint { +namespace { + +std::string DefaultPrinter(const Program*) { + return "<no program printer assigned>"; +} + +} // namespace + +Program::Printer Program::printer = DefaultPrinter; + +Program::Program() = default; + +Program::Program(Program&& program) + : id_(std::move(program.id_)), + types_(std::move(program.types_)), + ast_nodes_(std::move(program.ast_nodes_)), + sem_nodes_(std::move(program.sem_nodes_)), + ast_(std::move(program.ast_)), + sem_(std::move(program.sem_)), + symbols_(std::move(program.symbols_)), + diagnostics_(std::move(program.diagnostics_)), + is_valid_(program.is_valid_) { + program.AssertNotMoved(); + program.moved_ = true; +} + +Program::Program(ProgramBuilder&& builder) { + id_ = builder.ID(); + + is_valid_ = builder.IsValid(); + if (builder.ResolveOnBuild() && builder.IsValid()) { + resolver::Resolver resolver(&builder); + if (!resolver.Resolve()) { + is_valid_ = false; + } + } + + // The above must be called *before* the calls to std::move() below + types_ = std::move(builder.Types()); + ast_nodes_ = std::move(builder.ASTNodes()); + sem_nodes_ = std::move(builder.SemNodes()); + ast_ = &builder.AST(); // ast::Module is actually a heap allocation. + sem_ = std::move(builder.Sem()); + symbols_ = std::move(builder.Symbols()); + diagnostics_.add(std::move(builder.Diagnostics())); + builder.MarkAsMoved(); + + if (!is_valid_ && !diagnostics_.contains_errors()) { + // If the builder claims to be invalid, then we really should have an error + // message generated. If we find a situation where the program is not valid + // and there are no errors reported, add one here. + diagnostics_.add_error(diag::System::Program, "invalid program generated"); + } +} + +Program::~Program() = default; + +Program& Program::operator=(Program&& program) { + program.AssertNotMoved(); + program.moved_ = true; + moved_ = false; + id_ = std::move(program.id_); + types_ = std::move(program.types_); + ast_nodes_ = std::move(program.ast_nodes_); + sem_nodes_ = std::move(program.sem_nodes_); + ast_ = std::move(program.ast_); + sem_ = std::move(program.sem_); + symbols_ = std::move(program.symbols_); + diagnostics_ = std::move(program.diagnostics_); + is_valid_ = program.is_valid_; + return *this; +} + +Program Program::Clone() const { + AssertNotMoved(); + return Program(CloneAsBuilder()); +} + +ProgramBuilder Program::CloneAsBuilder() const { + AssertNotMoved(); + ProgramBuilder out; + CloneContext(&out, this).Clone(); + return out; +} + +bool Program::IsValid() const { + AssertNotMoved(); + return is_valid_; +} + +const sem::Type* Program::TypeOf(const ast::Expression* expr) const { + auto* sem = Sem().Get(expr); + return sem ? sem->Type() : nullptr; +} + +const sem::Type* Program::TypeOf(const ast::Type* type) const { + return Sem().Get(type); +} + +const sem::Type* Program::TypeOf(const ast::TypeDecl* type_decl) const { + return Sem().Get(type_decl); +} + +void Program::AssertNotMoved() const { + TINT_ASSERT(Program, !moved_); +} + +} // namespace tint
diff --git a/src/tint/program.h b/src/tint/program.h new file mode 100644 index 0000000..23e0945 --- /dev/null +++ b/src/tint/program.h
@@ -0,0 +1,180 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_PROGRAM_H_ +#define SRC_TINT_PROGRAM_H_ + +#include <string> +#include <unordered_set> + +#include "src/tint/ast/function.h" +#include "src/tint/program_id.h" +#include "src/tint/sem/info.h" +#include "src/tint/sem/type_manager.h" +#include "src/tint/symbol_table.h" + +namespace tint { + +// Forward declarations +class CloneContext; + +namespace ast { + +class Module; + +} // namespace ast + +/// Program holds the AST, Type information and SymbolTable for a tint program. +class Program { + public: + /// ASTNodeAllocator is an alias to BlockAllocator<ast::Node> + using ASTNodeAllocator = utils::BlockAllocator<ast::Node>; + + /// SemNodeAllocator is an alias to BlockAllocator<sem::Node> + using SemNodeAllocator = utils::BlockAllocator<sem::Node>; + + /// Constructor + Program(); + + /// Move constructor + /// @param rhs the Program to move + Program(Program&& rhs); + + /// Move constructor from builder + /// @param builder the builder used to construct the program + explicit Program(ProgramBuilder&& builder); + + /// Destructor + ~Program(); + + /// Move assignment operator + /// @param rhs the Program to move + /// @return this Program + Program& operator=(Program&& rhs); + + /// @returns the unique identifier for this program + ProgramID ID() const { return id_; } + + /// @returns a reference to the program's types + const sem::Manager& Types() const { + AssertNotMoved(); + return types_; + } + + /// @returns a reference to the program's AST nodes storage + const ASTNodeAllocator& ASTNodes() const { + AssertNotMoved(); + return ast_nodes_; + } + + /// @returns a reference to the program's semantic nodes storage + const SemNodeAllocator& SemNodes() const { + AssertNotMoved(); + return sem_nodes_; + } + + /// @returns a reference to the program's AST root Module + const ast::Module& AST() const { + AssertNotMoved(); + return *ast_; + } + + /// @returns a reference to the program's semantic info + const sem::Info& Sem() const { + AssertNotMoved(); + return sem_; + } + + /// @returns a reference to the program's SymbolTable + const SymbolTable& Symbols() const { + AssertNotMoved(); + return symbols_; + } + + /// @returns a reference to the program's diagnostics + const diag::List& Diagnostics() const { + AssertNotMoved(); + return diagnostics_; + } + + /// Performs a deep clone of this program. + /// The returned Program will contain no pointers to objects owned by this + /// Program, and so after calling, this Program can be safely destructed. + /// @return a new Program copied from this Program + Program Clone() const; + + /// Performs a deep clone of this Program's AST nodes, types and symbols into + /// a new ProgramBuilder. Semantic nodes are not cloned, as these will be + /// rebuilt when the ProgramBuilder builds its Program. + /// The returned ProgramBuilder will contain no pointers to objects owned by + /// this Program, and so after calling, this Program can be safely destructed. + /// @return a new ProgramBuilder copied from this Program + ProgramBuilder CloneAsBuilder() const; + + /// @returns true if the program has no error diagnostics and is not missing + /// information + bool IsValid() const; + + /// Helper for returning the resolved semantic type of the expression `expr`. + /// @param expr the AST expression + /// @return the resolved semantic type for the expression, or nullptr if the + /// expression has no resolved type. + const sem::Type* TypeOf(const ast::Expression* expr) const; + + /// Helper for returning the resolved semantic type of the AST type `type`. + /// @param type the AST type + /// @return the resolved semantic type for the type, or nullptr if the type + /// has no resolved type. + const sem::Type* TypeOf(const ast::Type* type) const; + + /// Helper for returning the resolved semantic type of the AST type + /// declaration `type_decl`. + /// @param type_decl the AST type declaration + /// @return the resolved semantic type for the type declaration, or nullptr if + /// the type declaration has no resolved type. + const sem::Type* TypeOf(const ast::TypeDecl* type_decl) const; + + /// A function that can be used to print a program + using Printer = std::string (*)(const Program*); + + /// The Program printer used for testing and debugging. + static Printer printer; + + private: + Program(const Program&) = delete; + + /// Asserts that the program has not been moved. + void AssertNotMoved() const; + + ProgramID id_; + sem::Manager types_; + ASTNodeAllocator ast_nodes_; + SemNodeAllocator sem_nodes_; + ast::Module* ast_ = nullptr; + sem::Info sem_; + SymbolTable symbols_{id_}; + diag::List diagnostics_; + bool is_valid_ = false; // Not valid until it is built + bool moved_ = false; +}; + +/// @param program the Program +/// @returns the ProgramID of the Program +inline ProgramID ProgramIDOf(const Program* program) { + return program->ID(); +} + +} // namespace tint + +#endif // SRC_TINT_PROGRAM_H_
diff --git a/src/tint/program_builder.cc b/src/tint/program_builder.cc new file mode 100644 index 0000000..c2f58ec --- /dev/null +++ b/src/tint/program_builder.cc
@@ -0,0 +1,138 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/program_builder.h" + +#include "src/tint/ast/assignment_statement.h" +#include "src/tint/ast/call_statement.h" +#include "src/tint/ast/variable_decl_statement.h" +#include "src/tint/debug.h" +#include "src/tint/demangler.h" +#include "src/tint/sem/expression.h" +#include "src/tint/sem/variable.h" + +namespace tint { + +ProgramBuilder::VarOptionals::~VarOptionals() = default; + +ProgramBuilder::ProgramBuilder() + : id_(ProgramID::New()), + ast_(ast_nodes_.Create<ast::Module>(id_, Source{})) {} + +ProgramBuilder::ProgramBuilder(ProgramBuilder&& rhs) + : id_(std::move(rhs.id_)), + types_(std::move(rhs.types_)), + ast_nodes_(std::move(rhs.ast_nodes_)), + sem_nodes_(std::move(rhs.sem_nodes_)), + ast_(rhs.ast_), + sem_(std::move(rhs.sem_)), + symbols_(std::move(rhs.symbols_)), + diagnostics_(std::move(rhs.diagnostics_)) { + rhs.MarkAsMoved(); +} + +ProgramBuilder::~ProgramBuilder() = default; + +ProgramBuilder& ProgramBuilder::operator=(ProgramBuilder&& rhs) { + rhs.MarkAsMoved(); + AssertNotMoved(); + id_ = std::move(rhs.id_); + types_ = std::move(rhs.types_); + ast_nodes_ = std::move(rhs.ast_nodes_); + sem_nodes_ = std::move(rhs.sem_nodes_); + ast_ = rhs.ast_; + sem_ = std::move(rhs.sem_); + symbols_ = std::move(rhs.symbols_); + diagnostics_ = std::move(rhs.diagnostics_); + + return *this; +} + +ProgramBuilder ProgramBuilder::Wrap(const Program* program) { + ProgramBuilder builder; + builder.id_ = program->ID(); + builder.types_ = sem::Manager::Wrap(program->Types()); + builder.ast_ = builder.create<ast::Module>( + program->AST().source, program->AST().GlobalDeclarations()); + builder.sem_ = sem::Info::Wrap(program->Sem()); + builder.symbols_ = program->Symbols(); + builder.diagnostics_ = program->Diagnostics(); + return builder; +} + +bool ProgramBuilder::IsValid() const { + return !diagnostics_.contains_errors(); +} + +void ProgramBuilder::MarkAsMoved() { + AssertNotMoved(); + moved_ = true; +} + +void ProgramBuilder::AssertNotMoved() const { + if (moved_) { + TINT_ICE(ProgramBuilder, const_cast<ProgramBuilder*>(this)->diagnostics_) + << "Attempting to use ProgramBuilder after it has been moved"; + } +} + +const sem::Type* ProgramBuilder::TypeOf(const ast::Expression* expr) const { + auto* sem = Sem().Get(expr); + return sem ? sem->Type() : nullptr; +} + +const sem::Type* ProgramBuilder::TypeOf(const ast::Variable* var) const { + auto* sem = Sem().Get(var); + return sem ? sem->Type() : nullptr; +} + +const sem::Type* ProgramBuilder::TypeOf(const ast::Type* type) const { + return Sem().Get(type); +} + +const sem::Type* ProgramBuilder::TypeOf(const ast::TypeDecl* type_decl) const { + return Sem().Get(type_decl); +} + +const ast::TypeName* ProgramBuilder::TypesBuilder::Of( + const ast::TypeDecl* decl) const { + return type_name(decl->name); +} + +ProgramBuilder::TypesBuilder::TypesBuilder(ProgramBuilder* pb) : builder(pb) {} + +const ast::Statement* ProgramBuilder::WrapInStatement( + const ast::Expression* expr) { + // Create a temporary variable of inferred type from expr. + return Decl(Const(symbols_.New(), nullptr, expr)); +} + +const ast::VariableDeclStatement* ProgramBuilder::WrapInStatement( + const ast::Variable* v) { + return create<ast::VariableDeclStatement>(v); +} + +const ast::Statement* ProgramBuilder::WrapInStatement( + const ast::Statement* stmt) { + return stmt; +} + +const ast::Function* ProgramBuilder::WrapInFunction( + const ast::StatementList stmts) { + return Func("test_function", {}, ty.void_(), std::move(stmts), + {create<ast::StageAttribute>(ast::PipelineStage::kCompute), + WorkgroupSize(1, 1, 1)}); +} + +} // namespace tint
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h new file mode 100644 index 0000000..eb87d3e --- /dev/null +++ b/src/tint/program_builder.h
@@ -0,0 +1,2779 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_PROGRAM_BUILDER_H_ +#define SRC_TINT_PROGRAM_BUILDER_H_ + +#include <string> +#include <unordered_set> +#include <utility> + +#include "src/tint/ast/alias.h" +#include "src/tint/ast/array.h" +#include "src/tint/ast/assignment_statement.h" +#include "src/tint/ast/atomic.h" +#include "src/tint/ast/binary_expression.h" +#include "src/tint/ast/binding_attribute.h" +#include "src/tint/ast/bitcast_expression.h" +#include "src/tint/ast/bool.h" +#include "src/tint/ast/bool_literal_expression.h" +#include "src/tint/ast/break_statement.h" +#include "src/tint/ast/call_expression.h" +#include "src/tint/ast/call_statement.h" +#include "src/tint/ast/case_statement.h" +#include "src/tint/ast/compound_assignment_statement.h" +#include "src/tint/ast/continue_statement.h" +#include "src/tint/ast/depth_multisampled_texture.h" +#include "src/tint/ast/depth_texture.h" +#include "src/tint/ast/disable_validation_attribute.h" +#include "src/tint/ast/discard_statement.h" +#include "src/tint/ast/external_texture.h" +#include "src/tint/ast/f32.h" +#include "src/tint/ast/fallthrough_statement.h" +#include "src/tint/ast/float_literal_expression.h" +#include "src/tint/ast/for_loop_statement.h" +#include "src/tint/ast/i32.h" +#include "src/tint/ast/id_attribute.h" +#include "src/tint/ast/if_statement.h" +#include "src/tint/ast/index_accessor_expression.h" +#include "src/tint/ast/interpolate_attribute.h" +#include "src/tint/ast/invariant_attribute.h" +#include "src/tint/ast/loop_statement.h" +#include "src/tint/ast/matrix.h" +#include "src/tint/ast/member_accessor_expression.h" +#include "src/tint/ast/module.h" +#include "src/tint/ast/multisampled_texture.h" +#include "src/tint/ast/phony_expression.h" +#include "src/tint/ast/pointer.h" +#include "src/tint/ast/return_statement.h" +#include "src/tint/ast/sampled_texture.h" +#include "src/tint/ast/sampler.h" +#include "src/tint/ast/sint_literal_expression.h" +#include "src/tint/ast/stage_attribute.h" +#include "src/tint/ast/storage_texture.h" +#include "src/tint/ast/stride_attribute.h" +#include "src/tint/ast/struct_member_align_attribute.h" +#include "src/tint/ast/struct_member_offset_attribute.h" +#include "src/tint/ast/struct_member_size_attribute.h" +#include "src/tint/ast/switch_statement.h" +#include "src/tint/ast/type_name.h" +#include "src/tint/ast/u32.h" +#include "src/tint/ast/uint_literal_expression.h" +#include "src/tint/ast/unary_op_expression.h" +#include "src/tint/ast/variable_decl_statement.h" +#include "src/tint/ast/vector.h" +#include "src/tint/ast/void.h" +#include "src/tint/ast/workgroup_attribute.h" +#include "src/tint/program.h" +#include "src/tint/program_id.h" +#include "src/tint/sem/array.h" +#include "src/tint/sem/bool_type.h" +#include "src/tint/sem/depth_texture_type.h" +#include "src/tint/sem/external_texture_type.h" +#include "src/tint/sem/f32_type.h" +#include "src/tint/sem/i32_type.h" +#include "src/tint/sem/matrix_type.h" +#include "src/tint/sem/multisampled_texture_type.h" +#include "src/tint/sem/pointer_type.h" +#include "src/tint/sem/sampled_texture_type.h" +#include "src/tint/sem/storage_texture_type.h" +#include "src/tint/sem/struct.h" +#include "src/tint/sem/u32_type.h" +#include "src/tint/sem/vector_type.h" +#include "src/tint/sem/void_type.h" + +#ifdef INCLUDE_TINT_TINT_H_ +#error "internal tint header being #included from tint.h" +#endif + +// Forward declarations +namespace tint { +namespace ast { +class VariableDeclStatement; +} // namespace ast +} // namespace tint + +namespace tint { +class CloneContext; + +/// ProgramBuilder is a mutable builder for a Program. +/// To construct a Program, populate the builder and then `std::move` it to a +/// Program. +class ProgramBuilder { + /// A helper used to disable overloads if the first type in `TYPES` is a + /// Source. Used to avoid ambiguities in overloads that take a Source as the + /// first parameter and those that perfectly-forward the first argument. + template <typename... TYPES> + using DisableIfSource = traits::EnableIfIsNotType< + traits::Decay<traits::NthTypeOf<0, TYPES..., void>>, + Source>; + + /// VarOptionals is a helper for accepting a number of optional, extra + /// arguments for Var() and Global(). + struct VarOptionals { + template <typename... ARGS> + explicit VarOptionals(ARGS&&... args) { + Apply(std::forward<ARGS>(args)...); + } + ~VarOptionals(); + + ast::StorageClass storage = ast::StorageClass::kNone; + ast::Access access = ast::Access::kUndefined; + const ast::Expression* constructor = nullptr; + ast::AttributeList attributes = {}; + + private: + void Set(ast::StorageClass sc) { storage = sc; } + void Set(ast::Access ac) { access = ac; } + void Set(const ast::Expression* c) { constructor = c; } + void Set(const ast::AttributeList& l) { attributes = l; } + + template <typename FIRST, typename... ARGS> + void Apply(FIRST&& first, ARGS&&... args) { + Set(std::forward<FIRST>(first)); + Apply(std::forward<ARGS>(args)...); + } + void Apply() {} + }; + + public: + /// ASTNodeAllocator is an alias to BlockAllocator<ast::Node> + using ASTNodeAllocator = utils::BlockAllocator<ast::Node>; + + /// SemNodeAllocator is an alias to BlockAllocator<sem::Node> + using SemNodeAllocator = utils::BlockAllocator<sem::Node>; + + /// `i32` is a type alias to `int`. + /// Useful for passing to template methods such as `vec2<i32>()` to imitate + /// WGSL syntax. + /// Note: this is intentionally not aliased to uint32_t as we want integer + /// literals passed to the builder to match WGSL's integer literal types. + using i32 = decltype(1); + /// `u32` is a type alias to `unsigned int`. + /// Useful for passing to template methods such as `vec2<u32>()` to imitate + /// WGSL syntax. + /// Note: this is intentionally not aliased to uint32_t as we want integer + /// literals passed to the builder to match WGSL's integer literal types. + using u32 = decltype(1u); + /// `f32` is a type alias to `float` + /// Useful for passing to template methods such as `vec2<f32>()` to imitate + /// WGSL syntax. + using f32 = float; + + /// Constructor + ProgramBuilder(); + + /// Move constructor + /// @param rhs the builder to move + ProgramBuilder(ProgramBuilder&& rhs); + + /// Destructor + virtual ~ProgramBuilder(); + + /// Move assignment operator + /// @param rhs the builder to move + /// @return this builder + ProgramBuilder& operator=(ProgramBuilder&& rhs); + + /// Wrap returns a new ProgramBuilder wrapping the Program `program` without + /// making a deep clone of the Program contents. + /// ProgramBuilder returned by Wrap() is intended to temporarily extend an + /// existing immutable program. + /// As the returned ProgramBuilder wraps `program`, `program` must not be + /// destructed or assigned while using the returned ProgramBuilder. + /// TODO(bclayton) - Evaluate whether there are safer alternatives to this + /// function. See crbug.com/tint/460. + /// @param program the immutable Program to wrap + /// @return the ProgramBuilder that wraps `program` + static ProgramBuilder Wrap(const Program* program); + + /// @returns the unique identifier for this program + ProgramID ID() const { return id_; } + + /// @returns a reference to the program's types + sem::Manager& Types() { + AssertNotMoved(); + return types_; + } + + /// @returns a reference to the program's types + const sem::Manager& Types() const { + AssertNotMoved(); + return types_; + } + + /// @returns a reference to the program's AST nodes storage + ASTNodeAllocator& ASTNodes() { + AssertNotMoved(); + return ast_nodes_; + } + + /// @returns a reference to the program's AST nodes storage + const ASTNodeAllocator& ASTNodes() const { + AssertNotMoved(); + return ast_nodes_; + } + + /// @returns a reference to the program's semantic nodes storage + SemNodeAllocator& SemNodes() { + AssertNotMoved(); + return sem_nodes_; + } + + /// @returns a reference to the program's semantic nodes storage + const SemNodeAllocator& SemNodes() const { + AssertNotMoved(); + return sem_nodes_; + } + + /// @returns a reference to the program's AST root Module + ast::Module& AST() { + AssertNotMoved(); + return *ast_; + } + + /// @returns a reference to the program's AST root Module + const ast::Module& AST() const { + AssertNotMoved(); + return *ast_; + } + + /// @returns a reference to the program's semantic info + sem::Info& Sem() { + AssertNotMoved(); + return sem_; + } + + /// @returns a reference to the program's semantic info + const sem::Info& Sem() const { + AssertNotMoved(); + return sem_; + } + + /// @returns a reference to the program's SymbolTable + SymbolTable& Symbols() { + AssertNotMoved(); + return symbols_; + } + + /// @returns a reference to the program's SymbolTable + const SymbolTable& Symbols() const { + AssertNotMoved(); + return symbols_; + } + + /// @returns a reference to the program's diagnostics + diag::List& Diagnostics() { + AssertNotMoved(); + return diagnostics_; + } + + /// @returns a reference to the program's diagnostics + const diag::List& Diagnostics() const { + AssertNotMoved(); + return diagnostics_; + } + + /// Controls whether the Resolver will be run on the program when it is built. + /// @param enable the new flag value (defaults to true) + void SetResolveOnBuild(bool enable) { resolve_on_build_ = enable; } + + /// @return true if the Resolver will be run on the program when it is + /// built. + bool ResolveOnBuild() const { return resolve_on_build_; } + + /// @returns true if the program has no error diagnostics and is not missing + /// information + bool IsValid() const; + + /// Creates a new ast::Node owned by the ProgramBuilder. When the + /// ProgramBuilder is destructed, the ast::Node will also be destructed. + /// @param source the Source of the node + /// @param args the arguments to pass to the type constructor + /// @returns the node pointer + template <typename T, typename... ARGS> + traits::EnableIfIsType<T, ast::Node>* create(const Source& source, + ARGS&&... args) { + AssertNotMoved(); + return ast_nodes_.Create<T>(id_, source, std::forward<ARGS>(args)...); + } + + /// Creates a new ast::Node owned by the ProgramBuilder, injecting the current + /// Source as set by the last call to SetSource() as the only argument to the + /// constructor. + /// When the ProgramBuilder is destructed, the ast::Node will also be + /// destructed. + /// @returns the node pointer + template <typename T> + traits::EnableIfIsType<T, ast::Node>* create() { + AssertNotMoved(); + return ast_nodes_.Create<T>(id_, source_); + } + + /// Creates a new ast::Node owned by the ProgramBuilder, injecting the current + /// Source as set by the last call to SetSource() as the first argument to the + /// constructor. + /// When the ProgramBuilder is destructed, the ast::Node will also be + /// destructed. + /// @param arg0 the first arguments to pass to the type constructor + /// @param args the remaining arguments to pass to the type constructor + /// @returns the node pointer + template <typename T, typename ARG0, typename... ARGS> + traits::EnableIf</* T is ast::Node and ARG0 is not Source */ + traits::IsTypeOrDerived<T, ast::Node> && + !traits::IsTypeOrDerived<ARG0, Source>, + T>* + create(ARG0&& arg0, ARGS&&... args) { + AssertNotMoved(); + return ast_nodes_.Create<T>(id_, source_, std::forward<ARG0>(arg0), + std::forward<ARGS>(args)...); + } + + /// Creates a new sem::Node owned by the ProgramBuilder. + /// When the ProgramBuilder is destructed, the sem::Node will also be + /// destructed. + /// @param args the arguments to pass to the type constructor + /// @returns the node pointer + template <typename T, typename... ARGS> + traits::EnableIf<traits::IsTypeOrDerived<T, sem::Node> && + !traits::IsTypeOrDerived<T, sem::Type>, + T>* + create(ARGS&&... args) { + AssertNotMoved(); + return sem_nodes_.Create<T>(std::forward<ARGS>(args)...); + } + + /// Creates a new sem::Type owned by the ProgramBuilder. + /// When the ProgramBuilder is destructed, owned ProgramBuilder and the + /// returned`Type` will also be destructed. + /// Types are unique (de-aliased), and so calling create() for the same `T` + /// and arguments will return the same pointer. + /// @warning Use this method to acquire a type only if all of its type + /// information is provided in the constructor arguments `args`.<br> + /// If the type requires additional configuration after construction that + /// affect its fundamental type, build the type with `std::make_unique`, make + /// any necessary alterations and then call unique_type() instead. + /// @param args the arguments to pass to the type constructor + /// @returns the de-aliased type pointer + template <typename T, typename... ARGS> + traits::EnableIfIsType<T, sem::Type>* create(ARGS&&... args) { + static_assert(std::is_base_of<sem::Type, T>::value, + "T does not derive from sem::Type"); + AssertNotMoved(); + return types_.Get<T>(std::forward<ARGS>(args)...); + } + + /// Marks this builder as moved, preventing any further use of the builder. + void MarkAsMoved(); + + ////////////////////////////////////////////////////////////////////////////// + // TypesBuilder + ////////////////////////////////////////////////////////////////////////////// + + /// TypesBuilder holds basic `tint` types and methods for constructing + /// complex types. + class TypesBuilder { + public: + /// Constructor + /// @param builder the program builder + explicit TypesBuilder(ProgramBuilder* builder); + + /// @return the tint AST type for the C type `T`. + template <typename T> + const ast::Type* Of() const { + return CToAST<T>::get(this); + } + + /// @returns a boolean type + const ast::Bool* bool_() const { return builder->create<ast::Bool>(); } + + /// @param source the Source of the node + /// @returns a boolean type + const ast::Bool* bool_(const Source& source) const { + return builder->create<ast::Bool>(source); + } + + /// @returns a f32 type + const ast::F32* f32() const { return builder->create<ast::F32>(); } + + /// @param source the Source of the node + /// @returns a f32 type + const ast::F32* f32(const Source& source) const { + return builder->create<ast::F32>(source); + } + + /// @returns a i32 type + const ast::I32* i32() const { return builder->create<ast::I32>(); } + + /// @param source the Source of the node + /// @returns a i32 type + const ast::I32* i32(const Source& source) const { + return builder->create<ast::I32>(source); + } + + /// @returns a u32 type + const ast::U32* u32() const { return builder->create<ast::U32>(); } + + /// @param source the Source of the node + /// @returns a u32 type + const ast::U32* u32(const Source& source) const { + return builder->create<ast::U32>(source); + } + + /// @returns a void type + const ast::Void* void_() const { return builder->create<ast::Void>(); } + + /// @param source the Source of the node + /// @returns a void type + const ast::Void* void_(const Source& source) const { + return builder->create<ast::Void>(source); + } + + /// @param type vector subtype + /// @param n vector width in elements + /// @return the tint AST type for a `n`-element vector of `type`. + const ast::Vector* vec(const ast::Type* type, uint32_t n) const { + return builder->create<ast::Vector>(type, n); + } + + /// @param source the Source of the node + /// @param type vector subtype + /// @param n vector width in elements + /// @return the tint AST type for a `n`-element vector of `type`. + const ast::Vector* vec(const Source& source, + const ast::Type* type, + uint32_t n) const { + return builder->create<ast::Vector>(source, type, n); + } + + /// @param type vector subtype + /// @return the tint AST type for a 2-element vector of `type`. + const ast::Vector* vec2(const ast::Type* type) const { + return vec(type, 2u); + } + + /// @param type vector subtype + /// @return the tint AST type for a 3-element vector of `type`. + const ast::Vector* vec3(const ast::Type* type) const { + return vec(type, 3u); + } + + /// @param type vector subtype + /// @return the tint AST type for a 4-element vector of `type`. + const ast::Vector* vec4(const ast::Type* type) const { + return vec(type, 4u); + } + + /// @param n vector width in elements + /// @return the tint AST type for a `n`-element vector of `type`. + template <typename T> + const ast::Vector* vec(uint32_t n) const { + return vec(Of<T>(), n); + } + + /// @return the tint AST type for a 2-element vector of the C type `T`. + template <typename T> + const ast::Vector* vec2() const { + return vec2(Of<T>()); + } + + /// @return the tint AST type for a 3-element vector of the C type `T`. + template <typename T> + const ast::Vector* vec3() const { + return vec3(Of<T>()); + } + + /// @return the tint AST type for a 4-element vector of the C type `T`. + template <typename T> + const ast::Vector* vec4() const { + return vec4(Of<T>()); + } + + /// @param type matrix subtype + /// @param columns number of columns for the matrix + /// @param rows number of rows for the matrix + /// @return the tint AST type for a matrix of `type` + const ast::Matrix* mat(const ast::Type* type, + uint32_t columns, + uint32_t rows) const { + return builder->create<ast::Matrix>(type, rows, columns); + } + + /// @param source the Source of the node + /// @param type matrix subtype + /// @param columns number of columns for the matrix + /// @param rows number of rows for the matrix + /// @return the tint AST type for a matrix of `type` + const ast::Matrix* mat(const Source& source, + const ast::Type* type, + uint32_t columns, + uint32_t rows) const { + return builder->create<ast::Matrix>(source, type, rows, columns); + } + + /// @param type matrix subtype + /// @return the tint AST type for a 2x3 matrix of `type`. + const ast::Matrix* mat2x2(const ast::Type* type) const { + return mat(type, 2u, 2u); + } + + /// @param type matrix subtype + /// @return the tint AST type for a 2x3 matrix of `type`. + const ast::Matrix* mat2x3(const ast::Type* type) const { + return mat(type, 2u, 3u); + } + + /// @param type matrix subtype + /// @return the tint AST type for a 2x4 matrix of `type`. + const ast::Matrix* mat2x4(const ast::Type* type) const { + return mat(type, 2u, 4u); + } + + /// @param type matrix subtype + /// @return the tint AST type for a 3x2 matrix of `type`. + const ast::Matrix* mat3x2(const ast::Type* type) const { + return mat(type, 3u, 2u); + } + + /// @param type matrix subtype + /// @return the tint AST type for a 3x3 matrix of `type`. + const ast::Matrix* mat3x3(const ast::Type* type) const { + return mat(type, 3u, 3u); + } + + /// @param type matrix subtype + /// @return the tint AST type for a 3x4 matrix of `type`. + const ast::Matrix* mat3x4(const ast::Type* type) const { + return mat(type, 3u, 4u); + } + + /// @param type matrix subtype + /// @return the tint AST type for a 4x2 matrix of `type`. + const ast::Matrix* mat4x2(const ast::Type* type) const { + return mat(type, 4u, 2u); + } + + /// @param type matrix subtype + /// @return the tint AST type for a 4x3 matrix of `type`. + const ast::Matrix* mat4x3(const ast::Type* type) const { + return mat(type, 4u, 3u); + } + + /// @param type matrix subtype + /// @return the tint AST type for a 4x4 matrix of `type`. + const ast::Matrix* mat4x4(const ast::Type* type) const { + return mat(type, 4u, 4u); + } + + /// @param columns number of columns for the matrix + /// @param rows number of rows for the matrix + /// @return the tint AST type for a matrix of `type` + template <typename T> + const ast::Matrix* mat(uint32_t columns, uint32_t rows) const { + return mat(Of<T>(), columns, rows); + } + + /// @return the tint AST type for a 2x3 matrix of the C type `T`. + template <typename T> + const ast::Matrix* mat2x2() const { + return mat2x2(Of<T>()); + } + + /// @return the tint AST type for a 2x3 matrix of the C type `T`. + template <typename T> + const ast::Matrix* mat2x3() const { + return mat2x3(Of<T>()); + } + + /// @return the tint AST type for a 2x4 matrix of the C type `T`. + template <typename T> + const ast::Matrix* mat2x4() const { + return mat2x4(Of<T>()); + } + + /// @return the tint AST type for a 3x2 matrix of the C type `T`. + template <typename T> + const ast::Matrix* mat3x2() const { + return mat3x2(Of<T>()); + } + + /// @return the tint AST type for a 3x3 matrix of the C type `T`. + template <typename T> + const ast::Matrix* mat3x3() const { + return mat3x3(Of<T>()); + } + + /// @return the tint AST type for a 3x4 matrix of the C type `T`. + template <typename T> + const ast::Matrix* mat3x4() const { + return mat3x4(Of<T>()); + } + + /// @return the tint AST type for a 4x2 matrix of the C type `T`. + template <typename T> + const ast::Matrix* mat4x2() const { + return mat4x2(Of<T>()); + } + + /// @return the tint AST type for a 4x3 matrix of the C type `T`. + template <typename T> + const ast::Matrix* mat4x3() const { + return mat4x3(Of<T>()); + } + + /// @return the tint AST type for a 4x4 matrix of the C type `T`. + template <typename T> + const ast::Matrix* mat4x4() const { + return mat4x4(Of<T>()); + } + + /// @param subtype the array element type + /// @param n the array size. nullptr represents a runtime-array + /// @param attrs the optional attributes for the array + /// @return the tint AST type for a array of size `n` of type `T` + template <typename EXPR = ast::Expression*> + const ast::Array* array(const ast::Type* subtype, + EXPR&& n = nullptr, + ast::AttributeList attrs = {}) const { + return builder->create<ast::Array>( + subtype, builder->Expr(std::forward<EXPR>(n)), attrs); + } + + /// @param source the Source of the node + /// @param subtype the array element type + /// @param n the array size. nullptr represents a runtime-array + /// @param attrs the optional attributes for the array + /// @return the tint AST type for a array of size `n` of type `T` + template <typename EXPR = ast::Expression*> + const ast::Array* array(const Source& source, + const ast::Type* subtype, + EXPR&& n = nullptr, + ast::AttributeList attrs = {}) const { + return builder->create<ast::Array>( + source, subtype, builder->Expr(std::forward<EXPR>(n)), attrs); + } + + /// @param subtype the array element type + /// @param n the array size. nullptr represents a runtime-array + /// @param stride the array stride. 0 represents implicit stride + /// @return the tint AST type for a array of size `n` of type `T` + template <typename EXPR> + const ast::Array* array(const ast::Type* subtype, + EXPR&& n, + uint32_t stride) const { + ast::AttributeList attrs; + if (stride) { + attrs.emplace_back(builder->create<ast::StrideAttribute>(stride)); + } + return array(subtype, std::forward<EXPR>(n), std::move(attrs)); + } + + /// @param source the Source of the node + /// @param subtype the array element type + /// @param n the array size. nullptr represents a runtime-array + /// @param stride the array stride. 0 represents implicit stride + /// @return the tint AST type for a array of size `n` of type `T` + template <typename EXPR> + const ast::Array* array(const Source& source, + const ast::Type* subtype, + EXPR&& n, + uint32_t stride) const { + ast::AttributeList attrs; + if (stride) { + attrs.emplace_back(builder->create<ast::StrideAttribute>(stride)); + } + return array(source, subtype, std::forward<EXPR>(n), std::move(attrs)); + } + + /// @return the tint AST type for a runtime-sized array of type `T` + template <typename T> + const ast::Array* array() const { + return array(Of<T>(), nullptr); + } + + /// @return the tint AST type for an array of size `N` of type `T` + template <typename T, int N> + const ast::Array* array() const { + return array(Of<T>(), builder->Expr(N)); + } + + /// @param stride the array stride + /// @return the tint AST type for a runtime-sized array of type `T` + template <typename T> + const ast::Array* array(uint32_t stride) const { + return array(Of<T>(), nullptr, stride); + } + + /// @param stride the array stride + /// @return the tint AST type for an array of size `N` of type `T` + template <typename T, int N> + const ast::Array* array(uint32_t stride) const { + return array(Of<T>(), builder->Expr(N), stride); + } + + /// Creates a type name + /// @param name the name + /// @returns the type name + template <typename NAME> + const ast::TypeName* type_name(NAME&& name) const { + return builder->create<ast::TypeName>( + builder->Sym(std::forward<NAME>(name))); + } + + /// Creates a type name + /// @param source the Source of the node + /// @param name the name + /// @returns the type name + template <typename NAME> + const ast::TypeName* type_name(const Source& source, NAME&& name) const { + return builder->create<ast::TypeName>( + source, builder->Sym(std::forward<NAME>(name))); + } + + /// Creates an alias type + /// @param name the alias name + /// @param type the alias type + /// @returns the alias pointer + template <typename NAME> + const ast::Alias* alias(NAME&& name, const ast::Type* type) const { + auto sym = builder->Sym(std::forward<NAME>(name)); + return builder->create<ast::Alias>(sym, type); + } + + /// Creates an alias type + /// @param source the Source of the node + /// @param name the alias name + /// @param type the alias type + /// @returns the alias pointer + template <typename NAME> + const ast::Alias* alias(const Source& source, + NAME&& name, + const ast::Type* type) const { + auto sym = builder->Sym(std::forward<NAME>(name)); + return builder->create<ast::Alias>(source, sym, type); + } + + /// @param type the type of the pointer + /// @param storage_class the storage class of the pointer + /// @param access the optional access control of the pointer + /// @return the pointer to `type` with the given ast::StorageClass + const ast::Pointer* pointer( + const ast::Type* type, + ast::StorageClass storage_class, + ast::Access access = ast::Access::kUndefined) const { + return builder->create<ast::Pointer>(type, storage_class, access); + } + + /// @param source the Source of the node + /// @param type the type of the pointer + /// @param storage_class the storage class of the pointer + /// @param access the optional access control of the pointer + /// @return the pointer to `type` with the given ast::StorageClass + const ast::Pointer* pointer( + const Source& source, + const ast::Type* type, + ast::StorageClass storage_class, + ast::Access access = ast::Access::kUndefined) const { + return builder->create<ast::Pointer>(source, type, storage_class, access); + } + + /// @param storage_class the storage class of the pointer + /// @param access the optional access control of the pointer + /// @return the pointer to type `T` with the given ast::StorageClass. + template <typename T> + const ast::Pointer* pointer( + ast::StorageClass storage_class, + ast::Access access = ast::Access::kUndefined) const { + return pointer(Of<T>(), storage_class, access); + } + + /// @param source the Source of the node + /// @param type the type of the atomic + /// @return the atomic to `type` + const ast::Atomic* atomic(const Source& source, + const ast::Type* type) const { + return builder->create<ast::Atomic>(source, type); + } + + /// @param type the type of the atomic + /// @return the atomic to `type` + const ast::Atomic* atomic(const ast::Type* type) const { + return builder->create<ast::Atomic>(type); + } + + /// @return the atomic to type `T` + template <typename T> + const ast::Atomic* atomic() const { + return atomic(Of<T>()); + } + + /// @param kind the kind of sampler + /// @returns the sampler + const ast::Sampler* sampler(ast::SamplerKind kind) const { + return builder->create<ast::Sampler>(kind); + } + + /// @param source the Source of the node + /// @param kind the kind of sampler + /// @returns the sampler + const ast::Sampler* sampler(const Source& source, + ast::SamplerKind kind) const { + return builder->create<ast::Sampler>(source, kind); + } + + /// @param dims the dimensionality of the texture + /// @returns the depth texture + const ast::DepthTexture* depth_texture(ast::TextureDimension dims) const { + return builder->create<ast::DepthTexture>(dims); + } + + /// @param source the Source of the node + /// @param dims the dimensionality of the texture + /// @returns the depth texture + const ast::DepthTexture* depth_texture(const Source& source, + ast::TextureDimension dims) const { + return builder->create<ast::DepthTexture>(source, dims); + } + + /// @param dims the dimensionality of the texture + /// @returns the multisampled depth texture + const ast::DepthMultisampledTexture* depth_multisampled_texture( + ast::TextureDimension dims) const { + return builder->create<ast::DepthMultisampledTexture>(dims); + } + + /// @param source the Source of the node + /// @param dims the dimensionality of the texture + /// @returns the multisampled depth texture + const ast::DepthMultisampledTexture* depth_multisampled_texture( + const Source& source, + ast::TextureDimension dims) const { + return builder->create<ast::DepthMultisampledTexture>(source, dims); + } + + /// @param dims the dimensionality of the texture + /// @param subtype the texture subtype. + /// @returns the sampled texture + const ast::SampledTexture* sampled_texture(ast::TextureDimension dims, + const ast::Type* subtype) const { + return builder->create<ast::SampledTexture>(dims, subtype); + } + + /// @param source the Source of the node + /// @param dims the dimensionality of the texture + /// @param subtype the texture subtype. + /// @returns the sampled texture + const ast::SampledTexture* sampled_texture(const Source& source, + ast::TextureDimension dims, + const ast::Type* subtype) const { + return builder->create<ast::SampledTexture>(source, dims, subtype); + } + + /// @param dims the dimensionality of the texture + /// @param subtype the texture subtype. + /// @returns the multisampled texture + const ast::MultisampledTexture* multisampled_texture( + ast::TextureDimension dims, + const ast::Type* subtype) const { + return builder->create<ast::MultisampledTexture>(dims, subtype); + } + + /// @param source the Source of the node + /// @param dims the dimensionality of the texture + /// @param subtype the texture subtype. + /// @returns the multisampled texture + const ast::MultisampledTexture* multisampled_texture( + const Source& source, + ast::TextureDimension dims, + const ast::Type* subtype) const { + return builder->create<ast::MultisampledTexture>(source, dims, subtype); + } + + /// @param dims the dimensionality of the texture + /// @param format the texel format of the texture + /// @param access the access control of the texture + /// @returns the storage texture + const ast::StorageTexture* storage_texture(ast::TextureDimension dims, + ast::TexelFormat format, + ast::Access access) const { + auto* subtype = ast::StorageTexture::SubtypeFor(format, *builder); + return builder->create<ast::StorageTexture>(dims, format, subtype, + access); + } + + /// @param source the Source of the node + /// @param dims the dimensionality of the texture + /// @param format the texel format of the texture + /// @param access the access control of the texture + /// @returns the storage texture + const ast::StorageTexture* storage_texture(const Source& source, + ast::TextureDimension dims, + ast::TexelFormat format, + ast::Access access) const { + auto* subtype = ast::StorageTexture::SubtypeFor(format, *builder); + return builder->create<ast::StorageTexture>(source, dims, format, subtype, + access); + } + + /// @returns the external texture + const ast::ExternalTexture* external_texture() const { + return builder->create<ast::ExternalTexture>(); + } + + /// @param source the Source of the node + /// @returns the external texture + const ast::ExternalTexture* external_texture(const Source& source) const { + return builder->create<ast::ExternalTexture>(source); + } + + /// Constructs a TypeName for the type declaration. + /// @param type the type + /// @return either type or a pointer to a new ast::TypeName + const ast::TypeName* Of(const ast::TypeDecl* type) const; + + /// The ProgramBuilder + ProgramBuilder* const builder; + + private: + /// CToAST<T> is specialized for various `T` types and each specialization + /// contains a single static `get()` method for obtaining the corresponding + /// AST type for the C type `T`. + /// `get()` has the signature: + /// `static const ast::Type* get(Types* t)` + template <typename T> + struct CToAST {}; + }; + + ////////////////////////////////////////////////////////////////////////////// + // AST helper methods + ////////////////////////////////////////////////////////////////////////////// + + /// @return a new unnamed symbol + Symbol Sym() { return Symbols().New(); } + + /// @param name the symbol string + /// @return a Symbol with the given name + Symbol Sym(const std::string& name) { return Symbols().Register(name); } + + /// @param sym the symbol + /// @return `sym` + Symbol Sym(Symbol sym) { return sym; } + + /// @param expr the expression + /// @return expr + template <typename T> + traits::EnableIfIsType<T, ast::Expression>* Expr(T* expr) { + return expr; + } + + /// Passthrough for nullptr + /// @return nullptr + const ast::IdentifierExpression* Expr(std::nullptr_t) { return nullptr; } + + /// @param source the source information + /// @param symbol the identifier symbol + /// @return an ast::IdentifierExpression with the given symbol + const ast::IdentifierExpression* Expr(const Source& source, Symbol symbol) { + return create<ast::IdentifierExpression>(source, symbol); + } + + /// @param symbol the identifier symbol + /// @return an ast::IdentifierExpression with the given symbol + const ast::IdentifierExpression* Expr(Symbol symbol) { + return create<ast::IdentifierExpression>(symbol); + } + + /// @param source the source information + /// @param variable the AST variable + /// @return an ast::IdentifierExpression with the variable's symbol + const ast::IdentifierExpression* Expr(const Source& source, + const ast::Variable* variable) { + return create<ast::IdentifierExpression>(source, variable->symbol); + } + + /// @param variable the AST variable + /// @return an ast::IdentifierExpression with the variable's symbol + const ast::IdentifierExpression* Expr(const ast::Variable* variable) { + return create<ast::IdentifierExpression>(variable->symbol); + } + + /// @param source the source information + /// @param name the identifier name + /// @return an ast::IdentifierExpression with the given name + const ast::IdentifierExpression* Expr(const Source& source, + const char* name) { + return create<ast::IdentifierExpression>(source, Symbols().Register(name)); + } + + /// @param name the identifier name + /// @return an ast::IdentifierExpression with the given name + const ast::IdentifierExpression* Expr(const char* name) { + return create<ast::IdentifierExpression>(Symbols().Register(name)); + } + + /// @param source the source information + /// @param name the identifier name + /// @return an ast::IdentifierExpression with the given name + const ast::IdentifierExpression* Expr(const Source& source, + const std::string& name) { + return create<ast::IdentifierExpression>(source, Symbols().Register(name)); + } + + /// @param name the identifier name + /// @return an ast::IdentifierExpression with the given name + const ast::IdentifierExpression* Expr(const std::string& name) { + return create<ast::IdentifierExpression>(Symbols().Register(name)); + } + + /// @param source the source information + /// @param value the boolean value + /// @return a Scalar constructor for the given value + const ast::BoolLiteralExpression* Expr(const Source& source, bool value) { + return create<ast::BoolLiteralExpression>(source, value); + } + + /// @param value the boolean value + /// @return a Scalar constructor for the given value + const ast::BoolLiteralExpression* Expr(bool value) { + return create<ast::BoolLiteralExpression>(value); + } + + /// @param source the source information + /// @param value the float value + /// @return a Scalar constructor for the given value + const ast::FloatLiteralExpression* Expr(const Source& source, f32 value) { + return create<ast::FloatLiteralExpression>(source, value); + } + + /// @param value the float value + /// @return a Scalar constructor for the given value + const ast::FloatLiteralExpression* Expr(f32 value) { + return create<ast::FloatLiteralExpression>(value); + } + + /// @param source the source information + /// @param value the integer value + /// @return a Scalar constructor for the given value + const ast::SintLiteralExpression* Expr(const Source& source, i32 value) { + return create<ast::SintLiteralExpression>(source, value); + } + + /// @param value the integer value + /// @return a Scalar constructor for the given value + const ast::SintLiteralExpression* Expr(i32 value) { + return create<ast::SintLiteralExpression>(value); + } + + /// @param source the source information + /// @param value the unsigned int value + /// @return a Scalar constructor for the given value + const ast::UintLiteralExpression* Expr(const Source& source, u32 value) { + return create<ast::UintLiteralExpression>(source, value); + } + + /// @param value the unsigned int value + /// @return a Scalar constructor for the given value + const ast::UintLiteralExpression* Expr(u32 value) { + return create<ast::UintLiteralExpression>(value); + } + + /// Converts `arg` to an `ast::Expression` using `Expr()`, then appends it to + /// `list`. + /// @param list the list to append too + /// @param arg the arg to create + template <typename ARG> + void Append(ast::ExpressionList& list, ARG&& arg) { + list.emplace_back(Expr(std::forward<ARG>(arg))); + } + + /// Converts `arg0` and `args` to `ast::Expression`s using `Expr()`, + /// then appends them to `list`. + /// @param list the list to append too + /// @param arg0 the first argument + /// @param args the rest of the arguments + template <typename ARG0, typename... ARGS> + void Append(ast::ExpressionList& list, ARG0&& arg0, ARGS&&... args) { + Append(list, std::forward<ARG0>(arg0)); + Append(list, std::forward<ARGS>(args)...); + } + + /// @return an empty list of expressions + ast::ExpressionList ExprList() { return {}; } + + /// @param args the list of expressions + /// @return the list of expressions converted to `ast::Expression`s using + /// `Expr()`, + template <typename... ARGS> + ast::ExpressionList ExprList(ARGS&&... args) { + ast::ExpressionList list; + list.reserve(sizeof...(args)); + Append(list, std::forward<ARGS>(args)...); + return list; + } + + /// @param list the list of expressions + /// @return `list` + ast::ExpressionList ExprList(ast::ExpressionList list) { return list; } + + /// @param args the arguments for the type constructor + /// @return an `ast::CallExpression` of type `ty`, with the values + /// of `args` converted to `ast::Expression`s using `Expr()` + template <typename T, typename... ARGS> + const ast::CallExpression* Construct(ARGS&&... args) { + return Construct(ty.Of<T>(), std::forward<ARGS>(args)...); + } + + /// @param type the type to construct + /// @param args the arguments for the constructor + /// @return an `ast::CallExpression` of `type` constructed with the + /// values `args`. + template <typename... ARGS> + const ast::CallExpression* Construct(const ast::Type* type, ARGS&&... args) { + return Construct(source_, type, std::forward<ARGS>(args)...); + } + + /// @param source the source information + /// @param type the type to construct + /// @param args the arguments for the constructor + /// @return an `ast::CallExpression` of `type` constructed with the + /// values `args`. + template <typename... ARGS> + const ast::CallExpression* Construct(const Source& source, + const ast::Type* type, + ARGS&&... args) { + return create<ast::CallExpression>(source, type, + ExprList(std::forward<ARGS>(args)...)); + } + + /// @param expr the expression for the bitcast + /// @return an `ast::BitcastExpression` of type `ty`, with the values of + /// `expr` converted to `ast::Expression`s using `Expr()` + template <typename T, typename EXPR> + const ast::BitcastExpression* Bitcast(EXPR&& expr) { + return Bitcast(ty.Of<T>(), std::forward<EXPR>(expr)); + } + + /// @param type the type to cast to + /// @param expr the expression for the bitcast + /// @return an `ast::BitcastExpression` of `type` constructed with the values + /// `expr`. + template <typename EXPR> + const ast::BitcastExpression* Bitcast(const ast::Type* type, EXPR&& expr) { + return create<ast::BitcastExpression>(type, Expr(std::forward<EXPR>(expr))); + } + + /// @param source the source information + /// @param type the type to cast to + /// @param expr the expression for the bitcast + /// @return an `ast::BitcastExpression` of `type` constructed with the values + /// `expr`. + template <typename EXPR> + const ast::BitcastExpression* Bitcast(const Source& source, + const ast::Type* type, + EXPR&& expr) { + return create<ast::BitcastExpression>(source, type, + Expr(std::forward<EXPR>(expr))); + } + + /// @param args the arguments for the vector constructor + /// @param type the vector type + /// @param size the vector size + /// @return an `ast::CallExpression` of a `size`-element vector of + /// type `type`, constructed with the values `args`. + template <typename... ARGS> + const ast::CallExpression* vec(const ast::Type* type, + uint32_t size, + ARGS&&... args) { + return Construct(ty.vec(type, size), std::forward<ARGS>(args)...); + } + + /// @param args the arguments for the vector constructor + /// @return an `ast::CallExpression` of a 2-element vector of type + /// `T`, constructed with the values `args`. + template <typename T, typename... ARGS> + const ast::CallExpression* vec2(ARGS&&... args) { + return Construct(ty.vec2<T>(), std::forward<ARGS>(args)...); + } + + /// @param args the arguments for the vector constructor + /// @return an `ast::CallExpression` of a 3-element vector of type + /// `T`, constructed with the values `args`. + template <typename T, typename... ARGS> + const ast::CallExpression* vec3(ARGS&&... args) { + return Construct(ty.vec3<T>(), std::forward<ARGS>(args)...); + } + + /// @param args the arguments for the vector constructor + /// @return an `ast::CallExpression` of a 4-element vector of type + /// `T`, constructed with the values `args`. + template <typename T, typename... ARGS> + const ast::CallExpression* vec4(ARGS&&... args) { + return Construct(ty.vec4<T>(), std::forward<ARGS>(args)...); + } + + /// @param args the arguments for the matrix constructor + /// @return an `ast::CallExpression` of a 2x2 matrix of type + /// `T`, constructed with the values `args`. + template <typename T, typename... ARGS> + const ast::CallExpression* mat2x2(ARGS&&... args) { + return Construct(ty.mat2x2<T>(), std::forward<ARGS>(args)...); + } + + /// @param args the arguments for the matrix constructor + /// @return an `ast::CallExpression` of a 2x3 matrix of type + /// `T`, constructed with the values `args`. + template <typename T, typename... ARGS> + const ast::CallExpression* mat2x3(ARGS&&... args) { + return Construct(ty.mat2x3<T>(), std::forward<ARGS>(args)...); + } + + /// @param args the arguments for the matrix constructor + /// @return an `ast::CallExpression` of a 2x4 matrix of type + /// `T`, constructed with the values `args`. + template <typename T, typename... ARGS> + const ast::CallExpression* mat2x4(ARGS&&... args) { + return Construct(ty.mat2x4<T>(), std::forward<ARGS>(args)...); + } + + /// @param args the arguments for the matrix constructor + /// @return an `ast::CallExpression` of a 3x2 matrix of type + /// `T`, constructed with the values `args`. + template <typename T, typename... ARGS> + const ast::CallExpression* mat3x2(ARGS&&... args) { + return Construct(ty.mat3x2<T>(), std::forward<ARGS>(args)...); + } + + /// @param args the arguments for the matrix constructor + /// @return an `ast::CallExpression` of a 3x3 matrix of type + /// `T`, constructed with the values `args`. + template <typename T, typename... ARGS> + const ast::CallExpression* mat3x3(ARGS&&... args) { + return Construct(ty.mat3x3<T>(), std::forward<ARGS>(args)...); + } + + /// @param args the arguments for the matrix constructor + /// @return an `ast::CallExpression` of a 3x4 matrix of type + /// `T`, constructed with the values `args`. + template <typename T, typename... ARGS> + const ast::CallExpression* mat3x4(ARGS&&... args) { + return Construct(ty.mat3x4<T>(), std::forward<ARGS>(args)...); + } + + /// @param args the arguments for the matrix constructor + /// @return an `ast::CallExpression` of a 4x2 matrix of type + /// `T`, constructed with the values `args`. + template <typename T, typename... ARGS> + const ast::CallExpression* mat4x2(ARGS&&... args) { + return Construct(ty.mat4x2<T>(), std::forward<ARGS>(args)...); + } + + /// @param args the arguments for the matrix constructor + /// @return an `ast::CallExpression` of a 4x3 matrix of type + /// `T`, constructed with the values `args`. + template <typename T, typename... ARGS> + const ast::CallExpression* mat4x3(ARGS&&... args) { + return Construct(ty.mat4x3<T>(), std::forward<ARGS>(args)...); + } + + /// @param args the arguments for the matrix constructor + /// @return an `ast::CallExpression` of a 4x4 matrix of type + /// `T`, constructed with the values `args`. + template <typename T, typename... ARGS> + const ast::CallExpression* mat4x4(ARGS&&... args) { + return Construct(ty.mat4x4<T>(), std::forward<ARGS>(args)...); + } + + /// @param args the arguments for the array constructor + /// @return an `ast::CallExpression` of an array with element type + /// `T` and size `N`, constructed with the values `args`. + template <typename T, int N, typename... ARGS> + const ast::CallExpression* array(ARGS&&... args) { + return Construct(ty.array<T, N>(), std::forward<ARGS>(args)...); + } + + /// @param subtype the array element type + /// @param n the array size. nullptr represents a runtime-array. + /// @param args the arguments for the array constructor + /// @return an `ast::CallExpression` of an array with element type + /// `subtype`, constructed with the values `args`. + template <typename EXPR, typename... ARGS> + const ast::CallExpression* array(const ast::Type* subtype, + EXPR&& n, + ARGS&&... args) { + return Construct(ty.array(subtype, std::forward<EXPR>(n)), + std::forward<ARGS>(args)...); + } + + /// @param name the variable name + /// @param type the variable type + /// @param optional the optional variable settings. + /// Can be any of the following, in any order: + /// * ast::StorageClass - specifies the variable storage class + /// * ast::Access - specifies the variable's access control + /// * ast::Expression* - specifies the variable's initializer expression + /// * ast::AttributeList - specifies the variable's attributes + /// Note that repeated arguments of the same type will use the last argument's + /// value. + /// @returns a `ast::Variable` with the given name, type and additional + /// options + template <typename NAME, typename... OPTIONAL> + const ast::Variable* Var(NAME&& name, + const ast::Type* type, + OPTIONAL&&... optional) { + VarOptionals opts(std::forward<OPTIONAL>(optional)...); + return create<ast::Variable>(Sym(std::forward<NAME>(name)), opts.storage, + opts.access, type, false /* is_const */, + false /* is_overridable */, opts.constructor, + std::move(opts.attributes)); + } + + /// @param source the variable source + /// @param name the variable name + /// @param type the variable type + /// @param optional the optional variable settings. + /// Can be any of the following, in any order: + /// * ast::StorageClass - specifies the variable storage class + /// * ast::Access - specifies the variable's access control + /// * ast::Expression* - specifies the variable's initializer expression + /// * ast::AttributeList - specifies the variable's attributes + /// Note that repeated arguments of the same type will use the last argument's + /// value. + /// @returns a `ast::Variable` with the given name, storage and type + template <typename NAME, typename... OPTIONAL> + const ast::Variable* Var(const Source& source, + NAME&& name, + const ast::Type* type, + OPTIONAL&&... optional) { + VarOptionals opts(std::forward<OPTIONAL>(optional)...); + return create<ast::Variable>( + source, Sym(std::forward<NAME>(name)), opts.storage, opts.access, type, + false /* is_const */, false /* is_overridable */, opts.constructor, + std::move(opts.attributes)); + } + + /// @param name the variable name + /// @param type the variable type + /// @param constructor constructor expression + /// @param attributes optional variable attributes + /// @returns a constant `ast::Variable` with the given name and type + template <typename NAME> + const ast::Variable* Const(NAME&& name, + const ast::Type* type, + const ast::Expression* constructor, + ast::AttributeList attributes = {}) { + return create<ast::Variable>( + Sym(std::forward<NAME>(name)), ast::StorageClass::kNone, + ast::Access::kUndefined, type, true /* is_const */, + false /* is_overridable */, constructor, attributes); + } + + /// @param source the variable source + /// @param name the variable name + /// @param type the variable type + /// @param constructor constructor expression + /// @param attributes optional variable attributes + /// @returns a constant `ast::Variable` with the given name and type + template <typename NAME> + const ast::Variable* Const(const Source& source, + NAME&& name, + const ast::Type* type, + const ast::Expression* constructor, + ast::AttributeList attributes = {}) { + return create<ast::Variable>( + source, Sym(std::forward<NAME>(name)), ast::StorageClass::kNone, + ast::Access::kUndefined, type, true /* is_const */, + false /* is_overridable */, constructor, attributes); + } + + /// @param name the parameter name + /// @param type the parameter type + /// @param attributes optional parameter attributes + /// @returns a constant `ast::Variable` with the given name and type + template <typename NAME> + const ast::Variable* Param(NAME&& name, + const ast::Type* type, + ast::AttributeList attributes = {}) { + return create<ast::Variable>( + Sym(std::forward<NAME>(name)), ast::StorageClass::kNone, + ast::Access::kUndefined, type, true /* is_const */, + false /* is_overridable */, nullptr, attributes); + } + + /// @param source the parameter source + /// @param name the parameter name + /// @param type the parameter type + /// @param attributes optional parameter attributes + /// @returns a constant `ast::Variable` with the given name and type + template <typename NAME> + const ast::Variable* Param(const Source& source, + NAME&& name, + const ast::Type* type, + ast::AttributeList attributes = {}) { + return create<ast::Variable>( + source, Sym(std::forward<NAME>(name)), ast::StorageClass::kNone, + ast::Access::kUndefined, type, true /* is_const */, + false /* is_overridable */, nullptr, attributes); + } + + /// @param name the variable name + /// @param type the variable type + /// @param optional the optional variable settings. + /// Can be any of the following, in any order: + /// * ast::StorageClass - specifies the variable storage class + /// * ast::Access - specifies the variable's access control + /// * ast::Expression* - specifies the variable's initializer expression + /// * ast::AttributeList - specifies the variable's attributes + /// Note that repeated arguments of the same type will use the last argument's + /// value. + /// @returns a new `ast::Variable`, which is automatically registered as a + /// global variable with the ast::Module. + template <typename NAME, + typename... OPTIONAL, + typename = DisableIfSource<NAME>> + const ast::Variable* Global(NAME&& name, + const ast::Type* type, + OPTIONAL&&... optional) { + auto* var = Var(std::forward<NAME>(name), type, + std::forward<OPTIONAL>(optional)...); + AST().AddGlobalVariable(var); + return var; + } + + /// @param source the variable source + /// @param name the variable name + /// @param type the variable type + /// @param optional the optional variable settings. + /// Can be any of the following, in any order: + /// * ast::StorageClass - specifies the variable storage class + /// * ast::Access - specifies the variable's access control + /// * ast::Expression* - specifies the variable's initializer expression + /// * ast::AttributeList - specifies the variable's attributes + /// Note that repeated arguments of the same type will use the last argument's + /// value. + /// @returns a new `ast::Variable`, which is automatically registered as a + /// global variable with the ast::Module. + template <typename NAME, typename... OPTIONAL> + const ast::Variable* Global(const Source& source, + NAME&& name, + const ast::Type* type, + OPTIONAL&&... optional) { + auto* var = Var(source, std::forward<NAME>(name), type, + std::forward<OPTIONAL>(optional)...); + AST().AddGlobalVariable(var); + return var; + } + + /// @param name the variable name + /// @param type the variable type + /// @param constructor constructor expression + /// @param attributes optional variable attributes + /// @returns a const `ast::Variable` constructed by calling Var() with the + /// arguments of `args`, which is automatically registered as a global + /// variable with the ast::Module. + template <typename NAME> + const ast::Variable* GlobalConst(NAME&& name, + const ast::Type* type, + const ast::Expression* constructor, + ast::AttributeList attributes = {}) { + auto* var = Const(std::forward<NAME>(name), type, constructor, + std::move(attributes)); + AST().AddGlobalVariable(var); + return var; + } + + /// @param source the variable source + /// @param name the variable name + /// @param type the variable type + /// @param constructor constructor expression + /// @param attributes optional variable attributes + /// @returns a const `ast::Variable` constructed by calling Var() with the + /// arguments of `args`, which is automatically registered as a global + /// variable with the ast::Module. + template <typename NAME> + const ast::Variable* GlobalConst(const Source& source, + NAME&& name, + const ast::Type* type, + const ast::Expression* constructor, + ast::AttributeList attributes = {}) { + auto* var = Const(source, std::forward<NAME>(name), type, constructor, + std::move(attributes)); + AST().AddGlobalVariable(var); + return var; + } + + /// @param name the variable name + /// @param type the variable type + /// @param constructor optional constructor expression + /// @param attributes optional variable attributes + /// @returns an overridable const `ast::Variable` which is automatically + /// registered as a global variable with the ast::Module. + template <typename NAME> + const ast::Variable* Override(NAME&& name, + const ast::Type* type, + const ast::Expression* constructor, + ast::AttributeList attributes = {}) { + auto* var = create<ast::Variable>( + source_, Sym(std::forward<NAME>(name)), ast::StorageClass::kNone, + ast::Access::kUndefined, type, true /* is_const */, + true /* is_overridable */, constructor, std::move(attributes)); + AST().AddGlobalVariable(var); + return var; + } + + /// @param source the variable source + /// @param name the variable name + /// @param type the variable type + /// @param constructor constructor expression + /// @param attributes optional variable attributes + /// @returns a const `ast::Variable` constructed by calling Var() with the + /// arguments of `args`, which is automatically registered as a global + /// variable with the ast::Module. + template <typename NAME> + const ast::Variable* Override(const Source& source, + NAME&& name, + const ast::Type* type, + const ast::Expression* constructor, + ast::AttributeList attributes = {}) { + auto* var = create<ast::Variable>( + source, Sym(std::forward<NAME>(name)), ast::StorageClass::kNone, + ast::Access::kUndefined, type, true /* is_const */, + true /* is_overridable */, constructor, std::move(attributes)); + AST().AddGlobalVariable(var); + return var; + } + + /// @param source the source information + /// @param expr the expression to take the address of + /// @return an ast::UnaryOpExpression that takes the address of `expr` + template <typename EXPR> + const ast::UnaryOpExpression* AddressOf(const Source& source, EXPR&& expr) { + return create<ast::UnaryOpExpression>(source, ast::UnaryOp::kAddressOf, + Expr(std::forward<EXPR>(expr))); + } + + /// @param expr the expression to take the address of + /// @return an ast::UnaryOpExpression that takes the address of `expr` + template <typename EXPR> + const ast::UnaryOpExpression* AddressOf(EXPR&& expr) { + return create<ast::UnaryOpExpression>(ast::UnaryOp::kAddressOf, + Expr(std::forward<EXPR>(expr))); + } + + /// @param source the source information + /// @param expr the expression to perform an indirection on + /// @return an ast::UnaryOpExpression that dereferences the pointer `expr` + template <typename EXPR> + const ast::UnaryOpExpression* Deref(const Source& source, EXPR&& expr) { + return create<ast::UnaryOpExpression>(source, ast::UnaryOp::kIndirection, + Expr(std::forward<EXPR>(expr))); + } + + /// @param expr the expression to perform an indirection on + /// @return an ast::UnaryOpExpression that dereferences the pointer `expr` + template <typename EXPR> + const ast::UnaryOpExpression* Deref(EXPR&& expr) { + return create<ast::UnaryOpExpression>(ast::UnaryOp::kIndirection, + Expr(std::forward<EXPR>(expr))); + } + + /// @param expr the expression to perform a unary not on + /// @return an ast::UnaryOpExpression that is the unary not of the input + /// expression + template <typename EXPR> + const ast::UnaryOpExpression* Not(EXPR&& expr) { + return create<ast::UnaryOpExpression>(ast::UnaryOp::kNot, + Expr(std::forward<EXPR>(expr))); + } + + /// @param expr the expression to perform a unary complement on + /// @return an ast::UnaryOpExpression that is the unary complement of the + /// input expression + template <typename EXPR> + const ast::UnaryOpExpression* Complement(EXPR&& expr) { + return create<ast::UnaryOpExpression>(ast::UnaryOp::kComplement, + Expr(std::forward<EXPR>(expr))); + } + + /// @param source the source information + /// @param func the function name + /// @param args the function call arguments + /// @returns a `ast::CallExpression` to the function `func`, with the + /// arguments of `args` converted to `ast::Expression`s using `Expr()`. + template <typename NAME, typename... ARGS> + const ast::CallExpression* Call(const Source& source, + NAME&& func, + ARGS&&... args) { + return create<ast::CallExpression>(source, Expr(func), + ExprList(std::forward<ARGS>(args)...)); + } + + /// @param func the function name + /// @param args the function call arguments + /// @returns a `ast::CallExpression` to the function `func`, with the + /// arguments of `args` converted to `ast::Expression`s using `Expr()`. + template <typename NAME, typename... ARGS, typename = DisableIfSource<NAME>> + const ast::CallExpression* Call(NAME&& func, ARGS&&... args) { + return create<ast::CallExpression>(Expr(func), + ExprList(std::forward<ARGS>(args)...)); + } + + /// @param source the source information + /// @param call the call expression to wrap in a call statement + /// @returns a `ast::CallStatement` for the given call expression + const ast::CallStatement* CallStmt(const Source& source, + const ast::CallExpression* call) { + return create<ast::CallStatement>(source, call); + } + + /// @param call the call expression to wrap in a call statement + /// @returns a `ast::CallStatement` for the given call expression + const ast::CallStatement* CallStmt(const ast::CallExpression* call) { + return create<ast::CallStatement>(call); + } + + /// @param source the source information + /// @returns a `ast::PhonyExpression` + const ast::PhonyExpression* Phony(const Source& source) { + return create<ast::PhonyExpression>(source); + } + + /// @returns a `ast::PhonyExpression` + const ast::PhonyExpression* Phony() { return create<ast::PhonyExpression>(); } + + /// @param expr the expression to ignore + /// @returns a `ast::AssignmentStatement` that assigns 'expr' to the phony + /// (underscore) variable. + template <typename EXPR> + const ast::AssignmentStatement* Ignore(EXPR&& expr) { + return create<ast::AssignmentStatement>(Phony(), Expr(expr)); + } + + /// @param lhs the left hand argument to the addition operation + /// @param rhs the right hand argument to the addition operation + /// @returns a `ast::BinaryExpression` summing the arguments `lhs` and `rhs` + template <typename LHS, typename RHS> + const ast::BinaryExpression* Add(LHS&& lhs, RHS&& rhs) { + return create<ast::BinaryExpression>(ast::BinaryOp::kAdd, + Expr(std::forward<LHS>(lhs)), + Expr(std::forward<RHS>(rhs))); + } + + /// @param lhs the left hand argument to the and operation + /// @param rhs the right hand argument to the and operation + /// @returns a `ast::BinaryExpression` bitwise anding `lhs` and `rhs` + template <typename LHS, typename RHS> + const ast::BinaryExpression* And(LHS&& lhs, RHS&& rhs) { + return create<ast::BinaryExpression>(ast::BinaryOp::kAnd, + Expr(std::forward<LHS>(lhs)), + Expr(std::forward<RHS>(rhs))); + } + + /// @param lhs the left hand argument to the or operation + /// @param rhs the right hand argument to the or operation + /// @returns a `ast::BinaryExpression` bitwise or-ing `lhs` and `rhs` + template <typename LHS, typename RHS> + const ast::BinaryExpression* Or(LHS&& lhs, RHS&& rhs) { + return create<ast::BinaryExpression>(ast::BinaryOp::kOr, + Expr(std::forward<LHS>(lhs)), + Expr(std::forward<RHS>(rhs))); + } + + /// @param lhs the left hand argument to the subtraction operation + /// @param rhs the right hand argument to the subtraction operation + /// @returns a `ast::BinaryExpression` subtracting `rhs` from `lhs` + template <typename LHS, typename RHS> + const ast::BinaryExpression* Sub(LHS&& lhs, RHS&& rhs) { + return create<ast::BinaryExpression>(ast::BinaryOp::kSubtract, + Expr(std::forward<LHS>(lhs)), + Expr(std::forward<RHS>(rhs))); + } + + /// @param lhs the left hand argument to the multiplication operation + /// @param rhs the right hand argument to the multiplication operation + /// @returns a `ast::BinaryExpression` multiplying `rhs` from `lhs` + template <typename LHS, typename RHS> + const ast::BinaryExpression* Mul(LHS&& lhs, RHS&& rhs) { + return create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, + Expr(std::forward<LHS>(lhs)), + Expr(std::forward<RHS>(rhs))); + } + + /// @param source the source information + /// @param lhs the left hand argument to the multiplication operation + /// @param rhs the right hand argument to the multiplication operation + /// @returns a `ast::BinaryExpression` multiplying `rhs` from `lhs` + template <typename LHS, typename RHS> + const ast::BinaryExpression* Mul(const Source& source, LHS&& lhs, RHS&& rhs) { + return create<ast::BinaryExpression>(source, ast::BinaryOp::kMultiply, + Expr(std::forward<LHS>(lhs)), + Expr(std::forward<RHS>(rhs))); + } + + /// @param lhs the left hand argument to the division operation + /// @param rhs the right hand argument to the division operation + /// @returns a `ast::BinaryExpression` dividing `lhs` by `rhs` + template <typename LHS, typename RHS> + const ast::BinaryExpression* Div(LHS&& lhs, RHS&& rhs) { + return create<ast::BinaryExpression>(ast::BinaryOp::kDivide, + Expr(std::forward<LHS>(lhs)), + Expr(std::forward<RHS>(rhs))); + } + + /// @param lhs the left hand argument to the modulo operation + /// @param rhs the right hand argument to the modulo operation + /// @returns a `ast::BinaryExpression` applying modulo of `lhs` by `rhs` + template <typename LHS, typename RHS> + const ast::BinaryExpression* Mod(LHS&& lhs, RHS&& rhs) { + return create<ast::BinaryExpression>(ast::BinaryOp::kModulo, + Expr(std::forward<LHS>(lhs)), + Expr(std::forward<RHS>(rhs))); + } + + /// @param lhs the left hand argument to the bit shift right operation + /// @param rhs the right hand argument to the bit shift right operation + /// @returns a `ast::BinaryExpression` bit shifting right `lhs` by `rhs` + template <typename LHS, typename RHS> + const ast::BinaryExpression* Shr(LHS&& lhs, RHS&& rhs) { + return create<ast::BinaryExpression>(ast::BinaryOp::kShiftRight, + Expr(std::forward<LHS>(lhs)), + Expr(std::forward<RHS>(rhs))); + } + + /// @param lhs the left hand argument to the bit shift left operation + /// @param rhs the right hand argument to the bit shift left operation + /// @returns a `ast::BinaryExpression` bit shifting left `lhs` by `rhs` + template <typename LHS, typename RHS> + const ast::BinaryExpression* Shl(LHS&& lhs, RHS&& rhs) { + return create<ast::BinaryExpression>(ast::BinaryOp::kShiftLeft, + Expr(std::forward<LHS>(lhs)), + Expr(std::forward<RHS>(rhs))); + } + + /// @param lhs the left hand argument to the xor operation + /// @param rhs the right hand argument to the xor operation + /// @returns a `ast::BinaryExpression` bitwise xor-ing `lhs` and `rhs` + template <typename LHS, typename RHS> + const ast::BinaryExpression* Xor(LHS&& lhs, RHS&& rhs) { + return create<ast::BinaryExpression>(ast::BinaryOp::kXor, + Expr(std::forward<LHS>(lhs)), + Expr(std::forward<RHS>(rhs))); + } + + /// @param lhs the left hand argument to the logical and operation + /// @param rhs the right hand argument to the logical and operation + /// @returns a `ast::BinaryExpression` of `lhs` && `rhs` + template <typename LHS, typename RHS> + const ast::BinaryExpression* LogicalAnd(LHS&& lhs, RHS&& rhs) { + return create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, + Expr(std::forward<LHS>(lhs)), + Expr(std::forward<RHS>(rhs))); + } + + /// @param lhs the left hand argument to the logical or operation + /// @param rhs the right hand argument to the logical or operation + /// @returns a `ast::BinaryExpression` of `lhs` || `rhs` + template <typename LHS, typename RHS> + const ast::BinaryExpression* LogicalOr(LHS&& lhs, RHS&& rhs) { + return create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, + Expr(std::forward<LHS>(lhs)), + Expr(std::forward<RHS>(rhs))); + } + + /// @param lhs the left hand argument to the greater than operation + /// @param rhs the right hand argument to the greater than operation + /// @returns a `ast::BinaryExpression` of `lhs` > `rhs` + template <typename LHS, typename RHS> + const ast::BinaryExpression* GreaterThan(LHS&& lhs, RHS&& rhs) { + return create<ast::BinaryExpression>(ast::BinaryOp::kGreaterThan, + Expr(std::forward<LHS>(lhs)), + Expr(std::forward<RHS>(rhs))); + } + + /// @param lhs the left hand argument to the greater than or equal operation + /// @param rhs the right hand argument to the greater than or equal operation + /// @returns a `ast::BinaryExpression` of `lhs` >= `rhs` + template <typename LHS, typename RHS> + const ast::BinaryExpression* GreaterThanEqual(LHS&& lhs, RHS&& rhs) { + return create<ast::BinaryExpression>(ast::BinaryOp::kGreaterThanEqual, + Expr(std::forward<LHS>(lhs)), + Expr(std::forward<RHS>(rhs))); + } + + /// @param lhs the left hand argument to the less than operation + /// @param rhs the right hand argument to the less than operation + /// @returns a `ast::BinaryExpression` of `lhs` < `rhs` + template <typename LHS, typename RHS> + const ast::BinaryExpression* LessThan(LHS&& lhs, RHS&& rhs) { + return create<ast::BinaryExpression>(ast::BinaryOp::kLessThan, + Expr(std::forward<LHS>(lhs)), + Expr(std::forward<RHS>(rhs))); + } + + /// @param lhs the left hand argument to the less than or equal operation + /// @param rhs the right hand argument to the less than or equal operation + /// @returns a `ast::BinaryExpression` of `lhs` <= `rhs` + template <typename LHS, typename RHS> + const ast::BinaryExpression* LessThanEqual(LHS&& lhs, RHS&& rhs) { + return create<ast::BinaryExpression>(ast::BinaryOp::kLessThanEqual, + Expr(std::forward<LHS>(lhs)), + Expr(std::forward<RHS>(rhs))); + } + + /// @param lhs the left hand argument to the equal expression + /// @param rhs the right hand argument to the equal expression + /// @returns a `ast::BinaryExpression` comparing `lhs` equal to `rhs` + template <typename LHS, typename RHS> + const ast::BinaryExpression* Equal(LHS&& lhs, RHS&& rhs) { + return create<ast::BinaryExpression>(ast::BinaryOp::kEqual, + Expr(std::forward<LHS>(lhs)), + Expr(std::forward<RHS>(rhs))); + } + + /// @param lhs the left hand argument to the not-equal expression + /// @param rhs the right hand argument to the not-equal expression + /// @returns a `ast::BinaryExpression` comparing `lhs` equal to `rhs` for + /// disequality + template <typename LHS, typename RHS> + const ast::BinaryExpression* NotEqual(LHS&& lhs, RHS&& rhs) { + return create<ast::BinaryExpression>(ast::BinaryOp::kNotEqual, + Expr(std::forward<LHS>(lhs)), + Expr(std::forward<RHS>(rhs))); + } + + /// @param source the source information + /// @param obj the object for the index accessor expression + /// @param idx the index argument for the index accessor expression + /// @returns a `ast::IndexAccessorExpression` that indexes `arr` with `idx` + template <typename OBJ, typename IDX> + const ast::IndexAccessorExpression* IndexAccessor(const Source& source, + OBJ&& obj, + IDX&& idx) { + return create<ast::IndexAccessorExpression>( + source, Expr(std::forward<OBJ>(obj)), Expr(std::forward<IDX>(idx))); + } + + /// @param obj the object for the index accessor expression + /// @param idx the index argument for the index accessor expression + /// @returns a `ast::IndexAccessorExpression` that indexes `arr` with `idx` + template <typename OBJ, typename IDX> + const ast::IndexAccessorExpression* IndexAccessor(OBJ&& obj, IDX&& idx) { + return create<ast::IndexAccessorExpression>(Expr(std::forward<OBJ>(obj)), + Expr(std::forward<IDX>(idx))); + } + + /// @param source the source information + /// @param obj the object for the member accessor expression + /// @param idx the index argument for the member accessor expression + /// @returns a `ast::MemberAccessorExpression` that indexes `obj` with `idx` + template <typename OBJ, typename IDX> + const ast::MemberAccessorExpression* MemberAccessor(const Source& source, + OBJ&& obj, + IDX&& idx) { + return create<ast::MemberAccessorExpression>( + source, Expr(std::forward<OBJ>(obj)), Expr(std::forward<IDX>(idx))); + } + + /// @param obj the object for the member accessor expression + /// @param idx the index argument for the member accessor expression + /// @returns a `ast::MemberAccessorExpression` that indexes `obj` with `idx` + template <typename OBJ, typename IDX> + const ast::MemberAccessorExpression* MemberAccessor(OBJ&& obj, IDX&& idx) { + return create<ast::MemberAccessorExpression>(Expr(std::forward<OBJ>(obj)), + Expr(std::forward<IDX>(idx))); + } + + /// Creates a ast::StructMemberOffsetAttribute + /// @param val the offset value + /// @returns the offset attribute pointer + const ast::StructMemberOffsetAttribute* MemberOffset(uint32_t val) { + return create<ast::StructMemberOffsetAttribute>(source_, val); + } + + /// Creates a ast::StructMemberSizeAttribute + /// @param source the source information + /// @param val the size value + /// @returns the size attribute pointer + const ast::StructMemberSizeAttribute* MemberSize(const Source& source, + uint32_t val) { + return create<ast::StructMemberSizeAttribute>(source, val); + } + + /// Creates a ast::StructMemberSizeAttribute + /// @param val the size value + /// @returns the size attribute pointer + const ast::StructMemberSizeAttribute* MemberSize(uint32_t val) { + return create<ast::StructMemberSizeAttribute>(source_, val); + } + + /// Creates a ast::StructMemberAlignAttribute + /// @param source the source information + /// @param val the align value + /// @returns the align attribute pointer + const ast::StructMemberAlignAttribute* MemberAlign(const Source& source, + uint32_t val) { + return create<ast::StructMemberAlignAttribute>(source, val); + } + + /// Creates a ast::StructMemberAlignAttribute + /// @param val the align value + /// @returns the align attribute pointer + const ast::StructMemberAlignAttribute* MemberAlign(uint32_t val) { + return create<ast::StructMemberAlignAttribute>(source_, val); + } + + /// Creates the ast::GroupAttribute + /// @param value group attribute index + /// @returns the group attribute pointer + const ast::GroupAttribute* Group(uint32_t value) { + return create<ast::GroupAttribute>(value); + } + + /// Creates the ast::BindingAttribute + /// @param value the binding index + /// @returns the binding deocration pointer + const ast::BindingAttribute* Binding(uint32_t value) { + return create<ast::BindingAttribute>(value); + } + + /// Convenience function to create both a ast::GroupAttribute and + /// ast::BindingAttribute + /// @param group the group index + /// @param binding the binding index + /// @returns a attribute list with both the group and binding attributes + ast::AttributeList GroupAndBinding(uint32_t group, uint32_t binding) { + return {Group(group), Binding(binding)}; + } + + /// Creates an ast::Function and registers it with the ast::Module. + /// @param source the source information + /// @param name the function name + /// @param params the function parameters + /// @param type the function return type + /// @param body the function body + /// @param attributes the optional function attributes + /// @param return_type_attributes the optional function return type + /// attributes + /// @returns the function pointer + template <typename NAME> + const ast::Function* Func(const Source& source, + NAME&& name, + ast::VariableList params, + const ast::Type* type, + ast::StatementList body, + ast::AttributeList attributes = {}, + ast::AttributeList return_type_attributes = {}) { + auto* func = create<ast::Function>( + source, Sym(std::forward<NAME>(name)), params, type, + create<ast::BlockStatement>(body), attributes, return_type_attributes); + AST().AddFunction(func); + return func; + } + + /// Creates an ast::Function and registers it with the ast::Module. + /// @param name the function name + /// @param params the function parameters + /// @param type the function return type + /// @param body the function body + /// @param attributes the optional function attributes + /// @param return_type_attributes the optional function return type + /// attributes + /// @returns the function pointer + template <typename NAME> + const ast::Function* Func(NAME&& name, + ast::VariableList params, + const ast::Type* type, + ast::StatementList body, + ast::AttributeList attributes = {}, + ast::AttributeList return_type_attributes = {}) { + auto* func = create<ast::Function>(Sym(std::forward<NAME>(name)), params, + type, create<ast::BlockStatement>(body), + attributes, return_type_attributes); + AST().AddFunction(func); + return func; + } + + /// Creates an ast::BreakStatement + /// @param source the source information + /// @returns the break statement pointer + const ast::BreakStatement* Break(const Source& source) { + return create<ast::BreakStatement>(source); + } + + /// Creates an ast::BreakStatement + /// @returns the break statement pointer + const ast::BreakStatement* Break() { return create<ast::BreakStatement>(); } + + /// Creates an ast::ContinueStatement + /// @param source the source information + /// @returns the continue statement pointer + const ast::ContinueStatement* Continue(const Source& source) { + return create<ast::ContinueStatement>(source); + } + + /// Creates an ast::ContinueStatement + /// @returns the continue statement pointer + const ast::ContinueStatement* Continue() { + return create<ast::ContinueStatement>(); + } + + /// Creates an ast::ReturnStatement with no return value + /// @param source the source information + /// @returns the return statement pointer + const ast::ReturnStatement* Return(const Source& source) { + return create<ast::ReturnStatement>(source); + } + + /// Creates an ast::ReturnStatement with no return value + /// @returns the return statement pointer + const ast::ReturnStatement* Return() { + return create<ast::ReturnStatement>(); + } + + /// Creates an ast::ReturnStatement with the given return value + /// @param source the source information + /// @param val the return value + /// @returns the return statement pointer + template <typename EXPR> + const ast::ReturnStatement* Return(const Source& source, EXPR&& val) { + return create<ast::ReturnStatement>(source, Expr(std::forward<EXPR>(val))); + } + + /// Creates an ast::ReturnStatement with the given return value + /// @param val the return value + /// @returns the return statement pointer + template <typename EXPR, typename = DisableIfSource<EXPR>> + const ast::ReturnStatement* Return(EXPR&& val) { + return create<ast::ReturnStatement>(Expr(std::forward<EXPR>(val))); + } + + /// Creates an ast::DiscardStatement + /// @param source the source information + /// @returns the discard statement pointer + const ast::DiscardStatement* Discard(const Source& source) { + return create<ast::DiscardStatement>(source); + } + + /// Creates an ast::DiscardStatement + /// @returns the discard statement pointer + const ast::DiscardStatement* Discard() { + return create<ast::DiscardStatement>(); + } + + /// Creates a ast::Alias registering it with the AST().TypeDecls(). + /// @param source the source information + /// @param name the alias name + /// @param type the alias target type + /// @returns the alias type + template <typename NAME> + const ast::Alias* Alias(const Source& source, + NAME&& name, + const ast::Type* type) { + auto* out = ty.alias(source, std::forward<NAME>(name), type); + AST().AddTypeDecl(out); + return out; + } + + /// Creates a ast::Alias registering it with the AST().TypeDecls(). + /// @param name the alias name + /// @param type the alias target type + /// @returns the alias type + template <typename NAME> + const ast::Alias* Alias(NAME&& name, const ast::Type* type) { + auto* out = ty.alias(std::forward<NAME>(name), type); + AST().AddTypeDecl(out); + return out; + } + + /// Creates a ast::Struct registering it with the AST().TypeDecls(). + /// @param source the source information + /// @param name the struct name + /// @param members the struct members + /// @returns the struct type + template <typename NAME> + const ast::Struct* Structure(const Source& source, + NAME&& name, + ast::StructMemberList members) { + auto sym = Sym(std::forward<NAME>(name)); + auto* type = create<ast::Struct>(source, sym, std::move(members), + ast::AttributeList{}); + AST().AddTypeDecl(type); + return type; + } + + /// Creates a ast::Struct registering it with the AST().TypeDecls(). + /// @param name the struct name + /// @param members the struct members + /// @returns the struct type + template <typename NAME> + const ast::Struct* Structure(NAME&& name, ast::StructMemberList members) { + auto sym = Sym(std::forward<NAME>(name)); + auto* type = + create<ast::Struct>(sym, std::move(members), ast::AttributeList{}); + AST().AddTypeDecl(type); + return type; + } + + /// Creates a ast::StructMember + /// @param source the source information + /// @param name the struct member name + /// @param type the struct member type + /// @param attributes the optional struct member attributes + /// @returns the struct member pointer + template <typename NAME> + const ast::StructMember* Member(const Source& source, + NAME&& name, + const ast::Type* type, + ast::AttributeList attributes = {}) { + return create<ast::StructMember>(source, Sym(std::forward<NAME>(name)), + type, std::move(attributes)); + } + + /// Creates a ast::StructMember + /// @param name the struct member name + /// @param type the struct member type + /// @param attributes the optional struct member attributes + /// @returns the struct member pointer + template <typename NAME> + const ast::StructMember* Member(NAME&& name, + const ast::Type* type, + ast::AttributeList attributes = {}) { + return create<ast::StructMember>(source_, Sym(std::forward<NAME>(name)), + type, std::move(attributes)); + } + + /// Creates a ast::StructMember with the given byte offset + /// @param offset the offset to use in the StructMemberOffsetattribute + /// @param name the struct member name + /// @param type the struct member type + /// @returns the struct member pointer + template <typename NAME> + const ast::StructMember* Member(uint32_t offset, + NAME&& name, + const ast::Type* type) { + return create<ast::StructMember>( + source_, Sym(std::forward<NAME>(name)), type, + ast::AttributeList{ + create<ast::StructMemberOffsetAttribute>(offset), + }); + } + + /// Creates a ast::BlockStatement with input statements + /// @param source the source information for the block + /// @param statements statements of block + /// @returns the block statement pointer + template <typename... Statements> + const ast::BlockStatement* Block(const Source& source, + Statements&&... statements) { + return create<ast::BlockStatement>( + source, ast::StatementList{std::forward<Statements>(statements)...}); + } + + /// Creates a ast::BlockStatement with input statements + /// @param statements statements of block + /// @returns the block statement pointer + template <typename... STATEMENTS, typename = DisableIfSource<STATEMENTS...>> + const ast::BlockStatement* Block(STATEMENTS&&... statements) { + return create<ast::BlockStatement>( + ast::StatementList{std::forward<STATEMENTS>(statements)...}); + } + + /// Creates a ast::ElseStatement with input condition and body + /// @param condition the else condition expression + /// @param body the else body + /// @returns the else statement pointer + template <typename CONDITION> + const ast::ElseStatement* Else(CONDITION&& condition, + const ast::BlockStatement* body) { + return create<ast::ElseStatement>(Expr(std::forward<CONDITION>(condition)), + body); + } + + /// Creates a ast::ElseStatement with no condition and body + /// @param body the else body + /// @returns the else statement pointer + const ast::ElseStatement* Else(const ast::BlockStatement* body) { + return create<ast::ElseStatement>(nullptr, body); + } + + /// Creates a ast::IfStatement with input condition, body, and optional + /// variadic else statements + /// @param source the source information for the if statement + /// @param condition the if statement condition expression + /// @param body the if statement body + /// @param elseStatements optional variadic else statements + /// @returns the if statement pointer + template <typename CONDITION, typename... ELSE_STATEMENTS> + const ast::IfStatement* If(const Source& source, + CONDITION&& condition, + const ast::BlockStatement* body, + ELSE_STATEMENTS&&... elseStatements) { + return create<ast::IfStatement>( + source, Expr(std::forward<CONDITION>(condition)), body, + ast::ElseStatementList{ + std::forward<ELSE_STATEMENTS>(elseStatements)...}); + } + + /// Creates a ast::IfStatement with input condition, body, and optional + /// variadic else statements + /// @param condition the if statement condition expression + /// @param body the if statement body + /// @param elseStatements optional variadic else statements + /// @returns the if statement pointer + template <typename CONDITION, typename... ELSE_STATEMENTS> + const ast::IfStatement* If(CONDITION&& condition, + const ast::BlockStatement* body, + ELSE_STATEMENTS&&... elseStatements) { + return create<ast::IfStatement>( + Expr(std::forward<CONDITION>(condition)), body, + ast::ElseStatementList{ + std::forward<ELSE_STATEMENTS>(elseStatements)...}); + } + + /// Creates a ast::AssignmentStatement with input lhs and rhs expressions + /// @param source the source information + /// @param lhs the left hand side expression initializer + /// @param rhs the right hand side expression initializer + /// @returns the assignment statement pointer + template <typename LhsExpressionInit, typename RhsExpressionInit> + const ast::AssignmentStatement* Assign(const Source& source, + LhsExpressionInit&& lhs, + RhsExpressionInit&& rhs) { + return create<ast::AssignmentStatement>( + source, Expr(std::forward<LhsExpressionInit>(lhs)), + Expr(std::forward<RhsExpressionInit>(rhs))); + } + + /// Creates a ast::AssignmentStatement with input lhs and rhs expressions + /// @param lhs the left hand side expression initializer + /// @param rhs the right hand side expression initializer + /// @returns the assignment statement pointer + template <typename LhsExpressionInit, typename RhsExpressionInit> + const ast::AssignmentStatement* Assign(LhsExpressionInit&& lhs, + RhsExpressionInit&& rhs) { + return create<ast::AssignmentStatement>( + Expr(std::forward<LhsExpressionInit>(lhs)), + Expr(std::forward<RhsExpressionInit>(rhs))); + } + + /// Creates a ast::CompoundAssignmentStatement with input lhs and rhs + /// expressions, and a binary operator. + /// @param source the source information + /// @param lhs the left hand side expression initializer + /// @param rhs the right hand side expression initializer + /// @param op the binary operator + /// @returns the compound assignment statement pointer + template <typename LhsExpressionInit, typename RhsExpressionInit> + const ast::CompoundAssignmentStatement* CompoundAssign( + const Source& source, + LhsExpressionInit&& lhs, + RhsExpressionInit&& rhs, + ast::BinaryOp op) { + return create<ast::CompoundAssignmentStatement>( + source, Expr(std::forward<LhsExpressionInit>(lhs)), + Expr(std::forward<RhsExpressionInit>(rhs)), op); + } + + /// Creates a ast::CompoundAssignmentStatement with input lhs and rhs + /// expressions, and a binary operator. + /// @param lhs the left hand side expression initializer + /// @param rhs the right hand side expression initializer + /// @param op the binary operator + /// @returns the compound assignment statement pointer + template <typename LhsExpressionInit, typename RhsExpressionInit> + const ast::CompoundAssignmentStatement* CompoundAssign( + LhsExpressionInit&& lhs, + RhsExpressionInit&& rhs, + ast::BinaryOp op) { + return create<ast::CompoundAssignmentStatement>( + Expr(std::forward<LhsExpressionInit>(lhs)), + Expr(std::forward<RhsExpressionInit>(rhs)), op); + } + + /// Creates a ast::LoopStatement with input body and optional continuing + /// @param source the source information + /// @param body the loop body + /// @param continuing the optional continuing block + /// @returns the loop statement pointer + const ast::LoopStatement* Loop( + const Source& source, + const ast::BlockStatement* body, + const ast::BlockStatement* continuing = nullptr) { + return create<ast::LoopStatement>(source, body, continuing); + } + + /// Creates a ast::LoopStatement with input body and optional continuing + /// @param body the loop body + /// @param continuing the optional continuing block + /// @returns the loop statement pointer + const ast::LoopStatement* Loop( + const ast::BlockStatement* body, + const ast::BlockStatement* continuing = nullptr) { + return create<ast::LoopStatement>(body, continuing); + } + + /// Creates a ast::ForLoopStatement with input body and optional initializer, + /// condition and continuing. + /// @param source the source information + /// @param init the optional loop initializer + /// @param cond the optional loop condition + /// @param cont the optional loop continuing + /// @param body the loop body + /// @returns the for loop statement pointer + template <typename COND> + const ast::ForLoopStatement* For(const Source& source, + const ast::Statement* init, + COND&& cond, + const ast::Statement* cont, + const ast::BlockStatement* body) { + return create<ast::ForLoopStatement>( + source, init, Expr(std::forward<COND>(cond)), cont, body); + } + + /// Creates a ast::ForLoopStatement with input body and optional initializer, + /// condition and continuing. + /// @param init the optional loop initializer + /// @param cond the optional loop condition + /// @param cont the optional loop continuing + /// @param body the loop body + /// @returns the for loop statement pointer + template <typename COND> + const ast::ForLoopStatement* For(const ast::Statement* init, + COND&& cond, + const ast::Statement* cont, + const ast::BlockStatement* body) { + return create<ast::ForLoopStatement>(init, Expr(std::forward<COND>(cond)), + cont, body); + } + + /// Creates a ast::VariableDeclStatement for the input variable + /// @param source the source information + /// @param var the variable to wrap in a decl statement + /// @returns the variable decl statement pointer + const ast::VariableDeclStatement* Decl(const Source& source, + const ast::Variable* var) { + return create<ast::VariableDeclStatement>(source, var); + } + + /// Creates a ast::VariableDeclStatement for the input variable + /// @param var the variable to wrap in a decl statement + /// @returns the variable decl statement pointer + const ast::VariableDeclStatement* Decl(const ast::Variable* var) { + return create<ast::VariableDeclStatement>(var); + } + + /// Creates a ast::SwitchStatement with input expression and cases + /// @param source the source information + /// @param condition the condition expression initializer + /// @param cases case statements + /// @returns the switch statement pointer + template <typename ExpressionInit, typename... Cases> + const ast::SwitchStatement* Switch(const Source& source, + ExpressionInit&& condition, + Cases&&... cases) { + return create<ast::SwitchStatement>( + source, Expr(std::forward<ExpressionInit>(condition)), + ast::CaseStatementList{std::forward<Cases>(cases)...}); + } + + /// Creates a ast::SwitchStatement with input expression and cases + /// @param condition the condition expression initializer + /// @param cases case statements + /// @returns the switch statement pointer + template <typename ExpressionInit, + typename... Cases, + typename = DisableIfSource<ExpressionInit>> + const ast::SwitchStatement* Switch(ExpressionInit&& condition, + Cases&&... cases) { + return create<ast::SwitchStatement>( + Expr(std::forward<ExpressionInit>(condition)), + ast::CaseStatementList{std::forward<Cases>(cases)...}); + } + + /// Creates a ast::CaseStatement with input list of selectors, and body + /// @param source the source information + /// @param selectors list of selectors + /// @param body the case body + /// @returns the case statement pointer + const ast::CaseStatement* Case(const Source& source, + ast::CaseSelectorList selectors, + const ast::BlockStatement* body = nullptr) { + return create<ast::CaseStatement>(source, std::move(selectors), + body ? body : Block()); + } + + /// Creates a ast::CaseStatement with input list of selectors, and body + /// @param selectors list of selectors + /// @param body the case body + /// @returns the case statement pointer + const ast::CaseStatement* Case(ast::CaseSelectorList selectors, + const ast::BlockStatement* body = nullptr) { + return create<ast::CaseStatement>(std::move(selectors), + body ? body : Block()); + } + + /// Convenient overload that takes a single selector + /// @param selector a single case selector + /// @param body the case body + /// @returns the case statement pointer + const ast::CaseStatement* Case(const ast::IntLiteralExpression* selector, + const ast::BlockStatement* body = nullptr) { + return Case(ast::CaseSelectorList{selector}, body); + } + + /// Convenience function that creates a 'default' ast::CaseStatement + /// @param source the source information + /// @param body the case body + /// @returns the case statement pointer + const ast::CaseStatement* DefaultCase( + const Source& source, + const ast::BlockStatement* body = nullptr) { + return Case(source, ast::CaseSelectorList{}, body); + } + + /// Convenience function that creates a 'default' ast::CaseStatement + /// @param body the case body + /// @returns the case statement pointer + const ast::CaseStatement* DefaultCase( + const ast::BlockStatement* body = nullptr) { + return Case(ast::CaseSelectorList{}, body); + } + + /// Creates an ast::FallthroughStatement + /// @param source the source information + /// @returns the fallthrough statement pointer + const ast::FallthroughStatement* Fallthrough(const Source& source) { + return create<ast::FallthroughStatement>(source); + } + + /// Creates an ast::FallthroughStatement + /// @returns the fallthrough statement pointer + const ast::FallthroughStatement* Fallthrough() { + return create<ast::FallthroughStatement>(); + } + + /// Creates an ast::BuiltinAttribute + /// @param source the source information + /// @param builtin the builtin value + /// @returns the builtin attribute pointer + const ast::BuiltinAttribute* Builtin(const Source& source, + ast::Builtin builtin) { + return create<ast::BuiltinAttribute>(source, builtin); + } + + /// Creates an ast::BuiltinAttribute + /// @param builtin the builtin value + /// @returns the builtin attribute pointer + const ast::BuiltinAttribute* Builtin(ast::Builtin builtin) { + return create<ast::BuiltinAttribute>(source_, builtin); + } + + /// Creates an ast::InterpolateAttribute + /// @param source the source information + /// @param type the interpolation type + /// @param sampling the interpolation sampling + /// @returns the interpolate attribute pointer + const ast::InterpolateAttribute* Interpolate( + const Source& source, + ast::InterpolationType type, + ast::InterpolationSampling sampling = ast::InterpolationSampling::kNone) { + return create<ast::InterpolateAttribute>(source, type, sampling); + } + + /// Creates an ast::InterpolateAttribute + /// @param type the interpolation type + /// @param sampling the interpolation sampling + /// @returns the interpolate attribute pointer + const ast::InterpolateAttribute* Interpolate( + ast::InterpolationType type, + ast::InterpolationSampling sampling = ast::InterpolationSampling::kNone) { + return create<ast::InterpolateAttribute>(source_, type, sampling); + } + + /// Creates an ast::InterpolateAttribute using flat interpolation + /// @param source the source information + /// @returns the interpolate attribute pointer + const ast::InterpolateAttribute* Flat(const Source& source) { + return Interpolate(source, ast::InterpolationType::kFlat); + } + + /// Creates an ast::InterpolateAttribute using flat interpolation + /// @returns the interpolate attribute pointer + const ast::InterpolateAttribute* Flat() { + return Interpolate(ast::InterpolationType::kFlat); + } + + /// Creates an ast::InvariantAttribute + /// @param source the source information + /// @returns the invariant attribute pointer + const ast::InvariantAttribute* Invariant(const Source& source) { + return create<ast::InvariantAttribute>(source); + } + + /// Creates an ast::InvariantAttribute + /// @returns the invariant attribute pointer + const ast::InvariantAttribute* Invariant() { + return create<ast::InvariantAttribute>(source_); + } + + /// Creates an ast::LocationAttribute + /// @param source the source information + /// @param location the location value + /// @returns the location attribute pointer + const ast::LocationAttribute* Location(const Source& source, + uint32_t location) { + return create<ast::LocationAttribute>(source, location); + } + + /// Creates an ast::LocationAttribute + /// @param location the location value + /// @returns the location attribute pointer + const ast::LocationAttribute* Location(uint32_t location) { + return create<ast::LocationAttribute>(source_, location); + } + + /// Creates an ast::IdAttribute + /// @param source the source information + /// @param id the id value + /// @returns the override attribute pointer + const ast::IdAttribute* Id(const Source& source, uint32_t id) { + return create<ast::IdAttribute>(source, id); + } + + /// Creates an ast::IdAttribute with a constant ID + /// @param id the optional id value + /// @returns the override attribute pointer + const ast::IdAttribute* Id(uint32_t id) { return Id(source_, id); } + + /// Creates an ast::StageAttribute + /// @param source the source information + /// @param stage the pipeline stage + /// @returns the stage attribute pointer + const ast::StageAttribute* Stage(const Source& source, + ast::PipelineStage stage) { + return create<ast::StageAttribute>(source, stage); + } + + /// Creates an ast::StageAttribute + /// @param stage the pipeline stage + /// @returns the stage attribute pointer + const ast::StageAttribute* Stage(ast::PipelineStage stage) { + return create<ast::StageAttribute>(source_, stage); + } + + /// Creates an ast::WorkgroupAttribute + /// @param x the x dimension expression + /// @returns the workgroup attribute pointer + template <typename EXPR_X> + const ast::WorkgroupAttribute* WorkgroupSize(EXPR_X&& x) { + return WorkgroupSize(std::forward<EXPR_X>(x), nullptr, nullptr); + } + + /// Creates an ast::WorkgroupAttribute + /// @param x the x dimension expression + /// @param y the y dimension expression + /// @returns the workgroup attribute pointer + template <typename EXPR_X, typename EXPR_Y> + const ast::WorkgroupAttribute* WorkgroupSize(EXPR_X&& x, EXPR_Y&& y) { + return WorkgroupSize(std::forward<EXPR_X>(x), std::forward<EXPR_Y>(y), + nullptr); + } + + /// Creates an ast::WorkgroupAttribute + /// @param source the source information + /// @param x the x dimension expression + /// @param y the y dimension expression + /// @param z the z dimension expression + /// @returns the workgroup attribute pointer + template <typename EXPR_X, typename EXPR_Y, typename EXPR_Z> + const ast::WorkgroupAttribute* WorkgroupSize(const Source& source, + EXPR_X&& x, + EXPR_Y&& y, + EXPR_Z&& z) { + return create<ast::WorkgroupAttribute>( + source, Expr(std::forward<EXPR_X>(x)), Expr(std::forward<EXPR_Y>(y)), + Expr(std::forward<EXPR_Z>(z))); + } + + /// Creates an ast::WorkgroupAttribute + /// @param x the x dimension expression + /// @param y the y dimension expression + /// @param z the z dimension expression + /// @returns the workgroup attribute pointer + template <typename EXPR_X, typename EXPR_Y, typename EXPR_Z> + const ast::WorkgroupAttribute* WorkgroupSize(EXPR_X&& x, + EXPR_Y&& y, + EXPR_Z&& z) { + return create<ast::WorkgroupAttribute>( + source_, Expr(std::forward<EXPR_X>(x)), Expr(std::forward<EXPR_Y>(y)), + Expr(std::forward<EXPR_Z>(z))); + } + + /// Creates an ast::DisableValidationAttribute + /// @param validation the validation to disable + /// @returns the disable validation attribute pointer + const ast::DisableValidationAttribute* Disable( + ast::DisabledValidation validation) { + return ASTNodes().Create<ast::DisableValidationAttribute>(ID(), validation); + } + + /// Sets the current builder source to `src` + /// @param src the Source used for future create() calls + void SetSource(const Source& src) { + AssertNotMoved(); + source_ = src; + } + + /// Sets the current builder source to `loc` + /// @param loc the Source used for future create() calls + void SetSource(const Source::Location& loc) { + AssertNotMoved(); + source_ = Source(loc); + } + + /// Helper for returning the resolved semantic type of the expression `expr`. + /// @note As the Resolver is run when the Program is built, this will only be + /// useful for the Resolver itself and tests that use their own Resolver. + /// @param expr the AST expression + /// @return the resolved semantic type for the expression, or nullptr if the + /// expression has no resolved type. + const sem::Type* TypeOf(const ast::Expression* expr) const; + + /// Helper for returning the resolved semantic type of the variable `var`. + /// @note As the Resolver is run when the Program is built, this will only be + /// useful for the Resolver itself and tests that use their own Resolver. + /// @param var the AST variable + /// @return the resolved semantic type for the variable, or nullptr if the + /// variable has no resolved type. + const sem::Type* TypeOf(const ast::Variable* var) const; + + /// Helper for returning the resolved semantic type of the AST type `type`. + /// @note As the Resolver is run when the Program is built, this will only be + /// useful for the Resolver itself and tests that use their own Resolver. + /// @param type the AST type + /// @return the resolved semantic type for the type, or nullptr if the type + /// has no resolved type. + const sem::Type* TypeOf(const ast::Type* type) const; + + /// Helper for returning the resolved semantic type of the AST type + /// declaration `type_decl`. + /// @note As the Resolver is run when the Program is built, this will only be + /// useful for the Resolver itself and tests that use their own Resolver. + /// @param type_decl the AST type declaration + /// @return the resolved semantic type for the type declaration, or nullptr if + /// the type declaration has no resolved type. + const sem::Type* TypeOf(const ast::TypeDecl* type_decl) const; + + /// Wraps the ast::Expression in a statement. This is used by tests that + /// construct a partial AST and require the Resolver to reach these + /// nodes. + /// @param expr the ast::Expression to be wrapped by an ast::Statement + /// @return the ast::Statement that wraps the ast::Expression + const ast::Statement* WrapInStatement(const ast::Expression* expr); + /// Wraps the ast::Variable in a ast::VariableDeclStatement. This is used by + /// tests that construct a partial AST and require the Resolver to reach + /// these nodes. + /// @param v the ast::Variable to be wrapped by an ast::VariableDeclStatement + /// @return the ast::VariableDeclStatement that wraps the ast::Variable + const ast::VariableDeclStatement* WrapInStatement(const ast::Variable* v); + /// Returns the statement argument. Used as a passthrough-overload by + /// WrapInFunction(). + /// @param stmt the ast::Statement + /// @return `stmt` + const ast::Statement* WrapInStatement(const ast::Statement* stmt); + /// Wraps the list of arguments in a simple function so that each is reachable + /// by the Resolver. + /// @param args a mix of ast::Expression, ast::Statement, ast::Variables. + /// @returns the function + template <typename... ARGS> + const ast::Function* WrapInFunction(ARGS&&... args) { + ast::StatementList stmts{WrapInStatement(std::forward<ARGS>(args))...}; + return WrapInFunction(std::move(stmts)); + } + /// @param stmts a list of ast::Statement that will be wrapped by a function, + /// so that each statement is reachable by the Resolver. + /// @returns the function + const ast::Function* WrapInFunction(ast::StatementList stmts); + + /// The builder types + TypesBuilder const ty{this}; + + protected: + /// Asserts that the builder has not been moved. + void AssertNotMoved() const; + + private: + ProgramID id_; + sem::Manager types_; + ASTNodeAllocator ast_nodes_; + SemNodeAllocator sem_nodes_; + ast::Module* ast_; + sem::Info sem_; + SymbolTable symbols_{id_}; + diag::List diagnostics_; + + /// The source to use when creating AST nodes without providing a Source as + /// the first argument. + Source source_; + + /// Set by SetResolveOnBuild(). If set, the Resolver will be run on the + /// program when built. + bool resolve_on_build_ = true; + + /// Set by MarkAsMoved(). Once set, no methods may be called on this builder. + bool moved_ = false; +}; + +//! @cond Doxygen_Suppress +// Various template specializations for ProgramBuilder::TypesBuilder::CToAST. +template <> +struct ProgramBuilder::TypesBuilder::CToAST<ProgramBuilder::i32> { + static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) { + return t->i32(); + } +}; +template <> +struct ProgramBuilder::TypesBuilder::CToAST<ProgramBuilder::u32> { + static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) { + return t->u32(); + } +}; +template <> +struct ProgramBuilder::TypesBuilder::CToAST<ProgramBuilder::f32> { + static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) { + return t->f32(); + } +}; +template <> +struct ProgramBuilder::TypesBuilder::CToAST<bool> { + static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) { + return t->bool_(); + } +}; +template <> +struct ProgramBuilder::TypesBuilder::CToAST<void> { + static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) { + return t->void_(); + } +}; +//! @endcond + +/// @param builder the ProgramBuilder +/// @returns the ProgramID of the ProgramBuilder +inline ProgramID ProgramIDOf(const ProgramBuilder* builder) { + return builder->ID(); +} + +} // namespace tint + +#endif // SRC_TINT_PROGRAM_BUILDER_H_
diff --git a/src/tint/program_builder_test.cc b/src/tint/program_builder_test.cc new file mode 100644 index 0000000..f18aa10 --- /dev/null +++ b/src/tint/program_builder_test.cc
@@ -0,0 +1,72 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/program_builder.h" + +#include "gtest/gtest.h" + +namespace tint { +namespace { + +using ProgramBuilderTest = testing::Test; + +TEST_F(ProgramBuilderTest, IDsAreUnique) { + Program program_a(ProgramBuilder{}); + Program program_b(ProgramBuilder{}); + Program program_c(ProgramBuilder{}); + EXPECT_NE(program_a.ID(), program_b.ID()); + EXPECT_NE(program_b.ID(), program_c.ID()); + EXPECT_NE(program_c.ID(), program_a.ID()); +} + +TEST_F(ProgramBuilderTest, WrapDoesntAffectInner) { + Program inner([] { + ProgramBuilder builder; + auto* ty = builder.ty.f32(); + builder.Func("a", {}, ty, {}, {}); + return builder; + }()); + + ASSERT_EQ(inner.AST().Functions().size(), 1u); + ASSERT_TRUE(inner.Symbols().Get("a").IsValid()); + ASSERT_FALSE(inner.Symbols().Get("b").IsValid()); + + ProgramBuilder outer = ProgramBuilder::Wrap(&inner); + + ASSERT_EQ(inner.AST().Functions().size(), 1u); + ASSERT_EQ(outer.AST().Functions().size(), 1u); + EXPECT_EQ(inner.AST().Functions()[0], outer.AST().Functions()[0]); + EXPECT_TRUE(inner.Symbols().Get("a").IsValid()); + EXPECT_EQ(inner.Symbols().Get("a"), outer.Symbols().Get("a")); + EXPECT_TRUE(inner.Symbols().Get("a").IsValid()); + EXPECT_TRUE(outer.Symbols().Get("a").IsValid()); + EXPECT_FALSE(inner.Symbols().Get("b").IsValid()); + EXPECT_FALSE(outer.Symbols().Get("b").IsValid()); + + auto* ty = outer.ty.f32(); + outer.Func("b", {}, ty, {}, {}); + + ASSERT_EQ(inner.AST().Functions().size(), 1u); + ASSERT_EQ(outer.AST().Functions().size(), 2u); + EXPECT_EQ(inner.AST().Functions()[0], outer.AST().Functions()[0]); + EXPECT_EQ(outer.AST().Functions()[1]->symbol, outer.Symbols().Get("b")); + EXPECT_EQ(inner.Symbols().Get("a"), outer.Symbols().Get("a")); + EXPECT_TRUE(inner.Symbols().Get("a").IsValid()); + EXPECT_TRUE(outer.Symbols().Get("a").IsValid()); + EXPECT_FALSE(inner.Symbols().Get("b").IsValid()); + EXPECT_TRUE(outer.Symbols().Get("b").IsValid()); +} + +} // namespace +} // namespace tint
diff --git a/src/tint/program_id.cc b/src/tint/program_id.cc new file mode 100644 index 0000000..5350de7 --- /dev/null +++ b/src/tint/program_id.cc
@@ -0,0 +1,58 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/program_id.h" + +#include <atomic> + +namespace tint { + +namespace { + +std::atomic<uint32_t> next_program_id{1}; + +} // namespace + +ProgramID::ProgramID() = default; + +ProgramID::ProgramID(uint32_t id) : val(id) {} + +ProgramID ProgramID::New() { + return ProgramID(next_program_id++); +} + +namespace detail { + +/// AssertProgramIDsEqual is called by TINT_ASSERT_PROGRAM_IDS_EQUAL() and +/// TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID() to assert that the ProgramIDs +/// `a` and `b` are equal. +void AssertProgramIDsEqual(ProgramID a, + ProgramID b, + bool if_valid, + diag::System system, + const char* msg, + const char* file, + size_t line) { + if (a == b) { + return; // matched + } + if (if_valid && (!a || !b)) { + return; // a or b were not valid + } + diag::List diagnostics; + tint::InternalCompilerError(file, line, system, diagnostics) << msg; +} + +} // namespace detail +} // namespace tint
diff --git a/src/tint/program_id.h b/src/tint/program_id.h new file mode 100644 index 0000000..09e232f --- /dev/null +++ b/src/tint/program_id.h
@@ -0,0 +1,126 @@ +// Copyright 2021 The Tint Authors. +// +// 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 SRC_TINT_PROGRAM_ID_H_ +#define SRC_TINT_PROGRAM_ID_H_ + +#include <stdint.h> +#include <iostream> +#include <utility> + +#include "src/tint/debug.h" + +namespace tint { + +/// If 1 then checks are enabled that AST nodes are not leaked from one program +/// to another. +/// TODO(bclayton): We'll want to disable this in production builds. For now we +/// always check. +#define TINT_CHECK_FOR_CROSS_PROGRAM_LEAKS 1 + +/// A ProgramID is a unique identifier of a Program. +/// ProgramID can be used to ensure that objects referenced by the Program are +/// owned exclusively by that Program and have accidentally not leaked from +/// another Program. +class ProgramID { + public: + /// Constructor + ProgramID(); + + /// @returns a new. globally unique ProgramID + static ProgramID New(); + + /// Equality operator + /// @param rhs the other ProgramID + /// @returns true if the ProgramIDs are equal + bool operator==(const ProgramID& rhs) const { return val == rhs.val; } + + /// Inequality operator + /// @param rhs the other ProgramID + /// @returns true if the ProgramIDs are not equal + bool operator!=(const ProgramID& rhs) const { return val != rhs.val; } + + /// @returns the numerical identifier value + uint32_t Value() const { return val; } + + /// @returns true if this ProgramID is valid + operator bool() const { return val != 0; } + + private: + explicit ProgramID(uint32_t); + + uint32_t val = 0; +}; + +/// A simple pass-through function for ProgramID. Intended to be overloaded for +/// other types. +/// @param id a ProgramID +/// @returns id. Simple pass-through function +inline ProgramID ProgramIDOf(ProgramID id) { + return id; +} + +/// Writes the ProgramID to the std::ostream. +/// @param out the std::ostream to write to +/// @param id the program identifier to write +/// @returns out so calls can be chained +inline std::ostream& operator<<(std::ostream& out, ProgramID id) { + out << "Program<" << id.Value() << ">"; + return out; +} + +namespace detail { + +/// AssertProgramIDsEqual is called by TINT_ASSERT_PROGRAM_IDS_EQUAL() and +/// TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID() to assert that the ProgramIDs +/// `a` and `b` are equal. +void AssertProgramIDsEqual(ProgramID a, + ProgramID b, + bool if_valid, + diag::System system, + const char* msg, + const char* file, + size_t line); + +} // namespace detail + +/// TINT_ASSERT_PROGRAM_IDS_EQUAL(SYSTEM, A, B) is a macro that asserts that the +/// program identifiers for A and B are equal. +/// +/// TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(SYSTEM, A, B) is a macro that asserts +/// that the program identifiers for A and B are equal, if both A and B have +/// valid program identifiers. +#if TINT_CHECK_FOR_CROSS_PROGRAM_LEAKS +#define TINT_ASSERT_PROGRAM_IDS_EQUAL(system, a, b) \ + detail::AssertProgramIDsEqual( \ + ProgramIDOf(a), ProgramIDOf(b), false, tint::diag::System::system, \ + "TINT_ASSERT_PROGRAM_IDS_EQUAL(" #system "," #a ", " #b ")", __FILE__, \ + __LINE__) +#define TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(system, a, b) \ + detail::AssertProgramIDsEqual( \ + ProgramIDOf(a), ProgramIDOf(b), true, tint::diag::System::system, \ + "TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(" #system ", " #a ", " #b ")", \ + __FILE__, __LINE__) +#else +#define TINT_ASSERT_PROGRAM_IDS_EQUAL(a, b) \ + do { \ + } while (false) +#define TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(a, b) \ + do { \ + } while (false) +#endif + +} // namespace tint + +#endif // SRC_TINT_PROGRAM_ID_H_
diff --git a/src/tint/program_test.cc b/src/tint/program_test.cc new file mode 100644 index 0000000..a161ecb --- /dev/null +++ b/src/tint/program_test.cc
@@ -0,0 +1,110 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "gtest/gtest-spi.h" +#include "src/tint/ast/return_statement.h" +#include "src/tint/ast/test_helper.h" + +namespace tint { +namespace { + +using ProgramTest = ast::TestHelper; + +TEST_F(ProgramTest, Unbuilt) { + Program program; + EXPECT_FALSE(program.IsValid()); +} + +TEST_F(ProgramTest, Creation) { + Program program(std::move(*this)); + EXPECT_EQ(program.AST().Functions().size(), 0u); +} + +TEST_F(ProgramTest, EmptyIsValid) { + Program program(std::move(*this)); + EXPECT_TRUE(program.IsValid()); +} + +TEST_F(ProgramTest, IDsAreUnique) { + Program program_a(ProgramBuilder{}); + Program program_b(ProgramBuilder{}); + Program program_c(ProgramBuilder{}); + EXPECT_NE(program_a.ID(), program_b.ID()); + EXPECT_NE(program_b.ID(), program_c.ID()); + EXPECT_NE(program_c.ID(), program_a.ID()); +} + +TEST_F(ProgramTest, Assert_GlobalVariable) { + Global("var", ty.f32(), ast::StorageClass::kPrivate); + + Program program(std::move(*this)); + EXPECT_TRUE(program.IsValid()); +} + +TEST_F(ProgramTest, Assert_NullGlobalVariable) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.AST().AddGlobalVariable(nullptr); + }, + "internal compiler error"); +} + +TEST_F(ProgramTest, Assert_NullTypeDecl) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.AST().AddTypeDecl(nullptr); + }, + "internal compiler error"); +} + +TEST_F(ProgramTest, Assert_Null_Function) { + EXPECT_FATAL_FAILURE( + { + ProgramBuilder b; + b.AST().AddFunction(nullptr); + }, + "internal compiler error"); +} + +TEST_F(ProgramTest, DiagnosticsMove) { + Diagnostics().add_error(diag::System::Program, "an error message"); + + Program program_a(std::move(*this)); + EXPECT_FALSE(program_a.IsValid()); + EXPECT_EQ(program_a.Diagnostics().count(), 1u); + EXPECT_EQ(program_a.Diagnostics().error_count(), 1u); + EXPECT_EQ(program_a.Diagnostics().begin()->message, "an error message"); + + Program program_b(std::move(program_a)); + EXPECT_FALSE(program_b.IsValid()); + EXPECT_EQ(program_b.Diagnostics().count(), 1u); + EXPECT_EQ(program_b.Diagnostics().error_count(), 1u); + EXPECT_EQ(program_b.Diagnostics().begin()->message, "an error message"); +} + +TEST_F(ProgramTest, ReuseMovedFromVariable) { + Program a(std::move(*this)); + EXPECT_TRUE(a.IsValid()); + + Program b = std::move(a); + EXPECT_TRUE(b.IsValid()); + + a = std::move(b); + EXPECT_TRUE(a.IsValid()); +} + +} // namespace +} // namespace tint
diff --git a/src/tint/reader/reader.cc b/src/tint/reader/reader.cc new file mode 100644 index 0000000..2937ff8 --- /dev/null +++ b/src/tint/reader/reader.cc
@@ -0,0 +1,25 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/reader/reader.h" + +namespace tint { +namespace reader { + +Reader::Reader() = default; + +Reader::~Reader() = default; + +} // namespace reader +} // namespace tint
diff --git a/src/tint/reader/reader.h b/src/tint/reader/reader.h new file mode 100644 index 0000000..6c9c52a --- /dev/null +++ b/src/tint/reader/reader.h
@@ -0,0 +1,65 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_READER_READER_H_ +#define SRC_TINT_READER_READER_H_ + +#include <string> + +#include "src/tint/program.h" + +namespace tint { +namespace reader { + +/// Base class for input readers +class Reader { + public: + virtual ~Reader(); + + /// Parses the input data + /// @returns true if the parse was successful + virtual bool Parse() = 0; + + /// @returns true if an error was encountered. + bool has_error() const { return diags_.contains_errors(); } + + /// @returns the parser error string + std::string error() const { + diag::Formatter formatter{{false, false, false, false}}; + return formatter.format(diags_); + } + + /// @returns the full list of diagnostic messages. + const diag::List& diagnostics() const { return diags_; } + + /// @returns the program. The program builder in the parser will be reset + /// after this. + virtual Program program() = 0; + + protected: + /// Constructor + Reader(); + + /// Sets the diagnostic messages + /// @param diags the list of diagnostic messages + void set_diagnostics(const diag::List& diags) { diags_ = diags; } + + /// All diagnostic messages from the reader. + diag::List diags_; +}; + +} // namespace reader +} // namespace tint + +#endif // SRC_TINT_READER_READER_H_
diff --git a/src/tint/reader/spirv/README.md b/src/tint/reader/spirv/README.md new file mode 100644 index 0000000..f782e21 --- /dev/null +++ b/src/tint/reader/spirv/README.md
@@ -0,0 +1,34 @@ +# SPIR-V Reader + +This component translates SPIR-V written for Vulkan into the Tint AST. + +The SPIR-V reader entry point is `tint::reader::spirv::Parser`, which +implements the Reader interface in `tint::reader::Reader`. + +It's usable from the Tint command line: + + # Translate SPIR-V into WGSL. + tint --format wgsl a.spv + +## Supported dialects + +The SPIR-V module must pass validation for the Vulkan 1.1 environment in SPIRV-Tools. +In particular, SPIR-V 1.4 and later are not supported. + +For example, the equivalent of the following must pass: + + spirv-val --target-env vulkan1.1 a.spv + +Additionally, the reader imposes additional constraints based on: + +* The features supported by WGSL. Some Vulkan features might not be supportable because + WebGPU must be portable to other graphics APIs. +* Limitations of the reader itself. These might be relaxed in the future with extra + engineering work. + +## Feedback + +Please file issues at https://crbug.com/tint, and apply label `SpirvReader`. + +Outstanding issues can be found by using the `SpirvReader` label in the Chromium project's +bug tracker: https://bugs.chromium.org/p/tint/issues/list?q=label:SpirvReader
diff --git a/src/tint/reader/spirv/construct.cc b/src/tint/reader/spirv/construct.cc new file mode 100644 index 0000000..23808e1 --- /dev/null +++ b/src/tint/reader/spirv/construct.cc
@@ -0,0 +1,70 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/reader/spirv/construct.h" + +namespace tint { +namespace reader { +namespace spirv { + +Construct::Construct(const Construct* the_parent, + int the_depth, + Kind the_kind, + uint32_t the_begin_id, + uint32_t the_end_id, + uint32_t the_begin_pos, + uint32_t the_end_pos, + uint32_t the_scope_end_pos) + : parent(the_parent), + enclosing_loop( + // Compute the enclosing loop construct. Doing this in the + // constructor member list lets us make the member const. + // Compare parent depth because loop and continue are siblings and + // it's incidental which will appear on the stack first. + the_kind == kLoop + ? this + : ((parent && parent->depth < the_depth) ? parent->enclosing_loop + : nullptr)), + enclosing_continue( + // Compute the enclosing continue construct. Doing this in the + // constructor member list lets us make the member const. + // Compare parent depth because loop and continue are siblings and + // it's incidental which will appear on the stack first. + the_kind == kContinue ? this + : ((parent && parent->depth < the_depth) + ? parent->enclosing_continue + : nullptr)), + enclosing_loop_or_continue_or_switch( + // Compute the enclosing loop or continue or switch construct. + // Doing this in the constructor member list lets us make the + // member const. + // Compare parent depth because loop and continue are siblings and + // it's incidental which will appear on the stack first. + (the_kind == kLoop || the_kind == kContinue || + the_kind == kSwitchSelection) + ? this + : ((parent && parent->depth < the_depth) + ? parent->enclosing_loop_or_continue_or_switch + : nullptr)), + depth(the_depth), + kind(the_kind), + begin_id(the_begin_id), + end_id(the_end_id), + begin_pos(the_begin_pos), + end_pos(the_end_pos), + scope_end_pos(the_scope_end_pos) {} + +} // namespace spirv +} // namespace reader +} // namespace tint
diff --git a/src/tint/reader/spirv/construct.h b/src/tint/reader/spirv/construct.h new file mode 100644 index 0000000..898b682 --- /dev/null +++ b/src/tint/reader/spirv/construct.h
@@ -0,0 +1,278 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_READER_SPIRV_CONSTRUCT_H_ +#define SRC_TINT_READER_SPIRV_CONSTRUCT_H_ + +#include <memory> +#include <sstream> +#include <string> +#include <vector> + +namespace tint { +namespace reader { +namespace spirv { + +/// A structured control flow construct, consisting of a set of basic blocks. +/// A construct is a span of blocks in the computed block order, +/// and will appear contiguously in the WGSL source. +/// +/// SPIR-V (2.11 Structured Control Flow) defines: +/// - loop construct +/// - continue construct +/// - selection construct +/// We also define a "function construct" consisting of all the basic blocks in +/// the function. +/// +/// The first block in a construct (by computed block order) is called a +/// "header". For the constructs defined by SPIR-V, the header block is the +/// basic block containing the merge instruction. The header for the function +/// construct is the entry block of the function. +/// +/// Given two constructs A and B, we say "A encloses B" if B is a subset of A, +/// i.e. if every basic block in B is also in A. Note that a construct encloses +/// itself. +/// +/// In a valid SPIR-V module, constructs will nest, meaning given +/// constructs A and B, either A encloses B, or B encloses A, or +/// or they are disjoint (have no basic blocks in commont). +/// +/// A loop in a high level language translates into either: +// +/// - a single-block loop, where the loop header branches back to itself. +/// In this case this single-block loop consists only of the *continue +/// construct*. There is no "loop construct" for this case. +// +/// - a multi-block loop, where the loop back-edge is different from the loop +/// header. +/// This case has both a non-empty loop construct containing at least the +/// loop header, and a non-empty continue construct, containing at least the +/// back-edge block. +/// +/// We care about two kinds of selection constructs: +/// +/// - if-selection: where the header block ends in OpBranchConditional +/// +/// - switch-selection: where the header block ends in OpSwitch +/// +struct Construct { + /// Enumeration for the kinds of structured constructs. + enum Kind { + /// The whole function. + kFunction, + /// A SPIR-V selection construct, header basic block ending in + /// OpBrancConditional. + kIfSelection, + /// A SPIR-V selection construct, header basic block ending in OpSwitch. + kSwitchSelection, + /// A SPIR-V loop construct. + kLoop, + /// A SPIR-V continue construct. + kContinue, + }; + + /// Constructor + /// @param the_parent parent construct + /// @param the_depth construct nesting depth + /// @param the_kind construct kind + /// @param the_begin_id block id of the first block in the construct + /// @param the_end_id block id of the first block after the construct, or 0 + /// @param the_begin_pos block order position of the_begin_id + /// @param the_end_pos block order position of the_end_id or a too-large value + /// @param the_scope_end_pos block position of the first block past the end of + /// the WGSL scope + Construct(const Construct* the_parent, + int the_depth, + Kind the_kind, + uint32_t the_begin_id, + uint32_t the_end_id, + uint32_t the_begin_pos, + uint32_t the_end_pos, + uint32_t the_scope_end_pos); + + /// @param pos a block position + /// @returns true if the given block position is inside this construct. + bool ContainsPos(uint32_t pos) const { + return begin_pos <= pos && pos < end_pos; + } + /// Returns true if the given block position is inside the WGSL scope + /// corresponding to this construct. A loop construct's WGSL scope encloses + /// the associated continue construct. Otherwise the WGSL scope extent is the + /// same as the block extent. + /// @param pos a block position + /// @returns true if the given block position is inside the WGSL scope. + bool ScopeContainsPos(uint32_t pos) const { + return begin_pos <= pos && pos < scope_end_pos; + } + + /// The nearest enclosing construct other than itself, or nullptr if + /// this construct represents the entire function. + const Construct* const parent = nullptr; + /// The nearest enclosing loop construct, if one exists. Points to `this` + /// when this is a loop construct. + const Construct* const enclosing_loop = nullptr; + /// The nearest enclosing continue construct, if one exists. Points to + /// `this` when this is a contnue construct. + const Construct* const enclosing_continue = nullptr; + /// The nearest enclosing loop construct or continue construct or + /// switch-selection construct, if one exists. The signficance is + /// that a high level language "break" will branch to the merge block + /// of such an enclosing construct. Points to `this` when this is + /// a loop construct, a continue construct, or a switch-selection construct. + const Construct* const enclosing_loop_or_continue_or_switch = nullptr; + + /// Control flow nesting depth. The entry block is at nesting depth 0. + const int depth = 0; + /// The construct kind + const Kind kind = kFunction; + /// The id of the first block in this structure. + const uint32_t begin_id = 0; + /// 0 for kFunction, or the id of the block immediately after this construct + /// in the computed block order. + const uint32_t end_id = 0; + /// The position of block #begin_id in the computed block order. + const uint32_t begin_pos = 0; + /// The position of block #end_id in the block order, or the number of + /// block order elements if #end_id is 0. + const uint32_t end_pos = 0; + /// The position of the first block after the WGSL scope corresponding to + /// this construct. + const uint32_t scope_end_pos = 0; +}; + +using ConstructList = std::vector<std::unique_ptr<Construct>>; + +/// Converts a construct kind to a string. +/// @param kind the construct kind to convert +/// @returns the string representation +inline std::string ToString(Construct::Kind kind) { + switch (kind) { + case Construct::kFunction: + return "Function"; + case Construct::kIfSelection: + return "IfSelection"; + case Construct::kSwitchSelection: + return "SwitchSelection"; + case Construct::kLoop: + return "Loop"; + case Construct::kContinue: + return "Continue"; + } + return "NONE"; +} + +/// Converts a construct into a short summary string. +/// @param c the construct, which can be null +/// @returns a short summary string +inline std::string ToStringBrief(const Construct* c) { + if (c) { + std::stringstream ss; + ss << ToString(c->kind) << "@" << c->begin_id; + return ss.str(); + } + return "null"; +} + +/// Emits a construct to a stream. +/// @param o the stream +/// @param c the structured construct +/// @returns the stream +inline std::ostream& operator<<(std::ostream& o, const Construct& c) { + o << "Construct{ " << ToString(c.kind) << " [" << c.begin_pos << "," + << c.end_pos << ")" + << " begin_id:" << c.begin_id << " end_id:" << c.end_id + << " depth:" << c.depth; + + o << " parent:" << ToStringBrief(c.parent); + + if (c.scope_end_pos != c.end_pos) { + o << " scope:[" << c.begin_pos << "," << c.scope_end_pos << ")"; + } + + if (c.enclosing_loop) { + o << " in-l:" << ToStringBrief(c.enclosing_loop); + } + + if (c.enclosing_continue) { + o << " in-c:" << ToStringBrief(c.enclosing_continue); + } + + if ((c.enclosing_loop_or_continue_or_switch != c.enclosing_loop) && + (c.enclosing_loop_or_continue_or_switch != c.enclosing_continue)) { + o << " in-c-l-s:" << ToStringBrief(c.enclosing_loop_or_continue_or_switch); + } + + o << " }"; + return o; +} + +/// Emits a construct to a stream. +/// @param o the stream +/// @param c the structured construct +/// @returns the stream +inline std::ostream& operator<<(std::ostream& o, + const std::unique_ptr<Construct>& c) { + return o << *(c.get()); +} + +/// Converts a construct to a string. +/// @param c the construct +/// @returns the string representation +inline std::string ToString(const Construct& c) { + std::stringstream ss; + ss << c; + return ss.str(); +} + +/// Converts a construct to a string. +/// @param c the construct +/// @returns the string representation +inline std::string ToString(const Construct* c) { + return c ? ToString(*c) : ToStringBrief(c); +} + +/// Converts a unique pointer to a construct to a string. +/// @param c the construct +/// @returns the string representation +inline std::string ToString(const std::unique_ptr<Construct>& c) { + return ToString(*(c.get())); +} + +/// Emits a construct list to a stream. +/// @param o the stream +/// @param cl the construct list +/// @returns the stream +inline std::ostream& operator<<(std::ostream& o, const ConstructList& cl) { + o << "ConstructList{\n"; + for (const auto& c : cl) { + o << " " << c << "\n"; + } + o << "}"; + return o; +} + +/// Converts a construct list to a string. +/// @param cl the construct list +/// @returns the string representation +inline std::string ToString(const ConstructList& cl) { + std::stringstream ss; + ss << cl; + return ss.str(); +} + +} // namespace spirv +} // namespace reader +} // namespace tint + +#endif // SRC_TINT_READER_SPIRV_CONSTRUCT_H_
diff --git a/src/tint/reader/spirv/entry_point_info.cc b/src/tint/reader/spirv/entry_point_info.cc new file mode 100644 index 0000000..5258606 --- /dev/null +++ b/src/tint/reader/spirv/entry_point_info.cc
@@ -0,0 +1,44 @@ +// Copyright 2021 The Tint Authors. +// +// 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 "src/tint/reader/spirv/entry_point_info.h" + +#include <utility> + +namespace tint { +namespace reader { +namespace spirv { + +EntryPointInfo::EntryPointInfo(std::string the_name, + ast::PipelineStage the_stage, + bool the_owns_inner_implementation, + std::string the_inner_name, + std::vector<uint32_t>&& the_inputs, + std::vector<uint32_t>&& the_outputs, + GridSize the_wg_size) + : name(the_name), + stage(the_stage), + owns_inner_implementation(the_owns_inner_implementation), + inner_name(std::move(the_inner_name)), + inputs(std::move(the_inputs)), + outputs(std::move(the_outputs)), + workgroup_size(the_wg_size) {} + +EntryPointInfo::EntryPointInfo(const EntryPointInfo&) = default; + +EntryPointInfo::~EntryPointInfo() = default; + +} // namespace spirv +} // namespace reader +} // namespace tint
diff --git a/src/tint/reader/spirv/entry_point_info.h b/src/tint/reader/spirv/entry_point_info.h new file mode 100644 index 0000000..e65b79d --- /dev/null +++ b/src/tint/reader/spirv/entry_point_info.h
@@ -0,0 +1,95 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_READER_SPIRV_ENTRY_POINT_INFO_H_ +#define SRC_TINT_READER_SPIRV_ENTRY_POINT_INFO_H_ + +#include <string> +#include <vector> + +#include "src/tint/ast/pipeline_stage.h" + +namespace tint { +namespace reader { +namespace spirv { + +/// The size of an integer-coordinate grid, in the x, y, and z dimensions. +struct GridSize { + /// x value + uint32_t x = 0; + /// y value + uint32_t y = 0; + /// z value + uint32_t z = 0; +}; + +/// Entry point information for a function +struct EntryPointInfo { + /// Constructor. + /// @param the_name the name of the entry point + /// @param the_stage the pipeline stage + /// @param the_owns_inner_implementation if true, this entry point is + /// responsible for generating the inner implementation function. + /// @param the_inner_name the name of the inner implementation function of the + /// entry point + /// @param the_inputs list of IDs for Input variables used by the shader + /// @param the_outputs list of IDs for Output variables used by the shader + /// @param the_wg_size the workgroup_size, for a compute shader + EntryPointInfo(std::string the_name, + ast::PipelineStage the_stage, + bool the_owns_inner_implementation, + std::string the_inner_name, + std::vector<uint32_t>&& the_inputs, + std::vector<uint32_t>&& the_outputs, + GridSize the_wg_size); + /// Copy constructor + /// @param other the other entry point info to be built from + EntryPointInfo(const EntryPointInfo& other); + /// Destructor + ~EntryPointInfo(); + + /// The entry point name. + /// In the WGSL output, this function will have pipeline inputs and outputs + /// as parameters. This function will store them into Private variables, + /// and then call the "inner" function, named by the next memeber. + /// Then outputs are copied from the private variables to the return value. + std::string name; + /// The entry point stage + ast::PipelineStage stage = ast::PipelineStage::kNone; + + /// True when this entry point is responsible for generating the + /// inner implementation function. False when this is the second entry + /// point encountered for the same function in SPIR-V. It's unusual, but + /// possible for the same function to be the implementation for multiple + /// entry points. + bool owns_inner_implementation; + /// The name of the inner implementation function of the entry point. + std::string inner_name; + /// IDs of pipeline input variables, sorted and without duplicates. + std::vector<uint32_t> inputs; + /// IDs of pipeline output variables, sorted and without duplicates. + std::vector<uint32_t> outputs; + + /// If this is a compute shader, this is the workgroup size in the x, y, + /// and z dimensions set via LocalSize, or via the composite value + /// decorated as the WorkgroupSize BuiltIn. The WorkgroupSize builtin + /// takes priority. + GridSize workgroup_size; +}; + +} // namespace spirv +} // namespace reader +} // namespace tint + +#endif // SRC_TINT_READER_SPIRV_ENTRY_POINT_INFO_H_
diff --git a/src/tint/reader/spirv/enum_converter.cc b/src/tint/reader/spirv/enum_converter.cc new file mode 100644 index 0000000..a197bac --- /dev/null +++ b/src/tint/reader/spirv/enum_converter.cc
@@ -0,0 +1,182 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/reader/spirv/enum_converter.h" + +namespace tint { +namespace reader { +namespace spirv { + +EnumConverter::EnumConverter(const FailStream& fs) : fail_stream_(fs) {} + +EnumConverter::~EnumConverter() = default; + +ast::PipelineStage EnumConverter::ToPipelineStage(SpvExecutionModel model) { + switch (model) { + case SpvExecutionModelVertex: + return ast::PipelineStage::kVertex; + case SpvExecutionModelFragment: + return ast::PipelineStage::kFragment; + case SpvExecutionModelGLCompute: + return ast::PipelineStage::kCompute; + default: + break; + } + + Fail() << "unknown SPIR-V execution model: " << uint32_t(model); + return ast::PipelineStage::kNone; +} + +ast::StorageClass EnumConverter::ToStorageClass(const SpvStorageClass sc) { + switch (sc) { + case SpvStorageClassInput: + return ast::StorageClass::kInput; + case SpvStorageClassOutput: + return ast::StorageClass::kOutput; + case SpvStorageClassUniform: + return ast::StorageClass::kUniform; + case SpvStorageClassWorkgroup: + return ast::StorageClass::kWorkgroup; + case SpvStorageClassUniformConstant: + return ast::StorageClass::kNone; + case SpvStorageClassStorageBuffer: + return ast::StorageClass::kStorage; + case SpvStorageClassPrivate: + return ast::StorageClass::kPrivate; + case SpvStorageClassFunction: + return ast::StorageClass::kFunction; + default: + break; + } + + Fail() << "unknown SPIR-V storage class: " << uint32_t(sc); + return ast::StorageClass::kInvalid; +} + +ast::Builtin EnumConverter::ToBuiltin(SpvBuiltIn b) { + switch (b) { + case SpvBuiltInPosition: + return ast::Builtin::kPosition; + case SpvBuiltInVertexIndex: + return ast::Builtin::kVertexIndex; + case SpvBuiltInInstanceIndex: + return ast::Builtin::kInstanceIndex; + case SpvBuiltInFrontFacing: + return ast::Builtin::kFrontFacing; + case SpvBuiltInFragCoord: + return ast::Builtin::kPosition; + case SpvBuiltInFragDepth: + return ast::Builtin::kFragDepth; + case SpvBuiltInLocalInvocationId: + return ast::Builtin::kLocalInvocationId; + case SpvBuiltInLocalInvocationIndex: + return ast::Builtin::kLocalInvocationIndex; + case SpvBuiltInGlobalInvocationId: + return ast::Builtin::kGlobalInvocationId; + case SpvBuiltInWorkgroupId: + return ast::Builtin::kWorkgroupId; + case SpvBuiltInSampleId: + return ast::Builtin::kSampleIndex; + case SpvBuiltInSampleMask: + return ast::Builtin::kSampleMask; + default: + break; + } + + Fail() << "unknown SPIR-V builtin: " << uint32_t(b); + return ast::Builtin::kNone; +} + +ast::TextureDimension EnumConverter::ToDim(SpvDim dim, bool arrayed) { + if (arrayed) { + switch (dim) { + case SpvDim2D: + return ast::TextureDimension::k2dArray; + case SpvDimCube: + return ast::TextureDimension::kCubeArray; + default: + break; + } + Fail() << "arrayed dimension must be 2D or Cube. Got " << int(dim); + return ast::TextureDimension::kNone; + } + // Assume non-arrayed + switch (dim) { + case SpvDim1D: + return ast::TextureDimension::k1d; + case SpvDim2D: + return ast::TextureDimension::k2d; + case SpvDim3D: + return ast::TextureDimension::k3d; + case SpvDimCube: + return ast::TextureDimension::kCube; + default: + break; + } + Fail() << "invalid dimension: " << int(dim); + return ast::TextureDimension::kNone; +} + +ast::TexelFormat EnumConverter::ToTexelFormat(SpvImageFormat fmt) { + switch (fmt) { + case SpvImageFormatUnknown: + return ast::TexelFormat::kNone; + + // 8 bit channels + case SpvImageFormatRgba8: + return ast::TexelFormat::kRgba8Unorm; + case SpvImageFormatRgba8Snorm: + return ast::TexelFormat::kRgba8Snorm; + case SpvImageFormatRgba8ui: + return ast::TexelFormat::kRgba8Uint; + case SpvImageFormatRgba8i: + return ast::TexelFormat::kRgba8Sint; + + // 16 bit channels + case SpvImageFormatRgba16ui: + return ast::TexelFormat::kRgba16Uint; + case SpvImageFormatRgba16i: + return ast::TexelFormat::kRgba16Sint; + case SpvImageFormatRgba16f: + return ast::TexelFormat::kRgba16Float; + + // 32 bit channels + case SpvImageFormatR32ui: + return ast::TexelFormat::kR32Uint; + case SpvImageFormatR32i: + return ast::TexelFormat::kR32Sint; + case SpvImageFormatR32f: + return ast::TexelFormat::kR32Float; + case SpvImageFormatRg32ui: + return ast::TexelFormat::kRg32Uint; + case SpvImageFormatRg32i: + return ast::TexelFormat::kRg32Sint; + case SpvImageFormatRg32f: + return ast::TexelFormat::kRg32Float; + case SpvImageFormatRgba32ui: + return ast::TexelFormat::kRgba32Uint; + case SpvImageFormatRgba32i: + return ast::TexelFormat::kRgba32Sint; + case SpvImageFormatRgba32f: + return ast::TexelFormat::kRgba32Float; + default: + break; + } + Fail() << "invalid image format: " << int(fmt); + return ast::TexelFormat::kNone; +} + +} // namespace spirv +} // namespace reader +} // namespace tint
diff --git a/src/tint/reader/spirv/enum_converter.h b/src/tint/reader/spirv/enum_converter.h new file mode 100644 index 0000000..d2caac3 --- /dev/null +++ b/src/tint/reader/spirv/enum_converter.h
@@ -0,0 +1,81 @@ +// Copyright 2020 The Tint Authors. +// +// 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 SRC_TINT_READER_SPIRV_ENUM_CONVERTER_H_ +#define SRC_TINT_READER_SPIRV_ENUM_CONVERTER_H_ + +#include "spirv/unified1/spirv.h" +#include "src/tint/ast/builtin.h" +#include "src/tint/ast/pipeline_stage.h" +#include "src/tint/ast/storage_class.h" +#include "src/tint/reader/spirv/fail_stream.h" +#include "src/tint/sem/storage_texture_type.h" + +namespace tint { +namespace reader { +namespace spirv { + +/// A converter from SPIR-V enums to Tint AST enums. +class EnumConverter { + public: + /// Creates a new enum converter. + /// @param fail_stream the error reporting stream. + explicit EnumConverter(const FailStream& fail_stream); + /// Destructor + ~EnumConverter(); + + /// Converts a SPIR-V execution model to a Tint pipeline stage. + /// On failure, logs an error and returns kNone + /// @param model the SPIR-V entry point execution model + /// @returns a Tint AST pipeline stage + ast::PipelineStage ToPipelineStage(SpvExecutionModel model); + + /// Converts a SPIR-V storage class to a Tint storage class. + /// On failure, logs an error and returns kNone + /// @param sc the SPIR-V storage class + /// @returns a Tint AST storage class + ast::StorageClass ToStorageClass(const SpvStorageClass sc); + + /// Converts a SPIR-V Builtin value a Tint Builtin. + /// On failure, logs an error and returns kNone + /// @param b the SPIR-V builtin + /// @returns a Tint AST builtin + ast::Builtin ToBuiltin(SpvBuiltIn b); + + /// Converts a possibly arrayed SPIR-V Dim to a Tint texture dimension. + /// On failure, logs an error and returns kNone + /// @param dim the SPIR-V Dim value + /// @param arrayed true if the texture is arrayed + /// @returns a Tint AST texture dimension + ast::TextureDimension ToDim(SpvDim dim, bool arrayed); + + /// Converts a SPIR-V Image Format to a TexelFormat + /// On failure, logs an error and returns kNone + /// @param fmt the SPIR-V format + /// @returns a Tint AST format + ast::TexelFormat ToTexelFormat(SpvImageFormat fmt); + + private: + /// Registers a failure and returns a stream for log diagnostics. + /// @returns a failure stream + FailStream Fail() { return fail_stream_.Fail(); } + + FailStream fail_stream_; +}; + +} // namespace spirv +} // namespace reader +} // namespace tint + +#endif // SRC_TINT_READER_SPIRV_ENUM_CONVERTER_H_
diff --git a/src/tint/reader/spirv/enum_converter_test.cc b/src/tint/reader/spirv/enum_converter_test.cc new file mode 100644 index 0000000..eec14f3 --- /dev/null +++ b/src/tint/reader/spirv/enum_converter_test.cc
@@ -0,0 +1,429 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/reader/spirv/enum_converter.h" + +#include <string> + +#include "gmock/gmock.h" + +namespace tint { +namespace reader { +namespace spirv { +namespace { + +// Pipeline stage + +struct PipelineStageCase { + SpvExecutionModel model; + bool expect_success; + ast::PipelineStage expected; +}; +inline std::ostream& operator<<(std::ostream& out, PipelineStageCase psc) { + out << "PipelineStageCase{ SpvExecutionModel:" << int(psc.model) + << " expect_success?:" << int(psc.expect_success) + << " expected:" << int(psc.expected) << "}"; + return out; +} + +class SpvPipelineStageTest : public testing::TestWithParam<PipelineStageCase> { + public: + SpvPipelineStageTest() + : success_(true), + fail_stream_(&success_, &errors_), + converter_(fail_stream_) {} + + std::string error() const { return errors_.str(); } + + protected: + bool success_ = true; + std::stringstream errors_; + FailStream fail_stream_; + EnumConverter converter_; +}; + +TEST_P(SpvPipelineStageTest, Samples) { + const auto params = GetParam(); + + const auto result = converter_.ToPipelineStage(params.model); + EXPECT_EQ(success_, params.expect_success); + if (params.expect_success) { + EXPECT_EQ(result, params.expected); + EXPECT_TRUE(error().empty()); + } else { + EXPECT_EQ(result, params.expected); + EXPECT_THAT(error(), + ::testing::StartsWith("unknown SPIR-V execution model:")); + } +} + +INSTANTIATE_TEST_SUITE_P( + EnumConverterGood, + SpvPipelineStageTest, + testing::Values(PipelineStageCase{SpvExecutionModelVertex, true, + ast::PipelineStage::kVertex}, + PipelineStageCase{SpvExecutionModelFragment, true, + ast::PipelineStage::kFragment}, + PipelineStageCase{SpvExecutionModelGLCompute, true, + ast::PipelineStage::kCompute})); + +INSTANTIATE_TEST_SUITE_P( + EnumConverterBad, + SpvPipelineStageTest, + testing::Values(PipelineStageCase{static_cast<SpvExecutionModel>(9999), + false, ast::PipelineStage::kNone}, + PipelineStageCase{SpvExecutionModelTessellationControl, + false, ast::PipelineStage::kNone})); + +// Storage class + +struct StorageClassCase { + SpvStorageClass sc; + bool expect_success; + ast::StorageClass expected; +}; +inline std::ostream& operator<<(std::ostream& out, StorageClassCase scc) { + out << "StorageClassCase{ SpvStorageClass:" << int(scc.sc) + << " expect_success?:" << int(scc.expect_success) + << " expected:" << int(scc.expected) << "}"; + return out; +} + +class SpvStorageClassTest : public testing::TestWithParam<StorageClassCase> { + public: + SpvStorageClassTest() + : success_(true), + fail_stream_(&success_, &errors_), + converter_(fail_stream_) {} + + std::string error() const { return errors_.str(); } + + protected: + bool success_ = true; + std::stringstream errors_; + FailStream fail_stream_; + EnumConverter converter_; +}; + +TEST_P(SpvStorageClassTest, Samples) { + const auto params = GetParam(); + + const auto result = converter_.ToStorageClass(params.sc); + EXPECT_EQ(success_, params.expect_success); + if (params.expect_success) { + EXPECT_EQ(result, params.expected); + EXPECT_TRUE(error().empty()); + } else { + EXPECT_EQ(result, params.expected); + EXPECT_THAT(error(), + ::testing::StartsWith("unknown SPIR-V storage class: ")); + } +} + +INSTANTIATE_TEST_SUITE_P( + EnumConverterGood, + SpvStorageClassTest, + testing::Values(StorageClassCase{SpvStorageClassInput, true, + ast::StorageClass::kInput}, + StorageClassCase{SpvStorageClassOutput, true, + ast::StorageClass::kOutput}, + StorageClassCase{SpvStorageClassUniform, true, + ast::StorageClass::kUniform}, + StorageClassCase{SpvStorageClassWorkgroup, true, + ast::StorageClass::kWorkgroup}, + StorageClassCase{SpvStorageClassUniformConstant, true, + ast::StorageClass::kNone}, + StorageClassCase{SpvStorageClassStorageBuffer, true, + ast::StorageClass::kStorage}, + StorageClassCase{SpvStorageClassPrivate, true, + ast::StorageClass::kPrivate}, + StorageClassCase{SpvStorageClassFunction, true, + ast::StorageClass::kFunction})); + +INSTANTIATE_TEST_SUITE_P(EnumConverterBad, + SpvStorageClassTest, + testing::Values(StorageClassCase{ + static_cast<SpvStorageClass>(9999), false, + ast::StorageClass::kInvalid})); + +// Builtin + +struct BuiltinCase { + SpvBuiltIn builtin; + bool expect_success; + ast::Builtin expected; +}; +inline std::ostream& operator<<(std::ostream& out, BuiltinCase bc) { + out << "BuiltinCase{ SpvBuiltIn:" << int(bc.builtin) + << " expect_success?:" << int(bc.expect_success) + << " expected:" << int(bc.expected) << "}"; + return out; +} + +class SpvBuiltinTest : public testing::TestWithParam<BuiltinCase> { + public: + SpvBuiltinTest() + : success_(true), + fail_stream_(&success_, &errors_), + converter_(fail_stream_) {} + + std::string error() const { return errors_.str(); } + + protected: + bool success_ = true; + std::stringstream errors_; + FailStream fail_stream_; + EnumConverter converter_; +}; + +TEST_P(SpvBuiltinTest, Samples) { + const auto params = GetParam(); + + const auto result = converter_.ToBuiltin(params.builtin); + EXPECT_EQ(success_, params.expect_success); + if (params.expect_success) { + EXPECT_EQ(result, params.expected); + EXPECT_TRUE(error().empty()); + } else { + EXPECT_EQ(result, params.expected); + EXPECT_THAT(error(), ::testing::StartsWith("unknown SPIR-V builtin: ")); + } +} + +INSTANTIATE_TEST_SUITE_P( + EnumConverterGood_Input, + SpvBuiltinTest, + testing::Values( + BuiltinCase{SpvBuiltInPosition, true, ast::Builtin::kPosition}, + BuiltinCase{SpvBuiltInInstanceIndex, true, + ast::Builtin::kInstanceIndex}, + BuiltinCase{SpvBuiltInFrontFacing, true, ast::Builtin::kFrontFacing}, + BuiltinCase{SpvBuiltInFragCoord, true, ast::Builtin::kPosition}, + BuiltinCase{SpvBuiltInLocalInvocationId, true, + ast::Builtin::kLocalInvocationId}, + BuiltinCase{SpvBuiltInLocalInvocationIndex, true, + ast::Builtin::kLocalInvocationIndex}, + BuiltinCase{SpvBuiltInGlobalInvocationId, true, + ast::Builtin::kGlobalInvocationId}, + BuiltinCase{SpvBuiltInWorkgroupId, true, ast::Builtin::kWorkgroupId}, + BuiltinCase{SpvBuiltInSampleId, true, ast::Builtin::kSampleIndex}, + BuiltinCase{SpvBuiltInSampleMask, true, ast::Builtin::kSampleMask})); + +INSTANTIATE_TEST_SUITE_P( + EnumConverterGood_Output, + SpvBuiltinTest, + testing::Values( + BuiltinCase{SpvBuiltInPosition, true, ast::Builtin::kPosition}, + BuiltinCase{SpvBuiltInFragDepth, true, ast::Builtin::kFragDepth}, + BuiltinCase{SpvBuiltInSampleMask, true, ast::Builtin::kSampleMask})); + +INSTANTIATE_TEST_SUITE_P( + EnumConverterBad, + SpvBuiltinTest, + testing::Values( + BuiltinCase{static_cast<SpvBuiltIn>(9999), false, ast::Builtin::kNone}, + BuiltinCase{static_cast<SpvBuiltIn>(9999), false, ast::Builtin::kNone}, + BuiltinCase{SpvBuiltInNumWorkgroups, false, ast::Builtin::kNone})); + +// Dim + +struct DimCase { + SpvDim dim; + bool arrayed; + bool expect_success; + ast::TextureDimension expected; +}; +inline std::ostream& operator<<(std::ostream& out, DimCase dc) { + out << "DimCase{ SpvDim:" << int(dc.dim) << " arrayed?:" << int(dc.arrayed) + << " expect_success?:" << int(dc.expect_success) + << " expected:" << int(dc.expected) << "}"; + return out; +} + +class SpvDimTest : public testing::TestWithParam<DimCase> { + public: + SpvDimTest() + : success_(true), + fail_stream_(&success_, &errors_), + converter_(fail_stream_) {} + + std::string error() const { return errors_.str(); } + + protected: + bool success_ = true; + std::stringstream errors_; + FailStream fail_stream_; + EnumConverter converter_; +}; + +TEST_P(SpvDimTest, Samples) { + const auto params = GetParam(); + + const auto result = converter_.ToDim(params.dim, params.arrayed); + EXPECT_EQ(success_, params.expect_success); + if (params.expect_success) { + EXPECT_EQ(result, params.expected); + EXPECT_TRUE(error().empty()); + } else { + EXPECT_EQ(result, params.expected); + EXPECT_THAT(error(), ::testing::HasSubstr("dimension")); + } +} + +INSTANTIATE_TEST_SUITE_P( + EnumConverterGood, + SpvDimTest, + testing::Values( + // Non-arrayed + DimCase{SpvDim1D, false, true, ast::TextureDimension::k1d}, + DimCase{SpvDim2D, false, true, ast::TextureDimension::k2d}, + DimCase{SpvDim3D, false, true, ast::TextureDimension::k3d}, + DimCase{SpvDimCube, false, true, ast::TextureDimension::kCube}, + // Arrayed + DimCase{SpvDim2D, true, true, ast::TextureDimension::k2dArray}, + DimCase{SpvDimCube, true, true, ast::TextureDimension::kCubeArray})); + +INSTANTIATE_TEST_SUITE_P( + EnumConverterBad, + SpvDimTest, + testing::Values( + // Invalid SPIR-V dimensionality. + DimCase{SpvDimMax, false, false, ast::TextureDimension::kNone}, + DimCase{SpvDimMax, true, false, ast::TextureDimension::kNone}, + // Vulkan non-arrayed dimensionalities not supported by WGSL. + DimCase{SpvDimRect, false, false, ast::TextureDimension::kNone}, + DimCase{SpvDimBuffer, false, false, ast::TextureDimension::kNone}, + DimCase{SpvDimSubpassData, false, false, ast::TextureDimension::kNone}, + // Arrayed dimensionalities not supported by WGSL + DimCase{SpvDim3D, true, false, ast::TextureDimension::kNone}, + DimCase{SpvDimRect, true, false, ast::TextureDimension::kNone}, + DimCase{SpvDimBuffer, true, false, ast::TextureDimension::kNone}, + DimCase{SpvDimSubpassData, true, false, ast::TextureDimension::kNone})); + +// TexelFormat + +struct TexelFormatCase { + SpvImageFormat format; + bool expect_success; + ast::TexelFormat expected; +}; +inline std::ostream& operator<<(std::ostream& out, TexelFormatCase ifc) { + out << "TexelFormatCase{ SpvImageFormat:" << int(ifc.format) + << " expect_success?:" << int(ifc.expect_success) + << " expected:" << int(ifc.expected) << "}"; + return out; +} + +class SpvImageFormatTest : public testing::TestWithParam<TexelFormatCase> { + public: + SpvImageFormatTest() + : success_(true), + fail_stream_(&success_, &errors_), + converter_(fail_stream_) {} + + std::string error() const { return errors_.str(); } + + protected: + bool success_ = true; + std::stringstream errors_; + FailStream fail_stream_; + EnumConverter converter_; +}; + +TEST_P(SpvImageFormatTest, Samples) { + const auto params = GetParam(); + + const auto result = converter_.ToTexelFormat(params.format); + EXPECT_EQ(success_, params.expect_success) << params; + if (params.expect_success) { + EXPECT_EQ(result, params.expected); + EXPECT_TRUE(error().empty()); + } else { + EXPECT_EQ(result, params.expected); + EXPECT_THAT(error(), ::testing::StartsWith("invalid image format: ")); + } +} + +INSTANTIATE_TEST_SUITE_P( + EnumConverterGood, + SpvImageFormatTest, + testing::Values( + // Unknown. This is used for sampled images. + TexelFormatCase{SpvImageFormatUnknown, true, ast::TexelFormat::kNone}, + // 8 bit channels + TexelFormatCase{SpvImageFormatRgba8, true, + ast::TexelFormat::kRgba8Unorm}, + TexelFormatCase{SpvImageFormatRgba8Snorm, true, + ast::TexelFormat::kRgba8Snorm}, + TexelFormatCase{SpvImageFormatRgba8ui, true, + ast::TexelFormat::kRgba8Uint}, + TexelFormatCase{SpvImageFormatRgba8i, true, + ast::TexelFormat::kRgba8Sint}, + // 16 bit channels + TexelFormatCase{SpvImageFormatRgba16ui, true, + ast::TexelFormat::kRgba16Uint}, + TexelFormatCase{SpvImageFormatRgba16i, true, + ast::TexelFormat::kRgba16Sint}, + TexelFormatCase{SpvImageFormatRgba16f, true, + ast::TexelFormat::kRgba16Float}, + // 32 bit channels + // ... 1 channel + TexelFormatCase{SpvImageFormatR32ui, true, ast::TexelFormat::kR32Uint}, + TexelFormatCase{SpvImageFormatR32i, true, ast::TexelFormat::kR32Sint}, + TexelFormatCase{SpvImageFormatR32f, true, ast::TexelFormat::kR32Float}, + // ... 2 channels + TexelFormatCase{SpvImageFormatRg32ui, true, + ast::TexelFormat::kRg32Uint}, + TexelFormatCase{SpvImageFormatRg32i, true, ast::TexelFormat::kRg32Sint}, + TexelFormatCase{SpvImageFormatRg32f, true, + ast::TexelFormat::kRg32Float}, + // ... 4 channels + TexelFormatCase{SpvImageFormatRgba32ui, true, + ast::TexelFormat::kRgba32Uint}, + TexelFormatCase{SpvImageFormatRgba32i, true, + ast::TexelFormat::kRgba32Sint}, + TexelFormatCase{SpvImageFormatRgba32f, true, + ast::TexelFormat::kRgba32Float})); + +INSTANTIATE_TEST_SUITE_P( + EnumConverterBad, + SpvImageFormatTest, + testing::Values( + // Scanning in order from the SPIR-V spec. + TexelFormatCase{SpvImageFormatRg16f, false, ast::TexelFormat::kNone}, + TexelFormatCase{SpvImageFormatR11fG11fB10f, false, + ast::TexelFormat::kNone}, + TexelFormatCase{SpvImageFormatR16f, false, ast::TexelFormat::kNone}, + TexelFormatCase{SpvImageFormatRgb10A2, false, ast::TexelFormat::kNone}, + TexelFormatCase{SpvImageFormatRg16, false, ast::TexelFormat::kNone}, + TexelFormatCase{SpvImageFormatRg8, false, ast::TexelFormat::kNone}, + TexelFormatCase{SpvImageFormatR16, false, ast::TexelFormat::kNone}, + TexelFormatCase{SpvImageFormatR8, false, ast::TexelFormat::kNone}, + TexelFormatCase{SpvImageFormatRgba16Snorm, false, + ast::TexelFormat::kNone}, + TexelFormatCase{SpvImageFormatRg16Snorm, false, + ast::TexelFormat::kNone}, + TexelFormatCase{SpvImageFormatRg8Snorm, false, ast::TexelFormat::kNone}, + TexelFormatCase{SpvImageFormatRg16i, false, ast::TexelFormat::kNone}, + TexelFormatCase{SpvImageFormatRg8i, false, ast::TexelFormat::kNone}, + TexelFormatCase{SpvImageFormatR8i, false, ast::TexelFormat::kNone}, + TexelFormatCase{SpvImageFormatRgb10a2ui, false, + ast::TexelFormat::kNone}, + TexelFormatCase{SpvImageFormatRg16ui, false, ast::TexelFormat::kNone}, + TexelFormatCase{SpvImageFormatRg8ui, false, ast::TexelFormat::kNone})); + +} // namespace +} // namespace spirv +} // namespace reader +} // namespace tint
diff --git a/src/tint/reader/spirv/fail_stream.h b/src/tint/reader/spirv/fail_stream.h new file mode 100644 index 0000000..a7530ca --- /dev/null +++ b/src/tint/reader/spirv/fail_stream.h
@@ -0,0 +1,74 @@ +// Copyright 2020 The Tint Authors +// +// 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 SRC_TINT_READER_SPIRV_FAIL_STREAM_H_ +#define SRC_TINT_READER_SPIRV_FAIL_STREAM_H_ + +#include <ostream> + +namespace tint { +namespace reader { +namespace spirv { + +/// A FailStream object accumulates values onto a given std::ostream, +/// and can be used to record failure by writing the false value +/// to given a pointer-to-bool. +class FailStream { + public: + /// Creates a new fail stream + /// @param status_ptr where we will write false to indicate failure. Assumed + /// to be a valid pointer to bool. + /// @param out output stream where a message should be written to explain + /// the failure + FailStream(bool* status_ptr, std::ostream* out) + : status_ptr_(status_ptr), out_(out) {} + /// Copy constructor + /// @param other the fail stream to clone + FailStream(const FailStream& other) = default; + + /// Converts to a boolean status. A true result indicates success, + /// and a false result indicates failure. + /// @returns the status + operator bool() const { return *status_ptr_; } + /// Returns the current status value. This can be more readable + /// the conversion operator. + /// @returns the status + bool status() const { return *status_ptr_; } + + /// Records failure. + /// @returns a FailStream + FailStream& Fail() { + *status_ptr_ = false; + return *this; + } + + /// Appends the given value to the message output stream. + /// @param val the value to write to the output stream. + /// @returns this object + template <typename T> + FailStream& operator<<(const T& val) { + *out_ << val; + return *this; + } + + private: + bool* status_ptr_; + std::ostream* out_; +}; + +} // namespace spirv +} // namespace reader +} // namespace tint + +#endif // SRC_TINT_READER_SPIRV_FAIL_STREAM_H_
diff --git a/src/tint/reader/spirv/fail_stream_test.cc b/src/tint/reader/spirv/fail_stream_test.cc new file mode 100644 index 0000000..4c6e9bf --- /dev/null +++ b/src/tint/reader/spirv/fail_stream_test.cc
@@ -0,0 +1,73 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/reader/spirv/fail_stream.h" + +#include "gmock/gmock.h" + +namespace tint { +namespace reader { +namespace spirv { +namespace { + +using ::testing::Eq; + +using FailStreamTest = ::testing::Test; + +TEST_F(FailStreamTest, ConversionToBoolIsSameAsStatusMethod) { + bool flag = true; + FailStream fs(&flag, nullptr); + + EXPECT_TRUE(fs.status()); + EXPECT_TRUE(bool(fs)); // NOLINT + flag = false; + EXPECT_FALSE(fs.status()); + EXPECT_FALSE(bool(fs)); // NOLINT + flag = true; + EXPECT_TRUE(fs.status()); + EXPECT_TRUE(bool(fs)); // NOLINT +} + +TEST_F(FailStreamTest, FailMethodChangesStatusToFalse) { + bool flag = true; + FailStream fs(&flag, nullptr); + EXPECT_TRUE(flag); + EXPECT_TRUE(bool(fs)); // NOLINT + fs.Fail(); + EXPECT_FALSE(flag); + EXPECT_FALSE(bool(fs)); // NOLINT +} + +TEST_F(FailStreamTest, FailMethodReturnsSelf) { + bool flag = true; + FailStream fs(&flag, nullptr); + FailStream& result = fs.Fail(); + EXPECT_THAT(&result, Eq(&fs)); +} + +TEST_F(FailStreamTest, ShiftOperatorAccumulatesValues) { + bool flag = true; + std::stringstream ss; + FailStream fs(&flag, &ss); + + ss << "prefix "; + fs << "cat " << 42; + + EXPECT_THAT(ss.str(), Eq("prefix cat 42")); +} + +} // namespace +} // namespace spirv +} // namespace reader +} // namespace tint
diff --git a/src/tint/reader/spirv/function.cc b/src/tint/reader/spirv/function.cc new file mode 100644 index 0000000..c130e85 --- /dev/null +++ b/src/tint/reader/spirv/function.cc
@@ -0,0 +1,6153 @@ +// Copyright 2020 The Tint Authors. +// +// 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 "src/tint/reader/spirv/function.h" + +#include <algorithm> +#include <array> + +#include "src/tint/ast/assignment_statement.h" +#include "src/tint/ast/bitcast_expression.h" +#include "src/tint/ast/break_statement.h" +#include "src/tint/ast/builtin.h" +#include "src/tint/ast/builtin_attribute.h" +#include "src/tint/ast/call_statement.h" +#include "src/tint/ast/continue_statement.h" +#include "src/tint/ast/discard_statement.h" +#include "src/tint/ast/fallthrough_statement.h" +#include "src/tint/ast/if_statement.h" +#include "src/tint/ast/loop_statement.h" +#include "src/tint/ast/return_statement.h" +#include "src/tint/ast/stage_attribute.h" +#include "src/tint/ast/switch_statement.h" +#include "src/tint/ast/unary_op_expression.h" +#include "src/tint/ast/variable_decl_statement.h" +#include "src/tint/sem/builtin_type.h" +#include "src/tint/sem/depth_texture_type.h" +#include "src/tint/sem/sampled_texture_type.h" + +// Terms: +// CFG: the control flow graph of the function, where basic blocks are the +// nodes, and branches form the directed arcs. The function entry block is +// the root of the CFG. +// +// Suppose H is a header block (i.e. has an OpSelectionMerge or OpLoopMerge). +// Then: +// - Let M(H) be the merge block named by the merge instruction in H. +// - If H is a loop header, i.e. has an OpLoopMerge instruction, then let +// CT(H) be the continue target block named by the OpLoopMerge +// instruction. +// - If H is a selection construct whose header ends in +// OpBranchConditional with true target %then and false target %else, +// then TT(H) = %then and FT(H) = %else +// +// Determining output block order: +// The "structured post-order traversal" of the CFG is a post-order traversal +// of the basic blocks in the CFG, where: +// We visit the entry node of the function first. +// When visiting a header block: +// We next visit its merge block +// Then if it's a loop header, we next visit the continue target, +// Then we visit the block's successors (whether it's a header or not) +// If the block ends in an OpBranchConditional, we visit the false target +// before the true target. +// +// The "reverse structured post-order traversal" of the CFG is the reverse +// of the structured post-order traversal. +// This is the order of basic blocks as they should be emitted to the WGSL +// function. It is the order computed by ComputeBlockOrder, and stored in +// the |FunctionEmiter::block_order_|. +// Blocks not in this ordering are ignored by the rest of the algorithm. +// +// Note: +// - A block D in the function might not appear in this order because +// no block in the order branches to D. +// - An unreachable block D might still be in the order because some header +// block in the order names D as its continue target, or merge block, +// or D is reachable from one of those otherwise-unreachable continue +// targets or merge blocks. +// +// Terms: +// Let Pos(B) be the index position of a block B in the computed block order. +// +// CFG intervals and valid nesting: +// +// A correctly structured CFG satisfies nesting rules that we can check by +// comparing positions of related blocks. +// +// If header block H is in the block order, then the following holds: +// +// Pos(H) < Pos(M(H)) +// +// If CT(H) exists, then: +// +// Pos(H) <= Pos(CT(H)) +// Pos(CT(H)) < Pos(M) +// +// This gives us the fundamental ordering of blocks in relation to a +// structured construct: +// The blocks before H in the block order, are not in the construct +// The blocks at M(H) or later in the block order, are not in the construct +// The blocks in a selection headed at H are in positions [ Pos(H), +// Pos(M(H)) ) The blocks in a loop construct headed at H are in positions +// [ Pos(H), Pos(CT(H)) ) The blocks in the continue construct for loop +// headed at H are in +// positions [ Pos(CT(H)), Pos(M(H)) ) +// +// Schematically, for a selection construct headed by H, the blocks are in +// order from left to right: +// +// ...a-b-c H d-e-f M(H) n-o-p... +// +// where ...a-b-c: blocks before the selection construct +// where H and d-e-f: blocks in the selection construct +// where M(H) and n-o-p...: blocks after the selection construct +// +// Schematically, for a loop construct headed by H that is its own +// continue construct, the blocks in order from left to right: +// +// ...a-b-c H=CT(H) d-e-f M(H) n-o-p... +// +// where ...a-b-c: blocks before the loop +// where H is the continue construct; CT(H)=H, and the loop construct +// is *empty* +// where d-e-f... are other blocks in the continue construct +// where M(H) and n-o-p...: blocks after the continue construct +// +// Schematically, for a multi-block loop construct headed by H, there are +// blocks in order from left to right: +// +// ...a-b-c H d-e-f CT(H) j-k-l M(H) n-o-p... +// +// where ...a-b-c: blocks before the loop +// where H and d-e-f: blocks in the loop construct +// where CT(H) and j-k-l: blocks in the continue construct +// where M(H) and n-o-p...: blocks after the loop and continue +// constructs +// + +namespace tint { +namespace reader { +namespace spirv { + +namespace { + +constexpr uint32_t kMaxVectorLen = 4; + +// Gets the AST unary opcode for the given SPIR-V opcode, if any +// @param opcode SPIR-V opcode +// @param ast_unary_op return parameter +// @returns true if it was a unary operation +bool GetUnaryOp(SpvOp opcode, ast::UnaryOp* ast_unary_op) { + switch (opcode) { + case SpvOpSNegate: + case SpvOpFNegate: + *ast_unary_op = ast::UnaryOp::kNegation; + return true; + case SpvOpLogicalNot: + *ast_unary_op = ast::UnaryOp::kNot; + return true; + case SpvOpNot: + *ast_unary_op = ast::UnaryOp::kComplement; + return true; + default: + break; + } + return false; +} + +/// Converts a SPIR-V opcode for a WGSL builtin function, if there is a +/// direct translation. Returns nullptr otherwise. +/// @returns the WGSL builtin function name for the given opcode, or nullptr. +const char* GetUnaryBuiltInFunctionName(SpvOp opcode) { + switch (opcode) { + case SpvOpAny: + return "any"; + case SpvOpAll: + return "all"; + case SpvOpIsNan: + return "isNan"; + case SpvOpIsInf: + return "isInf"; + case SpvOpTranspose: + return "transpose"; + default: + break; + } + return nullptr; +} + +// Converts a SPIR-V opcode to its corresponding AST binary opcode, if any +// @param opcode SPIR-V opcode +// @returns the AST binary op for the given opcode, or kNone +ast::BinaryOp ConvertBinaryOp(SpvOp opcode) { + switch (opcode) { + case SpvOpIAdd: + case SpvOpFAdd: + return ast::BinaryOp::kAdd; + case SpvOpISub: + case SpvOpFSub: + return ast::BinaryOp::kSubtract; + case SpvOpIMul: + case SpvOpFMul: + case SpvOpVectorTimesScalar: + case SpvOpMatrixTimesScalar: + case SpvOpVectorTimesMatrix: + case SpvOpMatrixTimesVector: + case SpvOpMatrixTimesMatrix: + return ast::BinaryOp::kMultiply; + case SpvOpUDiv: + case SpvOpSDiv: + case SpvOpFDiv: + return ast::BinaryOp::kDivide; + case SpvOpUMod: + case SpvOpSMod: + case SpvOpFRem: + return ast::BinaryOp::kModulo; + case SpvOpLogicalEqual: + case SpvOpIEqual: + case SpvOpFOrdEqual: + return ast::BinaryOp::kEqual; + case SpvOpLogicalNotEqual: + case SpvOpINotEqual: + case SpvOpFOrdNotEqual: + return ast::BinaryOp::kNotEqual; + case SpvOpBitwiseAnd: + return ast::BinaryOp::kAnd; + case SpvOpBitwiseOr: + return ast::BinaryOp::kOr; + case SpvOpBitwiseXor: + return ast::BinaryOp::kXor; + case SpvOpLogicalAnd: + return ast::BinaryOp::kAnd; + case SpvOpLogicalOr: + return ast::BinaryOp::kOr; + case SpvOpUGreaterThan: + case SpvOpSGreaterThan: + case SpvOpFOrdGreaterThan: + return ast::BinaryOp::kGreaterThan; + case SpvOpUGreaterThanEqual: + case SpvOpSGreaterThanEqual: + case SpvOpFOrdGreaterThanEqual: + return ast::BinaryOp::kGreaterThanEqual; + case SpvOpULessThan: + case SpvOpSLessThan: + case SpvOpFOrdLessThan: + return ast::BinaryOp::kLessThan; + case SpvOpULessThanEqual: + case SpvOpSLessThanEqual: + case SpvOpFOrdLessThanEqual: + return ast::BinaryOp::kLessThanEqual; + default: + break; + } + // It's not clear what OpSMod should map to. + // https://bugs.chromium.org/p/tint/issues/detail?id=52 + return ast::BinaryOp::kNone; +} + +// If the given SPIR-V opcode is a floating point unordered comparison, +// then returns the binary float comparison for which it is the negation. +// Othewrise returns BinaryOp::kNone. +// @param opcode SPIR-V opcode +// @returns operation corresponding to negated version of the SPIR-V opcode +ast::BinaryOp NegatedFloatCompare(SpvOp opcode) { + switch (opcode) { + case SpvOpFUnordEqual: + return ast::BinaryOp::kNotEqual; + case SpvOpFUnordNotEqual: + return ast::BinaryOp::kEqual; + case SpvOpFUnordLessThan: + return ast::BinaryOp::kGreaterThanEqual; + case SpvOpFUnordLessThanEqual: + return ast::BinaryOp::kGreaterThan; + case SpvOpFUnordGreaterThan: + return ast::BinaryOp::kLessThanEqual; + case SpvOpFUnordGreaterThanEqual: + return ast::BinaryOp::kLessThan; + default: + break; + } + return ast::BinaryOp::kNone; +} + +// Returns the WGSL standard library function for the given +// GLSL.std.450 extended instruction operation code. Unknown +// and invalid opcodes map to the empty string. +// @returns the WGSL standard function name, or an empty string. +std::string GetGlslStd450FuncName(uint32_t ext_opcode) { + switch (ext_opcode) { + case GLSLstd450FAbs: + case GLSLstd450SAbs: + return "abs"; + case GLSLstd450Acos: + return "acos"; + case GLSLstd450Asin: + return "asin"; + case GLSLstd450Atan: + return "atan"; + case GLSLstd450Atan2: + return "atan2"; + case GLSLstd450Ceil: + return "ceil"; + case GLSLstd450UClamp: + case GLSLstd450SClamp: + case GLSLstd450NClamp: + case GLSLstd450FClamp: // FClamp is less prescriptive about NaN operands + return "clamp"; + case GLSLstd450Cos: + return "cos"; + case GLSLstd450Cosh: + return "cosh"; + case GLSLstd450Cross: + return "cross"; + case GLSLstd450Degrees: + return "degrees"; + case GLSLstd450Distance: + return "distance"; + case GLSLstd450Exp: + return "exp"; + case GLSLstd450Exp2: + return "exp2"; + case GLSLstd450FaceForward: + return "faceForward"; + case GLSLstd450Floor: + return "floor"; + case GLSLstd450Fma: + return "fma"; + case GLSLstd450Fract: + return "fract"; + case GLSLstd450InverseSqrt: + return "inverseSqrt"; + case GLSLstd450Ldexp: + return "ldexp"; + case GLSLstd450Length: + return "length"; + case GLSLstd450Log: + return "log"; + case GLSLstd450Log2: + return "log2"; + case GLSLstd450NMax: + case GLSLstd450FMax: // FMax is less prescriptive about NaN operands + case GLSLstd450UMax: + case GLSLstd450SMax: + return "max"; + case GLSLstd450NMin: + case GLSLstd450FMin: // FMin is less prescriptive about NaN operands + case GLSLstd450UMin: + case GLSLstd450SMin: + return "min"; + case GLSLstd450FMix: + return "mix"; + case GLSLstd450Normalize: + return "normalize"; + case GLSLstd450PackSnorm4x8: + return "pack4x8snorm"; + case GLSLstd450PackUnorm4x8: + return "pack4x8unorm"; + case GLSLstd450PackSnorm2x16: + return "pack2x16snorm"; + case GLSLstd450PackUnorm2x16: + return "pack2x16unorm"; + case GLSLstd450PackHalf2x16: + return "pack2x16float"; + case GLSLstd450Pow: + return "pow"; + case GLSLstd450FSign: + return "sign"; + case GLSLstd450Radians: + return "radians"; + case GLSLstd450Reflect: + return "reflect"; + case GLSLstd450Refract: + return "refract"; + case GLSLstd450Round: + case GLSLstd450RoundEven: + return "round"; + case GLSLstd450Sin: + return "sin"; + case GLSLstd450Sinh: + return "sinh"; + case GLSLstd450SmoothStep: + return "smoothStep"; + case GLSLstd450Sqrt: + return "sqrt"; + case GLSLstd450Step: + return "step"; + case GLSLstd450Tan: + return "tan"; + case GLSLstd450Tanh: + return "tanh"; + case GLSLstd450Trunc: + return "trunc"; + case GLSLstd450UnpackSnorm4x8: + return "unpack4x8snorm"; + case GLSLstd450UnpackUnorm4x8: + return "unpack4x8unorm"; + case GLSLstd450UnpackSnorm2x16: + return "unpack2x16snorm"; + case GLSLstd450UnpackUnorm2x16: + return "unpack2x16unorm"; + case GLSLstd450UnpackHalf2x16: + return "unpack2x16float"; + + default: + // TODO(dneto) - The following are not implemented. + // They are grouped semantically, as in GLSL.std.450.h. + + case GLSLstd450SSign: + + case GLSLstd450Asinh: + case GLSLstd450Acosh: + case GLSLstd450Atanh: + + case GLSLstd450Determinant: + case GLSLstd450MatrixInverse: + + case GLSLstd450Modf: + case GLSLstd450ModfStruct: + case GLSLstd450IMix: + + case GLSLstd450Frexp: + case GLSLstd450FrexpStruct: + + case GLSLstd450PackDouble2x32: + case GLSLstd450UnpackDouble2x32: + + case GLSLstd450FindILsb: + case GLSLstd450FindSMsb: + case GLSLstd450FindUMsb: + + case GLSLstd450InterpolateAtCentroid: + case GLSLstd450InterpolateAtSample: + case GLSLstd450InterpolateAtOffset: + break; + } + return ""; +} + +// Returns the WGSL standard library function builtin for the +// given instruction, or sem::BuiltinType::kNone +sem::BuiltinType GetBuiltin(SpvOp opcode) { + switch (opcode) { + case SpvOpBitCount: + return sem::BuiltinType::kCountOneBits; + case SpvOpBitFieldInsert: + return sem::BuiltinType::kInsertBits; + case SpvOpBitFieldSExtract: + case SpvOpBitFieldUExtract: + return sem::BuiltinType::kExtractBits; + case SpvOpBitReverse: + return sem::BuiltinType::kReverseBits; + case SpvOpDot: + return sem::BuiltinType::kDot; + case SpvOpDPdx: + return sem::BuiltinType::kDpdx; + case SpvOpDPdy: + return sem::BuiltinType::kDpdy; + case SpvOpFwidth: + return sem::BuiltinType::kFwidth; + case SpvOpDPdxFine: + return sem::BuiltinType::kDpdxFine; + case SpvOpDPdyFine: + return sem::BuiltinType::kDpdyFine; + case SpvOpFwidthFine: + return sem::BuiltinType::kFwidthFine; + case SpvOpDPdxCoarse: + return sem::BuiltinType::kDpdxCoarse; + case SpvOpDPdyCoarse: + return sem::BuiltinType::kDpdyCoarse; + case SpvOpFwidthCoarse: + return sem::BuiltinType::kFwidthCoarse; + default: + break; + } + return sem::BuiltinType::kNone; +} + +// @param opcode a SPIR-V opcode +// @returns true if the given instruction is an image access instruction +// whose first input operand is an OpSampledImage value. +bool IsSampledImageAccess(SpvOp opcode) { + switch (opcode) { + case SpvOpImageSampleImplicitLod: + case SpvOpImageSampleExplicitLod: + case SpvOpImageSampleDrefImplicitLod: + case SpvOpImageSampleDrefExplicitLod: + // WGSL doesn't have *Proj* texturing; spirv reader emulates it. + case SpvOpImageSampleProjImplicitLod: + case SpvOpImageSampleProjExplicitLod: + case SpvOpImageSampleProjDrefImplicitLod: + case SpvOpImageSampleProjDrefExplicitLod: + case SpvOpImageGather: + case SpvOpImageDrefGather: + case SpvOpImageQueryLod: + return true; + default: + break; + } + return false; +} + +// @param opcode a SPIR-V opcode +// @returns true if the given instruction is an image sampling, gather, +// or gather-compare operation. +bool IsImageSamplingOrGatherOrDrefGather(SpvOp opcode) { + switch (opcode) { + case SpvOpImageSampleImplicitLod: + case SpvOpImageSampleExplicitLod: + case SpvOpImageSampleDrefImplicitLod: + case SpvOpImageSampleDrefExplicitLod: + // WGSL doesn't have *Proj* texturing; spirv reader emulates it. + case SpvOpImageSampleProjImplicitLod: + case SpvOpImageSampleProjExplicitLod: + case SpvOpImageSampleProjDrefImplicitLod: + case SpvOpImageSampleProjDrefExplicitLod: + case SpvOpImageGather: + case SpvOpImageDrefGather: + return true; + default: + break; + } + return false; +} + +// @param opcode a SPIR-V opcode +// @returns true if the given instruction is an image access instruction +// whose first input operand is an OpImage value. +bool IsRawImageAccess(SpvOp opcode) { + switch (opcode) { + case SpvOpImageRead: + case SpvOpImageWrite: + case SpvOpImageFetch: + return true; + default: + break; + } + return false; +} + +// @param opcode a SPIR-V opcode +// @returns true if the given instruction is an image query instruction +bool IsImageQuery(SpvOp opcode) { + switch (opcode) { + case SpvOpImageQuerySize: + case SpvOpImageQuerySizeLod: + case SpvOpImageQueryLevels: + case SpvOpImageQuerySamples: + case SpvOpImageQueryLod: + return true; + default: + break; + } + return false; +} + +// @returns the merge block ID for the given basic block, or 0 if there is none. +uint32_t MergeFor(const spvtools::opt::BasicBlock& bb) { + // Get the OpSelectionMerge or OpLoopMerge instruction, if any. + auto* inst = bb.GetMergeInst(); + return inst == nullptr ? 0 : inst->GetSingleWordInOperand(0); +} + +// @returns the continue target ID for the given basic block, or 0 if there +// is none. +uint32_t ContinueTargetFor(const spvtools::opt::BasicBlock& bb) { + // Get the OpLoopMerge instruction, if any. + auto* inst = bb.GetLoopMergeInst(); + return inst == nullptr ? 0 : inst->GetSingleWordInOperand(1); +} + +// A structured traverser produces the reverse structured post-order of the +// CFG of a function. The blocks traversed are the transitive closure (minimum +// fixed point) of: +// - the entry block +// - a block reached by a branch from another block in the set +// - a block mentioned as a merge block or continue target for a block in the +// set +class StructuredTraverser { + public: + explicit StructuredTraverser(const spvtools::opt::Function& function) + : function_(function) { + for (auto& block : function_) { + id_to_block_[block.id()] = █ + } + } + + // Returns the reverse postorder traversal of the CFG, where: + // - a merge block always follows its associated constructs + // - a continue target always follows the associated loop construct, if any + // @returns the IDs of blocks in reverse structured post order + std::vector<uint32_t> ReverseStructuredPostOrder() { + visit_order_.clear(); + visited_.clear(); + VisitBackward(function_.entry()->id()); + + std::vector<uint32_t> order(visit_order_.rbegin(), visit_order_.rend()); + return order; + } + + private: + // Executes a depth first search of the CFG, where right after we visit a + // header, we will visit its merge block, then its continue target (if any). + // Also records the post order ordering. + void VisitBackward(uint32_t id) { + if (id == 0) + return; + if (visited_.count(id)) + return; + visited_.insert(id); + + const spvtools::opt::BasicBlock* bb = + id_to_block_[id]; // non-null for valid modules + VisitBackward(MergeFor(*bb)); + VisitBackward(ContinueTargetFor(*bb)); + + // Visit successors. We will naturally skip the continue target and merge + // blocks. + auto* terminator = bb->terminator(); + auto opcode = terminator->opcode(); + if (opcode == SpvOpBranchConditional) { + // Visit the false branch, then the true branch, to make them come + // out in the natural order for an "if". + VisitBackward(terminator->GetSingleWordInOperand(2)); + VisitBackward(terminator->GetSingleWordInOperand(1)); + } else if (opcode == SpvOpBranch) { + VisitBackward(terminator->GetSingleWordInOperand(0)); + } else if (opcode == SpvOpSwitch) { + // TODO(dneto): Consider visiting the labels in literal-value order. + std::vector<uint32_t> successors; + bb->ForEachSuccessorLabel([&successors](const uint32_t succ_id) { + successors.push_back(succ_id); + }); + for (auto succ_id : successors) { + VisitBackward(succ_id); + } + } + + visit_order_.push_back(id); + } + + const spvtools::opt::Function& function_; + std::unordered_map<uint32_t, const spvtools::opt::BasicBlock*> id_to_block_; + std::vector<uint32_t> visit_order_; + std::unordered_set<uint32_t> visited_; +}; + +/// A StatementBuilder for ast::SwitchStatement +/// @see StatementBuilder +struct SwitchStatementBuilder final + : public Castable<SwitchStatementBuilder, StatementBuilder> { + /// Constructor + /// @param cond the switch statement condition + explicit SwitchStatementBuilder(const ast::Expression* cond) + : condition(cond) {} + + /// @param builder the program builder + /// @returns the built ast::SwitchStatement + const ast::SwitchStatement* Build(ProgramBuilder* builder) const override { + // We've listed cases in reverse order in the switch statement. + // Reorder them to match the presentation order in WGSL. + auto reversed_cases = cases; + std::reverse(reversed_cases.begin(), reversed_cases.end()); + + return builder->create<ast::SwitchStatement>(Source{}, condition, + reversed_cases); + } + + /// Switch statement condition + const ast::Expression* const condition; + /// Switch statement cases + ast::CaseStatementList cases; +}; + +/// A StatementBuilder for ast::IfStatement +/// @see StatementBuilder +struct IfStatementBuilder final + : public Castable<IfStatementBuilder, StatementBuilder> { + /// Constructor + /// @param c the if-statement condition + explicit IfStatementBuilder(const ast::Expression* c) : cond(c) {} + + /// @param builder the program builder + /// @returns the built ast::IfStatement + const ast::IfStatement* Build(ProgramBuilder* builder) const override { + return builder->create<ast::IfStatement>(Source{}, cond, body, else_stmts); + } + + /// If-statement condition + const ast::Expression* const cond; + /// If-statement block body + const ast::BlockStatement* body = nullptr; + /// Optional if-statement else statements + ast::ElseStatementList else_stmts; +}; + +/// A StatementBuilder for ast::LoopStatement +/// @see StatementBuilder +struct LoopStatementBuilder final + : public Castable<LoopStatementBuilder, StatementBuilder> { + /// @param builder the program builder + /// @returns the built ast::LoopStatement + ast::LoopStatement* Build(ProgramBuilder* builder) const override { + return builder->create<ast::LoopStatement>(Source{}, body, continuing); + } + + /// Loop-statement block body + const ast::BlockStatement* body = nullptr; + /// Loop-statement continuing body + /// @note the mutable keyword here is required as all non-StatementBuilders + /// `ast::Node`s are immutable and are referenced with `const` pointers. + /// StatementBuilders however exist to provide mutable state while the + /// FunctionEmitter is building the function. All StatementBuilders are + /// replaced with immutable AST nodes when Finalize() is called. + mutable const ast::BlockStatement* continuing = nullptr; +}; + +/// @param decos a list of parsed decorations +/// @returns true if the decorations include a SampleMask builtin +bool HasBuiltinSampleMask(const ast::AttributeList& decos) { + if (auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(decos)) { + return builtin->builtin == ast::Builtin::kSampleMask; + } + return false; +} + +} // namespace + +BlockInfo::BlockInfo(const spvtools::opt::BasicBlock& bb) + : basic_block(&bb), id(bb.id()) {} + +BlockInfo::~BlockInfo() = default; + +DefInfo::DefInfo(const spvtools::opt::Instruction& def_inst, + uint32_t the_block_pos, + size_t the_index) + : inst(def_inst), block_pos(the_block_pos), index(the_index) {} + +DefInfo::~DefInfo() = default; + +ast::Node* StatementBuilder::Clone(CloneContext*) const { + return nullptr; +} + +FunctionEmitter::FunctionEmitter(ParserImpl* pi, + const spvtools::opt::Function& function, + const EntryPointInfo* ep_info) + : parser_impl_(*pi), + ty_(pi->type_manager()), + builder_(pi->builder()), + ir_context_(*(pi->ir_context())), + def_use_mgr_(ir_context_.get_def_use_mgr()), + constant_mgr_(ir_context_.get_constant_mgr()), + type_mgr_(ir_context_.get_type_mgr()), + fail_stream_(pi->fail_stream()), + namer_(pi->namer()), + function_(function), + sample_mask_in_id(0u), + sample_mask_out_id(0u), + ep_info_(ep_info) { + PushNewStatementBlock(nullptr, 0, nullptr); +} + +FunctionEmitter::FunctionEmitter(ParserImpl* pi, + const spvtools::opt::Function& function) + : FunctionEmitter(pi, function, nullptr) {} + +FunctionEmitter::FunctionEmitter(FunctionEmitter&& other) + : parser_impl_(other.parser_impl_), + ty_(other.ty_), + builder_(other.builder_), + ir_context_(other.ir_context_), + def_use_mgr_(ir_context_.get_def_use_mgr()), + constant_mgr_(ir_context_.get_constant_mgr()), + type_mgr_(ir_context_.get_type_mgr()), + fail_stream_(other.fail_stream_), + namer_(other.namer_), + function_(other.function_), + sample_mask_in_id(other.sample_mask_out_id), + sample_mask_out_id(other.sample_mask_in_id), + ep_info_(other.ep_info_) { + other.statements_stack_.clear(); + PushNewStatementBlock(nullptr, 0, nullptr); +} + +FunctionEmitter::~FunctionEmitter() = default; + +FunctionEmitter::StatementBlock::StatementBlock( + const Construct* construct, + uint32_t end_id, + FunctionEmitter::CompletionAction completion_action) + : construct_(construct), + end_id_(end_id), + completion_action_(completion_action) {} + +FunctionEmitter::StatementBlock::StatementBlock(StatementBlock&& other) = + default; + +FunctionEmitter::StatementBlock::~StatementBlock() = default; + +void FunctionEmitter::StatementBlock::Finalize(ProgramBuilder* pb) { + TINT_ASSERT(Reader, !finalized_ /* Finalize() must only be called once */); + + for (size_t i = 0; i < statements_.size(); i++) { + if (auto* sb = statements_[i]->As<StatementBuilder>()) { + statements_[i] = sb->Build(pb); + } + } + + if (completion_action_ != nullptr) { + completion_action_(statements_); + } + + finalized_ = true; +} + +void FunctionEmitter::StatementBlock::Add(const ast::Statement* statement) { + TINT_ASSERT(Reader, + !finalized_ /* Add() must not be called after Finalize() */); + statements_.emplace_back(statement); +} + +void FunctionEmitter::PushNewStatementBlock(const Construct* construct, + uint32_t end_id, + CompletionAction action) { + statements_stack_.emplace_back(StatementBlock{construct, end_id, action}); +} + +void FunctionEmitter::PushGuard(const std::string& guard_name, + uint32_t end_id) { + TINT_ASSERT(Reader, !statements_stack_.empty()); + TINT_ASSERT(Reader, !guard_name.empty()); + // Guard control flow by the guard variable. Introduce a new + // if-selection with a then-clause ending at the same block + // as the statement block at the top of the stack. + const auto& top = statements_stack_.back(); + + auto* cond = create<ast::IdentifierExpression>( + Source{}, builder_.Symbols().Register(guard_name)); + auto* builder = AddStatementBuilder<IfStatementBuilder>(cond); + + PushNewStatementBlock( + top.GetConstruct(), end_id, [=](const ast::StatementList& stmts) { + builder->body = create<ast::BlockStatement>(Source{}, stmts); + }); +} + +void FunctionEmitter::PushTrueGuard(uint32_t end_id) { + TINT_ASSERT(Reader, !statements_stack_.empty()); + const auto& top = statements_stack_.back(); + + auto* cond = MakeTrue(Source{}); + auto* builder = AddStatementBuilder<IfStatementBuilder>(cond); + + PushNewStatementBlock( + top.GetConstruct(), end_id, [=](const ast::StatementList& stmts) { + builder->body = create<ast::BlockStatement>(Source{}, stmts); + }); +} + +const ast::StatementList FunctionEmitter::ast_body() { + TINT_ASSERT(Reader, !statements_stack_.empty()); + auto& entry = statements_stack_[0]; + entry.Finalize(&builder_); + return entry.GetStatements(); +} + +const ast::Statement* FunctionEmitter::AddStatement( + const ast::Statement* statement) { + TINT_ASSERT(Reader, !statements_stack_.empty()); + if (statement != nullptr) { + statements_stack_.back().Add(statement); + } + return statement; +} + +const ast::Statement* FunctionEmitter::LastStatement() { + TINT_ASSERT(Reader, !statements_stack_.empty()); + auto& statement_list = statements_stack_.back().GetStatements(); + TINT_ASSERT(Reader, !statement_list.empty()); + return statement_list.back(); +} + +bool FunctionEmitter::Emit() { + if (failed()) { + return false; + } + // We only care about functions with bodies. + if (function_.cbegin() == function_.cend()) { + return true; + } + + // The function declaration, corresponding to how it's written in SPIR-V, + // and without regard to whether it's an entry point. + FunctionDeclaration decl; + if (!ParseFunctionDeclaration(&decl)) { + return false; + } + + bool make_body_function = true; + if (ep_info_) { + TINT_ASSERT(Reader, !ep_info_->inner_name.empty()); + if (ep_info_->owns_inner_implementation) { + // This is an entry point, and we want to emit it as a wrapper around + // an implementation function. + decl.name = ep_info_->inner_name; + } else { + // This is a second entry point that shares an inner implementation + // function. + make_body_function = false; + } + } + + if (make_body_function) { + auto* body = MakeFunctionBody(); + if (!body) { + return false; + } + + builder_.AST().AddFunction(create<ast::Function>( + decl.source, builder_.Symbols().Register(decl.name), + std::move(decl.params), decl.return_type->Build(builder_), body, + std::move(decl.attributes), ast::AttributeList{})); + } + + if (ep_info_ && !ep_info_->inner_name.empty()) { + return EmitEntryPointAsWrapper(); + } + + return success(); +} + +const ast::BlockStatement* FunctionEmitter::MakeFunctionBody() { + TINT_ASSERT(Reader, statements_stack_.size() == 1); + + if (!EmitBody()) { + return nullptr; + } + + // Set the body of the AST function node. + if (statements_stack_.size() != 1) { + Fail() << "internal error: statement-list stack should have 1 " + "element but has " + << statements_stack_.size(); + return nullptr; + } + + statements_stack_[0].Finalize(&builder_); + auto& statements = statements_stack_[0].GetStatements(); + auto* body = create<ast::BlockStatement>(Source{}, statements); + + // Maintain the invariant by repopulating the one and only element. + statements_stack_.clear(); + PushNewStatementBlock(constructs_[0].get(), 0, nullptr); + + return body; +} + +bool FunctionEmitter::EmitPipelineInput(std::string var_name, + const Type* var_type, + ast::AttributeList* attrs, + std::vector<int> index_prefix, + const Type* tip_type, + const Type* forced_param_type, + ast::VariableList* params, + ast::StatementList* statements) { + // TODO(dneto): Handle structs where the locations are annotated on members. + tip_type = tip_type->UnwrapAlias(); + if (auto* ref_type = tip_type->As<Reference>()) { + tip_type = ref_type->type; + } + + // Recursively flatten matrices, arrays, and structures. + return Switch( + tip_type, + [&](const Matrix* matrix_type) -> bool { + index_prefix.push_back(0); + const auto num_columns = static_cast<int>(matrix_type->columns); + const Type* vec_ty = ty_.Vector(matrix_type->type, matrix_type->rows); + for (int col = 0; col < num_columns; col++) { + index_prefix.back() = col; + if (!EmitPipelineInput(var_name, var_type, attrs, index_prefix, + vec_ty, forced_param_type, params, + statements)) { + return false; + } + } + return success(); + }, + [&](const Array* array_type) -> bool { + if (array_type->size == 0) { + return Fail() << "runtime-size array not allowed on pipeline IO"; + } + index_prefix.push_back(0); + const Type* elem_ty = array_type->type; + for (int i = 0; i < static_cast<int>(array_type->size); i++) { + index_prefix.back() = i; + if (!EmitPipelineInput(var_name, var_type, attrs, index_prefix, + elem_ty, forced_param_type, params, + statements)) { + return false; + } + } + return success(); + }, + [&](const Struct* struct_type) -> bool { + const auto& members = struct_type->members; + index_prefix.push_back(0); + for (int i = 0; i < static_cast<int>(members.size()); ++i) { + index_prefix.back() = i; + ast::AttributeList member_attrs(*attrs); + if (!parser_impl_.ConvertPipelineDecorations( + struct_type, + parser_impl_.GetMemberPipelineDecorations(*struct_type, i), + &member_attrs)) { + return false; + } + if (!EmitPipelineInput(var_name, var_type, &member_attrs, + index_prefix, members[i], forced_param_type, + params, statements)) { + return false; + } + // Copy the location as updated by nested expansion of the member. + parser_impl_.SetLocation(attrs, GetLocation(member_attrs)); + } + return success(); + }, + [&](Default) { + const bool is_builtin = + ast::HasAttribute<ast::BuiltinAttribute>(*attrs); + + const Type* param_type = is_builtin ? forced_param_type : tip_type; + + const auto param_name = namer_.MakeDerivedName(var_name + "_param"); + // Create the parameter. + // TODO(dneto): Note: If the parameter has non-location decorations, + // then those decoration AST nodes will be reused between multiple + // elements of a matrix, array, or structure. Normally that's + // disallowed but currently the SPIR-V reader will make duplicates when + // the entire AST is cloned at the top level of the SPIR-V reader flow. + // Consider rewriting this to avoid this node-sharing. + params->push_back( + builder_.Param(param_name, param_type->Build(builder_), *attrs)); + + // Add a body statement to copy the parameter to the corresponding + // private variable. + const ast::Expression* param_value = builder_.Expr(param_name); + const ast::Expression* store_dest = builder_.Expr(var_name); + + // Index into the LHS as needed. + auto* current_type = + var_type->UnwrapAlias()->UnwrapRef()->UnwrapAlias(); + for (auto index : index_prefix) { + Switch( + current_type, + [&](const Matrix* matrix_type) { + store_dest = + builder_.IndexAccessor(store_dest, builder_.Expr(index)); + current_type = ty_.Vector(matrix_type->type, matrix_type->rows); + }, + [&](const Array* array_type) { + store_dest = + builder_.IndexAccessor(store_dest, builder_.Expr(index)); + current_type = array_type->type->UnwrapAlias(); + }, + [&](const Struct* struct_type) { + store_dest = builder_.MemberAccessor( + store_dest, builder_.Expr(parser_impl_.GetMemberName( + *struct_type, index))); + current_type = struct_type->members[index]; + }); + } + + if (is_builtin && (tip_type != forced_param_type)) { + // The parameter will have the WGSL type, but we need bitcast to + // the variable store type. + param_value = create<ast::BitcastExpression>( + tip_type->Build(builder_), param_value); + } + + statements->push_back(builder_.Assign(store_dest, param_value)); + + // Increment the location attribute, in case more parameters will + // follow. + IncrementLocation(attrs); + + return success(); + }); +} + +void FunctionEmitter::IncrementLocation(ast::AttributeList* attributes) { + for (auto*& attr : *attributes) { + if (auto* loc_attr = attr->As<ast::LocationAttribute>()) { + // Replace this location attribute with a new one with one higher index. + // The old one doesn't leak because it's kept in the builder's AST node + // list. + attr = builder_.Location(loc_attr->source, loc_attr->value + 1); + } + } +} + +const ast::Attribute* FunctionEmitter::GetLocation( + const ast::AttributeList& attributes) { + for (auto* const& attr : attributes) { + if (attr->Is<ast::LocationAttribute>()) { + return attr; + } + } + return nullptr; +} + +bool FunctionEmitter::EmitPipelineOutput(std::string var_name, + const Type* var_type, + ast::AttributeList* decos, + std::vector<int> index_prefix, + const Type* tip_type, + const Type* forced_member_type, + ast::StructMemberList* return_members, + ast::ExpressionList* return_exprs) { + tip_type = tip_type->UnwrapAlias(); + if (auto* ref_type = tip_type->As<Reference>()) { + tip_type = ref_type->type; + } + + // Recursively flatten matrices, arrays, and structures. + return Switch( + tip_type, + [&](const Matrix* matrix_type) { + index_prefix.push_back(0); + const auto num_columns = static_cast<int>(matrix_type->columns); + const Type* vec_ty = ty_.Vector(matrix_type->type, matrix_type->rows); + for (int col = 0; col < num_columns; col++) { + index_prefix.back() = col; + if (!EmitPipelineOutput(var_name, var_type, decos, index_prefix, + vec_ty, forced_member_type, return_members, + return_exprs)) { + return false; + } + } + return success(); + }, + [&](const Array* array_type) -> bool { + if (array_type->size == 0) { + return Fail() << "runtime-size array not allowed on pipeline IO"; + } + index_prefix.push_back(0); + const Type* elem_ty = array_type->type; + for (int i = 0; i < static_cast<int>(array_type->size); i++) { + index_prefix.back() = i; + if (!EmitPipelineOutput(var_name, var_type, decos, index_prefix, + elem_ty, forced_member_type, return_members, + return_exprs)) { + return false; + } + } + return success(); + }, + [&](const Struct* struct_type) -> bool { + const auto& members = struct_type->members; + index_prefix.push_back(0); + for (int i = 0; i < static_cast<int>(members.size()); ++i) { + index_prefix.back() = i; + ast::AttributeList member_attrs(*decos); + if (!parser_impl_.ConvertPipelineDecorations( + struct_type, + parser_impl_.GetMemberPipelineDecorations(*struct_type, i), + &member_attrs)) { + return false; + } + if (!EmitPipelineOutput(var_name, var_type, &member_attrs, + index_prefix, members[i], forced_member_type, + return_members, return_exprs)) { + return false; + } + // Copy the location as updated by nested expansion of the member. + parser_impl_.SetLocation(decos, GetLocation(member_attrs)); + } + return success(); + }, + [&](Default) { + const bool is_builtin = + ast::HasAttribute<ast::BuiltinAttribute>(*decos); + + const Type* member_type = is_builtin ? forced_member_type : tip_type; + // Derive the member name directly from the variable name. They can't + // collide. + const auto member_name = namer_.MakeDerivedName(var_name); + // Create the member. + // TODO(dneto): Note: If the parameter has non-location decorations, + // then those decoration AST nodes will be reused between multiple + // elements of a matrix, array, or structure. Normally that's + // disallowed but currently the SPIR-V reader will make duplicates when + // the entire AST is cloned at the top level of the SPIR-V reader flow. + // Consider rewriting this to avoid this node-sharing. + return_members->push_back( + builder_.Member(member_name, member_type->Build(builder_), *decos)); + + // Create an expression to evaluate the part of the variable indexed by + // the index_prefix. + const ast::Expression* load_source = builder_.Expr(var_name); + + // Index into the variable as needed to pick out the flattened member. + auto* current_type = + var_type->UnwrapAlias()->UnwrapRef()->UnwrapAlias(); + for (auto index : index_prefix) { + Switch( + current_type, + [&](const Matrix* matrix_type) { + load_source = + builder_.IndexAccessor(load_source, builder_.Expr(index)); + current_type = ty_.Vector(matrix_type->type, matrix_type->rows); + }, + [&](const Array* array_type) { + load_source = + builder_.IndexAccessor(load_source, builder_.Expr(index)); + current_type = array_type->type->UnwrapAlias(); + }, + [&](const Struct* struct_type) { + load_source = builder_.MemberAccessor( + load_source, builder_.Expr(parser_impl_.GetMemberName( + *struct_type, index))); + current_type = struct_type->members[index]; + }); + } + + if (is_builtin && (tip_type != forced_member_type)) { + // The member will have the WGSL type, but we need bitcast to + // the variable store type. + load_source = create<ast::BitcastExpression>( + forced_member_type->Build(builder_), load_source); + } + return_exprs->push_back(load_source); + + // Increment the location attribute, in case more parameters will + // follow. + IncrementLocation(decos); + + return success(); + }); +} + +bool FunctionEmitter::EmitEntryPointAsWrapper() { + Source source; + + // The statements in the body. + ast::StatementList stmts; + + FunctionDeclaration decl; + decl.source = source; + decl.name = ep_info_->name; + const ast::Type* return_type = nullptr; // Populated below. + + // Pipeline inputs become parameters to the wrapper function, and + // their values are saved into the corresponding private variables that + // have already been created. + for (uint32_t var_id : ep_info_->inputs) { + const auto* var = def_use_mgr_->GetDef(var_id); + TINT_ASSERT(Reader, var != nullptr); + TINT_ASSERT(Reader, var->opcode() == SpvOpVariable); + auto* store_type = GetVariableStoreType(*var); + auto* forced_param_type = store_type; + ast::AttributeList param_decos; + if (!parser_impl_.ConvertDecorationsForVariable(var_id, &forced_param_type, + ¶m_decos, true)) { + // This occurs, and is not an error, for the PointSize builtin. + if (!success()) { + // But exit early if an error was logged. + return false; + } + continue; + } + + // We don't have to handle initializers because in Vulkan SPIR-V, Input + // variables must not have them. + + const auto var_name = namer_.GetName(var_id); + + bool ok = true; + if (HasBuiltinSampleMask(param_decos)) { + // In Vulkan SPIR-V, the sample mask is an array. In WGSL it's a scalar. + // Use the first element only. + auto* sample_mask_array_type = + store_type->UnwrapRef()->UnwrapAlias()->As<Array>(); + TINT_ASSERT(Reader, sample_mask_array_type); + ok = EmitPipelineInput(var_name, store_type, ¶m_decos, {0}, + sample_mask_array_type->type, forced_param_type, + &(decl.params), &stmts); + } else { + // The normal path. + ok = EmitPipelineInput(var_name, store_type, ¶m_decos, {}, store_type, + forced_param_type, &(decl.params), &stmts); + } + if (!ok) { + return false; + } + } + + // Call the inner function. It has no parameters. + stmts.push_back(create<ast::CallStatement>( + source, + create<ast::CallExpression>( + source, + create<ast::IdentifierExpression>( + source, builder_.Symbols().Register(ep_info_->inner_name)), + ast::ExpressionList{}))); + + // Pipeline outputs are mapped to the return value. + if (ep_info_->outputs.empty()) { + // There is nothing to return. + return_type = ty_.Void()->Build(builder_); + } else { + // Pipeline outputs are converted to a structure that is written + // to just before returning. + + const auto return_struct_name = + namer_.MakeDerivedName(ep_info_->name + "_out"); + const auto return_struct_sym = + builder_.Symbols().Register(return_struct_name); + + // Define the structure. + std::vector<const ast::StructMember*> return_members; + ast::ExpressionList return_exprs; + + const auto& builtin_position_info = parser_impl_.GetBuiltInPositionInfo(); + + for (uint32_t var_id : ep_info_->outputs) { + if (var_id == builtin_position_info.per_vertex_var_id) { + // The SPIR-V gl_PerVertex variable has already been remapped to + // a gl_Position variable. Substitute the type. + const Type* param_type = ty_.Vector(ty_.F32(), 4); + ast::AttributeList out_decos{ + create<ast::BuiltinAttribute>(source, ast::Builtin::kPosition)}; + + const auto var_name = namer_.GetName(var_id); + return_members.push_back( + builder_.Member(var_name, param_type->Build(builder_), out_decos)); + return_exprs.push_back(builder_.Expr(var_name)); + + } else { + const auto* var = def_use_mgr_->GetDef(var_id); + TINT_ASSERT(Reader, var != nullptr); + TINT_ASSERT(Reader, var->opcode() == SpvOpVariable); + const Type* store_type = GetVariableStoreType(*var); + const Type* forced_member_type = store_type; + ast::AttributeList out_decos; + if (!parser_impl_.ConvertDecorationsForVariable( + var_id, &forced_member_type, &out_decos, true)) { + // This occurs, and is not an error, for the PointSize builtin. + if (!success()) { + // But exit early if an error was logged. + return false; + } + continue; + } + + const auto var_name = namer_.GetName(var_id); + bool ok = true; + if (HasBuiltinSampleMask(out_decos)) { + // In Vulkan SPIR-V, the sample mask is an array. In WGSL it's a + // scalar. Use the first element only. + auto* sample_mask_array_type = + store_type->UnwrapRef()->UnwrapAlias()->As<Array>(); + TINT_ASSERT(Reader, sample_mask_array_type); + ok = EmitPipelineOutput(var_name, store_type, &out_decos, {0}, + sample_mask_array_type->type, + forced_member_type, &return_members, + &return_exprs); + } else { + // The normal path. + ok = EmitPipelineOutput(var_name, store_type, &out_decos, {}, + store_type, forced_member_type, + &return_members, &return_exprs); + } + if (!ok) { + return false; + } + } + } + + if (return_members.empty()) { + // This can occur if only the PointSize member is accessed, because we + // never emit it. + return_type = ty_.Void()->Build(builder_); + } else { + // Create and register the result type. + auto* str = create<ast::Struct>(Source{}, return_struct_sym, + return_members, ast::AttributeList{}); + parser_impl_.AddTypeDecl(return_struct_sym, str); + return_type = builder_.ty.Of(str); + + // Add the return-value statement. + stmts.push_back(create<ast::ReturnStatement>( + source, + builder_.Construct(source, return_type, std::move(return_exprs)))); + } + } + + auto* body = create<ast::BlockStatement>(source, stmts); + ast::AttributeList fn_attrs; + fn_attrs.emplace_back(create<ast::StageAttribute>(source, ep_info_->stage)); + + if (ep_info_->stage == ast::PipelineStage::kCompute) { + auto& size = ep_info_->workgroup_size; + if (size.x != 0 && size.y != 0 && size.z != 0) { + const ast::Expression* x = builder_.Expr(static_cast<int>(size.x)); + const ast::Expression* y = + size.y ? builder_.Expr(static_cast<int>(size.y)) : nullptr; + const ast::Expression* z = + size.z ? builder_.Expr(static_cast<int>(size.z)) : nullptr; + fn_attrs.emplace_back(create<ast::WorkgroupAttribute>(Source{}, x, y, z)); + } + } + + builder_.AST().AddFunction( + create<ast::Function>(source, builder_.Symbols().Register(ep_info_->name), + std::move(decl.params), return_type, body, + std::move(fn_attrs), ast::AttributeList{})); + + return true; +} + +bool FunctionEmitter::ParseFunctionDeclaration(FunctionDeclaration* decl) { + if (failed()) { + return false; + } + + const std::string name = namer_.Name(function_.result_id()); + + // Surprisingly, the "type id" on an OpFunction is the result type of the + // function, not the type of the function. This is the one exceptional case + // in SPIR-V where the type ID is not the type of the result ID. + auto* ret_ty = parser_impl_.ConvertType(function_.type_id()); + if (failed()) { + return false; + } + if (ret_ty == nullptr) { + return Fail() + << "internal error: unregistered return type for function with ID " + << function_.result_id(); + } + + ast::VariableList ast_params; + function_.ForEachParam( + [this, &ast_params](const spvtools::opt::Instruction* param) { + auto* type = parser_impl_.ConvertType(param->type_id()); + if (type != nullptr) { + auto* ast_param = parser_impl_.MakeVariable( + param->result_id(), ast::StorageClass::kNone, type, true, false, + nullptr, ast::AttributeList{}); + // Parameters are treated as const declarations. + ast_params.emplace_back(ast_param); + // The value is accessible by name. + identifier_types_.emplace(param->result_id(), type); + } else { + // We've already logged an error and emitted a diagnostic. Do nothing + // here. + } + }); + if (failed()) { + return false; + } + decl->name = name; + decl->params = std::move(ast_params); + decl->return_type = ret_ty; + decl->attributes.clear(); + + return success(); +} + +const Type* FunctionEmitter::GetVariableStoreType( + const spvtools::opt::Instruction& var_decl_inst) { + const auto type_id = var_decl_inst.type_id(); + // Normally we use the SPIRV-Tools optimizer to manage types. + // But when two struct types have the same member types and decorations, + // but differ only in member names, the two struct types will be + // represented by a single common internal struct type. + // So avoid the optimizer's representation and instead follow the + // SPIR-V instructions themselves. + const auto* ptr_ty = def_use_mgr_->GetDef(type_id); + const auto store_ty_id = ptr_ty->GetSingleWordInOperand(1); + const auto* result = parser_impl_.ConvertType(store_ty_id); + return result; +} + +bool FunctionEmitter::EmitBody() { + RegisterBasicBlocks(); + + if (!TerminatorsAreValid()) { + return false; + } + if (!RegisterMerges()) { + return false; + } + + ComputeBlockOrderAndPositions(); + if (!VerifyHeaderContinueMergeOrder()) { + return false; + } + if (!LabelControlFlowConstructs()) { + return false; + } + if (!FindSwitchCaseHeaders()) { + return false; + } + if (!ClassifyCFGEdges()) { + return false; + } + if (!FindIfSelectionInternalHeaders()) { + return false; + } + + if (!RegisterSpecialBuiltInVariables()) { + return false; + } + if (!RegisterLocallyDefinedValues()) { + return false; + } + FindValuesNeedingNamedOrHoistedDefinition(); + + if (!EmitFunctionVariables()) { + return false; + } + if (!EmitFunctionBodyStatements()) { + return false; + } + return success(); +} + +void FunctionEmitter::RegisterBasicBlocks() { + for (auto& block : function_) { + block_info_[block.id()] = std::make_unique<BlockInfo>(block); + } +} + +bool FunctionEmitter::TerminatorsAreValid() { + if (failed()) { + return false; + } + + const auto entry_id = function_.begin()->id(); + for (const auto& block : function_) { + if (!block.terminator()) { + return Fail() << "Block " << block.id() << " has no terminator"; + } + } + for (const auto& block : function_) { + block.WhileEachSuccessorLabel( + [this, &block, entry_id](const uint32_t succ_id) -> bool { + if (succ_id == entry_id) { + return Fail() << "Block " << block.id() + << " branches to function entry block " << entry_id; + } + if (!GetBlockInfo(succ_id)) { + return Fail() << "Block " << block.id() << " in function " + << function_.DefInst().result_id() << " branches to " + << succ_id << " which is not a block in the function"; + } + return true; + }); + } + return success(); +} + +bool FunctionEmitter::RegisterMerges() { + if (failed()) { + return false; + } + + const auto entry_id = function_.begin()->id(); + for (const auto& block : function_) { + const auto block_id = block.id(); + auto* block_info = GetBlockInfo(block_id); + if (!block_info) { + return Fail() << "internal error: block " << block_id + << " missing; blocks should already " + "have been registered"; + } + + if (const auto* inst = block.GetMergeInst()) { + auto terminator_opcode = block.terminator()->opcode(); + switch (inst->opcode()) { + case SpvOpSelectionMerge: + if ((terminator_opcode != SpvOpBranchConditional) && + (terminator_opcode != SpvOpSwitch)) { + return Fail() << "Selection header " << block_id + << " does not end in an OpBranchConditional or " + "OpSwitch instruction"; + } + break; + case SpvOpLoopMerge: + if ((terminator_opcode != SpvOpBranchConditional) && + (terminator_opcode != SpvOpBranch)) { + return Fail() << "Loop header " << block_id + << " does not end in an OpBranch or " + "OpBranchConditional instruction"; + } + break; + default: + break; + } + + const uint32_t header = block.id(); + auto* header_info = block_info; + const uint32_t merge = inst->GetSingleWordInOperand(0); + auto* merge_info = GetBlockInfo(merge); + if (!merge_info) { + return Fail() << "Structured header block " << header + << " declares invalid merge block " << merge; + } + if (merge == header) { + return Fail() << "Structured header block " << header + << " cannot be its own merge block"; + } + if (merge_info->header_for_merge) { + return Fail() << "Block " << merge + << " declared as merge block for more than one header: " + << merge_info->header_for_merge << ", " << header; + } + merge_info->header_for_merge = header; + header_info->merge_for_header = merge; + + if (inst->opcode() == SpvOpLoopMerge) { + if (header == entry_id) { + return Fail() << "Function entry block " << entry_id + << " cannot be a loop header"; + } + const uint32_t ct = inst->GetSingleWordInOperand(1); + auto* ct_info = GetBlockInfo(ct); + if (!ct_info) { + return Fail() << "Structured header " << header + << " declares invalid continue target " << ct; + } + if (ct == merge) { + return Fail() << "Invalid structured header block " << header + << ": declares block " << ct + << " as both its merge block and continue target"; + } + if (ct_info->header_for_continue) { + return Fail() + << "Block " << ct + << " declared as continue target for more than one header: " + << ct_info->header_for_continue << ", " << header; + } + ct_info->header_for_continue = header; + header_info->continue_for_header = ct; + } + } + + // Check single-block loop cases. + bool is_single_block_loop = false; + block_info->basic_block->ForEachSuccessorLabel( + [&is_single_block_loop, block_id](const uint32_t succ) { + if (block_id == succ) + is_single_block_loop = true; + }); + const auto ct = block_info->continue_for_header; + block_info->is_continue_entire_loop = ct == block_id; + if (is_single_block_loop && !block_info->is_continue_entire_loop) { + return Fail() << "Block " << block_id + << " branches to itself but is not its own continue target"; + } + // It's valid for a the header of a multi-block loop header to declare + // itself as its own continue target. + } + return success(); +} + +void FunctionEmitter::ComputeBlockOrderAndPositions() { + block_order_ = StructuredTraverser(function_).ReverseStructuredPostOrder(); + + for (uint32_t i = 0; i < block_order_.size(); ++i) { + GetBlockInfo(block_order_[i])->pos = i; + } + // The invalid block position is not the position of any block that is in the + // order. + assert(block_order_.size() <= kInvalidBlockPos); +} + +bool FunctionEmitter::VerifyHeaderContinueMergeOrder() { + // Verify interval rules for a structured header block: + // + // If the CFG satisfies structured control flow rules, then: + // If header H is reachable, then the following "interval rules" hold, + // where M(H) is H's merge block, and CT(H) is H's continue target: + // + // Pos(H) < Pos(M(H)) + // + // If CT(H) exists, then: + // Pos(H) <= Pos(CT(H)) + // Pos(CT(H)) < Pos(M) + // + for (auto block_id : block_order_) { + const auto* block_info = GetBlockInfo(block_id); + const auto merge = block_info->merge_for_header; + if (merge == 0) { + continue; + } + // This is a header. + const auto header = block_id; + const auto* header_info = block_info; + const auto header_pos = header_info->pos; + const auto merge_pos = GetBlockInfo(merge)->pos; + + // Pos(H) < Pos(M(H)) + // Note: When recording merges we made sure H != M(H) + if (merge_pos <= header_pos) { + return Fail() << "Header " << header + << " does not strictly dominate its merge block " << merge; + // TODO(dneto): Report a path from the entry block to the merge block + // without going through the header block. + } + + const auto ct = block_info->continue_for_header; + if (ct == 0) { + continue; + } + // Furthermore, this is a loop header. + const auto* ct_info = GetBlockInfo(ct); + const auto ct_pos = ct_info->pos; + // Pos(H) <= Pos(CT(H)) + if (ct_pos < header_pos) { + Fail() << "Loop header " << header + << " does not dominate its continue target " << ct; + } + // Pos(CT(H)) < Pos(M(H)) + // Note: When recording merges we made sure CT(H) != M(H) + if (merge_pos <= ct_pos) { + return Fail() << "Merge block " << merge << " for loop headed at block " + << header + << " appears at or before the loop's continue " + "construct headed by " + "block " + << ct; + } + } + return success(); +} + +bool FunctionEmitter::LabelControlFlowConstructs() { + // Label each block in the block order with its nearest enclosing structured + // control flow construct. Populates the |construct| member of BlockInfo. + + // Keep a stack of enclosing structured control flow constructs. Start + // with the synthetic construct representing the entire function. + // + // Scan from left to right in the block order, and check conditions + // on each block in the following order: + // + // a. When you reach a merge block, the top of the stack should + // be the associated header. Pop it off. + // b. When you reach a header, push it on the stack. + // c. When you reach a continue target, push it on the stack. + // (A block can be both a header and a continue target.) + // c. When you reach a block with an edge branching backward (in the + // structured order) to block T: + // T should be a loop header, and the top of the stack should be a + // continue target associated with T. + // This is the end of the continue construct. Pop the continue + // target off the stack. + // + // Note: A loop header can declare itself as its own continue target. + // + // Note: For a single-block loop, that block is a header, its own + // continue target, and its own backedge block. + // + // Note: We pop the merge off first because a merge block that marks + // the end of one construct can be a single-block loop. So that block + // is a merge, a header, a continue target, and a backedge block. + // But we want to finish processing of the merge before dealing with + // the loop. + // + // In the same scan, mark each basic block with the nearest enclosing + // header: the most recent header for which we haven't reached its merge + // block. Also mark the the most recent continue target for which we + // haven't reached the backedge block. + + TINT_ASSERT(Reader, block_order_.size() > 0); + constructs_.clear(); + const auto entry_id = block_order_[0]; + + // The stack of enclosing constructs. + std::vector<Construct*> enclosing; + + // Creates a control flow construct and pushes it onto the stack. + // Its parent is the top of the stack, or nullptr if the stack is empty. + // Returns the newly created construct. + auto push_construct = [this, &enclosing](size_t depth, Construct::Kind k, + uint32_t begin_id, + uint32_t end_id) -> Construct* { + const auto begin_pos = GetBlockInfo(begin_id)->pos; + const auto end_pos = + end_id == 0 ? uint32_t(block_order_.size()) : GetBlockInfo(end_id)->pos; + const auto* parent = enclosing.empty() ? nullptr : enclosing.back(); + auto scope_end_pos = end_pos; + // A loop construct is added right after its associated continue construct. + // In that case, adjust the parent up. + if (k == Construct::kLoop) { + TINT_ASSERT(Reader, parent); + TINT_ASSERT(Reader, parent->kind == Construct::kContinue); + scope_end_pos = parent->end_pos; + parent = parent->parent; + } + constructs_.push_back(std::make_unique<Construct>( + parent, static_cast<int>(depth), k, begin_id, end_id, begin_pos, + end_pos, scope_end_pos)); + Construct* result = constructs_.back().get(); + enclosing.push_back(result); + return result; + }; + + // Make a synthetic kFunction construct to enclose all blocks in the function. + push_construct(0, Construct::kFunction, entry_id, 0); + // The entry block can be a selection construct, so be sure to process + // it anyway. + + for (uint32_t i = 0; i < block_order_.size(); ++i) { + const auto block_id = block_order_[i]; + TINT_ASSERT(Reader, block_id > 0); + auto* block_info = GetBlockInfo(block_id); + TINT_ASSERT(Reader, block_info); + + if (enclosing.empty()) { + return Fail() << "internal error: too many merge blocks before block " + << block_id; + } + const Construct* top = enclosing.back(); + + while (block_id == top->end_id) { + // We've reached a predeclared end of the construct. Pop it off the + // stack. + enclosing.pop_back(); + if (enclosing.empty()) { + return Fail() << "internal error: too many merge blocks before block " + << block_id; + } + top = enclosing.back(); + } + + const auto merge = block_info->merge_for_header; + if (merge != 0) { + // The current block is a header. + const auto header = block_id; + const auto* header_info = block_info; + const auto depth = 1 + top->depth; + const auto ct = header_info->continue_for_header; + if (ct != 0) { + // The current block is a loop header. + // We should see the continue construct after the loop construct, so + // push the loop construct last. + + // From the interval rule, the continue construct consists of blocks + // in the block order, starting at the continue target, until just + // before the merge block. + top = push_construct(depth, Construct::kContinue, ct, merge); + // A loop header that is its own continue target will have an + // empty loop construct. Only create a loop construct when + // the continue target is *not* the same as the loop header. + if (header != ct) { + // From the interval rule, the loop construct consists of blocks + // in the block order, starting at the header, until just + // before the continue target. + top = push_construct(depth, Construct::kLoop, header, ct); + + // If the loop header branches to two different blocks inside the loop + // construct, then the loop body should be modeled as an if-selection + // construct + std::vector<uint32_t> targets; + header_info->basic_block->ForEachSuccessorLabel( + [&targets](const uint32_t target) { targets.push_back(target); }); + if ((targets.size() == 2u) && targets[0] != targets[1]) { + const auto target0_pos = GetBlockInfo(targets[0])->pos; + const auto target1_pos = GetBlockInfo(targets[1])->pos; + if (top->ContainsPos(target0_pos) && + top->ContainsPos(target1_pos)) { + // Insert a synthetic if-selection + top = push_construct(depth + 1, Construct::kIfSelection, header, + ct); + } + } + } + } else { + // From the interval rule, the selection construct consists of blocks + // in the block order, starting at the header, until just before the + // merge block. + const auto branch_opcode = + header_info->basic_block->terminator()->opcode(); + const auto kind = (branch_opcode == SpvOpBranchConditional) + ? Construct::kIfSelection + : Construct::kSwitchSelection; + top = push_construct(depth, kind, header, merge); + } + } + + TINT_ASSERT(Reader, top); + block_info->construct = top; + } + + // At the end of the block list, we should only have the kFunction construct + // left. + if (enclosing.size() != 1) { + return Fail() << "internal error: unbalanced structured constructs when " + "labeling structured constructs: ended with " + << enclosing.size() - 1 << " unterminated constructs"; + } + const auto* top = enclosing[0]; + if (top->kind != Construct::kFunction || top->depth != 0) { + return Fail() << "internal error: outermost construct is not a function?!"; + } + + return success(); +} + +bool FunctionEmitter::FindSwitchCaseHeaders() { + if (failed()) { + return false; + } + for (auto& construct : constructs_) { + if (construct->kind != Construct::kSwitchSelection) { + continue; + } + const auto* branch = + GetBlockInfo(construct->begin_id)->basic_block->terminator(); + + // Mark the default block + const auto default_id = branch->GetSingleWordInOperand(1); + auto* default_block = GetBlockInfo(default_id); + // A default target can't be a backedge. + if (construct->begin_pos >= default_block->pos) { + // An OpSwitch must dominate its cases. Also, it can't be a self-loop + // as that would be a backedge, and backedges can only target a loop, + // and loops use an OpLoopMerge instruction, which can't precede an + // OpSwitch. + return Fail() << "Switch branch from block " << construct->begin_id + << " to default target block " << default_id + << " can't be a back-edge"; + } + // A default target can be the merge block, but can't go past it. + if (construct->end_pos < default_block->pos) { + return Fail() << "Switch branch from block " << construct->begin_id + << " to default block " << default_id + << " escapes the selection construct"; + } + if (default_block->default_head_for) { + // An OpSwitch must dominate its cases, including the default target. + return Fail() << "Block " << default_id + << " is declared as the default target for two OpSwitch " + "instructions, at blocks " + << default_block->default_head_for->begin_id << " and " + << construct->begin_id; + } + if ((default_block->header_for_merge != 0) && + (default_block->header_for_merge != construct->begin_id)) { + // The switch instruction for this default block is an alternate path to + // the merge block, and hence the merge block is not dominated by its own + // (different) header. + return Fail() << "Block " << default_block->id + << " is the default block for switch-selection header " + << construct->begin_id << " and also the merge block for " + << default_block->header_for_merge + << " (violates dominance rule)"; + } + + default_block->default_head_for = construct.get(); + default_block->default_is_merge = default_block->pos == construct->end_pos; + + // Map a case target to the list of values selecting that case. + std::unordered_map<uint32_t, std::vector<uint64_t>> block_to_values; + std::vector<uint32_t> case_targets; + std::unordered_set<uint64_t> case_values; + + // Process case targets. + for (uint32_t iarg = 2; iarg + 1 < branch->NumInOperands(); iarg += 2) { + const auto value = branch->GetInOperand(iarg).AsLiteralUint64(); + const auto case_target_id = branch->GetSingleWordInOperand(iarg + 1); + + if (case_values.count(value)) { + return Fail() << "Duplicate case value " << value + << " in OpSwitch in block " << construct->begin_id; + } + case_values.insert(value); + if (block_to_values.count(case_target_id) == 0) { + case_targets.push_back(case_target_id); + } + block_to_values[case_target_id].push_back(value); + } + + for (uint32_t case_target_id : case_targets) { + auto* case_block = GetBlockInfo(case_target_id); + + case_block->case_values = std::make_unique<std::vector<uint64_t>>( + std::move(block_to_values[case_target_id])); + + // A case target can't be a back-edge. + if (construct->begin_pos >= case_block->pos) { + // An OpSwitch must dominate its cases. Also, it can't be a self-loop + // as that would be a backedge, and backedges can only target a loop, + // and loops use an OpLoopMerge instruction, which can't preceded an + // OpSwitch. + return Fail() << "Switch branch from block " << construct->begin_id + << " to case target block " << case_target_id + << " can't be a back-edge"; + } + // A case target can be the merge block, but can't go past it. + if (construct->end_pos < case_block->pos) { + return Fail() << "Switch branch from block " << construct->begin_id + << " to case target block " << case_target_id + << " escapes the selection construct"; + } + if (case_block->header_for_merge != 0 && + case_block->header_for_merge != construct->begin_id) { + // The switch instruction for this case block is an alternate path to + // the merge block, and hence the merge block is not dominated by its + // own (different) header. + return Fail() << "Block " << case_block->id + << " is a case block for switch-selection header " + << construct->begin_id << " and also the merge block for " + << case_block->header_for_merge + << " (violates dominance rule)"; + } + + // Mark the target as a case target. + if (case_block->case_head_for) { + // An OpSwitch must dominate its cases. + return Fail() + << "Block " << case_target_id + << " is declared as the switch case target for two OpSwitch " + "instructions, at blocks " + << case_block->case_head_for->begin_id << " and " + << construct->begin_id; + } + case_block->case_head_for = construct.get(); + } + } + return success(); +} + +BlockInfo* FunctionEmitter::HeaderIfBreakable(const Construct* c) { + if (c == nullptr) { + return nullptr; + } + switch (c->kind) { + case Construct::kLoop: + case Construct::kSwitchSelection: + return GetBlockInfo(c->begin_id); + case Construct::kContinue: { + const auto* continue_target = GetBlockInfo(c->begin_id); + return GetBlockInfo(continue_target->header_for_continue); + } + default: + break; + } + return nullptr; +} + +const Construct* FunctionEmitter::SiblingLoopConstruct( + const Construct* c) const { + if (c == nullptr || c->kind != Construct::kContinue) { + return nullptr; + } + const uint32_t continue_target_id = c->begin_id; + const auto* continue_target = GetBlockInfo(continue_target_id); + const uint32_t header_id = continue_target->header_for_continue; + if (continue_target_id == header_id) { + // The continue target is the whole loop. + return nullptr; + } + const auto* candidate = GetBlockInfo(header_id)->construct; + // Walk up the construct tree until we hit the loop. In future + // we might handle the corner case where the same block is both a + // loop header and a selection header. For example, where the + // loop header block has a conditional branch going to distinct + // targets inside the loop body. + while (candidate && candidate->kind != Construct::kLoop) { + candidate = candidate->parent; + } + return candidate; +} + +bool FunctionEmitter::ClassifyCFGEdges() { + if (failed()) { + return false; + } + + // Checks validity of CFG edges leaving each basic block. This implicitly + // checks dominance rules for headers and continue constructs. + // + // For each branch encountered, classify each edge (S,T) as: + // - a back-edge + // - a structured exit (specific ways of branching to enclosing construct) + // - a normal (forward) edge, either natural control flow or a case + // fallthrough + // + // If more than one block is targeted by a normal edge, then S must be a + // structured header. + // + // Term: NEC(B) is the nearest enclosing construct for B. + // + // If edge (S,T) is a normal edge, and NEC(S) != NEC(T), then + // T is the header block of its NEC(T), and + // NEC(S) is the parent of NEC(T). + + for (const auto src : block_order_) { + TINT_ASSERT(Reader, src > 0); + auto* src_info = GetBlockInfo(src); + TINT_ASSERT(Reader, src_info); + const auto src_pos = src_info->pos; + const auto& src_construct = *(src_info->construct); + + // Compute the ordered list of unique successors. + std::vector<uint32_t> successors; + { + std::unordered_set<uint32_t> visited; + src_info->basic_block->ForEachSuccessorLabel( + [&successors, &visited](const uint32_t succ) { + if (visited.count(succ) == 0) { + successors.push_back(succ); + visited.insert(succ); + } + }); + } + + // There should only be one backedge per backedge block. + uint32_t num_backedges = 0; + + // Track destinations for normal forward edges, either kForward + // or kCaseFallThrough. These count toward the need + // to have a merge instruction. We also track kIfBreak edges + // because when used with normal forward edges, we'll need + // to generate a flow guard variable. + std::vector<uint32_t> normal_forward_edges; + std::vector<uint32_t> if_break_edges; + + if (successors.empty() && src_construct.enclosing_continue) { + // Kill and return are not allowed in a continue construct. + return Fail() << "Invalid function exit at block " << src + << " from continue construct starting at " + << src_construct.enclosing_continue->begin_id; + } + + for (const auto dest : successors) { + const auto* dest_info = GetBlockInfo(dest); + // We've already checked terminators are valid. + TINT_ASSERT(Reader, dest_info); + const auto dest_pos = dest_info->pos; + + // Insert the edge kind entry and keep a handle to update + // its classification. + EdgeKind& edge_kind = src_info->succ_edge[dest]; + + if (src_pos >= dest_pos) { + // This is a backedge. + edge_kind = EdgeKind::kBack; + num_backedges++; + const auto* continue_construct = src_construct.enclosing_continue; + if (!continue_construct) { + return Fail() << "Invalid backedge (" << src << "->" << dest + << "): " << src << " is not in a continue construct"; + } + if (src_pos != continue_construct->end_pos - 1) { + return Fail() << "Invalid exit (" << src << "->" << dest + << ") from continue construct: " << src + << " is not the last block in the continue construct " + "starting at " + << src_construct.begin_id + << " (violates post-dominance rule)"; + } + const auto* ct_info = GetBlockInfo(continue_construct->begin_id); + TINT_ASSERT(Reader, ct_info); + if (ct_info->header_for_continue != dest) { + return Fail() + << "Invalid backedge (" << src << "->" << dest + << "): does not branch to the corresponding loop header, " + "expected " + << ct_info->header_for_continue; + } + } else { + // This is a forward edge. + // For now, classify it that way, but we might update it. + edge_kind = EdgeKind::kForward; + + // Exit from a continue construct can only be from the last block. + const auto* continue_construct = src_construct.enclosing_continue; + if (continue_construct != nullptr) { + if (continue_construct->ContainsPos(src_pos) && + !continue_construct->ContainsPos(dest_pos) && + (src_pos != continue_construct->end_pos - 1)) { + return Fail() << "Invalid exit (" << src << "->" << dest + << ") from continue construct: " << src + << " is not the last block in the continue construct " + "starting at " + << continue_construct->begin_id + << " (violates post-dominance rule)"; + } + } + + // Check valid structured exit cases. + + if (edge_kind == EdgeKind::kForward) { + // Check for a 'break' from a loop or from a switch. + const auto* breakable_header = HeaderIfBreakable( + src_construct.enclosing_loop_or_continue_or_switch); + if (breakable_header != nullptr) { + if (dest == breakable_header->merge_for_header) { + // It's a break. + edge_kind = (breakable_header->construct->kind == + Construct::kSwitchSelection) + ? EdgeKind::kSwitchBreak + : EdgeKind::kLoopBreak; + } + } + } + + if (edge_kind == EdgeKind::kForward) { + // Check for a 'continue' from within a loop. + const auto* loop_header = + HeaderIfBreakable(src_construct.enclosing_loop); + if (loop_header != nullptr) { + if (dest == loop_header->continue_for_header) { + // It's a continue. + edge_kind = EdgeKind::kLoopContinue; + } + } + } + + if (edge_kind == EdgeKind::kForward) { + const auto& header_info = *GetBlockInfo(src_construct.begin_id); + if (dest == header_info.merge_for_header) { + // Branch to construct's merge block. The loop break and + // switch break cases have already been covered. + edge_kind = EdgeKind::kIfBreak; + } + } + + // A forward edge into a case construct that comes from something + // other than the OpSwitch is actually a fallthrough. + if (edge_kind == EdgeKind::kForward) { + const auto* switch_construct = + (dest_info->case_head_for ? dest_info->case_head_for + : dest_info->default_head_for); + if (switch_construct != nullptr) { + if (src != switch_construct->begin_id) { + edge_kind = EdgeKind::kCaseFallThrough; + } + } + } + + // The edge-kind has been finalized. + + if ((edge_kind == EdgeKind::kForward) || + (edge_kind == EdgeKind::kCaseFallThrough)) { + normal_forward_edges.push_back(dest); + } + if (edge_kind == EdgeKind::kIfBreak) { + if_break_edges.push_back(dest); + } + + if ((edge_kind == EdgeKind::kForward) || + (edge_kind == EdgeKind::kCaseFallThrough)) { + // Check for an invalid forward exit out of this construct. + if (dest_info->pos > src_construct.end_pos) { + // In most cases we're bypassing the merge block for the source + // construct. + auto end_block = src_construct.end_id; + const char* end_block_desc = "merge block"; + if (src_construct.kind == Construct::kLoop) { + // For a loop construct, we have two valid places to go: the + // continue target or the merge for the loop header, which is + // further down. + const auto loop_merge = + GetBlockInfo(src_construct.begin_id)->merge_for_header; + if (dest_info->pos >= GetBlockInfo(loop_merge)->pos) { + // We're bypassing the loop's merge block. + end_block = loop_merge; + } else { + // We're bypassing the loop's continue target, and going into + // the middle of the continue construct. + end_block_desc = "continue target"; + } + } + return Fail() + << "Branch from block " << src << " to block " << dest + << " is an invalid exit from construct starting at block " + << src_construct.begin_id << "; branch bypasses " + << end_block_desc << " " << end_block; + } + + // Check dominance. + + // Look for edges that violate the dominance condition: a branch + // from X to Y where: + // If Y is in a nearest enclosing continue construct headed by + // CT: + // Y is not CT, and + // In the structured order, X appears before CT order or + // after CT's backedge block. + // Otherwise, if Y is in a nearest enclosing construct + // headed by H: + // Y is not H, and + // In the structured order, X appears before H or after H's + // merge block. + + const auto& dest_construct = *(dest_info->construct); + if (dest != dest_construct.begin_id && + !dest_construct.ContainsPos(src_pos)) { + return Fail() << "Branch from " << src << " to " << dest + << " bypasses " + << (dest_construct.kind == Construct::kContinue + ? "continue target " + : "header ") + << dest_construct.begin_id + << " (dominance rule violated)"; + } + } + } // end forward edge + } // end successor + + if (num_backedges > 1) { + return Fail() << "Block " << src + << " has too many backedges: " << num_backedges; + } + if ((normal_forward_edges.size() > 1) && + (src_info->merge_for_header == 0)) { + return Fail() << "Control flow diverges at block " << src << " (to " + << normal_forward_edges[0] << ", " + << normal_forward_edges[1] + << ") but it is not a structured header (it has no merge " + "instruction)"; + } + if ((normal_forward_edges.size() + if_break_edges.size() > 1) && + (src_info->merge_for_header == 0)) { + // There is a branch to the merge of an if-selection combined + // with an other normal forward