diff options
Diffstat (limited to 'chromium/third_party/SPIRV-Tools')
60 files changed, 2193 insertions, 625 deletions
diff --git a/chromium/third_party/SPIRV-Tools/src/BUILD.gn b/chromium/third_party/SPIRV-Tools/src/BUILD.gn index 5860aff03f6..fae795727a2 100644 --- a/chromium/third_party/SPIRV-Tools/src/BUILD.gn +++ b/chromium/third_party/SPIRV-Tools/src/BUILD.gn @@ -792,8 +792,12 @@ static_library("spvtools_reduce") { "source/reduce/remove_selection_reduction_opportunity.h", "source/reduce/remove_selection_reduction_opportunity_finder.cpp", "source/reduce/remove_selection_reduction_opportunity_finder.h", - "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp", - "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h", + "source/reduce/remove_struct_member_reduction_opportunity.cpp", + "source/reduce/remove_struct_member_reduction_opportunity.h", + "source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp", + "source/reduce/remove_unused_instruction_reduction_opportunity_finder.h", + "source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp", + "source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h", "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp", "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h", "source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp", diff --git a/chromium/third_party/SPIRV-Tools/src/CHANGES b/chromium/third_party/SPIRV-Tools/src/CHANGES index fe6641ecf04..a46755ecc8a 100644 --- a/chromium/third_party/SPIRV-Tools/src/CHANGES +++ b/chromium/third_party/SPIRV-Tools/src/CHANGES @@ -1,7 +1,43 @@ Revision history for SPIRV-Tools -v2020.3-dev 2020-03-26 - - Start v2020.3-dev +v2020.4-dev 2020-05-27 + - Start v2020.4-dev + +v2020.3 2020-05-27 + - General + - Prevent Effcee install his things when build spirv-tools with testing enabled (#3256) + - Update acorn version (#3294) + - If SPIRV-Headers is in our tree, include it as subproject (#3299) + - allow cross compiling for Windows Store, UWP, etc. (#3330) + - Optimizer + - Remove deprecated interfaces from instrument passes (#3361) + - Preserve debug info in inline pass (#3349) + - Handle more cases in dead member elim (#3289) + - Preserve debug info in eliminate-dead-functions (#3251) + - Fix Struct CFG analysis for single block loop (#3293) + - Add tests for recently added command line option (#3297) + - Consider sampled images as read-only storage (#3295) + - Allow various validation options to be passed to spirv-opt (#3314) + - Add debug information analysis (#3305) + - Preserve debug info for wrap-opkill (#3331) + - refactor inlining pass (#3328) + - Add unrolling to performance passes (#3082) + - Validator + - Add validation support for ImageGatherBiasLodAMD (#3363) + - Validate ShaderCallKHR memory scope (#3332) + - Validate Buffer and BufferBlock apply only to struct types (#3259) + - Reduce + - increase default step limit (#3327) + - Remove unused uniforms and similar (#3321) + - Fuzz + - Add support for StorageBuffer (#3348) + - Add validator options (#3254) + - Limit adding of new variables to 'basic' types (#3257) + - Transformation to add OpConstantNull (#3273) + - Handling of more fuzzing opportunities (#3277, #3280, #3281, #3290, #3292) + - Respect rules for OpSampledImage (#3287) + - Do not outline regions that produce pointer outputs (#3291) + - Linker v2020.2 2020-03-26 - General: diff --git a/chromium/third_party/SPIRV-Tools/src/CMakeLists.txt b/chromium/third_party/SPIRV-Tools/src/CMakeLists.txt index 9d0eb8b544c..73248f23439 100644 --- a/chromium/third_party/SPIRV-Tools/src/CMakeLists.txt +++ b/chromium/third_party/SPIRV-Tools/src/CMakeLists.txt @@ -40,7 +40,7 @@ if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") set(SPIRV_TIMER_ENABLED ${SPIRV_ALLOW_TIMERS}) elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Emscripten") add_definitions(-DSPIRV_EMSCRIPTEN) -elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows") +elseif("${CMAKE_SYSTEM_NAME}" MATCHES "Windows") add_definitions(-DSPIRV_WINDOWS) elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "CYGWIN") add_definitions(-DSPIRV_WINDOWS) diff --git a/chromium/third_party/SPIRV-Tools/src/DEPS b/chromium/third_party/SPIRV-Tools/src/DEPS index c3e78a26478..a27b921bfa8 100644 --- a/chromium/third_party/SPIRV-Tools/src/DEPS +++ b/chromium/third_party/SPIRV-Tools/src/DEPS @@ -3,10 +3,10 @@ use_relative_paths = True vars = { 'github': 'https://github.com', - 'effcee_revision': 'cd25ec17e9382f99a895b9ef53ff3c277464d07d', - 'googletest_revision': 'f2fb48c3b3d79a75a88a99fba6576b25d42ec528', - 're2_revision': '5bd613749fd530b576b890283bfb6bc6ea6246cb', - 'spirv_headers_revision': 'f8bf11a0253a32375c32cad92c841237b96696c0', + 'effcee_revision': '5af957bbfc7da4e9f7aa8cac11379fa36dd79b84', + 'googletest_revision': '011959aafddcd30611003de96cfd8d7a7685c700', + 're2_revision': 'aecba11114cf1fac5497aeb844b6966106de3eb6', + 'spirv_headers_revision': 'ac638f1815425403e946d0ab78bac71d2bdbf3be', } deps = { diff --git a/chromium/third_party/SPIRV-Tools/src/README.md b/chromium/third_party/SPIRV-Tools/src/README.md index 41c26a0a905..c82ca192f1f 100644 --- a/chromium/third_party/SPIRV-Tools/src/README.md +++ b/chromium/third_party/SPIRV-Tools/src/README.md @@ -324,8 +324,7 @@ The script `utils/git-sync-deps` can be used to checkout and/or update the contents of the repos under `external/` instead of manually maintaining them. ### Build using CMake -You can build The project using [CMake][cmake] to generate platform-specific -build configurations. +You can build the project using [CMake][cmake]: ```sh cd <spirv-dir> @@ -333,8 +332,36 @@ mkdir build && cd build cmake [-G <platform-generator>] <spirv-dir> ``` -Once the build files have been generated, build using your preferred -development environment. +Once the build files have been generated, build using the appropriate build +command (e.g. `ninja`, `make`, `msbuild`, etc.; this depends on the platform +generator used above), or use your IDE, or use CMake to run the appropriate build +command for you: + +```sh +cmake --build . [--config Debug] # runs `make` or `ninja` or `msbuild` etc. +``` + +#### Note about the fuzzer + +The SPIR-V fuzzer, `spirv-fuzz`, can only be built via CMake, and is disabled by +default. To build it, clone protobuf and use the `SPIRV_BUILD_FUZZER` CMake +option, like so: + +```sh +# In <spirv-dir> (the SPIRV-Tools repo root): +git clone https://github.com/protocolbuffers/protobuf external/protobuf +pushd external/protobuf +git checkout v3.7.1 +popd + +# In your build directory: +cmake [-G <platform-generator>] <spirv-dir> -DSPIRV_BUILD_FUZZER=ON +cmake --build . --config Debug +``` + +You can also add `-DSPIRV_ENABLE_LONG_FUZZER_TESTS=ON` to build additional +fuzzer tests. + ### Build using Bazel You can also use [Bazel](https://bazel.build/) to build the project. diff --git a/chromium/third_party/SPIRV-Tools/src/external/CMakeLists.txt b/chromium/third_party/SPIRV-Tools/src/external/CMakeLists.txt index 3c7b40331d7..5b341598cbe 100644 --- a/chromium/third_party/SPIRV-Tools/src/external/CMakeLists.txt +++ b/chromium/third_party/SPIRV-Tools/src/external/CMakeLists.txt @@ -47,7 +47,7 @@ if (NOT ${SPIRV_SKIP_TESTS}) if (TARGET gmock) message(STATUS "Google Mock already configured") else() - set(GMOCK_DIR ${CMAKE_CURRENT_SOURCE_DIR}/googletest/googlemock) + set(GMOCK_DIR ${CMAKE_CURRENT_SOURCE_DIR}/googletest) if(EXISTS ${GMOCK_DIR}) if(MSVC) # Our tests use ::testing::Combine. Work around a compiler @@ -88,7 +88,7 @@ if (NOT ${SPIRV_SKIP_TESTS}) set(RE2_BUILD_TESTING OFF CACHE STRING "Run RE2 Tests") if (NOT RE2_SOURCE_DIR) if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/re2) - set(RE2_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/re2" CACHE STRING "RE2 source dir" ) + set(RE2_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/re2" CACHE STRING "RE2 source dir" ) endif() endif() endif() @@ -98,13 +98,17 @@ if (NOT ${SPIRV_SKIP_TESTS}) if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/effcee) # If we're configuring RE2 (via Effcee), then turn off RE2 testing. if (NOT TARGET re2) - set(RE2_BUILD_TESTING OFF) + set(RE2_BUILD_TESTING OFF) endif() if (MSVC) - # SPIRV-Tools uses the shared CRT with MSVC. Tell Effcee to do the same. - set(EFFCEE_ENABLE_SHARED_CRT ON) + # SPIRV-Tools uses the shared CRT with MSVC. Tell Effcee to do the same. + set(EFFCEE_ENABLE_SHARED_CRT ON) endif() - add_subdirectory(effcee) + set(EFFCEE_BUILD_SAMPLES OFF CACHE BOOL "Do not build Effcee examples") + if (NOT TARGET effcee) + set(EFFCEE_BUILD_TESTING OFF CACHE BOOL "Do not build Effcee test suite") + endif() + add_subdirectory(effcee EXCLUDE_FROM_ALL) set_property(TARGET effcee PROPERTY FOLDER Effcee) # Turn off warnings for effcee and re2 set_property(TARGET effcee APPEND PROPERTY COMPILE_OPTIONS -w) diff --git a/chromium/third_party/SPIRV-Tools/src/include/spirv-tools/instrument.hpp b/chromium/third_party/SPIRV-Tools/src/include/spirv-tools/instrument.hpp index d3180e4442c..ef5136a8caf 100644 --- a/chromium/third_party/SPIRV-Tools/src/include/spirv-tools/instrument.hpp +++ b/chromium/third_party/SPIRV-Tools/src/include/spirv-tools/instrument.hpp @@ -35,10 +35,6 @@ namespace spvtools { // generated by InstrumentPass::GenDebugStreamWrite. This method is utilized // by InstBindlessCheckPass. // -// kInst2* values support version 2 of the output record format and were used -// for the transition to this format. These values have now been transferred -// to the original kInst* values. The kInst2* values are therefore DEPRECATED. -// // The first member of the debug output buffer contains the next available word // in the data stream to be written. Shaders will atomically read and update // this value so as not to overwrite each others records. This value must be @@ -94,10 +90,6 @@ static const int kInstCompOutGlobalInvocationIdX = kInstCommonOutCnt; static const int kInstCompOutGlobalInvocationIdY = kInstCommonOutCnt + 1; static const int kInstCompOutGlobalInvocationIdZ = kInstCommonOutCnt + 2; -// Compute Shader Output Record Offsets - Version 1 (DEPRECATED) -static const int kInstCompOutGlobalInvocationId = kInstCommonOutCnt; -static const int kInstCompOutUnused = kInstCommonOutCnt + 1; - // Tessellation Control Shader Output Record Offsets static const int kInstTessCtlOutInvocationId = kInstCommonOutCnt; static const int kInstTessCtlOutPrimitiveId = kInstCommonOutCnt + 1; @@ -108,10 +100,6 @@ static const int kInstTessEvalOutPrimitiveId = kInstCommonOutCnt; static const int kInstTessEvalOutTessCoordU = kInstCommonOutCnt + 1; static const int kInstTessEvalOutTessCoordV = kInstCommonOutCnt + 2; -// Tessellation Shader Output Record Offsets - Version 1 (DEPRECATED) -static const int kInstTessOutInvocationId = kInstCommonOutCnt; -static const int kInstTessOutUnused = kInstCommonOutCnt + 1; - // Geometry Shader Output Record Offsets static const int kInstGeomOutPrimitiveId = kInstCommonOutCnt; static const int kInstGeomOutInvocationId = kInstCommonOutCnt + 1; @@ -124,14 +112,12 @@ static const int kInstRayTracingOutLaunchIdZ = kInstCommonOutCnt + 2; // Size of Common and Stage-specific Members static const int kInstStageOutCnt = kInstCommonOutCnt + 3; -static const int kInst2StageOutCnt = kInstCommonOutCnt + 3; // Validation Error Code Offset // // This identifies the validation error. It also helps to identify // how many words follow in the record and their meaning. static const int kInstValidationOutError = kInstStageOutCnt; -static const int kInst2ValidationOutError = kInst2StageOutCnt; // Validation-specific Output Record Offsets // @@ -144,37 +130,19 @@ static const int kInstBindlessBoundsOutDescIndex = kInstStageOutCnt + 1; static const int kInstBindlessBoundsOutDescBound = kInstStageOutCnt + 2; static const int kInstBindlessBoundsOutCnt = kInstStageOutCnt + 3; -static const int kInst2BindlessBoundsOutDescIndex = kInst2StageOutCnt + 1; -static const int kInst2BindlessBoundsOutDescBound = kInst2StageOutCnt + 2; -static const int kInst2BindlessBoundsOutCnt = kInst2StageOutCnt + 3; - // A bindless uninitialized error will output the index. static const int kInstBindlessUninitOutDescIndex = kInstStageOutCnt + 1; static const int kInstBindlessUninitOutUnused = kInstStageOutCnt + 2; static const int kInstBindlessUninitOutCnt = kInstStageOutCnt + 3; -static const int kInst2BindlessUninitOutDescIndex = kInst2StageOutCnt + 1; -static const int kInst2BindlessUninitOutUnused = kInst2StageOutCnt + 2; -static const int kInst2BindlessUninitOutCnt = kInst2StageOutCnt + 3; - // A buffer address unalloc error will output the 64-bit pointer in // two 32-bit pieces, lower bits first. static const int kInstBuffAddrUnallocOutDescPtrLo = kInstStageOutCnt + 1; static const int kInstBuffAddrUnallocOutDescPtrHi = kInstStageOutCnt + 2; static const int kInstBuffAddrUnallocOutCnt = kInstStageOutCnt + 3; -static const int kInst2BuffAddrUnallocOutDescPtrLo = kInst2StageOutCnt + 1; -static const int kInst2BuffAddrUnallocOutDescPtrHi = kInst2StageOutCnt + 2; -static const int kInst2BuffAddrUnallocOutCnt = kInst2StageOutCnt + 3; - -// DEPRECATED -static const int kInstBindlessOutDescIndex = kInstStageOutCnt + 1; -static const int kInstBindlessOutDescBound = kInstStageOutCnt + 2; -static const int kInstBindlessOutCnt = kInstStageOutCnt + 3; - // Maximum Output Record Member Count static const int kInstMaxOutCnt = kInstStageOutCnt + 3; -static const int kInst2MaxOutCnt = kInst2StageOutCnt + 3; // Validation Error Codes // @@ -223,9 +191,6 @@ static const int kDebugOutputPrintfStream = 3; // Data[ i + Data[ b + Data[ s + Data[ kDebugInputBindlessInitOffset ] ] ] ] static const int kDebugInputBindlessInitOffset = 0; -// DEPRECATED -static const int kDebugInputBindlessOffsetReserved = 0; - // At offset kDebugInputBindlessOffsetLengths is some number of uints which // provide the bindless length data. More specifically, the number of // descriptors at (set=s, binding=b) is: diff --git a/chromium/third_party/SPIRV-Tools/src/include/spirv-tools/optimizer.hpp b/chromium/third_party/SPIRV-Tools/src/include/spirv-tools/optimizer.hpp index b9049232300..d393495bae3 100644 --- a/chromium/third_party/SPIRV-Tools/src/include/spirv-tools/optimizer.hpp +++ b/chromium/third_party/SPIRV-Tools/src/include/spirv-tools/optimizer.hpp @@ -762,10 +762,9 @@ Optimizer::PassToken CreateCombineAccessChainsPass(); // |input_length_enable| controls instrumentation of runtime descriptor array // references, and |input_init_enable| controls instrumentation of descriptor // initialization checking, both of which require input buffer support. -// |version| specifies the buffer record format. Optimizer::PassToken CreateInstBindlessCheckPass( uint32_t desc_set, uint32_t shader_id, bool input_length_enable = false, - bool input_init_enable = false, uint32_t version = 2); + bool input_init_enable = false); // Create a pass to instrument physical buffer address checking // This pass instruments all physical buffer address references to check that @@ -786,10 +785,8 @@ Optimizer::PassToken CreateInstBindlessCheckPass( // The instrumentation will read and write buffers in debug // descriptor set |desc_set|. It will write |shader_id| in each output record // to identify the shader module which generated the record. -// |version| specifies the output buffer record format. Optimizer::PassToken CreateInstBuffAddrCheckPass(uint32_t desc_set, - uint32_t shader_id, - uint32_t version = 2); + uint32_t shader_id); // Create a pass to instrument OpDebugPrintf instructions. // This pass replaces all OpDebugPrintf instructions with instructions to write diff --git a/chromium/third_party/SPIRV-Tools/src/source/opcode.cpp b/chromium/third_party/SPIRV-Tools/src/source/opcode.cpp index 80fe3b3a9f9..079def626c6 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opcode.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/opcode.cpp @@ -650,6 +650,22 @@ bool spvOpcodeIsCommutativeBinaryOperator(SpvOp opcode) { } } +bool spvOpcodeIsLinearAlgebra(SpvOp opcode) { + switch (opcode) { + case SpvOpTranspose: + case SpvOpVectorTimesScalar: + case SpvOpMatrixTimesScalar: + case SpvOpVectorTimesMatrix: + case SpvOpMatrixTimesVector: + case SpvOpMatrixTimesMatrix: + case SpvOpOuterProduct: + case SpvOpDot: + return true; + default: + return false; + } +} + std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode) { switch (opcode) { case SpvOpMemoryBarrier: diff --git a/chromium/third_party/SPIRV-Tools/src/source/opcode.h b/chromium/third_party/SPIRV-Tools/src/source/opcode.h index b4f02718f87..0d8ec925716 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opcode.h +++ b/chromium/third_party/SPIRV-Tools/src/source/opcode.h @@ -134,6 +134,9 @@ bool spvOpcodeIsDebug(SpvOp opcode); // where the order of the operands is irrelevant. bool spvOpcodeIsCommutativeBinaryOperator(SpvOp opcode); +// Returns true for opcodes that represents linear algebra instructions. +bool spvOpcodeIsLinearAlgebra(SpvOp opcode); + // Returns a vector containing the indices of the memory semantics <id> // operands for |opcode|. std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode); diff --git a/chromium/third_party/SPIRV-Tools/src/source/operand.cpp b/chromium/third_party/SPIRV-Tools/src/source/operand.cpp index 755ad6ac782..09105958ed2 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/operand.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/operand.cpp @@ -455,7 +455,7 @@ bool spvIsInIdType(spv_operand_type_t type) { return false; } switch (type) { - // Blacklist non-input IDs. + // Deny non-input IDs. case SPV_OPERAND_TYPE_TYPE_ID: case SPV_OPERAND_TYPE_RESULT_ID: return false; diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/aggressive_dead_code_elim_pass.cpp b/chromium/third_party/SPIRV-Tools/src/source/opt/aggressive_dead_code_elim_pass.cpp index db2b67b923d..760d8e8435e 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/aggressive_dead_code_elim_pass.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/aggressive_dead_code_elim_pass.cpp @@ -131,11 +131,11 @@ void AggressiveDCEPass::AddStores(uint32_t ptrId) { } bool AggressiveDCEPass::AllExtensionsSupported() const { - // If any extension not in whitelist, return false + // If any extension not in allowlist, return false for (auto& ei : get_module()->extensions()) { const char* extName = reinterpret_cast<const char*>(&ei.GetInOperand(0).words[0]); - if (extensions_whitelist_.find(extName) == extensions_whitelist_.end()) + if (extensions_allowlist_.find(extName) == extensions_allowlist_.end()) return false; } return true; @@ -882,14 +882,14 @@ bool AggressiveDCEPass::ProcessGlobalValues() { AggressiveDCEPass::AggressiveDCEPass() = default; Pass::Status AggressiveDCEPass::Process() { - // Initialize extensions whitelist + // Initialize extensions allowlist InitExtensions(); return ProcessImpl(); } void AggressiveDCEPass::InitExtensions() { - extensions_whitelist_.clear(); - extensions_whitelist_.insert({ + extensions_allowlist_.clear(); + extensions_allowlist_.insert({ "SPV_AMD_shader_explicit_vertex_parameter", "SPV_AMD_shader_trinary_minmax", "SPV_AMD_gcn_shader", diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/aggressive_dead_code_elim_pass.h b/chromium/third_party/SPIRV-Tools/src/source/opt/aggressive_dead_code_elim_pass.h index c043a96f401..131e33a26b8 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/aggressive_dead_code_elim_pass.h +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/aggressive_dead_code_elim_pass.h @@ -87,7 +87,7 @@ class AggressiveDCEPass : public MemPass { // to the live instruction worklist. void AddStores(uint32_t ptrId); - // Initialize extensions whitelist + // Initialize extensions allowlist void InitExtensions(); // Return true if all extensions in this module are supported by this pass. @@ -191,7 +191,7 @@ class AggressiveDCEPass : public MemPass { std::vector<Instruction*> to_kill_; // Extensions supported by this pass. - std::unordered_set<std::string> extensions_whitelist_; + std::unordered_set<std::string> extensions_allowlist_; }; } // namespace opt diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/dead_branch_elim_pass.cpp b/chromium/third_party/SPIRV-Tools/src/source/opt/dead_branch_elim_pass.cpp index 16d9fd56361..0054f57647e 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/dead_branch_elim_pass.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/dead_branch_elim_pass.cpp @@ -41,6 +41,7 @@ bool DeadBranchElimPass::GetConstCondition(uint32_t condId, bool* condVal) { bool condIsConst; Instruction* cInst = get_def_use_mgr()->GetDef(condId); switch (cInst->opcode()) { + case SpvOpConstantNull: case SpvOpConstantFalse: { *condVal = false; condIsConst = true; diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/debug_info_manager.cpp b/chromium/third_party/SPIRV-Tools/src/source/opt/debug_info_manager.cpp index 3df3f2b49cc..3e17d4b81fd 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/debug_info_manager.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/debug_info_manager.cpp @@ -24,10 +24,54 @@ static const uint32_t kOpLineOperandLineIndex = 1; static const uint32_t kLineOperandIndexDebugFunction = 7; static const uint32_t kLineOperandIndexDebugLexicalBlock = 5; static const uint32_t kDebugFunctionOperandFunctionIndex = 13; +static const uint32_t kDebugFunctionOperandParentIndex = 9; +static const uint32_t kDebugTypeCompositeOperandParentIndex = 9; +static const uint32_t kDebugLexicalBlockOperandParentIndex = 7; +static const uint32_t kDebugInlinedAtOperandInlinedIndex = 6; +static const uint32_t kDebugExpressOperandOperationIndex = 4; +static const uint32_t kDebugDeclareOperandLocalVariableIndex = 4; +static const uint32_t kDebugDeclareOperandVariableIndex = 5; +static const uint32_t kDebugValueOperandLocalVariableIndex = 4; +static const uint32_t kDebugValueOperandExpressionIndex = 6; +static const uint32_t kDebugOperationOperandOperationIndex = 4; +static const uint32_t kOpVariableOperandStorageClassIndex = 2; +static const uint32_t kDebugLocalVariableOperandParentIndex = 9; namespace spvtools { namespace opt { namespace analysis { +namespace { + +void SetInlinedOperand(Instruction* dbg_inlined_at, uint32_t inlined_operand) { + assert(dbg_inlined_at); + assert(dbg_inlined_at->GetOpenCL100DebugOpcode() == + OpenCLDebugInfo100DebugInlinedAt); + if (dbg_inlined_at->NumOperands() <= kDebugInlinedAtOperandInlinedIndex) { + dbg_inlined_at->AddOperand( + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {inlined_operand}}); + } else { + dbg_inlined_at->SetOperand(kDebugInlinedAtOperandInlinedIndex, + {inlined_operand}); + } +} + +uint32_t GetInlinedOperand(Instruction* dbg_inlined_at) { + assert(dbg_inlined_at); + assert(dbg_inlined_at->GetOpenCL100DebugOpcode() == + OpenCLDebugInfo100DebugInlinedAt); + if (dbg_inlined_at->NumOperands() <= kDebugInlinedAtOperandInlinedIndex) + return kNoInlinedAt; + return dbg_inlined_at->GetSingleWordOperand( + kDebugInlinedAtOperandInlinedIndex); +} + +bool IsEmptyDebugExpression(Instruction* instr) { + return instr->GetOpenCL100DebugOpcode() == + OpenCLDebugInfo100DebugExpression && + instr->NumOperands() == kDebugExpressOperandOperationIndex; +} + +} // namespace DebugInfoManager::DebugInfoManager(IRContext* c) : context_(c) { AnalyzeDebugInsts(*c->module()); @@ -57,6 +101,20 @@ void DebugInfoManager::RegisterDbgFunction(Instruction* inst) { fn_id_to_dbg_fn_[fn_id] = inst; } +void DebugInfoManager::RegisterDbgDeclare(uint32_t var_id, + Instruction* dbg_declare) { + assert(dbg_declare->GetOpenCL100DebugOpcode() == + OpenCLDebugInfo100DebugDeclare || + dbg_declare->GetOpenCL100DebugOpcode() == + OpenCLDebugInfo100DebugValue); + auto dbg_decl_itr = var_id_to_dbg_decl_.find(var_id); + if (dbg_decl_itr == var_id_to_dbg_decl_.end()) { + var_id_to_dbg_decl_[var_id] = {dbg_declare}; + } else { + dbg_decl_itr->second.push_back(dbg_declare); + } +} + uint32_t DebugInfoManager::CreateDebugInlinedAt(const Instruction* line, const DebugScope& scope) { if (context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo() == @@ -113,14 +171,79 @@ uint32_t DebugInfoManager::CreateDebugInlinedAt(const Instruction* line, // |scope| already has DebugInlinedAt. We put the existing DebugInlinedAt // into the Inlined operand of this new DebugInlinedAt. if (scope.GetInlinedAt() != kNoInlinedAt) { - inlined_at->AddOperand({spv_operand_type_t::SPV_OPERAND_TYPE_RESULT_ID, - {scope.GetInlinedAt()}}); + inlined_at->AddOperand( + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {scope.GetInlinedAt()}}); } RegisterDbgInst(inlined_at.get()); + if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse)) + context()->get_def_use_mgr()->AnalyzeInstDefUse(inlined_at.get()); context()->module()->AddExtInstDebugInfo(std::move(inlined_at)); return result_id; } +DebugScope DebugInfoManager::BuildDebugScope( + const DebugScope& callee_instr_scope, + DebugInlinedAtContext* inlined_at_ctx) { + return DebugScope(callee_instr_scope.GetLexicalScope(), + BuildDebugInlinedAtChain(callee_instr_scope.GetInlinedAt(), + inlined_at_ctx)); +} + +uint32_t DebugInfoManager::BuildDebugInlinedAtChain( + uint32_t callee_inlined_at, DebugInlinedAtContext* inlined_at_ctx) { + if (inlined_at_ctx->GetScopeOfCallInstruction().GetLexicalScope() == + kNoDebugScope) + return kNoInlinedAt; + + // Reuse the already generated DebugInlinedAt chain if exists. + uint32_t already_generated_chain_head_id = + inlined_at_ctx->GetDebugInlinedAtChain(callee_inlined_at); + if (already_generated_chain_head_id != kNoInlinedAt) { + return already_generated_chain_head_id; + } + + const uint32_t new_dbg_inlined_at_id = + CreateDebugInlinedAt(inlined_at_ctx->GetLineOfCallInstruction(), + inlined_at_ctx->GetScopeOfCallInstruction()); + if (new_dbg_inlined_at_id == kNoInlinedAt) return kNoInlinedAt; + + if (callee_inlined_at == kNoInlinedAt) { + inlined_at_ctx->SetDebugInlinedAtChain(kNoInlinedAt, new_dbg_inlined_at_id); + return new_dbg_inlined_at_id; + } + + uint32_t chain_head_id = kNoInlinedAt; + uint32_t chain_iter_id = callee_inlined_at; + Instruction* last_inlined_at_in_chain = nullptr; + do { + Instruction* new_inlined_at_in_chain = CloneDebugInlinedAt( + chain_iter_id, /* insert_before */ last_inlined_at_in_chain); + assert(new_inlined_at_in_chain != nullptr); + + // Set DebugInlinedAt of the new scope as the head of the chain. + if (chain_head_id == kNoInlinedAt) + chain_head_id = new_inlined_at_in_chain->result_id(); + + // Previous DebugInlinedAt of the chain must point to the new + // DebugInlinedAt as its Inlined operand to build a recursive + // chain. + if (last_inlined_at_in_chain != nullptr) { + SetInlinedOperand(last_inlined_at_in_chain, + new_inlined_at_in_chain->result_id()); + } + last_inlined_at_in_chain = new_inlined_at_in_chain; + + chain_iter_id = GetInlinedOperand(new_inlined_at_in_chain); + } while (chain_iter_id != kNoInlinedAt); + + // Put |new_dbg_inlined_at_id| into the end of the chain. + SetInlinedOperand(last_inlined_at_in_chain, new_dbg_inlined_at_id); + + // Keep the new chain information that will be reused it. + inlined_at_ctx->SetDebugInlinedAtChain(callee_inlined_at, chain_head_id); + return chain_head_id; +} + Instruction* DebugInfoManager::GetDebugInfoNone() { if (debug_info_none_inst_ != nullptr) return debug_info_none_inst_; @@ -129,11 +252,11 @@ Instruction* DebugInfoManager::GetDebugInfoNone() { context(), SpvOpExtInst, context()->get_type_mgr()->GetVoidTypeId(), result_id, { - {SPV_OPERAND_TYPE_RESULT_ID, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {context() ->get_feature_mgr() ->GetExtInstImportId_OpenCL100DebugInfo()}}, - {SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, + {spv_operand_type_t::SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, {static_cast<uint32_t>(OpenCLDebugInfo100DebugInfoNone)}}, })); @@ -143,9 +266,38 @@ Instruction* DebugInfoManager::GetDebugInfoNone() { std::move(dbg_info_none_inst)); RegisterDbgInst(debug_info_none_inst_); + if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse)) + context()->get_def_use_mgr()->AnalyzeInstDefUse(debug_info_none_inst_); return debug_info_none_inst_; } +Instruction* DebugInfoManager::GetEmptyDebugExpression() { + if (empty_debug_expr_inst_ != nullptr) return empty_debug_expr_inst_; + + uint32_t result_id = context()->TakeNextId(); + std::unique_ptr<Instruction> empty_debug_expr(new Instruction( + context(), SpvOpExtInst, context()->get_type_mgr()->GetVoidTypeId(), + result_id, + { + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, + {context() + ->get_feature_mgr() + ->GetExtInstImportId_OpenCL100DebugInfo()}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, + {static_cast<uint32_t>(OpenCLDebugInfo100DebugExpression)}}, + })); + + // Add to the front of |ext_inst_debuginfo_|. + empty_debug_expr_inst_ = + context()->module()->ext_inst_debuginfo_begin()->InsertBefore( + std::move(empty_debug_expr)); + + RegisterDbgInst(empty_debug_expr_inst_); + if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse)) + context()->get_def_use_mgr()->AnalyzeInstDefUse(empty_debug_expr_inst_); + return empty_debug_expr_inst_; +} + Instruction* DebugInfoManager::GetDebugInlinedAt(uint32_t dbg_inlined_at_id) { auto* inlined_at = GetDbgInst(dbg_inlined_at_id); if (inlined_at == nullptr) return nullptr; @@ -163,12 +315,162 @@ Instruction* DebugInfoManager::CloneDebugInlinedAt(uint32_t clone_inlined_at_id, std::unique_ptr<Instruction> new_inlined_at(inlined_at->Clone(context())); new_inlined_at->SetResultId(context()->TakeNextId()); RegisterDbgInst(new_inlined_at.get()); + if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse)) + context()->get_def_use_mgr()->AnalyzeInstDefUse(new_inlined_at.get()); if (insert_before != nullptr) return insert_before->InsertBefore(std::move(new_inlined_at)); return context()->module()->ext_inst_debuginfo_end()->InsertBefore( std::move(new_inlined_at)); } +uint32_t DebugInfoManager::GetParentScope(uint32_t child_scope) { + auto dbg_scope_itr = id_to_dbg_inst_.find(child_scope); + assert(dbg_scope_itr != id_to_dbg_inst_.end()); + OpenCLDebugInfo100Instructions debug_opcode = + dbg_scope_itr->second->GetOpenCL100DebugOpcode(); + uint32_t parent_scope = kNoDebugScope; + switch (debug_opcode) { + case OpenCLDebugInfo100DebugFunction: + parent_scope = dbg_scope_itr->second->GetSingleWordOperand( + kDebugFunctionOperandParentIndex); + break; + case OpenCLDebugInfo100DebugLexicalBlock: + parent_scope = dbg_scope_itr->second->GetSingleWordOperand( + kDebugLexicalBlockOperandParentIndex); + break; + case OpenCLDebugInfo100DebugTypeComposite: + parent_scope = dbg_scope_itr->second->GetSingleWordOperand( + kDebugTypeCompositeOperandParentIndex); + break; + case OpenCLDebugInfo100DebugCompilationUnit: + // DebugCompilationUnit does not have a parent scope. + break; + default: + assert(false && + "Unreachable. A debug scope instruction must be " + "DebugFunction, DebugTypeComposite, DebugLexicalBlock, " + "or DebugCompilationUnit."); + break; + } + return parent_scope; +} + +bool DebugInfoManager::IsAncestorOfScope(uint32_t scope, uint32_t ancestor) { + uint32_t ancestor_scope_itr = scope; + while (ancestor_scope_itr != kNoDebugScope) { + if (ancestor == ancestor_scope_itr) return true; + ancestor_scope_itr = GetParentScope(ancestor_scope_itr); + } + return false; +} + +bool DebugInfoManager::IsDeclareVisibleToInstr(Instruction* dbg_declare, + uint32_t instr_scope_id) { + if (instr_scope_id == kNoDebugScope) return false; + + uint32_t dbg_local_var_id = + dbg_declare->GetSingleWordOperand(kDebugDeclareOperandLocalVariableIndex); + auto dbg_local_var_itr = id_to_dbg_inst_.find(dbg_local_var_id); + assert(dbg_local_var_itr != id_to_dbg_inst_.end()); + uint32_t decl_scope_id = dbg_local_var_itr->second->GetSingleWordOperand( + kDebugLocalVariableOperandParentIndex); + + // If the scope of DebugDeclare is an ancestor scope of the instruction's + // scope, the local variable is visible to the instruction. + return IsAncestorOfScope(instr_scope_id, decl_scope_id); +} + +void DebugInfoManager::AddDebugValue(Instruction* scope_and_line, + uint32_t variable_id, uint32_t value_id, + Instruction* insert_pos) { + auto dbg_decl_itr = var_id_to_dbg_decl_.find(variable_id); + if (dbg_decl_itr == var_id_to_dbg_decl_.end()) return; + + uint32_t instr_scope_id = scope_and_line->GetDebugScope().GetLexicalScope(); + for (auto* dbg_decl_or_val : dbg_decl_itr->second) { + if (!IsDeclareVisibleToInstr(dbg_decl_or_val, instr_scope_id)) continue; + + uint32_t result_id = context()->TakeNextId(); + std::unique_ptr<Instruction> new_dbg_value(new Instruction( + context(), SpvOpExtInst, context()->get_type_mgr()->GetVoidTypeId(), + result_id, + { + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, + {context() + ->get_feature_mgr() + ->GetExtInstImportId_OpenCL100DebugInfo()}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, + {static_cast<uint32_t>(OpenCLDebugInfo100DebugValue)}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, + {dbg_decl_or_val->GetSingleWordOperand( + kDebugValueOperandLocalVariableIndex)}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {value_id}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, + {GetEmptyDebugExpression()->result_id()}}, + })); + + if (dbg_decl_or_val->NumOperands() > + kDebugValueOperandExpressionIndex + 1) { + assert(dbg_decl_or_val->GetOpenCL100DebugOpcode() == + OpenCLDebugInfo100DebugValue); + for (uint32_t i = kDebugValueOperandExpressionIndex + 1; + i < dbg_decl_or_val->NumOperands(); ++i) { + new_dbg_value->AddOperand({spv_operand_type_t::SPV_OPERAND_TYPE_ID, + {dbg_decl_or_val->GetSingleWordOperand(i)}}); + } + } + + // Avoid inserting the new DebugValue between OpPhi or OpVariable + // instructions. + Instruction* insert_before = insert_pos->NextNode(); + while (insert_before->opcode() == SpvOpPhi || + insert_before->opcode() == SpvOpVariable) { + insert_before = insert_before->NextNode(); + } + + Instruction* added_dbg_value = + insert_before->InsertBefore(std::move(new_dbg_value)); + added_dbg_value->UpdateDebugInfo(scope_and_line); + AnalyzeDebugInst(added_dbg_value); + if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse)) + context()->get_def_use_mgr()->AnalyzeInstDefUse(added_dbg_value); + } +} + +uint32_t DebugInfoManager::GetVariableIdOfDebugValueUsedForDeclare( + Instruction* inst) { + if (inst->GetOpenCL100DebugOpcode() != OpenCLDebugInfo100DebugValue) return 0; + + auto* expr = + GetDbgInst(inst->GetSingleWordOperand(kDebugValueOperandExpressionIndex)); + if (expr == nullptr) return 0; + if (expr->NumOperands() != kDebugExpressOperandOperationIndex + 1) return 0; + + auto* operation = GetDbgInst( + expr->GetSingleWordOperand(kDebugExpressOperandOperationIndex)); + if (operation == nullptr) return 0; + if (operation->GetSingleWordOperand(kDebugOperationOperandOperationIndex) != + OpenCLDebugInfo100Deref) { + return 0; + } + + uint32_t var_id = + inst->GetSingleWordOperand(kDebugDeclareOperandVariableIndex); + if (!context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse)) { + assert(false && + "Checking a DebugValue can be used for declare needs DefUseManager"); + return 0; + } + + auto* var = context()->get_def_use_mgr()->GetDef(var_id); + if (var->opcode() == SpvOpVariable && + SpvStorageClass(var->GetSingleWordOperand( + kOpVariableOperandStorageClassIndex)) == SpvStorageClassFunction) { + return var_id; + } + return 0; +} + void DebugInfoManager::AnalyzeDebugInst(Instruction* dbg_inst) { if (dbg_inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100InstructionsMax) return; @@ -186,12 +488,37 @@ void DebugInfoManager::AnalyzeDebugInst(Instruction* dbg_inst) { dbg_inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugInfoNone) { debug_info_none_inst_ = dbg_inst; } + + if (empty_debug_expr_inst_ == nullptr && IsEmptyDebugExpression(dbg_inst)) { + empty_debug_expr_inst_ = dbg_inst; + } + + if (dbg_inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugDeclare) { + uint32_t var_id = + dbg_inst->GetSingleWordOperand(kDebugDeclareOperandVariableIndex); + RegisterDbgDeclare(var_id, dbg_inst); + } + + if (uint32_t var_id = GetVariableIdOfDebugValueUsedForDeclare(dbg_inst)) { + RegisterDbgDeclare(var_id, dbg_inst); + } } void DebugInfoManager::AnalyzeDebugInsts(Module& module) { debug_info_none_inst_ = nullptr; + empty_debug_expr_inst_ = nullptr; module.ForEachInst([this](Instruction* cpi) { AnalyzeDebugInst(cpi); }); + // Move |empty_debug_expr_inst_| to the beginning of the debug instruction + // list. + if (empty_debug_expr_inst_ != nullptr && + empty_debug_expr_inst_->PreviousNode() != nullptr && + empty_debug_expr_inst_->PreviousNode()->GetOpenCL100DebugOpcode() != + OpenCLDebugInfo100InstructionsMax) { + empty_debug_expr_inst_->InsertBefore( + &*context()->module()->ext_inst_debuginfo_begin()); + } + // Move |debug_info_none_inst_| to the beginning of the debug instruction // list. if (debug_info_none_inst_ != nullptr && @@ -203,6 +530,50 @@ void DebugInfoManager::AnalyzeDebugInsts(Module& module) { } } +void DebugInfoManager::ClearDebugInfo(Instruction* instr) { + if (instr == nullptr || + instr->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100InstructionsMax) { + return; + } + + id_to_dbg_inst_.erase(instr->result_id()); + + if (instr->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugFunction) { + auto fn_id = + instr->GetSingleWordOperand(kDebugFunctionOperandFunctionIndex); + fn_id_to_dbg_fn_.erase(fn_id); + } + + if (instr->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugDeclare) { + auto var_id = + instr->GetSingleWordOperand(kDebugDeclareOperandVariableIndex); + var_id_to_dbg_decl_.erase(var_id); + } + + if (debug_info_none_inst_ == instr) { + debug_info_none_inst_ = nullptr; + for (auto dbg_instr_itr = context()->module()->ext_inst_debuginfo_begin(); + dbg_instr_itr != context()->module()->ext_inst_debuginfo_end(); + ++dbg_instr_itr) { + if (dbg_instr_itr->GetOpenCL100DebugOpcode() == + OpenCLDebugInfo100DebugInfoNone) { + debug_info_none_inst_ = &*dbg_instr_itr; + } + } + } + + if (empty_debug_expr_inst_ == instr) { + empty_debug_expr_inst_ = nullptr; + for (auto dbg_instr_itr = context()->module()->ext_inst_debuginfo_begin(); + dbg_instr_itr != context()->module()->ext_inst_debuginfo_end(); + ++dbg_instr_itr) { + if (IsEmptyDebugExpression(&*dbg_instr_itr)) { + empty_debug_expr_inst_ = &*dbg_instr_itr; + } + } + } +} + } // namespace analysis } // namespace opt } // namespace spvtools diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/debug_info_manager.h b/chromium/third_party/SPIRV-Tools/src/source/opt/debug_info_manager.h index a2746decae6..c0ac3ce57c2 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/debug_info_manager.h +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/debug_info_manager.h @@ -16,6 +16,7 @@ #define SOURCE_OPT_DEBUG_INFO_MANAGER_H_ #include <unordered_map> +#include <vector> #include "source/opt/instruction.h" #include "source/opt/module.h" @@ -24,6 +25,48 @@ namespace spvtools { namespace opt { namespace analysis { +// When an instruction of a callee function is inlined to its caller function, +// we need the line and the scope information of the function call instruction +// to generate DebugInlinedAt. This class keeps the data. For multiple inlining +// of a single instruction, we have to create multiple DebugInlinedAt +// instructions as a chain. This class keeps the information of the generated +// DebugInlinedAt chains to reduce the number of chains. +class DebugInlinedAtContext { + public: + explicit DebugInlinedAtContext(Instruction* call_inst) + : call_inst_line_(call_inst->dbg_line_inst()), + call_inst_scope_(call_inst->GetDebugScope()) {} + + const Instruction* GetLineOfCallInstruction() { return call_inst_line_; } + const DebugScope& GetScopeOfCallInstruction() { return call_inst_scope_; } + // Puts the DebugInlinedAt chain that is generated for the callee instruction + // whose DebugInlinedAt of DebugScope is |callee_instr_inlined_at| into + // |callee_inlined_at2chain_|. + void SetDebugInlinedAtChain(uint32_t callee_instr_inlined_at, + uint32_t chain_head_id) { + callee_inlined_at2chain_[callee_instr_inlined_at] = chain_head_id; + } + // Gets the DebugInlinedAt chain from |callee_inlined_at2chain_|. + uint32_t GetDebugInlinedAtChain(uint32_t callee_instr_inlined_at) { + auto chain_itr = callee_inlined_at2chain_.find(callee_instr_inlined_at); + if (chain_itr != callee_inlined_at2chain_.end()) return chain_itr->second; + return kNoInlinedAt; + } + + private: + // The line information of the function call instruction that will be + // replaced by the callee function. + const Instruction* call_inst_line_; + + // The scope information of the function call instruction that will be + // replaced by the callee function. + const DebugScope call_inst_scope_; + + // Map from DebugInlinedAt ids of callee to head ids of new generated + // DebugInlinedAt chain. + std::unordered_map<uint32_t, uint32_t> callee_inlined_at2chain_; +}; + // A class for analyzing, managing, and creating OpenCL.DebugInfo.100 extension // instructions. class DebugInfoManager { @@ -74,6 +117,27 @@ class DebugInfoManager { Instruction* CloneDebugInlinedAt(uint32_t clone_inlined_at_id, Instruction* insert_before = nullptr); + // Returns the debug scope corresponding to an inlining instruction in the + // scope |callee_instr_scope| into |inlined_at_ctx|. Generates all new + // debug instructions needed to represent the scope. + DebugScope BuildDebugScope(const DebugScope& callee_instr_scope, + DebugInlinedAtContext* inlined_at_ctx); + + // Returns DebugInlinedAt corresponding to inlining an instruction, which + // was inlined at |callee_inlined_at|, into |inlined_at_ctx|. Generates all + // new debug instructions needed to represent the DebugInlinedAt. + uint32_t BuildDebugInlinedAtChain(uint32_t callee_inlined_at, + DebugInlinedAtContext* inlined_at_ctx); + + // Generates a DebugValue instruction with value |value_id| for every local + // variable that is in the scope of |scope_and_line| and whose memory is + // |variable_id| and inserts it after the instruction |insert_pos|. + void AddDebugValue(Instruction* scope_and_line, uint32_t variable_id, + uint32_t value_id, Instruction* insert_pos); + + // Erases |instr| from data structures of this class. + void ClearDebugInfo(Instruction* instr); + private: IRContext* context() { return context_; } @@ -93,6 +157,30 @@ class DebugInfoManager { // in |inst| must not already be registered. void RegisterDbgFunction(Instruction* inst); + // Register the DebugDeclare instruction |dbg_declare| into + // |var_id_to_dbg_decl_| using OpVariable id |var_id| as a key. + void RegisterDbgDeclare(uint32_t var_id, Instruction* dbg_declare); + + // Returns a DebugExpression instruction without Operation operands. + Instruction* GetEmptyDebugExpression(); + + // Returns the id of Value operand if |inst| is DebugValue who has Deref + // operation and its Value operand is a result id of OpVariable with + // Function storage class. Otherwise, returns 0. + uint32_t GetVariableIdOfDebugValueUsedForDeclare(Instruction* inst); + + // Returns true if a scope |ancestor| is |scope| or an ancestor scope + // of |scope|. + bool IsAncestorOfScope(uint32_t scope, uint32_t ancestor); + + // Returns true if the declaration of a local variable |dbg_declare| + // is visible in the scope of an instruction |instr_scope_id|. + bool IsDeclareVisibleToInstr(Instruction* dbg_declare, + uint32_t instr_scope_id); + + // Returns the parent scope of the scope |child_scope|. + uint32_t GetParentScope(uint32_t child_scope); + IRContext* context_; // Mapping from ids of OpenCL.DebugInfo.100 extension instructions @@ -103,9 +191,18 @@ class DebugInfoManager { // operand is the function. std::unordered_map<uint32_t, Instruction*> fn_id_to_dbg_fn_; + // Mapping from local variable ids to DebugDeclare instructions whose + // operand is the local variable. + std::unordered_map<uint32_t, std::vector<Instruction*>> var_id_to_dbg_decl_; + // DebugInfoNone instruction. We need only a single DebugInfoNone. // To reuse the existing one, we keep it using this member variable. Instruction* debug_info_none_inst_; + + // DebugExpression instruction without Operation operands. We need only + // a single DebugExpression without Operation operands. To reuse the + // existing one, we keep it using this member variable. + Instruction* empty_debug_expr_inst_; }; } // namespace analysis diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/desc_sroa.cpp b/chromium/third_party/SPIRV-Tools/src/source/opt/desc_sroa.cpp index 1f25b33b82f..b68549a0c53 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/desc_sroa.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/desc_sroa.cpp @@ -56,7 +56,23 @@ bool DescriptorScalarReplacement::IsCandidate(Instruction* var) { uint32_t var_type_id = ptr_type_inst->GetSingleWordInOperand(1); Instruction* var_type_inst = context()->get_def_use_mgr()->GetDef(var_type_id); - if (var_type_inst->opcode() != SpvOpTypeArray) { + if (var_type_inst->opcode() != SpvOpTypeArray && + var_type_inst->opcode() != SpvOpTypeStruct) { + return false; + } + + // All structures with descriptor assignments must be replaced by variables, + // one for each of their members - with the exceptions of buffers. + // Buffers are represented as structures, but we shouldn't replace a buffer + // with its elements. All buffers have offset decorations for members of their + // structure types. + bool has_offset_decoration = false; + context()->get_decoration_mgr()->ForEachDecoration( + var_type_inst->result_id(), SpvDecorationOffset, + [&has_offset_decoration](const Instruction&) { + has_offset_decoration = true; + }); + if (has_offset_decoration) { return false; } @@ -84,9 +100,11 @@ bool DescriptorScalarReplacement::IsCandidate(Instruction* var) { } bool DescriptorScalarReplacement::ReplaceCandidate(Instruction* var) { - std::vector<Instruction*> work_list; + std::vector<Instruction*> access_chain_work_list; + std::vector<Instruction*> load_work_list; bool failed = !get_def_use_mgr()->WhileEachUser( - var->result_id(), [this, &work_list](Instruction* use) { + var->result_id(), + [this, &access_chain_work_list, &load_work_list](Instruction* use) { if (use->opcode() == SpvOpName) { return true; } @@ -98,7 +116,10 @@ bool DescriptorScalarReplacement::ReplaceCandidate(Instruction* var) { switch (use->opcode()) { case SpvOpAccessChain: case SpvOpInBoundsAccessChain: - work_list.push_back(use); + access_chain_work_list.push_back(use); + return true; + case SpvOpLoad: + load_work_list.push_back(use); return true; default: context()->EmitErrorMessage( @@ -112,11 +133,16 @@ bool DescriptorScalarReplacement::ReplaceCandidate(Instruction* var) { return false; } - for (Instruction* use : work_list) { + for (Instruction* use : access_chain_work_list) { if (!ReplaceAccessChain(var, use)) { return false; } } + for (Instruction* use : load_work_list) { + if (!ReplaceLoadedValue(var, use)) { + return false; + } + } return true; } @@ -177,21 +203,36 @@ uint32_t DescriptorScalarReplacement::GetReplacementVariable(Instruction* var, uint32_t ptr_type_id = var->type_id(); Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id); assert(ptr_type_inst->opcode() == SpvOpTypePointer && - "Variable should be a pointer to an array."); - uint32_t arr_type_id = ptr_type_inst->GetSingleWordInOperand(1); - Instruction* arr_type_inst = get_def_use_mgr()->GetDef(arr_type_id); - assert(arr_type_inst->opcode() == SpvOpTypeArray && - "Variable should be a pointer to an array."); - - uint32_t array_len_id = arr_type_inst->GetSingleWordInOperand(1); - const analysis::Constant* array_len_const = - context()->get_constant_mgr()->FindDeclaredConstant(array_len_id); - assert(array_len_const != nullptr && "Array length must be a constant."); - uint32_t array_len = array_len_const->GetU32(); - - replacement_vars = replacement_variables_ - .insert({var, std::vector<uint32_t>(array_len, 0)}) - .first; + "Variable should be a pointer to an array or structure."); + uint32_t pointee_type_id = ptr_type_inst->GetSingleWordInOperand(1); + Instruction* pointee_type_inst = get_def_use_mgr()->GetDef(pointee_type_id); + const bool is_array = pointee_type_inst->opcode() == SpvOpTypeArray; + const bool is_struct = pointee_type_inst->opcode() == SpvOpTypeStruct; + assert((is_array || is_struct) && + "Variable should be a pointer to an array or structure."); + + // For arrays, each array element should be replaced with a new replacement + // variable + if (is_array) { + uint32_t array_len_id = pointee_type_inst->GetSingleWordInOperand(1); + const analysis::Constant* array_len_const = + context()->get_constant_mgr()->FindDeclaredConstant(array_len_id); + assert(array_len_const != nullptr && "Array length must be a constant."); + uint32_t array_len = array_len_const->GetU32(); + + replacement_vars = replacement_variables_ + .insert({var, std::vector<uint32_t>(array_len, 0)}) + .first; + } + // For structures, each member should be replaced with a new replacement + // variable + if (is_struct) { + const uint32_t num_members = pointee_type_inst->NumInOperands(); + replacement_vars = + replacement_variables_ + .insert({var, std::vector<uint32_t>(num_members, 0)}) + .first; + } } if (replacement_vars->second[idx] == 0) { @@ -212,12 +253,17 @@ uint32_t DescriptorScalarReplacement::CreateReplacementVariable( uint32_t ptr_type_id = var->type_id(); Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id); assert(ptr_type_inst->opcode() == SpvOpTypePointer && - "Variable should be a pointer to an array."); - uint32_t arr_type_id = ptr_type_inst->GetSingleWordInOperand(1); - Instruction* arr_type_inst = get_def_use_mgr()->GetDef(arr_type_id); - assert(arr_type_inst->opcode() == SpvOpTypeArray && - "Variable should be a pointer to an array."); - uint32_t element_type_id = arr_type_inst->GetSingleWordInOperand(0); + "Variable should be a pointer to an array or structure."); + uint32_t pointee_type_id = ptr_type_inst->GetSingleWordInOperand(1); + Instruction* pointee_type_inst = get_def_use_mgr()->GetDef(pointee_type_id); + const bool is_array = pointee_type_inst->opcode() == SpvOpTypeArray; + const bool is_struct = pointee_type_inst->opcode() == SpvOpTypeStruct; + assert((is_array || is_struct) && + "Variable should be a pointer to an array or structure."); + + uint32_t element_type_id = + is_array ? pointee_type_inst->GetSingleWordInOperand(0) + : pointee_type_inst->GetSingleWordInOperand(idx); uint32_t ptr_element_type_id = context()->get_type_mgr()->FindPointerToType( element_type_id, storage_class); @@ -242,19 +288,42 @@ uint32_t DescriptorScalarReplacement::CreateReplacementVariable( uint32_t decoration = new_decoration->GetSingleWordInOperand(1u); if (decoration == SpvDecorationBinding) { - uint32_t new_binding = new_decoration->GetSingleWordInOperand(2) + idx; + uint32_t new_binding = new_decoration->GetSingleWordInOperand(2); + if (is_array) { + new_binding += idx * GetNumBindingsUsedByType(ptr_element_type_id); + } + if (is_struct) { + // The binding offset that should be added is the sum of binding numbers + // used by previous members of the current struct. + for (uint32_t i = 0; i < idx; ++i) { + new_binding += GetNumBindingsUsedByType( + pointee_type_inst->GetSingleWordInOperand(i)); + } + } new_decoration->SetInOperand(2, {new_binding}); } context()->AddAnnotationInst(std::move(new_decoration)); } // Create a new OpName for the replacement variable. + std::vector<std::unique_ptr<Instruction>> names_to_add; for (auto p : context()->GetNames(var->result_id())) { Instruction* name_inst = p.second; std::string name_str = utils::MakeString(name_inst->GetOperand(1).words); - name_str += "["; - name_str += utils::ToString(idx); - name_str += "]"; + if (is_array) { + name_str += "[" + utils::ToString(idx) + "]"; + } + if (is_struct) { + Instruction* member_name_inst = + context()->GetMemberName(pointee_type_inst->result_id(), idx); + name_str += "."; + if (member_name_inst) + name_str += utils::MakeString(member_name_inst->GetOperand(2).words); + else + // In case the member does not have a name assigned to it, use the + // member index. + name_str += utils::ToString(idx); + } std::unique_ptr<Instruction> new_name(new Instruction( context(), SpvOpName, 0, 0, @@ -262,12 +331,118 @@ uint32_t DescriptorScalarReplacement::CreateReplacementVariable( {SPV_OPERAND_TYPE_ID, {id}}, {SPV_OPERAND_TYPE_LITERAL_STRING, utils::MakeVector(name_str)}})); Instruction* new_name_inst = new_name.get(); - context()->AddDebug2Inst(std::move(new_name)); get_def_use_mgr()->AnalyzeInstDefUse(new_name_inst); + names_to_add.push_back(std::move(new_name)); } + // We shouldn't add the new names when we are iterating over name ranges + // above. We can add all the new names now. + for (auto& new_name : names_to_add) + context()->AddDebug2Inst(std::move(new_name)); + return id; } +uint32_t DescriptorScalarReplacement::GetNumBindingsUsedByType( + uint32_t type_id) { + Instruction* type_inst = get_def_use_mgr()->GetDef(type_id); + + // If it's a pointer, look at the underlying type. + if (type_inst->opcode() == SpvOpTypePointer) { + type_id = type_inst->GetSingleWordInOperand(1); + type_inst = get_def_use_mgr()->GetDef(type_id); + } + + // Arrays consume N*M binding numbers where N is the array length, and M is + // the number of bindings used by each array element. + if (type_inst->opcode() == SpvOpTypeArray) { + uint32_t element_type_id = type_inst->GetSingleWordInOperand(0); + uint32_t length_id = type_inst->GetSingleWordInOperand(1); + const analysis::Constant* length_const = + context()->get_constant_mgr()->FindDeclaredConstant(length_id); + // OpTypeArray's length must always be a constant + assert(length_const != nullptr); + uint32_t num_elems = length_const->GetU32(); + return num_elems * GetNumBindingsUsedByType(element_type_id); + } + + // The number of bindings consumed by a structure is the sum of the bindings + // used by its members. + if (type_inst->opcode() == SpvOpTypeStruct) { + uint32_t sum = 0; + for (uint32_t i = 0; i < type_inst->NumInOperands(); i++) + sum += GetNumBindingsUsedByType(type_inst->GetSingleWordInOperand(i)); + return sum; + } + + // All other types are considered to take up 1 binding number. + return 1; +} + +bool DescriptorScalarReplacement::ReplaceLoadedValue(Instruction* var, + Instruction* value) { + // |var| is the global variable that has to be eliminated (OpVariable). + // |value| is the OpLoad instruction that has loaded |var|. + // The function expects all users of |value| to be OpCompositeExtract + // instructions. Otherwise the function returns false with an error message. + assert(value->opcode() == SpvOpLoad); + assert(value->GetSingleWordInOperand(0) == var->result_id()); + std::vector<Instruction*> work_list; + bool failed = !get_def_use_mgr()->WhileEachUser( + value->result_id(), [this, &work_list](Instruction* use) { + if (use->opcode() != SpvOpCompositeExtract) { + context()->EmitErrorMessage( + "Variable cannot be replaced: invalid instruction", use); + return false; + } + work_list.push_back(use); + return true; + }); + + if (failed) { + return false; + } + + for (Instruction* use : work_list) { + if (!ReplaceCompositeExtract(var, use)) { + return false; + } + } + + // All usages of the loaded value have been killed. We can kill the OpLoad. + context()->KillInst(value); + return true; +} + +bool DescriptorScalarReplacement::ReplaceCompositeExtract( + Instruction* var, Instruction* extract) { + assert(extract->opcode() == SpvOpCompositeExtract); + // We're currently only supporting extractions of one index at a time. If we + // need to, we can handle cases with multiple indexes in the future. + if (extract->NumInOperands() != 2) { + context()->EmitErrorMessage( + "Variable cannot be replaced: invalid instruction", extract); + return false; + } + + uint32_t replacement_var = + GetReplacementVariable(var, extract->GetSingleWordInOperand(1)); + + // The result type of the OpLoad is the same as the result type of the + // OpCompositeExtract. + uint32_t load_id = TakeNextId(); + std::unique_ptr<Instruction> load( + new Instruction(context(), SpvOpLoad, extract->type_id(), load_id, + std::initializer_list<Operand>{ + {SPV_OPERAND_TYPE_ID, {replacement_var}}})); + Instruction* load_instr = load.get(); + get_def_use_mgr()->AnalyzeInstDefUse(load_instr); + context()->set_instr_block(load_instr, context()->get_instr_block(extract)); + extract->InsertBefore(std::move(load)); + context()->ReplaceAllUsesWith(extract->result_id(), load_id); + context()->KillInst(extract); + return true; +} + } // namespace opt } // namespace spvtools diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/desc_sroa.h b/chromium/third_party/SPIRV-Tools/src/source/opt/desc_sroa.h index a95c6b582c9..c3aa0ea2b1f 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/desc_sroa.h +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/desc_sroa.h @@ -61,6 +61,20 @@ class DescriptorScalarReplacement : public Pass { // |true| if successful. bool ReplaceAccessChain(Instruction* var, Instruction* use); + // Replaces the given compososite variable |var| loaded by OpLoad |value| with + // replacement variables, one for each component that's accessed in the + // shader. Assumes that |value| is only used by OpCompositeExtract + // instructions, one index at a time. Returns true on success, and false + // otherwise. + bool ReplaceLoadedValue(Instruction* var, Instruction* value); + + // Replaces the given OpCompositeExtract |extract| and all of its references + // with an OpLoad of a replacement variable. |var| is the variable with + // composite type whose value is being used by |extract|. Assumes that + // |extract| is extracting one index only. Returns true on success, and false + // otherwise. + bool ReplaceCompositeExtract(Instruction* var, Instruction* extract); + // Returns the id of the variable that will be used to replace the |idx|th // element of |var|. The variable is created if it has not already been // created. @@ -70,6 +84,15 @@ class DescriptorScalarReplacement : public Pass { // element of |var|. uint32_t CreateReplacementVariable(Instruction* var, uint32_t idx); + // Returns the number of bindings used by the given |type_id|. + // All types are considered to use 1 binding slot, except: + // 1- A pointer type consumes as many binding numbers as its pointee. + // 2- An array of size N consumes N*M binding numbers, where M is the number + // of bindings used by each array element. + // 3- The number of bindings consumed by a structure is the sum of the + // bindings used by its members. + uint32_t GetNumBindingsUsedByType(uint32_t type_id); + // A map from an OpVariable instruction to the set of variables that will be // used to replace it. The entry |replacement_variables_[var][i]| is the id of // a variable that will be used in the place of the the ith element of the diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/function.cpp b/chromium/third_party/SPIRV-Tools/src/source/opt/function.cpp index 2b7b4fb809e..320f8cabf67 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/function.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/function.cpp @@ -154,6 +154,18 @@ void Function::ForEachParam(const std::function<void(const Instruction*)>& f, ->ForEachInst(f, run_on_debug_line_insts); } +void Function::ForEachDebugInstructionsInHeader( + const std::function<void(Instruction*)>& f) { + if (debug_insts_in_header_.empty()) return; + + Instruction* di = &debug_insts_in_header_.front(); + while (di != nullptr) { + Instruction* next_instruction = di->NextNode(); + di->ForEachInst(f); + di = next_instruction; + } +} + BasicBlock* Function::InsertBasicBlockAfter( std::unique_ptr<BasicBlock>&& new_block, BasicBlock* position) { for (auto bb_iter = begin(); bb_iter != end(); ++bb_iter) { diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/function.h b/chromium/third_party/SPIRV-Tools/src/source/opt/function.h index e68a1d0a961..f5035f08b38 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/function.h +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/function.h @@ -72,6 +72,10 @@ class Function { // Delete all basic blocks that contain no instructions. inline void RemoveEmptyBlocks(); + // Removes a parameter from the function with result id equal to |id|. + // Does nothing if the function doesn't have such a parameter. + inline void RemoveParameter(uint32_t id); + // Saves the given function end instruction. inline void SetFunctionEnd(std::unique_ptr<Instruction> end_inst); @@ -133,6 +137,11 @@ class Function { void ForEachParam(const std::function<void(Instruction*)>& f, bool run_on_debug_line_insts = false); + // Runs the given function |f| on each debug instruction in this function's + // header in order. + void ForEachDebugInstructionsInHeader( + const std::function<void(Instruction*)>& f); + BasicBlock* InsertBasicBlockAfter(std::unique_ptr<BasicBlock>&& new_block, BasicBlock* position); @@ -214,6 +223,14 @@ inline void Function::RemoveEmptyBlocks() { blocks_.erase(first_empty, std::end(blocks_)); } +inline void Function::RemoveParameter(uint32_t id) { + params_.erase(std::remove_if(params_.begin(), params_.end(), + [id](const std::unique_ptr<Instruction>& param) { + return param->result_id() == id; + }), + params_.end()); +} + inline void Function::SetFunctionEnd(std::unique_ptr<Instruction> end_inst) { end_inst_ = std::move(end_inst); } diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/inline_pass.cpp b/chromium/third_party/SPIRV-Tools/src/source/opt/inline_pass.cpp index 3c874a7ef00..cb5a1265ec6 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/inline_pass.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/inline_pass.cpp @@ -20,6 +20,7 @@ #include <utility> #include "source/cfa.h" +#include "source/opt/reflect.h" #include "source/util/make_unique.h" // Indices of operands in SPIR-V instructions @@ -83,19 +84,31 @@ void InlinePass::AddLoopMerge(uint32_t merge_id, uint32_t continue_id, } void InlinePass::AddStore(uint32_t ptr_id, uint32_t val_id, - std::unique_ptr<BasicBlock>* block_ptr) { + std::unique_ptr<BasicBlock>* block_ptr, + const Instruction* line_inst, + const DebugScope& dbg_scope) { std::unique_ptr<Instruction> newStore( new Instruction(context(), SpvOpStore, 0, 0, {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ptr_id}}, {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {val_id}}})); + if (line_inst != nullptr) { + newStore->dbg_line_insts().push_back(*line_inst); + } + newStore->SetDebugScope(dbg_scope); (*block_ptr)->AddInstruction(std::move(newStore)); } void InlinePass::AddLoad(uint32_t type_id, uint32_t resultId, uint32_t ptr_id, - std::unique_ptr<BasicBlock>* block_ptr) { + std::unique_ptr<BasicBlock>* block_ptr, + const Instruction* line_inst, + const DebugScope& dbg_scope) { std::unique_ptr<Instruction> newLoad( new Instruction(context(), SpvOpLoad, type_id, resultId, {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ptr_id}}})); + if (line_inst != nullptr) { + newLoad->dbg_line_insts().push_back(*line_inst); + } + newLoad->SetDebugScope(dbg_scope); (*block_ptr)->AddInstruction(std::move(newLoad)); } @@ -140,10 +153,18 @@ void InlinePass::MapParams( bool InlinePass::CloneAndMapLocals( Function* calleeFn, std::vector<std::unique_ptr<Instruction>>* new_vars, - std::unordered_map<uint32_t, uint32_t>* callee2caller) { + std::unordered_map<uint32_t, uint32_t>* callee2caller, + analysis::DebugInlinedAtContext* inlined_at_ctx) { auto callee_block_itr = calleeFn->begin(); auto callee_var_itr = callee_block_itr->begin(); - while (callee_var_itr->opcode() == SpvOp::SpvOpVariable) { + while (callee_var_itr->opcode() == SpvOp::SpvOpVariable || + callee_var_itr->GetOpenCL100DebugOpcode() == + OpenCLDebugInfo100DebugDeclare) { + if (callee_var_itr->opcode() != SpvOp::SpvOpVariable) { + ++callee_var_itr; + continue; + } + std::unique_ptr<Instruction> var_inst(callee_var_itr->Clone(context())); uint32_t newId = context()->TakeNextId(); if (newId == 0) { @@ -151,6 +172,9 @@ bool InlinePass::CloneAndMapLocals( } get_decoration_mgr()->CloneDecorations(callee_var_itr->result_id(), newId); var_inst->SetResultId(newId); + var_inst->UpdateDebugInlinedAt( + context()->get_debug_info_mgr()->BuildDebugInlinedAtChain( + callee_var_itr->GetDebugInlinedAt(), inlined_at_ctx)); (*callee2caller)[callee_var_itr->result_id()] = newId; new_vars->push_back(std::move(var_inst)); ++callee_var_itr; @@ -232,6 +256,248 @@ bool InlinePass::CloneSameBlockOps( }); } +void InlinePass::MoveInstsBeforeEntryBlock( + std::unordered_map<uint32_t, Instruction*>* preCallSB, + BasicBlock* new_blk_ptr, BasicBlock::iterator call_inst_itr, + UptrVectorIterator<BasicBlock> call_block_itr) { + for (auto cii = call_block_itr->begin(); cii != call_inst_itr; + cii = call_block_itr->begin()) { + Instruction* inst = &*cii; + inst->RemoveFromList(); + std::unique_ptr<Instruction> cp_inst(inst); + // Remember same-block ops for possible regeneration. + if (IsSameBlockOp(&*cp_inst)) { + auto* sb_inst_ptr = cp_inst.get(); + (*preCallSB)[cp_inst->result_id()] = sb_inst_ptr; + } + new_blk_ptr->AddInstruction(std::move(cp_inst)); + } +} + +std::unique_ptr<BasicBlock> InlinePass::AddGuardBlock( + std::vector<std::unique_ptr<BasicBlock>>* new_blocks, + std::unordered_map<uint32_t, uint32_t>* callee2caller, + std::unique_ptr<BasicBlock> new_blk_ptr, uint32_t entry_blk_label_id) { + const auto guard_block_id = context()->TakeNextId(); + if (guard_block_id == 0) { + return nullptr; + } + AddBranch(guard_block_id, &new_blk_ptr); + new_blocks->push_back(std::move(new_blk_ptr)); + // Start the next block. + new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(guard_block_id)); + // Reset the mapping of the callee's entry block to point to + // the guard block. Do this so we can fix up phis later on to + // satisfy dominance. + (*callee2caller)[entry_blk_label_id] = guard_block_id; + return new_blk_ptr; +} + +InstructionList::iterator InlinePass::AddStoresForVariableInitializers( + const std::unordered_map<uint32_t, uint32_t>& callee2caller, + analysis::DebugInlinedAtContext* inlined_at_ctx, + std::unique_ptr<BasicBlock>* new_blk_ptr, + UptrVectorIterator<BasicBlock> callee_first_block_itr) { + auto callee_itr = callee_first_block_itr->begin(); + while (callee_itr->opcode() == SpvOp::SpvOpVariable || + callee_itr->GetOpenCL100DebugOpcode() == + OpenCLDebugInfo100DebugDeclare) { + if (callee_itr->opcode() == SpvOp::SpvOpVariable && + callee_itr->NumInOperands() == 2) { + assert(callee2caller.count(callee_itr->result_id()) && + "Expected the variable to have already been mapped."); + uint32_t new_var_id = callee2caller.at(callee_itr->result_id()); + + // The initializer must be a constant or global value. No mapped + // should be used. + uint32_t val_id = callee_itr->GetSingleWordInOperand(1); + AddStore(new_var_id, val_id, new_blk_ptr, callee_itr->dbg_line_inst(), + context()->get_debug_info_mgr()->BuildDebugScope( + callee_itr->GetDebugScope(), inlined_at_ctx)); + } + if (callee_itr->GetOpenCL100DebugOpcode() == + OpenCLDebugInfo100DebugDeclare) { + InlineSingleInstruction( + callee2caller, new_blk_ptr->get(), &*callee_itr, + context()->get_debug_info_mgr()->BuildDebugInlinedAtChain( + callee_itr->GetDebugScope().GetInlinedAt(), inlined_at_ctx)); + } + ++callee_itr; + } + return callee_itr; +} + +bool InlinePass::InlineSingleInstruction( + const std::unordered_map<uint32_t, uint32_t>& callee2caller, + BasicBlock* new_blk_ptr, const Instruction* inst, uint32_t dbg_inlined_at) { + // If we have return, it must be at the end of the callee. We will handle + // it at the end. + if (inst->opcode() == SpvOpReturnValue || inst->opcode() == SpvOpReturn) + return true; + + // Copy callee instruction and remap all input Ids. + std::unique_ptr<Instruction> cp_inst(inst->Clone(context())); + cp_inst->ForEachInId([&callee2caller](uint32_t* iid) { + const auto mapItr = callee2caller.find(*iid); + if (mapItr != callee2caller.end()) { + *iid = mapItr->second; + } + }); + + // If result id is non-zero, remap it. + const uint32_t rid = cp_inst->result_id(); + if (rid != 0) { + const auto mapItr = callee2caller.find(rid); + if (mapItr == callee2caller.end()) { + return false; + } + uint32_t nid = mapItr->second; + cp_inst->SetResultId(nid); + get_decoration_mgr()->CloneDecorations(rid, nid); + } + + cp_inst->UpdateDebugInlinedAt(dbg_inlined_at); + new_blk_ptr->AddInstruction(std::move(cp_inst)); + return true; +} + +std::unique_ptr<BasicBlock> InlinePass::InlineReturn( + const std::unordered_map<uint32_t, uint32_t>& callee2caller, + std::vector<std::unique_ptr<BasicBlock>>* new_blocks, + std::unique_ptr<BasicBlock> new_blk_ptr, + analysis::DebugInlinedAtContext* inlined_at_ctx, Function* calleeFn, + const Instruction* inst, uint32_t returnVarId) { + // Store return value to return variable. + if (inst->opcode() == SpvOpReturnValue) { + assert(returnVarId != 0); + uint32_t valId = inst->GetInOperand(kSpvReturnValueId).words[0]; + const auto mapItr = callee2caller.find(valId); + if (mapItr != callee2caller.end()) { + valId = mapItr->second; + } + AddStore(returnVarId, valId, &new_blk_ptr, inst->dbg_line_inst(), + context()->get_debug_info_mgr()->BuildDebugScope( + inst->GetDebugScope(), inlined_at_ctx)); + } + + uint32_t returnLabelId = 0; + for (auto callee_block_itr = calleeFn->begin(); + callee_block_itr != calleeFn->end(); ++callee_block_itr) { + if (callee_block_itr->tail()->opcode() == SpvOpUnreachable || + callee_block_itr->tail()->opcode() == SpvOpKill) { + returnLabelId = context()->TakeNextId(); + break; + } + } + if (returnLabelId == 0) return new_blk_ptr; + + if (inst->opcode() == SpvOpReturn || inst->opcode() == SpvOpReturnValue) + AddBranch(returnLabelId, &new_blk_ptr); + new_blocks->push_back(std::move(new_blk_ptr)); + return MakeUnique<BasicBlock>(NewLabel(returnLabelId)); +} + +bool InlinePass::InlineEntryBlock( + const std::unordered_map<uint32_t, uint32_t>& callee2caller, + std::unique_ptr<BasicBlock>* new_blk_ptr, + UptrVectorIterator<BasicBlock> callee_first_block, + analysis::DebugInlinedAtContext* inlined_at_ctx) { + auto callee_inst_itr = AddStoresForVariableInitializers( + callee2caller, inlined_at_ctx, new_blk_ptr, callee_first_block); + + while (callee_inst_itr != callee_first_block->end()) { + if (!InlineSingleInstruction( + callee2caller, new_blk_ptr->get(), &*callee_inst_itr, + context()->get_debug_info_mgr()->BuildDebugInlinedAtChain( + callee_inst_itr->GetDebugScope().GetInlinedAt(), + inlined_at_ctx))) { + return false; + } + ++callee_inst_itr; + } + return true; +} + +std::unique_ptr<BasicBlock> InlinePass::InlineBasicBlocks( + std::vector<std::unique_ptr<BasicBlock>>* new_blocks, + const std::unordered_map<uint32_t, uint32_t>& callee2caller, + std::unique_ptr<BasicBlock> new_blk_ptr, + analysis::DebugInlinedAtContext* inlined_at_ctx, Function* calleeFn) { + auto callee_block_itr = calleeFn->begin(); + ++callee_block_itr; + + while (callee_block_itr != calleeFn->end()) { + new_blocks->push_back(std::move(new_blk_ptr)); + const auto mapItr = + callee2caller.find(callee_block_itr->GetLabelInst()->result_id()); + if (mapItr == callee2caller.end()) return nullptr; + new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(mapItr->second)); + + auto tail_inst_itr = callee_block_itr->end(); + for (auto inst_itr = callee_block_itr->begin(); inst_itr != tail_inst_itr; + ++inst_itr) { + if (!InlineSingleInstruction( + callee2caller, new_blk_ptr.get(), &*inst_itr, + context()->get_debug_info_mgr()->BuildDebugInlinedAtChain( + inst_itr->GetDebugScope().GetInlinedAt(), inlined_at_ctx))) { + return nullptr; + } + } + + ++callee_block_itr; + } + return new_blk_ptr; +} + +bool InlinePass::MoveCallerInstsAfterFunctionCall( + std::unordered_map<uint32_t, Instruction*>* preCallSB, + std::unordered_map<uint32_t, uint32_t>* postCallSB, + std::unique_ptr<BasicBlock>* new_blk_ptr, + BasicBlock::iterator call_inst_itr, bool multiBlocks) { + // Copy remaining instructions from caller block. + for (Instruction* inst = call_inst_itr->NextNode(); inst; + inst = call_inst_itr->NextNode()) { + inst->RemoveFromList(); + std::unique_ptr<Instruction> cp_inst(inst); + // If multiple blocks generated, regenerate any same-block + // instruction that has not been seen in this last block. + if (multiBlocks) { + if (!CloneSameBlockOps(&cp_inst, postCallSB, preCallSB, new_blk_ptr)) { + return false; + } + + // Remember same-block ops in this block. + if (IsSameBlockOp(&*cp_inst)) { + const uint32_t rid = cp_inst->result_id(); + (*postCallSB)[rid] = rid; + } + } + new_blk_ptr->get()->AddInstruction(std::move(cp_inst)); + } + + return true; +} + +void InlinePass::MoveLoopMergeInstToFirstBlock( + std::vector<std::unique_ptr<BasicBlock>>* new_blocks) { + // Move the OpLoopMerge from the last block back to the first, where + // it belongs. + auto& first = new_blocks->front(); + auto& last = new_blocks->back(); + assert(first != last); + + // Insert a modified copy of the loop merge into the first block. + auto loop_merge_itr = last->tail(); + --loop_merge_itr; + assert(loop_merge_itr->opcode() == SpvOpLoopMerge); + std::unique_ptr<Instruction> cp_inst(loop_merge_itr->Clone(context())); + first->tail().InsertBefore(std::move(cp_inst)); + + // Remove the loop merge from the last block. + loop_merge_itr->RemoveFromList(); + delete &*loop_merge_itr; +} + bool InlinePass::GenInlineCode( std::vector<std::unique_ptr<BasicBlock>>* new_blocks, std::vector<std::unique_ptr<Instruction>>* new_vars, @@ -245,27 +511,60 @@ bool InlinePass::GenInlineCode( // Post-call same-block op ids std::unordered_map<uint32_t, uint32_t> postCallSB; + analysis::DebugInlinedAtContext inlined_at_ctx(&*call_inst_itr); + // Invalidate the def-use chains. They are not kept up to date while // inlining. However, certain calls try to keep them up-to-date if they are // valid. These operations can fail. context()->InvalidateAnalyses(IRContext::kAnalysisDefUse); + // If the caller is a loop header and the callee has multiple blocks, then the + // normal inlining logic will place the OpLoopMerge in the last of several + // blocks in the loop. Instead, it should be placed at the end of the first + // block. We'll wait to move the OpLoopMerge until the end of the regular + // inlining logic, and only if necessary. + bool caller_is_loop_header = call_block_itr->GetLoopMergeInst() != nullptr; + + // Single-trip loop continue block + std::unique_ptr<BasicBlock> single_trip_loop_cont_blk; + Function* calleeFn = id2function_[call_inst_itr->GetSingleWordOperand( kSpvFunctionCallFunctionId)]; - // Check for multiple returns in the callee. - auto fi = early_return_funcs_.find(calleeFn->result_id()); - const bool earlyReturn = fi != early_return_funcs_.end(); - // Map parameters to actual arguments. MapParams(calleeFn, call_inst_itr, &callee2caller); // Define caller local variables for all callee variables and create map to // them. - if (!CloneAndMapLocals(calleeFn, new_vars, &callee2caller)) { + if (!CloneAndMapLocals(calleeFn, new_vars, &callee2caller, &inlined_at_ctx)) { return false; } + // First block needs to use label of original block + // but map callee label in case of phi reference. + uint32_t entry_blk_label_id = calleeFn->begin()->GetLabelInst()->result_id(); + callee2caller[entry_blk_label_id] = call_block_itr->id(); + std::unique_ptr<BasicBlock> new_blk_ptr = + MakeUnique<BasicBlock>(NewLabel(call_block_itr->id())); + + // Move instructions of original caller block up to call instruction. + MoveInstsBeforeEntryBlock(&preCallSB, new_blk_ptr.get(), call_inst_itr, + call_block_itr); + + if (caller_is_loop_header && + (*(calleeFn->begin())).GetMergeInst() != nullptr) { + // We can't place both the caller's merge instruction and + // another merge instruction in the same block. So split the + // calling block. Insert an unconditional branch to a new guard + // block. Later, once we know the ID of the last block, we + // will move the caller's OpLoopMerge from the last generated + // block into the first block. We also wait to avoid + // invalidating various iterators. + new_blk_ptr = AddGuardBlock(new_blocks, &callee2caller, + std::move(new_blk_ptr), entry_blk_label_id); + if (new_blk_ptr == nullptr) return false; + } + // Create return var if needed. const uint32_t calleeTypeId = calleeFn->type_id(); uint32_t returnVarId = 0; @@ -277,341 +576,62 @@ bool InlinePass::GenInlineCode( } } - // Create set of callee result ids. Used to detect forward references - std::unordered_set<uint32_t> callee_result_ids; - calleeFn->ForEachInst([&callee_result_ids](const Instruction* cpi) { + calleeFn->WhileEachInst([&callee2caller, this](const Instruction* cpi) { + // Create set of callee result ids. Used to detect forward references const uint32_t rid = cpi->result_id(); - if (rid != 0) callee_result_ids.insert(rid); + if (rid != 0 && callee2caller.find(rid) == callee2caller.end()) { + const uint32_t nid = context()->TakeNextId(); + if (nid == 0) return false; + callee2caller[rid] = nid; + } + return true; }); - // If the caller is a loop header and the callee has multiple blocks, then the - // normal inlining logic will place the OpLoopMerge in the last of several - // blocks in the loop. Instead, it should be placed at the end of the first - // block. We'll wait to move the OpLoopMerge until the end of the regular - // inlining logic, and only if necessary. - bool caller_is_loop_header = false; - if (call_block_itr->GetLoopMergeInst()) { - caller_is_loop_header = true; - } - - bool callee_begins_with_structured_header = - (*(calleeFn->begin())).GetMergeInst() != nullptr; - - // Clone and map callee code. Copy caller block code to beginning of - // first block and end of last block. - bool prevInstWasReturn = false; - uint32_t singleTripLoopHeaderId = 0; - uint32_t singleTripLoopContinueId = 0; - uint32_t returnLabelId = 0; - bool multiBlocks = false; - // new_blk_ptr is a new basic block in the caller. New instructions are - // written to it. It is created when we encounter the OpLabel - // of the first callee block. It is appended to new_blocks only when - // it is complete. - std::unique_ptr<BasicBlock> new_blk_ptr; - bool successful = calleeFn->WhileEachInst( - [&new_blocks, &callee2caller, &call_block_itr, &call_inst_itr, - &new_blk_ptr, &prevInstWasReturn, &returnLabelId, &returnVarId, - caller_is_loop_header, callee_begins_with_structured_header, - &calleeTypeId, &multiBlocks, &postCallSB, &preCallSB, earlyReturn, - &singleTripLoopHeaderId, &singleTripLoopContinueId, &callee_result_ids, - this](const Instruction* cpi) { - switch (cpi->opcode()) { - case SpvOpFunction: - case SpvOpFunctionParameter: - // Already processed - break; - case SpvOpVariable: - if (cpi->NumInOperands() == 2) { - assert(callee2caller.count(cpi->result_id()) && - "Expected the variable to have already been mapped."); - uint32_t new_var_id = callee2caller.at(cpi->result_id()); - - // The initializer must be a constant or global value. No mapped - // should be used. - uint32_t val_id = cpi->GetSingleWordInOperand(1); - AddStore(new_var_id, val_id, &new_blk_ptr); - } - break; - case SpvOpUnreachable: - case SpvOpKill: { - // Generate a return label so that we split the block with the - // function call. Copy the terminator into the new block. - if (returnLabelId == 0) { - returnLabelId = context()->TakeNextId(); - if (returnLabelId == 0) { - return false; - } - } - std::unique_ptr<Instruction> terminator( - new Instruction(context(), cpi->opcode(), 0, 0, {})); - new_blk_ptr->AddInstruction(std::move(terminator)); - break; - } - case SpvOpLabel: { - // If previous instruction was early return, insert branch - // instruction to return block. - if (prevInstWasReturn) { - if (returnLabelId == 0) { - returnLabelId = context()->TakeNextId(); - if (returnLabelId == 0) { - return false; - } - } - AddBranch(returnLabelId, &new_blk_ptr); - prevInstWasReturn = false; - } - // Finish current block (if it exists) and get label for next block. - uint32_t labelId; - bool firstBlock = false; - if (new_blk_ptr != nullptr) { - new_blocks->push_back(std::move(new_blk_ptr)); - // If result id is already mapped, use it, otherwise get a new - // one. - const uint32_t rid = cpi->result_id(); - const auto mapItr = callee2caller.find(rid); - labelId = (mapItr != callee2caller.end()) - ? mapItr->second - : context()->TakeNextId(); - if (labelId == 0) { - return false; - } - } else { - // First block needs to use label of original block - // but map callee label in case of phi reference. - labelId = call_block_itr->id(); - callee2caller[cpi->result_id()] = labelId; - firstBlock = true; - } - // Create first/next block. - new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(labelId)); - if (firstBlock) { - // Copy contents of original caller block up to call instruction. - for (auto cii = call_block_itr->begin(); cii != call_inst_itr; - cii = call_block_itr->begin()) { - Instruction* inst = &*cii; - inst->RemoveFromList(); - std::unique_ptr<Instruction> cp_inst(inst); - // Remember same-block ops for possible regeneration. - if (IsSameBlockOp(&*cp_inst)) { - auto* sb_inst_ptr = cp_inst.get(); - preCallSB[cp_inst->result_id()] = sb_inst_ptr; - } - new_blk_ptr->AddInstruction(std::move(cp_inst)); - } - if (caller_is_loop_header && - callee_begins_with_structured_header) { - // We can't place both the caller's merge instruction and - // another merge instruction in the same block. So split the - // calling block. Insert an unconditional branch to a new guard - // block. Later, once we know the ID of the last block, we - // will move the caller's OpLoopMerge from the last generated - // block into the first block. We also wait to avoid - // invalidating various iterators. - const auto guard_block_id = context()->TakeNextId(); - if (guard_block_id == 0) { - return false; - } - AddBranch(guard_block_id, &new_blk_ptr); - new_blocks->push_back(std::move(new_blk_ptr)); - // Start the next block. - new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(guard_block_id)); - // Reset the mapping of the callee's entry block to point to - // the guard block. Do this so we can fix up phis later on to - // satisfy dominance. - callee2caller[cpi->result_id()] = guard_block_id; - } - // If callee has early return, insert a header block for - // single-trip loop that will encompass callee code. Start - // postheader block. - // - // Note: Consider the following combination: - // - the caller is a single block loop - // - the callee does not begin with a structure header - // - the callee has multiple returns. - // We still need to split the caller block and insert a guard - // block. But we only need to do it once. We haven't done it yet, - // but the single-trip loop header will serve the same purpose. - if (earlyReturn) { - singleTripLoopHeaderId = context()->TakeNextId(); - if (singleTripLoopHeaderId == 0) { - return false; - } - AddBranch(singleTripLoopHeaderId, &new_blk_ptr); - new_blocks->push_back(std::move(new_blk_ptr)); - new_blk_ptr = - MakeUnique<BasicBlock>(NewLabel(singleTripLoopHeaderId)); - returnLabelId = context()->TakeNextId(); - singleTripLoopContinueId = context()->TakeNextId(); - if (returnLabelId == 0 || singleTripLoopContinueId == 0) { - return false; - } - AddLoopMerge(returnLabelId, singleTripLoopContinueId, - &new_blk_ptr); - uint32_t postHeaderId = context()->TakeNextId(); - if (postHeaderId == 0) { - return false; - } - AddBranch(postHeaderId, &new_blk_ptr); - new_blocks->push_back(std::move(new_blk_ptr)); - new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(postHeaderId)); - multiBlocks = true; - // Reset the mapping of the callee's entry block to point to - // the post-header block. Do this so we can fix up phis later - // on to satisfy dominance. - callee2caller[cpi->result_id()] = postHeaderId; - } - } else { - multiBlocks = true; - } - } break; - case SpvOpReturnValue: { - // Store return value to return variable. - assert(returnVarId != 0); - uint32_t valId = cpi->GetInOperand(kSpvReturnValueId).words[0]; - const auto mapItr = callee2caller.find(valId); - if (mapItr != callee2caller.end()) { - valId = mapItr->second; - } - AddStore(returnVarId, valId, &new_blk_ptr); - - // Remember we saw a return; if followed by a label, will need to - // insert branch. - prevInstWasReturn = true; - } break; - case SpvOpReturn: { - // Remember we saw a return; if followed by a label, will need to - // insert branch. - prevInstWasReturn = true; - } break; - case SpvOpFunctionEnd: { - // If there was an early return, we generated a return label id - // for it. Now we have to generate the return block with that Id. - if (returnLabelId != 0) { - // If previous instruction was return, insert branch instruction - // to return block. - if (prevInstWasReturn) AddBranch(returnLabelId, &new_blk_ptr); - if (earlyReturn) { - // If we generated a loop header for the single-trip loop - // to accommodate early returns, insert the continue - // target block now, with a false branch back to the loop - // header. - new_blocks->push_back(std::move(new_blk_ptr)); - new_blk_ptr = - MakeUnique<BasicBlock>(NewLabel(singleTripLoopContinueId)); - uint32_t false_id = GetFalseId(); - if (false_id == 0) { - return false; - } - AddBranchCond(false_id, singleTripLoopHeaderId, returnLabelId, - &new_blk_ptr); - } - // Generate the return block. - new_blocks->push_back(std::move(new_blk_ptr)); - new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(returnLabelId)); - multiBlocks = true; - } - // Load return value into result id of call, if it exists. - if (returnVarId != 0) { - const uint32_t resId = call_inst_itr->result_id(); - assert(resId != 0); - AddLoad(calleeTypeId, resId, returnVarId, &new_blk_ptr); - } - // Copy remaining instructions from caller block. - for (Instruction* inst = call_inst_itr->NextNode(); inst; - inst = call_inst_itr->NextNode()) { - inst->RemoveFromList(); - std::unique_ptr<Instruction> cp_inst(inst); - // If multiple blocks generated, regenerate any same-block - // instruction that has not been seen in this last block. - if (multiBlocks) { - if (!CloneSameBlockOps(&cp_inst, &postCallSB, &preCallSB, - &new_blk_ptr)) { - return false; - } - - // Remember same-block ops in this block. - if (IsSameBlockOp(&*cp_inst)) { - const uint32_t rid = cp_inst->result_id(); - postCallSB[rid] = rid; - } - } - new_blk_ptr->AddInstruction(std::move(cp_inst)); - } - // Finalize inline code. - new_blocks->push_back(std::move(new_blk_ptr)); - } break; - default: { - // Copy callee instruction and remap all input Ids. - std::unique_ptr<Instruction> cp_inst(cpi->Clone(context())); - bool succeeded = cp_inst->WhileEachInId( - [&callee2caller, &callee_result_ids, this](uint32_t* iid) { - const auto mapItr = callee2caller.find(*iid); - if (mapItr != callee2caller.end()) { - *iid = mapItr->second; - } else if (callee_result_ids.find(*iid) != - callee_result_ids.end()) { - // Forward reference. Allocate a new id, map it, - // use it and check for it when remapping result ids - const uint32_t nid = context()->TakeNextId(); - if (nid == 0) { - return false; - } - callee2caller[*iid] = nid; - *iid = nid; - } - return true; - }); - if (!succeeded) { - return false; - } - // If result id is non-zero, remap it. If already mapped, use mapped - // value, else use next id. - const uint32_t rid = cp_inst->result_id(); - if (rid != 0) { - const auto mapItr = callee2caller.find(rid); - uint32_t nid; - if (mapItr != callee2caller.end()) { - nid = mapItr->second; - } else { - nid = context()->TakeNextId(); - if (nid == 0) { - return false; - } - callee2caller[rid] = nid; - } - cp_inst->SetResultId(nid); - get_decoration_mgr()->CloneDecorations(rid, nid); - } - new_blk_ptr->AddInstruction(std::move(cp_inst)); - } break; - } - return true; + // Inline DebugClare instructions in the callee's header. + calleeFn->ForEachDebugInstructionsInHeader( + [&new_blk_ptr, &callee2caller, &inlined_at_ctx, this](Instruction* inst) { + InlineSingleInstruction( + callee2caller, new_blk_ptr.get(), inst, + context()->get_debug_info_mgr()->BuildDebugInlinedAtChain( + inst->GetDebugScope().GetInlinedAt(), &inlined_at_ctx)); }); - if (!successful) { + // Inline the entry block of the callee function. + if (!InlineEntryBlock(callee2caller, &new_blk_ptr, calleeFn->begin(), + &inlined_at_ctx)) { return false; } - if (caller_is_loop_header && (new_blocks->size() > 1)) { - // Move the OpLoopMerge from the last block back to the first, where - // it belongs. - auto& first = new_blocks->front(); - auto& last = new_blocks->back(); - assert(first != last); - - // Insert a modified copy of the loop merge into the first block. - auto loop_merge_itr = last->tail(); - --loop_merge_itr; - assert(loop_merge_itr->opcode() == SpvOpLoopMerge); - std::unique_ptr<Instruction> cp_inst(loop_merge_itr->Clone(context())); - first->tail().InsertBefore(std::move(cp_inst)); - - // Remove the loop merge from the last block. - loop_merge_itr->RemoveFromList(); - delete &*loop_merge_itr; + // Inline blocks of the callee function other than the entry block. + new_blk_ptr = + InlineBasicBlocks(new_blocks, callee2caller, std::move(new_blk_ptr), + &inlined_at_ctx, calleeFn); + if (new_blk_ptr == nullptr) return false; + + new_blk_ptr = InlineReturn(callee2caller, new_blocks, std::move(new_blk_ptr), + &inlined_at_ctx, calleeFn, + &*(calleeFn->tail()->tail()), returnVarId); + + // Load return value into result id of call, if it exists. + if (returnVarId != 0) { + const uint32_t resId = call_inst_itr->result_id(); + assert(resId != 0); + AddLoad(calleeTypeId, resId, returnVarId, &new_blk_ptr, + call_inst_itr->dbg_line_inst(), call_inst_itr->GetDebugScope()); } + // Move instructions of original caller block after call instruction. + if (!MoveCallerInstsAfterFunctionCall(&preCallSB, &postCallSB, &new_blk_ptr, + call_inst_itr, + calleeFn->begin() != calleeFn->end())) + return false; + + // Finalize inline code. + new_blocks->push_back(std::move(new_blk_ptr)); + + if (caller_is_loop_header && (new_blocks->size() > 1)) + MoveLoopMergeInstToFirstBlock(new_blocks); + // Update block map given replacement blocks. for (auto& blk : *new_blocks) { id2block_[blk->id()] = &*blk; @@ -624,7 +644,21 @@ bool InlinePass::IsInlinableFunctionCall(const Instruction* inst) { const uint32_t calleeFnId = inst->GetSingleWordOperand(kSpvFunctionCallFunctionId); const auto ci = inlinable_.find(calleeFnId); - return ci != inlinable_.cend(); + if (ci == inlinable_.cend()) return false; + + if (early_return_funcs_.find(calleeFnId) != early_return_funcs_.end()) { + // We rely on the merge-return pass to handle the early return case + // in advance. + std::string message = + "The function '" + id2function_[calleeFnId]->DefInst().PrettyPrint() + + "' could not be inlined because the return instruction " + "is not at the end of the function. This could be fixed by " + "running merge-return before inlining."; + consumer()(SPV_MSG_WARNING, "", {0, 0, 0}, message.c_str()); + return false; + } + + return true; } void InlinePass::UpdateSucceedingPhis( @@ -645,26 +679,6 @@ void InlinePass::UpdateSucceedingPhis( }); } -bool InlinePass::HasNoReturnInStructuredConstruct(Function* func) { - // If control not structured, do not do loop/return analysis - // TODO: Analyze returns in non-structured control flow - if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader)) - return false; - const auto structured_analysis = context()->GetStructuredCFGAnalysis(); - // Search for returns in structured construct. - bool return_in_construct = false; - for (auto& blk : *func) { - auto terminal_ii = blk.cend(); - --terminal_ii; - if (spvOpcodeIsReturn(terminal_ii->opcode()) && - structured_analysis->ContainingConstruct(blk.id()) != 0) { - return_in_construct = true; - break; - } - } - return !return_in_construct; -} - bool InlinePass::HasNoReturnInLoop(Function* func) { // If control not structured, do not do loop/return analysis // TODO: Analyze returns in non-structured control flow @@ -686,10 +700,18 @@ bool InlinePass::HasNoReturnInLoop(Function* func) { } void InlinePass::AnalyzeReturns(Function* func) { + // Analyze functions without a return in loop. if (HasNoReturnInLoop(func)) { no_return_in_loop_.insert(func->result_id()); - if (!HasNoReturnInStructuredConstruct(func)) + } + // Analyze functions with a return before its tail basic block. + for (auto& blk : *func) { + auto terminal_ii = blk.cend(); + --terminal_ii; + if (spvOpcodeIsReturn(terminal_ii->opcode()) && &blk != func->tail()) { early_return_funcs_.insert(func->result_id()); + break; + } } } diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/inline_pass.h b/chromium/third_party/SPIRV-Tools/src/source/opt/inline_pass.h index bc5f78127dd..202bc97fdcc 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/inline_pass.h +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/inline_pass.h @@ -24,6 +24,7 @@ #include <unordered_map> #include <vector> +#include "source/opt/debug_info_manager.h" #include "source/opt/decoration_manager.h" #include "source/opt/module.h" #include "source/opt/pass.h" @@ -58,11 +59,13 @@ class InlinePass : public Pass { // Add store of valId to ptrId to end of block block_ptr. void AddStore(uint32_t ptrId, uint32_t valId, - std::unique_ptr<BasicBlock>* block_ptr); + std::unique_ptr<BasicBlock>* block_ptr, + const Instruction* line_inst, const DebugScope& dbg_scope); // Add load of ptrId into resultId to end of block block_ptr. void AddLoad(uint32_t typeId, uint32_t resultId, uint32_t ptrId, - std::unique_ptr<BasicBlock>* block_ptr); + std::unique_ptr<BasicBlock>* block_ptr, + const Instruction* line_inst, const DebugScope& dbg_scope); // Return new label. std::unique_ptr<Instruction> NewLabel(uint32_t label_id); @@ -79,7 +82,8 @@ class InlinePass : public Pass { // Clone and map callee locals. Return true if successful. bool CloneAndMapLocals(Function* calleeFn, std::vector<std::unique_ptr<Instruction>>* new_vars, - std::unordered_map<uint32_t, uint32_t>* callee2caller); + std::unordered_map<uint32_t, uint32_t>* callee2caller, + analysis::DebugInlinedAtContext* inlined_at_ctx); // Create return variable for callee clone code. The return type of // |calleeFn| must not be void. Returns the id of the return variable if @@ -124,10 +128,6 @@ class InlinePass : public Pass { // Return true if |inst| is a function call that can be inlined. bool IsInlinableFunctionCall(const Instruction* inst); - // Return true if |func| does not have a return that is - // nested in a structured if, switch or loop. - bool HasNoReturnInStructuredConstruct(Function* func); - // Return true if |func| has no return in a loop. The current analysis // requires structured control flow, so return false if control flow not // structured ie. module is not a shader. @@ -171,6 +171,69 @@ class InlinePass : public Pass { // Set of functions that are originally called directly or indirectly from a // continue construct. std::unordered_set<uint32_t> funcs_called_from_continue_; + + private: + // Moves instructions of the caller function up to the call instruction + // to |new_blk_ptr|. + void MoveInstsBeforeEntryBlock( + std::unordered_map<uint32_t, Instruction*>* preCallSB, + BasicBlock* new_blk_ptr, BasicBlock::iterator call_inst_itr, + UptrVectorIterator<BasicBlock> call_block_itr); + + // Returns a new guard block after adding a branch to the end of + // |new_blocks|. + std::unique_ptr<BasicBlock> AddGuardBlock( + std::vector<std::unique_ptr<BasicBlock>>* new_blocks, + std::unordered_map<uint32_t, uint32_t>* callee2caller, + std::unique_ptr<BasicBlock> new_blk_ptr, uint32_t entry_blk_label_id); + + // Add store instructions for initializers of variables. + InstructionList::iterator AddStoresForVariableInitializers( + const std::unordered_map<uint32_t, uint32_t>& callee2caller, + analysis::DebugInlinedAtContext* inlined_at_ctx, + std::unique_ptr<BasicBlock>* new_blk_ptr, + UptrVectorIterator<BasicBlock> callee_block_itr); + + // Inlines a single instruction of the callee function. + bool InlineSingleInstruction( + const std::unordered_map<uint32_t, uint32_t>& callee2caller, + BasicBlock* new_blk_ptr, const Instruction* inst, + uint32_t dbg_inlined_at); + + // Inlines the return instruction of the callee function. + std::unique_ptr<BasicBlock> InlineReturn( + const std::unordered_map<uint32_t, uint32_t>& callee2caller, + std::vector<std::unique_ptr<BasicBlock>>* new_blocks, + std::unique_ptr<BasicBlock> new_blk_ptr, + analysis::DebugInlinedAtContext* inlined_at_ctx, Function* calleeFn, + const Instruction* inst, uint32_t returnVarId); + + // Inlines the entry block of the callee function. + bool InlineEntryBlock( + const std::unordered_map<uint32_t, uint32_t>& callee2caller, + std::unique_ptr<BasicBlock>* new_blk_ptr, + UptrVectorIterator<BasicBlock> callee_first_block, + analysis::DebugInlinedAtContext* inlined_at_ctx); + + // Inlines basic blocks of the callee function other than the entry basic + // block. + std::unique_ptr<BasicBlock> InlineBasicBlocks( + std::vector<std::unique_ptr<BasicBlock>>* new_blocks, + const std::unordered_map<uint32_t, uint32_t>& callee2caller, + std::unique_ptr<BasicBlock> new_blk_ptr, + analysis::DebugInlinedAtContext* inlined_at_ctx, Function* calleeFn); + + // Moves instructions of the caller function after the call instruction + // to |new_blk_ptr|. + bool MoveCallerInstsAfterFunctionCall( + std::unordered_map<uint32_t, Instruction*>* preCallSB, + std::unordered_map<uint32_t, uint32_t>* postCallSB, + std::unique_ptr<BasicBlock>* new_blk_ptr, + BasicBlock::iterator call_inst_itr, bool multiBlocks); + + // Move the OpLoopMerge from the last block back to the first. + void MoveLoopMergeInstToFirstBlock( + std::vector<std::unique_ptr<BasicBlock>>* new_blocks); }; } // namespace opt diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/inst_bindless_check_pass.h b/chromium/third_party/SPIRV-Tools/src/source/opt/inst_bindless_check_pass.h index 447871bfc96..9335fa5baf0 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/inst_bindless_check_pass.h +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/inst_bindless_check_pass.h @@ -28,13 +28,6 @@ namespace opt { // external design may change as the layer evolves. class InstBindlessCheckPass : public InstrumentPass { public: - // Deprecated interface - InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id, - bool input_length_enable, bool input_init_enable, - uint32_t version) - : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless, version), - input_length_enabled_(input_length_enable), - input_init_enabled_(input_init_enable) {} // Preferred Interface InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id, bool input_length_enable, bool input_init_enable) diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/inst_buff_addr_check_pass.h b/chromium/third_party/SPIRV-Tools/src/source/opt/inst_buff_addr_check_pass.h index 67ffcc39241..ec7bb6846f9 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/inst_buff_addr_check_pass.h +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/inst_buff_addr_check_pass.h @@ -28,10 +28,6 @@ namespace opt { // external design of this class may change as the layer evolves. class InstBuffAddrCheckPass : public InstrumentPass { public: - // Deprecated interface - InstBuffAddrCheckPass(uint32_t desc_set, uint32_t shader_id, uint32_t version) - : InstrumentPass(desc_set, shader_id, kInstValidationIdBuffAddr, - version) {} // Preferred interface InstBuffAddrCheckPass(uint32_t desc_set, uint32_t shader_id) : InstrumentPass(desc_set, shader_id, kInstValidationIdBuffAddr) {} diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/inst_debug_printf_pass.h b/chromium/third_party/SPIRV-Tools/src/source/opt/inst_debug_printf_pass.h index 2968a203af0..70b0a72bd77 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/inst_debug_printf_pass.h +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/inst_debug_printf_pass.h @@ -28,11 +28,10 @@ namespace opt { class InstDebugPrintfPass : public InstrumentPass { public: // For test harness only - InstDebugPrintfPass() - : InstrumentPass(7, 23, kInstValidationIdDebugPrintf, 2) {} + InstDebugPrintfPass() : InstrumentPass(7, 23, kInstValidationIdDebugPrintf) {} // For all other interfaces InstDebugPrintfPass(uint32_t desc_set, uint32_t shader_id) - : InstrumentPass(desc_set, shader_id, kInstValidationIdDebugPrintf, 2) {} + : InstrumentPass(desc_set, shader_id, kInstValidationIdDebugPrintf) {} ~InstDebugPrintfPass() override = default; diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/instruction.cpp b/chromium/third_party/SPIRV-Tools/src/source/opt/instruction.cpp index 7052d3e7249..126848e719f 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/instruction.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/instruction.cpp @@ -38,6 +38,10 @@ const uint32_t kExtInstInstructionInIdx = 1; const uint32_t kDebugScopeNumWords = 7; const uint32_t kDebugScopeNumWordsWithoutInlinedAt = 6; const uint32_t kDebugNoScopeNumWords = 5; + +// Number of operands of an OpBranchConditional instruction +// with weights. +const uint32_t kOpBranchConditionalWithWeightsNumOperands = 5; } // namespace Instruction::Instruction(IRContext* c) @@ -166,6 +170,15 @@ uint32_t Instruction::NumInOperandWords() const { return size; } +bool Instruction::HasBranchWeights() const { + if (opcode_ == SpvOpBranchConditional && + NumOperands() == kOpBranchConditionalWithWeightsNumOperands) { + return true; + } + + return false; +} + void Instruction::ToBinaryWithoutAttachedDebugInsts( std::vector<uint32_t>* binary) const { const uint32_t num_words = 1 + NumOperandWords(); @@ -610,7 +623,19 @@ bool Instruction::IsFoldableByFoldScalar() const { return false; } Instruction* type = context()->get_def_use_mgr()->GetDef(type_id()); - return folder.IsFoldableType(type); + if (!folder.IsFoldableType(type)) { + return false; + } + + // Even if the type of the instruction is foldable, its operands may not be + // foldable (e.g., comparisons of 64bit types). Check that all operand types + // are foldable before accepting the instruction. + return WhileEachInOperand([&folder, this](const uint32_t* op_id) { + Instruction* def_inst = context()->get_def_use_mgr()->GetDef(*op_id); + Instruction* def_inst_type = + context()->get_def_use_mgr()->GetDef(def_inst->type_id()); + return folder.IsFoldableType(def_inst_type); + }); } bool Instruction::IsFloatingPointFoldingAllowed() const { diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/instruction.h b/chromium/third_party/SPIRV-Tools/src/source/opt/instruction.h index aa29c5e7b47..7d8fed84788 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/instruction.h +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/instruction.h @@ -239,6 +239,10 @@ class Instruction : public utils::IntrusiveNodeBase<Instruction> { return dbg_line_insts_; } + const Instruction* dbg_line_inst() const { + return dbg_line_insts_.empty() ? nullptr : &dbg_line_insts_[0]; + } + // Clear line-related debug instructions attached to this instruction. void clear_dbg_line_insts() { dbg_line_insts_.clear(); } @@ -291,6 +295,11 @@ class Instruction : public utils::IntrusiveNodeBase<Instruction> { // Sets DebugScope. inline void SetDebugScope(const DebugScope& scope); inline const DebugScope& GetDebugScope() const { return dbg_scope_; } + // Updates DebugInlinedAt of DebugScope and OpLine. + inline void UpdateDebugInlinedAt(uint32_t new_inlined_at); + inline uint32_t GetDebugInlinedAt() const { + return dbg_scope_.GetInlinedAt(); + } // Updates OpLine and DebugScope based on the information of |from|. inline void UpdateDebugInfo(const Instruction* from); // Remove the |index|-th operand @@ -366,6 +375,10 @@ class Instruction : public utils::IntrusiveNodeBase<Instruction> { inline bool WhileEachInOperand( const std::function<bool(const uint32_t*)>& f) const; + // Returns true if it's an OpBranchConditional instruction + // with branch weights. + bool HasBranchWeights() const; + // Returns true if any operands can be labels inline bool HasLabels() const; @@ -638,6 +651,13 @@ inline void Instruction::SetDebugScope(const DebugScope& scope) { } } +inline void Instruction::UpdateDebugInlinedAt(uint32_t new_inlined_at) { + dbg_scope_.SetInlinedAt(new_inlined_at); + for (auto& i : dbg_line_insts_) { + i.dbg_scope_.SetInlinedAt(new_inlined_at); + } +} + inline void Instruction::UpdateDebugInfo(const Instruction* from) { if (from == nullptr) return; clear_dbg_line_insts(); diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/instruction_list.h b/chromium/third_party/SPIRV-Tools/src/source/opt/instruction_list.h index ea1cc7c46ca..417cbd768b9 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/instruction_list.h +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/instruction_list.h @@ -61,6 +61,16 @@ class InstructionList : public utils::IntrusiveList<Instruction> { : utils::IntrusiveList<Instruction>::iterator(i) {} iterator(Instruction* i) : utils::IntrusiveList<Instruction>::iterator(i) {} + iterator& operator++() { + utils::IntrusiveList<Instruction>::iterator::operator++(); + return *this; + } + + iterator& operator--() { + utils::IntrusiveList<Instruction>::iterator::operator--(); + return *this; + } + // DEPRECATED: Please use MoveBefore with an InstructionList instead. // // Moves the nodes in |list| to the list that |this| points to. The diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/instrument_pass.cpp b/chromium/third_party/SPIRV-Tools/src/source/opt/instrument_pass.cpp index c8c6c211301..4210ad5d7eb 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/instrument_pass.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/instrument_pass.cpp @@ -885,14 +885,6 @@ bool InstrumentPass::InstProcessCallTreeFromRoots(InstProcessFunction& pfn, } bool InstrumentPass::InstProcessEntryPointCallTree(InstProcessFunction& pfn) { - // Check that format version 2 requested - if (version_ != 2u) { - if (consumer()) { - std::string message = "Unsupported instrumentation format requested"; - consumer()(SPV_MSG_ERROR, 0, {0, 0, 0}, message.c_str()); - } - return false; - } // Make sure all entry points have the same execution model. Do not // instrument if they do not. // TODO(greg-lunarg): Handle mixed stages. Technically, a shader module diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/instrument_pass.h b/chromium/third_party/SPIRV-Tools/src/source/opt/instrument_pass.h index 11afdce82bb..f6884d2da39 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/instrument_pass.h +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/instrument_pass.h @@ -87,18 +87,7 @@ class InstrumentPass : public Pass { : Pass(), desc_set_(desc_set), shader_id_(shader_id), - validation_id_(validation_id), - version_(2u) {} - // Create instrumentation pass for |validation_id| which utilizes descriptor - // set |desc_set| for debug input and output buffers and writes |shader_id| - // into debug output records with format |version|. Deprecated. - InstrumentPass(uint32_t desc_set, uint32_t shader_id, uint32_t validation_id, - uint32_t version) - : Pass(), - desc_set_(desc_set), - shader_id_(shader_id), - validation_id_(validation_id), - version_(version) {} + validation_id_(validation_id) {} // Initialize state for instrumentation of module. void InitializeInstrument(); @@ -425,9 +414,6 @@ class InstrumentPass : public Pass { // id for void type uint32_t void_id_; - // Record format version - uint32_t version_; - // boolean to remember storage buffer extension bool storage_buffer_ext_defined_; diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/ir_context.cpp b/chromium/third_party/SPIRV-Tools/src/source/opt/ir_context.cpp index df04066f332..baff0000310 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/ir_context.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/ir_context.cpp @@ -97,8 +97,9 @@ void IRContext::InvalidateAnalysesExceptFor( } void IRContext::InvalidateAnalyses(IRContext::Analysis analyses_to_invalidate) { - // The ConstantManager contains Type pointers. If the TypeManager goes - // away, the ConstantManager has to go away. + // The ConstantManager and DebugInfoManager contain Type pointers. If the + // TypeManager goes away, the ConstantManager and DebugInfoManager have to + // go away. if (analyses_to_invalidate & kAnalysisTypes) { analyses_to_invalidate |= kAnalysisConstants; analyses_to_invalidate |= kAnalysisDebugInfo; @@ -179,6 +180,9 @@ Instruction* IRContext::KillInst(Instruction* inst) { decoration_mgr_->RemoveDecoration(inst); } } + if (AreAnalysesValid(kAnalysisDebugInfo)) { + get_debug_info_mgr()->ClearDebugInfo(inst); + } if (type_mgr_ && IsTypeInst(inst->opcode())) { type_mgr_->RemoveId(inst->result_id()); } @@ -218,6 +222,13 @@ bool IRContext::KillDef(uint32_t id) { return false; } +void IRContext::KillDebugDeclareInsts(Function* fn) { + fn->ForEachInst([this](Instruction* inst) { + if (inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugDeclare) + KillInst(inst); + }); +} + bool IRContext::ReplaceAllUsesWith(uint32_t before, uint32_t after) { return ReplaceAllUsesWithPredicate( before, after, [](Instruction*, uint32_t) { return true; }); @@ -394,6 +405,7 @@ void IRContext::KillOperandFromDebugInstructions(Instruction* inst) { if (operand.words[0] == id) { operand.words[0] = get_debug_info_mgr()->GetDebugInfoNone()->result_id(); + get_def_use_mgr()->AnalyzeInstUse(&*it); } } } @@ -408,6 +420,7 @@ void IRContext::KillOperandFromDebugInstructions(Instruction* inst) { if (operand.words[0] == id) { operand.words[0] = get_debug_info_mgr()->GetDebugInfoNone()->result_id(); + get_def_use_mgr()->AnalyzeInstUse(&*it); } } } diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/ir_context.h b/chromium/third_party/SPIRV-Tools/src/source/opt/ir_context.h index a1b63ff9e07..37be8365318 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/ir_context.h +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/ir_context.h @@ -357,6 +357,13 @@ class IRContext { inline IteratorRange<std::multimap<uint32_t, Instruction*>::iterator> GetNames(uint32_t id); + // Returns an OpMemberName instruction that targets |struct_type_id| at + // index |index|. Returns nullptr if no such instruction exists. + // While the SPIR-V spec does not prohibit having multiple OpMemberName + // instructions for the same structure member, it is hard to imagine a member + // having more than one name. This method returns the first one it finds. + inline Instruction* GetMemberName(uint32_t struct_type_id, uint32_t index); + // Sets the message consumer to the given |consumer|. |consumer| which will be // invoked every time there is a message to be communicated to the outside. void SetMessageConsumer(MessageConsumer c) { consumer_ = std::move(c); } @@ -396,6 +403,9 @@ class IRContext { // instruction exists. Instruction* KillInst(Instruction* inst); + // Deletes DebugDeclare instructions in the given function |fn|. + void KillDebugDeclareInsts(Function* fn); + // Returns true if all of the given analyses are valid. bool AreAnalysesValid(Analysis set) { return (set & valid_analyses_) == set; } @@ -1061,7 +1071,9 @@ void IRContext::AddDebug1Inst(std::unique_ptr<Instruction>&& d) { void IRContext::AddDebug2Inst(std::unique_ptr<Instruction>&& d) { if (AreAnalysesValid(kAnalysisNameMap)) { if (d->opcode() == SpvOpName || d->opcode() == SpvOpMemberName) { - id_to_name_->insert({d->result_id(), d.get()}); + // OpName and OpMemberName do not have result-ids. The target of the + // instruction is at InOperand index 0. + id_to_name_->insert({d->GetSingleWordInOperand(0), d.get()}); } } module()->AddDebug2Inst(std::move(d)); @@ -1135,6 +1147,21 @@ IRContext::GetNames(uint32_t id) { return make_range(std::move(result.first), std::move(result.second)); } +Instruction* IRContext::GetMemberName(uint32_t struct_type_id, uint32_t index) { + if (!AreAnalysesValid(kAnalysisNameMap)) { + BuildIdToNameMap(); + } + auto result = id_to_name_->equal_range(struct_type_id); + for (auto i = result.first; i != result.second; ++i) { + auto* name_instr = i->second; + if (name_instr->opcode() == SpvOpMemberName && + name_instr->GetSingleWordInOperand(1) == index) { + return name_instr; + } + } + return nullptr; +} + } // namespace opt } // namespace spvtools diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/local_access_chain_convert_pass.cpp b/chromium/third_party/SPIRV-Tools/src/source/opt/local_access_chain_convert_pass.cpp index 0afe798582f..05704c1489a 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/local_access_chain_convert_pass.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/local_access_chain_convert_pass.cpp @@ -281,7 +281,7 @@ void LocalAccessChainConvertPass::Initialize() { // Initialize collections supported_ref_ptrs_.clear(); - // Initialize extension whitelist + // Initialize extension allowlist InitExtensions(); } @@ -292,11 +292,11 @@ bool LocalAccessChainConvertPass::AllExtensionsSupported() const { if (context()->get_feature_mgr()->HasCapability( SpvCapabilityVariablePointers)) return false; - // If any extension not in whitelist, return false + // If any extension not in allowlist, return false for (auto& ei : get_module()->extensions()) { const char* extName = reinterpret_cast<const char*>(&ei.GetInOperand(0).words[0]); - if (extensions_whitelist_.find(extName) == extensions_whitelist_.end()) + if (extensions_allowlist_.find(extName) == extensions_allowlist_.end()) return false; } return true; @@ -336,8 +336,8 @@ Pass::Status LocalAccessChainConvertPass::Process() { } void LocalAccessChainConvertPass::InitExtensions() { - extensions_whitelist_.clear(); - extensions_whitelist_.insert({ + extensions_allowlist_.clear(); + extensions_allowlist_.insert({ "SPV_AMD_shader_explicit_vertex_parameter", "SPV_AMD_shader_trinary_minmax", "SPV_AMD_gcn_shader", diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/local_access_chain_convert_pass.h b/chromium/third_party/SPIRV-Tools/src/source/opt/local_access_chain_convert_pass.h index e3592bf0ce0..552062e52f4 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/local_access_chain_convert_pass.h +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/local_access_chain_convert_pass.h @@ -110,7 +110,7 @@ class LocalAccessChainConvertPass : public MemPass { // Returns a status to indicate success or failure, and change or no change. Status ConvertLocalAccessChains(Function* func); - // Initialize extensions whitelist + // Initialize extensions allowlist void InitExtensions(); // Return true if all extensions in this module are allowed by this pass. @@ -124,7 +124,7 @@ class LocalAccessChainConvertPass : public MemPass { std::unordered_set<uint32_t> supported_ref_ptrs_; // Extensions supported by this pass. - std::unordered_set<std::string> extensions_whitelist_; + std::unordered_set<std::string> extensions_allowlist_; }; } // namespace opt diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/local_single_block_elim_pass.cpp b/chromium/third_party/SPIRV-Tools/src/source/opt/local_single_block_elim_pass.cpp index b5435bb7a54..401dad8e3b3 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/local_single_block_elim_pass.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/local_single_block_elim_pass.cpp @@ -168,16 +168,16 @@ void LocalSingleBlockLoadStoreElimPass::Initialize() { // Clear collections supported_ref_ptrs_.clear(); - // Initialize extensions whitelist + // Initialize extensions allowlist InitExtensions(); } bool LocalSingleBlockLoadStoreElimPass::AllExtensionsSupported() const { - // If any extension not in whitelist, return false + // If any extension not in allowlist, return false for (auto& ei : get_module()->extensions()) { const char* extName = reinterpret_cast<const char*>(&ei.GetInOperand(0).words[0]); - if (extensions_whitelist_.find(extName) == extensions_whitelist_.end()) + if (extensions_allowlist_.find(extName) == extensions_allowlist_.end()) return false; } return true; @@ -214,8 +214,8 @@ Pass::Status LocalSingleBlockLoadStoreElimPass::Process() { } void LocalSingleBlockLoadStoreElimPass::InitExtensions() { - extensions_whitelist_.clear(); - extensions_whitelist_.insert({ + extensions_allowlist_.clear(); + extensions_allowlist_.insert({ "SPV_AMD_shader_explicit_vertex_parameter", "SPV_AMD_shader_trinary_minmax", "SPV_AMD_gcn_shader", diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/local_single_block_elim_pass.h b/chromium/third_party/SPIRV-Tools/src/source/opt/local_single_block_elim_pass.h index 0fe7732a822..ea72816a8e6 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/local_single_block_elim_pass.h +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/local_single_block_elim_pass.h @@ -63,7 +63,7 @@ class LocalSingleBlockLoadStoreElimPass : public MemPass { // where possible. Assumes logical addressing. bool LocalSingleBlockLoadStoreElim(Function* func); - // Initialize extensions whitelist + // Initialize extensions allowlist void InitExtensions(); // Return true if all extensions in this module are supported by this pass. @@ -94,7 +94,7 @@ class LocalSingleBlockLoadStoreElimPass : public MemPass { std::unordered_set<uint32_t> pinned_vars_; // Extensions supported by this pass. - std::unordered_set<std::string> extensions_whitelist_; + std::unordered_set<std::string> extensions_allowlist_; // Variables that are only referenced by supported operations for this // pass ie. loads and stores. diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/local_single_store_elim_pass.cpp b/chromium/third_party/SPIRV-Tools/src/source/opt/local_single_store_elim_pass.cpp index 4c71ce1ca85..3f618534776 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/local_single_store_elim_pass.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/local_single_store_elim_pass.cpp @@ -46,11 +46,11 @@ bool LocalSingleStoreElimPass::LocalSingleStoreElim(Function* func) { } bool LocalSingleStoreElimPass::AllExtensionsSupported() const { - // If any extension not in whitelist, return false + // If any extension not in allowlist, return false for (auto& ei : get_module()->extensions()) { const char* extName = reinterpret_cast<const char*>(&ei.GetInOperand(0).words[0]); - if (extensions_whitelist_.find(extName) == extensions_whitelist_.end()) + if (extensions_allowlist_.find(extName) == extensions_allowlist_.end()) return false; } return true; @@ -74,12 +74,12 @@ Pass::Status LocalSingleStoreElimPass::ProcessImpl() { LocalSingleStoreElimPass::LocalSingleStoreElimPass() = default; Pass::Status LocalSingleStoreElimPass::Process() { - InitExtensionWhiteList(); + InitExtensionAllowList(); return ProcessImpl(); } -void LocalSingleStoreElimPass::InitExtensionWhiteList() { - extensions_whitelist_.insert({ +void LocalSingleStoreElimPass::InitExtensionAllowList() { + extensions_allowlist_.insert({ "SPV_AMD_shader_explicit_vertex_parameter", "SPV_AMD_shader_trinary_minmax", "SPV_AMD_gcn_shader", diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/local_single_store_elim_pass.h b/chromium/third_party/SPIRV-Tools/src/source/opt/local_single_store_elim_pass.h index 4cf8bbb86ae..a7cdd1920a8 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/local_single_store_elim_pass.h +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/local_single_store_elim_pass.h @@ -57,8 +57,8 @@ class LocalSingleStoreElimPass : public Pass { // any resulting dead code. bool LocalSingleStoreElim(Function* func); - // Initialize extensions whitelist - void InitExtensionWhiteList(); + // Initialize extensions allowlist + void InitExtensionAllowList(); // Return true if all extensions in this module are allowed by this pass. bool AllExtensionsSupported() const; @@ -94,7 +94,7 @@ class LocalSingleStoreElimPass : public Pass { const std::vector<Instruction*>& uses); // Extensions supported by this pass. - std::unordered_set<std::string> extensions_whitelist_; + std::unordered_set<std::string> extensions_allowlist_; }; } // namespace opt diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/loop_descriptor.cpp b/chromium/third_party/SPIRV-Tools/src/source/opt/loop_descriptor.cpp index 11f7e9cfac4..ed0dd28f42e 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/loop_descriptor.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/loop_descriptor.cpp @@ -485,10 +485,27 @@ void Loop::ComputeLoopStructuredOrder( if (include_pre_header && GetPreHeaderBlock()) ordered_loop_blocks->push_back(loop_preheader_); - cfg.ForEachBlockInReversePostOrder( - loop_header_, [ordered_loop_blocks, this](BasicBlock* bb) { - if (IsInsideLoop(bb)) ordered_loop_blocks->push_back(bb); - }); + + bool is_shader = + context_->get_feature_mgr()->HasCapability(SpvCapabilityShader); + if (!is_shader) { + cfg.ForEachBlockInReversePostOrder( + loop_header_, [ordered_loop_blocks, this](BasicBlock* bb) { + if (IsInsideLoop(bb)) ordered_loop_blocks->push_back(bb); + }); + } else { + // If this is a shader, it is possible that there are unreachable merge and + // continue blocks that must be copied to retain the structured order. + // The structured order will include these. + std::list<BasicBlock*> order; + cfg.ComputeStructuredOrder(loop_header_->GetParent(), loop_header_, &order); + for (BasicBlock* bb : order) { + if (bb == GetMergeBlock()) { + break; + } + ordered_loop_blocks->push_back(bb); + } + } if (include_merge && GetMergeBlock()) ordered_loop_blocks->push_back(loop_merge_); } diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/mem_pass.cpp b/chromium/third_party/SPIRV-Tools/src/source/opt/mem_pass.cpp index 04e2e8aea93..573879842cb 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/mem_pass.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/mem_pass.cpp @@ -20,6 +20,7 @@ #include <set> #include <vector> +#include "OpenCLDebugInfo100.h" #include "source/cfa.h" #include "source/opt/basic_block.h" #include "source/opt/dominator_analysis.h" @@ -97,6 +98,11 @@ Instruction* MemPass::GetPtr(uint32_t ptrId, uint32_t* varId) { Instruction* ptrInst = get_def_use_mgr()->GetDef(*varId); Instruction* varInst; + if (ptrInst->opcode() == SpvOpConstantNull) { + *varId = 0; + return ptrInst; + } + if (ptrInst->opcode() != SpvOpVariable && ptrInst->opcode() != SpvOpFunctionParameter) { varInst = ptrInst->GetBaseAddress(); @@ -220,6 +226,11 @@ MemPass::MemPass() {} bool MemPass::HasOnlySupportedRefs(uint32_t varId) { return get_def_use_mgr()->WhileEachUser(varId, [this](Instruction* user) { + auto dbg_op = user->GetOpenCL100DebugOpcode(); + if (dbg_op == OpenCLDebugInfo100DebugDeclare || + dbg_op == OpenCLDebugInfo100DebugValue) { + return true; + } SpvOp op = user->opcode(); if (op != SpvOpStore && op != SpvOpLoad && op != SpvOpName && !IsNonTypeDecorate(op)) { diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/merge_return_pass.cpp b/chromium/third_party/SPIRV-Tools/src/source/opt/merge_return_pass.cpp index 7d238daef95..8cb429945aa 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/merge_return_pass.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/merge_return_pass.cpp @@ -424,7 +424,6 @@ bool MergeReturnPass::BreakFromConstruct( auto old_body_id = TakeNextId(); BasicBlock* old_body = block->SplitBasicBlock(context(), old_body_id, iter); predicated->insert(old_body); - cfg()->AddEdges(old_body); // If a return block is being split, mark the new body block also as a return // block. diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/optimizer.cpp b/chromium/third_party/SPIRV-Tools/src/source/opt/optimizer.cpp index f11dc346f6f..25adee976c9 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/optimizer.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/optimizer.cpp @@ -175,9 +175,18 @@ Optimizer& Optimizer::RegisterPerformancePasses() { .RegisterPass(CreateAggressiveDCEPass()) .RegisterPass(CreateCCPPass()) .RegisterPass(CreateAggressiveDCEPass()) + .RegisterPass(CreateLoopUnrollPass(true)) + .RegisterPass(CreateDeadBranchElimPass()) .RegisterPass(CreateRedundancyEliminationPass()) .RegisterPass(CreateCombineAccessChainsPass()) .RegisterPass(CreateSimplificationPass()) + .RegisterPass(CreateScalarReplacementPass()) + .RegisterPass(CreateLocalAccessChainConvertPass()) + .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass()) + .RegisterPass(CreateLocalSingleStoreElimPass()) + .RegisterPass(CreateAggressiveDCEPass()) + .RegisterPass(CreateSSARewritePass()) + .RegisterPass(CreateAggressiveDCEPass()) .RegisterPass(CreateVectorDCEPass()) .RegisterPass(CreateDeadInsertElimPass()) .RegisterPass(CreateDeadBranchElimPass()) @@ -407,19 +416,19 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) { } else if (pass_name == "replace-invalid-opcode") { RegisterPass(CreateReplaceInvalidOpcodePass()); } else if (pass_name == "inst-bindless-check") { - RegisterPass(CreateInstBindlessCheckPass(7, 23, false, false, 2)); + RegisterPass(CreateInstBindlessCheckPass(7, 23, false, false)); RegisterPass(CreateSimplificationPass()); RegisterPass(CreateDeadBranchElimPass()); RegisterPass(CreateBlockMergePass()); RegisterPass(CreateAggressiveDCEPass()); } else if (pass_name == "inst-desc-idx-check") { - RegisterPass(CreateInstBindlessCheckPass(7, 23, true, true, 2)); + RegisterPass(CreateInstBindlessCheckPass(7, 23, true, true)); RegisterPass(CreateSimplificationPass()); RegisterPass(CreateDeadBranchElimPass()); RegisterPass(CreateBlockMergePass()); RegisterPass(CreateAggressiveDCEPass()); } else if (pass_name == "inst-buff-addr-check") { - RegisterPass(CreateInstBuffAddrCheckPass(7, 23, 2)); + RegisterPass(CreateInstBuffAddrCheckPass(7, 23)); RegisterPass(CreateAggressiveDCEPass()); } else if (pass_name == "convert-relaxed-to-half") { RegisterPass(CreateConvertRelaxedToHalfPass()); @@ -885,12 +894,10 @@ Optimizer::PassToken CreateUpgradeMemoryModelPass() { Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id, bool input_length_enable, - bool input_init_enable, - uint32_t version) { + bool input_init_enable) { return MakeUnique<Optimizer::PassToken::Impl>( - MakeUnique<opt::InstBindlessCheckPass>(desc_set, shader_id, - input_length_enable, - input_init_enable, version)); + MakeUnique<opt::InstBindlessCheckPass>( + desc_set, shader_id, input_length_enable, input_init_enable)); } Optimizer::PassToken CreateInstDebugPrintfPass(uint32_t desc_set, @@ -900,10 +907,9 @@ Optimizer::PassToken CreateInstDebugPrintfPass(uint32_t desc_set, } Optimizer::PassToken CreateInstBuffAddrCheckPass(uint32_t desc_set, - uint32_t shader_id, - uint32_t version) { + uint32_t shader_id) { return MakeUnique<Optimizer::PassToken::Impl>( - MakeUnique<opt::InstBuffAddrCheckPass>(desc_set, shader_id, version)); + MakeUnique<opt::InstBuffAddrCheckPass>(desc_set, shader_id)); } Optimizer::PassToken CreateConvertRelaxedToHalfPass() { diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/pass.h b/chromium/third_party/SPIRV-Tools/src/source/opt/pass.h index a8c9c4b43c1..a1192079225 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/pass.h +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/pass.h @@ -71,6 +71,10 @@ class Pass { return context()->get_def_use_mgr(); } + analysis::DebugInfoManager* get_debug_info_mgr() const { + return context()->get_debug_info_mgr(); + } + analysis::DecorationManager* get_decoration_mgr() const { return context()->get_decoration_mgr(); } diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/ssa_rewrite_pass.cpp b/chromium/third_party/SPIRV-Tools/src/source/opt/ssa_rewrite_pass.cpp index 69c3a1f3edc..6eed1fdd465 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/ssa_rewrite_pass.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/ssa_rewrite_pass.cpp @@ -307,6 +307,7 @@ void SSARewriter::ProcessStore(Instruction* inst, BasicBlock* bb) { } if (pass_->IsTargetVar(var_id)) { WriteVariable(var_id, bb, val_id); + pass_->get_debug_info_mgr()->AddDebugValue(inst, var_id, val_id, inst); #if SSA_REWRITE_DEBUGGING_LEVEL > 1 std::cerr << "\tFound store '%" << var_id << " = %" << val_id << "': " @@ -437,6 +438,8 @@ bool SSARewriter::ApplyReplacements() { // Add Phi instructions from completed Phi candidates. std::vector<Instruction*> generated_phis; + // Add DebugValue instructions for Phi instructions. + std::vector<Instruction*> dbg_values_for_phis; for (const PhiCandidate* phi_candidate : phis_to_generate_) { #if SSA_REWRITE_DEBUGGING_LEVEL > 2 std::cerr << "Phi candidate: " << phi_candidate->PrettyPrint(pass_->cfg()) @@ -447,9 +450,10 @@ bool SSARewriter::ApplyReplacements() { "Tried to instantiate a Phi instruction from an incomplete Phi " "candidate"); + auto* local_var = pass_->get_def_use_mgr()->GetDef(phi_candidate->var_id()); + // Build the vector of operands for the new OpPhi instruction. - uint32_t type_id = pass_->GetPointeeTypeId( - pass_->get_def_use_mgr()->GetDef(phi_candidate->var_id())); + uint32_t type_id = pass_->GetPointeeTypeId(local_var); std::vector<Operand> phi_operands; uint32_t arg_ix = 0; std::unordered_map<uint32_t, uint32_t> already_seen; @@ -479,11 +483,17 @@ bool SSARewriter::ApplyReplacements() { pass_->get_def_use_mgr()->AnalyzeInstDef(&*phi_inst); pass_->context()->set_instr_block(&*phi_inst, phi_candidate->bb()); auto insert_it = phi_candidate->bb()->begin(); - insert_it.InsertBefore(std::move(phi_inst)); + insert_it = insert_it.InsertBefore(std::move(phi_inst)); pass_->context()->get_decoration_mgr()->CloneDecorations( phi_candidate->var_id(), phi_candidate->result_id(), {SpvDecorationRelaxedPrecision}); + // Add DebugValue for the new OpPhi instruction. + insert_it->SetDebugScope(local_var->GetDebugScope()); + pass_->get_debug_info_mgr()->AddDebugValue( + &*insert_it, phi_candidate->var_id(), phi_candidate->result_id(), + &*insert_it); + modified = true; } @@ -604,6 +614,8 @@ Pass::Status SSARewriter::RewriteFunctionIntoSSA(Function* fp) { << fp->PrettyPrint(0) << "\n"; #endif + if (modified) pass_->context()->KillDebugDeclareInsts(fp); + return modified ? Pass::Status::SuccessWithChange : Pass::Status::SuccessWithoutChange; } diff --git a/chromium/third_party/SPIRV-Tools/src/source/opt/ssa_rewrite_pass.h b/chromium/third_party/SPIRV-Tools/src/source/opt/ssa_rewrite_pass.h index bbe89c8afa0..bbbfebbf145 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/opt/ssa_rewrite_pass.h +++ b/chromium/third_party/SPIRV-Tools/src/source/opt/ssa_rewrite_pass.h @@ -39,8 +39,7 @@ namespace opt { // (https://link.springer.com/chapter/10.1007/978-3-642-37051-9_6) class SSARewriter { public: - SSARewriter(MemPass* pass) - : pass_(pass), first_phi_id_(pass_->get_module()->IdBound()) {} + SSARewriter(MemPass* pass) : pass_(pass) {} // Rewrites SSA-target variables in function |fp| into SSA. This is the // entry point for the SSA rewrite algorithm. SSA-target variables are @@ -287,10 +286,6 @@ class SSARewriter { // Memory pass requesting the SSA rewriter. MemPass* pass_; - - // ID of the first Phi created by the SSA rewriter. During rewriting, any - // ID bigger than this corresponds to a Phi candidate. - uint32_t first_phi_id_; }; class SSARewritePass : public MemPass { diff --git a/chromium/third_party/SPIRV-Tools/src/source/reduce/CMakeLists.txt b/chromium/third_party/SPIRV-Tools/src/source/reduce/CMakeLists.txt index 51e9b1d0ffd..d945bd20a60 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/reduce/CMakeLists.txt +++ b/chromium/third_party/SPIRV-Tools/src/source/reduce/CMakeLists.txt @@ -26,12 +26,14 @@ set(SPIRV_TOOLS_REDUCE_SOURCES reduction_util.h remove_block_reduction_opportunity.h remove_block_reduction_opportunity_finder.h - remove_instruction_reduction_opportunity.h remove_function_reduction_opportunity.h remove_function_reduction_opportunity_finder.h + remove_instruction_reduction_opportunity.h remove_selection_reduction_opportunity.h remove_selection_reduction_opportunity_finder.h - remove_unreferenced_instruction_reduction_opportunity_finder.h + remove_struct_member_reduction_opportunity.h + remove_unused_instruction_reduction_opportunity_finder.h + remove_unused_struct_member_reduction_opportunity_finder.h structured_loop_to_selection_reduction_opportunity.h structured_loop_to_selection_reduction_opportunity_finder.h conditional_branch_to_simple_conditional_branch_opportunity_finder.h @@ -57,7 +59,9 @@ set(SPIRV_TOOLS_REDUCE_SOURCES remove_instruction_reduction_opportunity.cpp remove_selection_reduction_opportunity.cpp remove_selection_reduction_opportunity_finder.cpp - remove_unreferenced_instruction_reduction_opportunity_finder.cpp + remove_struct_member_reduction_opportunity.cpp + remove_unused_instruction_reduction_opportunity_finder.cpp + remove_unused_struct_member_reduction_opportunity_finder.cpp structured_loop_to_selection_reduction_opportunity.cpp structured_loop_to_selection_reduction_opportunity_finder.cpp conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp diff --git a/chromium/third_party/SPIRV-Tools/src/source/reduce/pch_source_reduce.h b/chromium/third_party/SPIRV-Tools/src/source/reduce/pch_source_reduce.h index 6c0da0c7b52..81bed2086b5 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/reduce/pch_source_reduce.h +++ b/chromium/third_party/SPIRV-Tools/src/source/reduce/pch_source_reduce.h @@ -20,4 +20,4 @@ #include "source/reduce/reduction_opportunity.h" #include "source/reduce/reduction_pass.h" #include "source/reduce/remove_instruction_reduction_opportunity.h" -#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h" +#include "source/reduce/remove_unused_instruction_reduction_opportunity_finder.h" diff --git a/chromium/third_party/SPIRV-Tools/src/source/reduce/reducer.cpp b/chromium/third_party/SPIRV-Tools/src/source/reduce/reducer.cpp index bda41ce9459..092d4090746 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/reduce/reducer.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/reduce/reducer.cpp @@ -25,7 +25,8 @@ #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_unreferenced_instruction_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 "source/spirv_reducer_options.h" @@ -103,8 +104,8 @@ Reducer::ReductionResultStatus Reducer::Run( void Reducer::AddDefaultReductionPasses() { AddReductionPass( - spvtools::MakeUnique< - RemoveUnreferencedInstructionReductionOpportunityFinder>(false)); + spvtools::MakeUnique<RemoveUnusedInstructionReductionOpportunityFinder>( + false)); AddReductionPass( spvtools::MakeUnique<OperandToUndefReductionOpportunityFinder>()); AddReductionPass( @@ -126,12 +127,14 @@ void Reducer::AddDefaultReductionPasses() { ConditionalBranchToSimpleConditionalBranchOpportunityFinder>()); AddReductionPass( spvtools::MakeUnique<SimpleConditionalBranchToBranchOpportunityFinder>()); + AddReductionPass(spvtools::MakeUnique< + RemoveUnusedStructMemberReductionOpportunityFinder>()); // Cleanup passes. AddCleanupReductionPass( - spvtools::MakeUnique< - RemoveUnreferencedInstructionReductionOpportunityFinder>(true)); + spvtools::MakeUnique<RemoveUnusedInstructionReductionOpportunityFinder>( + true)); } void Reducer::AddReductionPass( diff --git a/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_instruction_reduction_opportunity.cpp b/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_instruction_reduction_opportunity.cpp index 9ca093bf3ef..8026204f516 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_instruction_reduction_opportunity.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_instruction_reduction_opportunity.cpp @@ -22,6 +22,18 @@ namespace reduce { bool RemoveInstructionReductionOpportunity::PreconditionHolds() { return true; } void RemoveInstructionReductionOpportunity::Apply() { + const uint32_t kNumEntryPointInOperandsBeforeInterfaceIds = 3; + for (auto& entry_point : inst_->context()->module()->entry_points()) { + opt::Instruction::OperandList new_entry_point_in_operands; + for (uint32_t index = 0; index < entry_point.NumInOperands(); index++) { + if (index >= kNumEntryPointInOperandsBeforeInterfaceIds && + entry_point.GetSingleWordInOperand(index) == inst_->result_id()) { + continue; + } + new_entry_point_in_operands.push_back(entry_point.GetInOperand(index)); + } + entry_point.SetInOperands(std::move(new_entry_point_in_operands)); + } inst_->context()->KillInst(inst_); } diff --git a/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_struct_member_reduction_opportunity.cpp b/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_struct_member_reduction_opportunity.cpp new file mode 100644 index 00000000000..787c629b7aa --- /dev/null +++ b/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_struct_member_reduction_opportunity.cpp @@ -0,0 +1,208 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/reduce/remove_struct_member_reduction_opportunity.h" + +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace reduce { + +bool RemoveStructMemberReductionOpportunity::PreconditionHolds() { + return struct_type_->NumInOperands() == original_number_of_members_; +} + +void RemoveStructMemberReductionOpportunity::Apply() { + std::set<opt::Instruction*> decorations_to_kill; + + // We need to remove decorations that target the removed struct member, and + // adapt decorations that target later struct members by decrementing the + // member identifier. We also need to adapt composite construction + // instructions so that no id is provided for the member being removed. + // + // To do this, we consider every use of the struct type. + struct_type_->context()->get_def_use_mgr()->ForEachUse( + struct_type_, [this, &decorations_to_kill](opt::Instruction* user, + uint32_t /*operand_index*/) { + switch (user->opcode()) { + case SpvOpCompositeConstruct: + case SpvOpConstantComposite: + // This use is constructing a composite of the struct type, so we + // must remove the id that was provided for the member we are + // removing. + user->RemoveInOperand(member_index_); + break; + case SpvOpMemberDecorate: + // This use is decorating a member of the struct. + if (user->GetSingleWordInOperand(1) == member_index_) { + // The member we are removing is being decorated, so we record + // that we need to get rid of the decoration. + decorations_to_kill.insert(user); + } else if (user->GetSingleWordInOperand(1) > member_index_) { + // A member beyond the one we are removing is being decorated, so + // we adjust the index that identifies the member. + user->SetInOperand(1, {user->GetSingleWordInOperand(1) - 1}); + } + break; + default: + break; + } + }); + + // Get rid of all the decorations that were found to target the member being + // removed. + for (auto decoration_to_kill : decorations_to_kill) { + decoration_to_kill->context()->KillInst(decoration_to_kill); + } + + // We now look through all instructions that access composites via sequences + // of indices. Every time we find an index into the struct whose member is + // being removed, and if the member being accessed comes after the member + // being removed, we need to adjust the index accordingly. + // + // We go through every relevant instruction in every block of every function, + // and invoke a helper to adjust it. + auto context = struct_type_->context(); + for (auto& function : *context->module()) { + for (auto& block : function) { + for (auto& inst : block) { + switch (inst.opcode()) { + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: { + // These access chain instructions take sequences of ids for + // indexing, starting from input operand 1. + auto composite_type_id = + context->get_def_use_mgr() + ->GetDef(context->get_def_use_mgr() + ->GetDef(inst.GetSingleWordInOperand(0)) + ->type_id()) + ->GetSingleWordInOperand(1); + AdjustAccessedIndices(composite_type_id, 1, false, context, &inst); + } break; + case SpvOpPtrAccessChain: + case SpvOpInBoundsPtrAccessChain: { + // These access chain instructions take sequences of ids for + // indexing, starting from input operand 2. + auto composite_type_id = + context->get_def_use_mgr() + ->GetDef(context->get_def_use_mgr() + ->GetDef(inst.GetSingleWordInOperand(1)) + ->type_id()) + ->GetSingleWordInOperand(1); + AdjustAccessedIndices(composite_type_id, 2, false, context, &inst); + } break; + case SpvOpCompositeExtract: { + // OpCompositeExtract uses literals for indexing, starting at input + // operand 1. + auto composite_type_id = + context->get_def_use_mgr() + ->GetDef(inst.GetSingleWordInOperand(0)) + ->type_id(); + AdjustAccessedIndices(composite_type_id, 1, true, context, &inst); + } break; + case SpvOpCompositeInsert: { + // OpCompositeInsert uses literals for indexing, starting at input + // operand 2. + auto composite_type_id = + context->get_def_use_mgr() + ->GetDef(inst.GetSingleWordInOperand(1)) + ->type_id(); + AdjustAccessedIndices(composite_type_id, 2, true, context, &inst); + } break; + default: + break; + } + } + } + } + + // Remove the member from the struct type. + struct_type_->RemoveInOperand(member_index_); +} + +void RemoveStructMemberReductionOpportunity::AdjustAccessedIndices( + uint32_t composite_type_id, uint32_t first_index_input_operand, + bool literal_indices, opt::IRContext* context, + opt::Instruction* composite_access_instruction) const { + // Walk the series of types that are encountered by following the + // instruction's sequence of indices. For all types except structs, this is + // routine: the type of the composite dictates what the next type will be + // regardless of the specific index value. + uint32_t next_type = composite_type_id; + for (uint32_t i = first_index_input_operand; + i < composite_access_instruction->NumInOperands(); i++) { + auto type_inst = context->get_def_use_mgr()->GetDef(next_type); + switch (type_inst->opcode()) { + case SpvOpTypeArray: + case SpvOpTypeMatrix: + case SpvOpTypeRuntimeArray: + case SpvOpTypeVector: + next_type = type_inst->GetSingleWordInOperand(0); + break; + case SpvOpTypeStruct: { + // Struct types are special becuase (a) we may need to adjust the index + // being used, if the struct type is the one from which we are removing + // a member, and (b) the type encountered by following the current index + // is dependent on the value of the index. + + // Work out the member being accessed. If literal indexing is used this + // is simple; otherwise we need to look up the id of the constant + // instruction being used as an index and get the value of the constant. + uint32_t index_operand = + composite_access_instruction->GetSingleWordInOperand(i); + uint32_t member = literal_indices ? index_operand + : context->get_def_use_mgr() + ->GetDef(index_operand) + ->GetSingleWordInOperand(0); + + // The next type we will consider is obtained by looking up the struct + // type at |member|. + next_type = type_inst->GetSingleWordInOperand(member); + + if (type_inst == struct_type_ && member > member_index_) { + // The struct type is the struct from which we are removing a member, + // and the member being accessed is beyond the member we are removing. + // We thus need to decrement the index by 1. + uint32_t new_in_operand; + if (literal_indices) { + // With literal indexing this is straightforward. + new_in_operand = member - 1; + } else { + // With id-based indexing this is more tricky: we need to find or + // create a constant instruction whose value is one less than + // |member|, and use the id of this constant as the replacement + // input operand. + auto constant_inst = + context->get_def_use_mgr()->GetDef(index_operand); + auto int_type = context->get_type_mgr() + ->GetType(constant_inst->type_id()) + ->AsInteger(); + auto new_index_constant = + opt::analysis::IntConstant(int_type, {member - 1}); + new_in_operand = context->get_constant_mgr() + ->GetDefiningInstruction(&new_index_constant) + ->result_id(); + } + composite_access_instruction->SetInOperand(i, {new_in_operand}); + } + } break; + default: + assert(0 && "Unknown composite type."); + break; + } + } +} + +} // namespace reduce +} // namespace spvtools diff --git a/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_struct_member_reduction_opportunity.h b/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_struct_member_reduction_opportunity.h new file mode 100644 index 00000000000..899e5ea7760 --- /dev/null +++ b/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_struct_member_reduction_opportunity.h @@ -0,0 +1,84 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_REDUCE_REMOVE_STRUCT_MEMBER_REDUCTION_OPPORTUNITY_H_ +#define SOURCE_REDUCE_REMOVE_STRUCT_MEMBER_REDUCTION_OPPORTUNITY_H_ + +#include "source/reduce/reduction_opportunity.h" + +#include "source/opt/instruction.h" + +namespace spvtools { +namespace reduce { + +// An opportunity for removing a member from a struct type, adjusting all uses +// of the struct accordingly. +class RemoveStructMemberReductionOpportunity : public ReductionOpportunity { + public: + // Constructs a reduction opportunity from the struct type |struct_type|, for + // removal of member |member_index|. + RemoveStructMemberReductionOpportunity(opt::Instruction* struct_type, + uint32_t member_index) + : struct_type_(struct_type), + member_index_(member_index), + original_number_of_members_(struct_type->NumInOperands()) {} + + // Opportunities to remove fields from a common struct type mutually + // invalidate each other. We guard against this by requiring that the struct + // still has the number of members it had when the opportunity was created. + bool PreconditionHolds() override; + + protected: + void Apply() override; + + private: + // |composite_access_instruction| is an instruction that accesses a composite + // id using either a series of literal indices (e.g. in the case of + // OpCompositeInsert) or a series of index ids (e.g. in the case of + // OpAccessChain). + // + // This function adjusts the indices that are used by + // |composite_access_instruction| to that whenever an index is accessing a + // member of |struct_type_|, it is decremented if the member is beyond + // |member_index_|, to account for the removal of the |member_index_|-th + // member. + // + // |composite_type_id| is the id of the composite type that the series of + // indices is to be applied to. + // + // |first_index_input_operand| specifies the first input operand that is an + // index. + // + // |literal_indices| specifies whether indices are given as literals (true), + // or as ids (false). + // + // If id-based indexing is used, this function will add a constant for + // |member_index_| - 1 to the module if needed. + void AdjustAccessedIndices( + uint32_t composite_type_id, uint32_t first_index_input_operand, + bool literal_indices, opt::IRContext* context, + opt::Instruction* composite_access_instruction) const; + + // The struct type from which a member is to be removed. + opt::Instruction* struct_type_; + + uint32_t member_index_; + + uint32_t original_number_of_members_; +}; + +} // namespace reduce +} // namespace spvtools + +#endif // SOURCE_REDUCE_REMOVE_STRUCT_MEMBER_REDUCTION_OPPORTUNITY_H_ diff --git a/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp b/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp index ce66691155f..91ec542c6f1 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h" +#include "source/reduce/remove_unused_instruction_reduction_opportunity_finder.h" #include "source/opcode.h" #include "source/opt/instruction.h" @@ -21,14 +21,14 @@ namespace spvtools { namespace reduce { -RemoveUnreferencedInstructionReductionOpportunityFinder:: - RemoveUnreferencedInstructionReductionOpportunityFinder( +RemoveUnusedInstructionReductionOpportunityFinder:: + RemoveUnusedInstructionReductionOpportunityFinder( bool remove_constants_and_undefs) : remove_constants_and_undefs_(remove_constants_and_undefs) {} std::vector<std::unique_ptr<ReductionOpportunity>> -RemoveUnreferencedInstructionReductionOpportunityFinder:: - GetAvailableOpportunities(opt::IRContext* context) const { +RemoveUnusedInstructionReductionOpportunityFinder::GetAvailableOpportunities( + opt::IRContext* context) const { std::vector<std::unique_ptr<ReductionOpportunity>> result; for (auto& inst : context->module()->debugs1()) { @@ -60,13 +60,14 @@ RemoveUnreferencedInstructionReductionOpportunityFinder:: } for (auto& inst : context->module()->types_values()) { - if (context->get_def_use_mgr()->NumUsers(&inst) > 0) { - continue; - } if (!remove_constants_and_undefs_ && spvOpcodeIsConstantOrUndef(inst.opcode())) { continue; } + if (!OnlyReferencedByIntimateDecorationOrEntryPointInterface(context, + inst)) { + continue; + } result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst)); } @@ -74,38 +75,9 @@ RemoveUnreferencedInstructionReductionOpportunityFinder:: if (context->get_def_use_mgr()->NumUsers(&inst) > 0) { continue; } - - uint32_t decoration = SpvDecorationMax; - switch (inst.opcode()) { - case SpvOpDecorate: - case SpvOpDecorateId: - case SpvOpDecorateString: - decoration = inst.GetSingleWordInOperand(1u); - break; - case SpvOpMemberDecorate: - case SpvOpMemberDecorateString: - decoration = inst.GetSingleWordInOperand(2u); - break; - default: - break; - } - - // We conservatively only remove specific decorations that we believe will - // not change the shader interface, will not make the shader invalid, will - // actually be found in practice, etc. - - switch (decoration) { - case SpvDecorationRelaxedPrecision: - case SpvDecorationNoSignedWrap: - case SpvDecorationNoContraction: - case SpvDecorationNoUnsignedWrap: - case SpvDecorationUserSemantic: - break; - default: - // Give up. - continue; + if (!IsIndependentlyRemovableDecoration(inst)) { + continue; } - result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst)); } @@ -139,9 +111,54 @@ RemoveUnreferencedInstructionReductionOpportunityFinder:: return result; } -std::string RemoveUnreferencedInstructionReductionOpportunityFinder::GetName() - const { - return "RemoveUnreferencedInstructionReductionOpportunityFinder"; +std::string RemoveUnusedInstructionReductionOpportunityFinder::GetName() const { + return "RemoveUnusedInstructionReductionOpportunityFinder"; +} + +bool RemoveUnusedInstructionReductionOpportunityFinder:: + OnlyReferencedByIntimateDecorationOrEntryPointInterface( + opt::IRContext* context, const opt::Instruction& inst) const { + return context->get_def_use_mgr()->WhileEachUse( + &inst, [this](opt::Instruction* user, uint32_t use_index) -> bool { + return (user->IsDecoration() && + !IsIndependentlyRemovableDecoration(*user)) || + (user->opcode() == SpvOpEntryPoint && use_index > 2); + }); +} + +bool RemoveUnusedInstructionReductionOpportunityFinder:: + IsIndependentlyRemovableDecoration(const opt::Instruction& inst) const { + uint32_t decoration; + switch (inst.opcode()) { + case SpvOpDecorate: + case SpvOpDecorateId: + case SpvOpDecorateString: + decoration = inst.GetSingleWordInOperand(1u); + break; + case SpvOpMemberDecorate: + case SpvOpMemberDecorateString: + decoration = inst.GetSingleWordInOperand(2u); + break; + default: + // The instruction is not a decoration. It is legitimate for this to be + // reached: it allows the method to be invoked on arbitrary instructions. + return false; + } + + // We conservatively only remove specific decorations that we believe will + // not change the shader interface, will not make the shader invalid, will + // actually be found in practice, etc. + + switch (decoration) { + case SpvDecorationRelaxedPrecision: + case SpvDecorationNoSignedWrap: + case SpvDecorationNoContraction: + case SpvDecorationNoUnsignedWrap: + case SpvDecorationUserSemantic: + return true; + default: + return false; + } } } // namespace reduce diff --git a/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h b/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h index bc4f137f130..cbf6a5bd169 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h +++ b/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h @@ -21,17 +21,19 @@ namespace spvtools { namespace reduce { // A finder for opportunities to remove non-control-flow instructions in blocks -// in cases where the instruction's id is not referenced. As well as making the -// module smaller, removing an instruction that references particular ids may -// create opportunities for subsequently removing the instructions that +// in cases where the instruction's id is either not referenced at all, or +// referenced only in a trivial manner (for example, we regard a struct type as +// unused if it is referenced only by struct layout decorations). As well as +// making the module smaller, removing an instruction that references particular +// ids may create opportunities for subsequently removing the instructions that // generated those ids. -class RemoveUnreferencedInstructionReductionOpportunityFinder +class RemoveUnusedInstructionReductionOpportunityFinder : public ReductionOpportunityFinder { public: - explicit RemoveUnreferencedInstructionReductionOpportunityFinder( + explicit RemoveUnusedInstructionReductionOpportunityFinder( bool remove_constants_and_undefs); - ~RemoveUnreferencedInstructionReductionOpportunityFinder() override = default; + ~RemoveUnusedInstructionReductionOpportunityFinder() override = default; std::string GetName() const final; @@ -39,6 +41,17 @@ class RemoveUnreferencedInstructionReductionOpportunityFinder opt::IRContext* context) const final; private: + // Returns true if and only if the only uses of |inst| are by decorations that + // relate intimately to the instruction (as opposed to decorations that could + // be removed independently), or by interface ids in OpEntryPoint. + bool OnlyReferencedByIntimateDecorationOrEntryPointInterface( + opt::IRContext* context, const opt::Instruction& inst) const; + + // Returns true if and only if |inst| is a decoration instruction that can + // legitimately be removed on its own (rather than one that has to be removed + // simultaneously with other instructions). + bool IsIndependentlyRemovableDecoration(const opt::Instruction& inst) const; + bool remove_constants_and_undefs_; }; diff --git a/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp b/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp new file mode 100644 index 00000000000..39ce47f311a --- /dev/null +++ b/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp @@ -0,0 +1,193 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h" + +#include <map> +#include <set> + +#include "source/reduce/remove_struct_member_reduction_opportunity.h" + +namespace spvtools { +namespace reduce { + +std::vector<std::unique_ptr<ReductionOpportunity>> +RemoveUnusedStructMemberReductionOpportunityFinder::GetAvailableOpportunities( + opt::IRContext* context) const { + std::vector<std::unique_ptr<ReductionOpportunity>> result; + + // We track those struct members that are never accessed. We do this by + // associating a member index to all the structs that have this member index + // but do not use it. This representation is designed to allow reduction + // opportunities to be provided in a useful manner, so that opportunities + // associated with the same struct are unlikely to be adjacent. + std::map<uint32_t, std::set<opt::Instruction*>> unused_member_to_structs; + + // Consider every struct type in the module. + for (auto& type_or_value : context->types_values()) { + if (type_or_value.opcode() != SpvOpTypeStruct) { + continue; + } + + // Initially, we assume that *every* member of the struct is unused. We + // then refine this based on observed uses. + std::set<uint32_t> unused_members; + for (uint32_t i = 0; i < type_or_value.NumInOperands(); i++) { + unused_members.insert(i); + } + + // A separate reduction pass deals with removal of names. If a struct + // member is still named, we treat it as being used. + context->get_def_use_mgr()->ForEachUse( + &type_or_value, + [&unused_members](opt::Instruction* user, uint32_t /*operand_index*/) { + switch (user->opcode()) { + case SpvOpMemberName: + unused_members.erase(user->GetSingleWordInOperand(1)); + break; + default: + break; + } + }); + + for (uint32_t member : unused_members) { + if (!unused_member_to_structs.count(member)) { + unused_member_to_structs.insert( + {member, std::set<opt::Instruction*>()}); + } + unused_member_to_structs.at(member).insert(&type_or_value); + } + } + + // We now go through every instruction that might index into a struct, and + // refine our tracking of which struct members are used based on the struct + // indexing we observe. We cannot just go through all uses of a struct type + // because the type is not necessarily even referenced, e.g. when walking + // arrays of structs. + for (auto& function : *context->module()) { + for (auto& block : function) { + for (auto& inst : block) { + switch (inst.opcode()) { + // For each indexing operation we observe, we invoke a helper to + // remove from our map those struct indices that are found to be used. + // The way the helper is invoked depends on whether the instruction + // uses literal or id indices, and the offset into the instruction's + // input operands from which index operands are provided. + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: { + auto composite_type_id = + context->get_def_use_mgr() + ->GetDef(context->get_def_use_mgr() + ->GetDef(inst.GetSingleWordInOperand(0)) + ->type_id()) + ->GetSingleWordInOperand(1); + MarkAccessedMembersAsUsed(context, composite_type_id, 1, false, + inst, &unused_member_to_structs); + } break; + case SpvOpPtrAccessChain: + case SpvOpInBoundsPtrAccessChain: { + auto composite_type_id = + context->get_def_use_mgr() + ->GetDef(context->get_def_use_mgr() + ->GetDef(inst.GetSingleWordInOperand(1)) + ->type_id()) + ->GetSingleWordInOperand(1); + MarkAccessedMembersAsUsed(context, composite_type_id, 2, false, + inst, &unused_member_to_structs); + } break; + case SpvOpCompositeExtract: { + auto composite_type_id = + context->get_def_use_mgr() + ->GetDef(inst.GetSingleWordInOperand(0)) + ->type_id(); + MarkAccessedMembersAsUsed(context, composite_type_id, 1, true, inst, + &unused_member_to_structs); + } break; + case SpvOpCompositeInsert: { + auto composite_type_id = + context->get_def_use_mgr() + ->GetDef(inst.GetSingleWordInOperand(1)) + ->type_id(); + MarkAccessedMembersAsUsed(context, composite_type_id, 2, true, inst, + &unused_member_to_structs); + } break; + default: + break; + } + } + } + } + + // We now know those struct indices that are unsed, and we make a reduction + // opportunity for each of them. By mapping each relevant member index to the + // structs in which it is unsed, we will group all opportunities to remove + // member k of a struct (for some k) together. This reduces the likelihood + // that opportunities to remove members from the same struct will be adjacent, + // which is good because such opportunities mutually disable one another. + for (auto& entry : unused_member_to_structs) { + for (auto struct_type : entry.second) { + result.push_back(MakeUnique<RemoveStructMemberReductionOpportunity>( + struct_type, entry.first)); + } + } + return result; +} + +void RemoveUnusedStructMemberReductionOpportunityFinder:: + MarkAccessedMembersAsUsed( + opt::IRContext* context, uint32_t composite_type_id, + uint32_t first_index_in_operand, bool literal_indices, + const opt::Instruction& composite_access_instruction, + std::map<uint32_t, std::set<opt::Instruction*>>* + unused_member_to_structs) const { + uint32_t next_type = composite_type_id; + for (uint32_t i = first_index_in_operand; + i < composite_access_instruction.NumInOperands(); i++) { + auto type_inst = context->get_def_use_mgr()->GetDef(next_type); + switch (type_inst->opcode()) { + case SpvOpTypeArray: + case SpvOpTypeMatrix: + case SpvOpTypeRuntimeArray: + case SpvOpTypeVector: + next_type = type_inst->GetSingleWordInOperand(0); + break; + case SpvOpTypeStruct: { + uint32_t index_operand = + composite_access_instruction.GetSingleWordInOperand(i); + uint32_t member = literal_indices ? index_operand + : context->get_def_use_mgr() + ->GetDef(index_operand) + ->GetSingleWordInOperand(0); + // Remove the struct type from the struct types associated with this + // member index, but only if a set of struct types is known to be + // associated with this member index. + if (unused_member_to_structs->count(member)) { + unused_member_to_structs->at(member).erase(type_inst); + } + next_type = type_inst->GetSingleWordInOperand(member); + } break; + default: + assert(0 && "Unknown composite type."); + break; + } + } +} + +std::string RemoveUnusedStructMemberReductionOpportunityFinder::GetName() + const { + return "RemoveUnusedStructMemberReductionOpportunityFinder"; +} + +} // namespace reduce +} // namespace spvtools diff --git a/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h b/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h new file mode 100644 index 00000000000..13f40172bfd --- /dev/null +++ b/chromium/third_party/SPIRV-Tools/src/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h @@ -0,0 +1,61 @@ +// Copyright (c) 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_REDUCE_REMOVE_UNUSED_STRUCT_MEMBER_REDUCTION_OPPORTUNITY_FINDER_H_ +#define SOURCE_REDUCE_REMOVE_UNUSED_STRUCT_MEMBER_REDUCTION_OPPORTUNITY_FINDER_H_ + +#include "source/reduce/reduction_opportunity_finder.h" + +namespace spvtools { +namespace reduce { + +// A finder for opportunities to remove struct members that are not explicitly +// used by extract, insert or access chain instructions. +class RemoveUnusedStructMemberReductionOpportunityFinder + : public ReductionOpportunityFinder { + public: + RemoveUnusedStructMemberReductionOpportunityFinder() = default; + + ~RemoveUnusedStructMemberReductionOpportunityFinder() override = default; + + std::string GetName() const final; + + std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities( + opt::IRContext* context) const final; + + private: + // A helper method to update |unused_members_to_structs| by removing from it + // all struct member accesses that take place in + // |composite_access_instruction|. + // + // |composite_type_id| is the type of the root object indexed into by the + // instruction. + // + // |first_index_in_operand| provides indicates where in the input operands the + // sequence of indices begins. + // + // |literal_indices| indicates whether indices are literals (true) or ids + // (false). + void MarkAccessedMembersAsUsed( + opt::IRContext* context, uint32_t composite_type_id, + uint32_t first_index_in_operand, bool literal_indices, + const opt::Instruction& composite_access_instruction, + std::map<uint32_t, std::set<opt::Instruction*>>* unused_member_to_structs) + const; +}; + +} // namespace reduce +} // namespace spvtools + +#endif // SOURCE_REDUCE_REMOVE_UNUSED_STRUCT_MEMBER_REDUCTION_OPPORTUNITY_FINDER_H_ diff --git a/chromium/third_party/SPIRV-Tools/src/source/val/validate_extensions.cpp b/chromium/third_party/SPIRV-Tools/src/source/val/validate_extensions.cpp index 1e311c19d0f..7ce681c4f85 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/val/validate_extensions.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/val/validate_extensions.cpp @@ -2300,7 +2300,14 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name); if (validate_parent != SPV_SUCCESS) return validate_parent; CHECK_OPERAND("Linkage Name", SpvOpString, 11); - CHECK_OPERAND("Size", SpvOpConstant, 12); + if (!DoesDebugInfoOperandMatchExpectation( + _, + [](OpenCLDebugInfo100Instructions dbg_inst) { + return dbg_inst == OpenCLDebugInfo100DebugInfoNone; + }, + inst, 12)) { + CHECK_OPERAND("Size", SpvOpConstant, 12); + } for (uint32_t word_index = 14; word_index < num_words; ++word_index) { if (!DoesDebugInfoOperandMatchExpectation( _, diff --git a/chromium/third_party/SPIRV-Tools/src/source/val/validate_function.cpp b/chromium/third_party/SPIRV-Tools/src/source/val/validate_function.cpp index f130eacdad2..596186bbe6a 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/val/validate_function.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/val/validate_function.cpp @@ -71,6 +71,7 @@ spv_result_t ValidateFunction(ValidationState_t& _, const Instruction* inst) { } const std::vector<SpvOp> acceptable = { + SpvOpGroupDecorate, SpvOpDecorate, SpvOpEnqueueKernel, SpvOpEntryPoint, diff --git a/chromium/third_party/SPIRV-Tools/src/source/val/validate_image.cpp b/chromium/third_party/SPIRV-Tools/src/source/val/validate_image.cpp index 5b77058c89e..9ce74a3d0a5 100644 --- a/chromium/third_party/SPIRV-Tools/src/source/val/validate_image.cpp +++ b/chromium/third_party/SPIRV-Tools/src/source/val/validate_image.cpp @@ -160,6 +160,17 @@ bool IsValidLodOperand(const ValidationState_t& _, SpvOp opcode) { } } +bool IsValidGatherLodBiasAMD(const ValidationState_t& _, SpvOp opcode) { + switch (opcode) { + case SpvOpImageGather: + case SpvOpImageSparseGather: + return _.HasCapability(SpvCapabilityImageGatherBiasLodAMD); + default: + break; + } + return false; +} + // Returns true if the opcode is a Image instruction which applies // homogenous projection to the coordinates. bool IsProj(SpvOp opcode) { @@ -260,11 +271,12 @@ spv_result_t ValidateImageOperands(ValidationState_t& _, const bool is_implicit_lod = IsImplicitLod(opcode); const bool is_explicit_lod = IsExplicitLod(opcode); const bool is_valid_lod_operand = IsValidLodOperand(_, opcode); + const bool is_valid_gather_lod_bias_amd = IsValidGatherLodBiasAMD(_, opcode); // The checks should be done in the order of definition of OperandImage. if (mask & SpvImageOperandsBiasMask) { - if (!is_implicit_lod) { + if (!is_implicit_lod && !is_valid_gather_lod_bias_amd) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Image Operand Bias can only be used with ImplicitLod opcodes"; } @@ -290,7 +302,7 @@ spv_result_t ValidateImageOperands(ValidationState_t& _, if (mask & SpvImageOperandsLodMask) { if (!is_valid_lod_operand && opcode != SpvOpImageFetch && - opcode != SpvOpImageSparseFetch) { + opcode != SpvOpImageSparseFetch && !is_valid_gather_lod_bias_amd) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Image Operand Lod can only be used with ExplicitLod opcodes " << "and OpImageFetch"; @@ -303,7 +315,7 @@ spv_result_t ValidateImageOperands(ValidationState_t& _, } const uint32_t type_id = _.GetTypeId(inst->word(word_index++)); - if (is_explicit_lod) { + if (is_explicit_lod || is_valid_gather_lod_bias_amd) { if (!_.IsFloatScalarType(type_id)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Expected Image Operand Lod to be float scalar when used " diff --git a/chromium/third_party/SPIRV-Tools/src/utils/check_symbol_exports.py b/chromium/third_party/SPIRV-Tools/src/utils/check_symbol_exports.py index e14c2eb8964..bcd77da6876 100755 --- a/chromium/third_party/SPIRV-Tools/src/utils/check_symbol_exports.py +++ b/chromium/third_party/SPIRV-Tools/src/utils/check_symbol_exports.py @@ -55,11 +55,11 @@ def check_library(library): # _Z[0-9]+spv[A-Z_] : C++ symbol starting with spv[A-Z_] symbol_ok_pattern = re.compile(r'^(spv[A-Z]|_ZN|_Z[0-9]+spv[A-Z_])') - # In addition, the following pattern whitelists global functions that are added + # In addition, the following pattern allowlists global functions that are added # by the protobuf compiler: # - AddDescriptors_spvtoolsfuzz_2eproto() # - InitDefaults_spvtoolsfuzz_2eproto() - symbol_whitelist_pattern = re.compile(r'_Z[0-9]+(InitDefaults|AddDescriptors)_spvtoolsfuzz_2eprotov') + symbol_allowlist_pattern = re.compile(r'_Z[0-9]+(InitDefaults|AddDescriptors)_spvtoolsfuzz_2eprotov') seen = set() result = 0 @@ -70,7 +70,7 @@ def check_library(library): if symbol not in seen: seen.add(symbol) #print("look at '{}'".format(symbol)) - if not (symbol_whitelist_pattern.match(symbol) or symbol_ok_pattern.match(symbol)): + if not (symbol_allowlist_pattern.match(symbol) or symbol_ok_pattern.match(symbol)): print('{}: error: Unescaped exported symbol: {}'.format(PROG, symbol)) result = 1 return result diff --git a/chromium/third_party/SPIRV-Tools/src/utils/roll_deps.sh b/chromium/third_party/SPIRV-Tools/src/utils/roll_deps.sh index 622afc9ed89..d5c547fe073 100755 --- a/chromium/third_party/SPIRV-Tools/src/utils/roll_deps.sh +++ b/chromium/third_party/SPIRV-Tools/src/utils/roll_deps.sh @@ -13,19 +13,34 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Attempts to roll all entries in DEPS to origin/master and creates a -# commit. +# Attempts to roll all entries in DEPS to tip-of-tree and create a commit. # # Depends on roll-dep from depot_path being in PATH. +effcee_dir="third_party/effcee/" +effcee_trunk="origin/main" +googletest_dir="third_party/googletest/" +googletest_trunk="origin/master" +re2_dir="third_party/re2/" +re2_trunk="origin/master" +spirv_headers_dir="third_party/spirv-headers/" +spirv_headers_trunk="origin/master" + # This script assumes it's parent directory is the repo root. repo_path=$(dirname "$0")/.. -effcee_dir="external/effcee/" -googletest_dir="external/googletest/" -re2_dir="external/re2/" -spirv_headers_dir="external/spirv-headers/" - cd "$repo_path" -roll-dep "$@" "${effcee_dir}" "${googletest_dir}" "${re2_dir}" "${spirv_headers_dir}" +if [[ $(git diff --stat) != '' ]]; then + echo "Working tree is dirty, commit changes before attempting to roll DEPS" + exit 1 +fi + +old_head=$(git rev-parse HEAD) + +roll-dep --ignore-dirty-tree --roll-to="${effcee_trunk}" "${effcee_dir}" +roll-dep --ignore-dirty-tree --roll-to="${googletest_trunk}" "${googletest_dir}" +roll-dep --ignore-dirty-tree --roll-to="${re2_trunk}" "${re2_dir}" +roll-dep --ignore-dirty-tree --roll-to="${spirv_headers_trunk}" "${spirv_headers_dir}" + +git rebase --interactive "${old_head}" |