Squashed 'third_party/SPIRV-Tools/' changes from b930e734e..54cd5e196 54cd5e196 spirv-opt : SPV_NV_bindless_texture related changes (#4870) b5d0bf285 Require ColMajor or RowMajor for matrices (#4878) a90ccc240 Remove default copy constructor in header. (#4879) 4773879b6 Update structure layout validation (#4876) 8dc0030ec spirv-as: Avoid overflow when parsing exponents on hex floats (#4874) cc5fca057 spirv-val: Fix Vulkan memory scope (#4869) 388ce0ee6 spirv-as: Avoid recursion when skipping whitespace (#4866) e4cfa190d spirv-val: Add SPV_KHR_ray_query (#4848) 60615b8ec Implement SPV_NV_bindless_texture related changes (#4847) 93ebf698a spirv-val: Add OpConvertUToAccelerationStructureKHR (#4838) e2cf76930 spirv-val: Label VUID 06925 (#4852) faa8d6a65 Revert "Optimize DefUseManager allocations (#4709)" (#4846) 69e1deabc Fix small bug traversing users in interface_var_sroa (#4850) bc5c8760a spirv-val: Add Vulkan decoration interface (#4831) 05de65037 Use structural reachability in CFG checks (#4849) dcee3a5de Update validator diagnostics with "structurally dominated" (#4844) 5f4284aa7 Add limit for scalar replacment when fuzzing (#4843) 92fe420c8 Reduce load size does not work for array with spec const size (#4845) d5a3bfcf2 Avoid undefined behaviour when getting debug opcode (#4842) 6803cc512 use exec_tools instead of tools for better RBE compatibility (#4837) 32622ba7c DCE: clean up the cfg for all functions that were processed. (#4840) 8ec4a0772 Fix unreachable loop increment (#4836) 286e9c118 Use structural dominance to validate cfg (#4832) 91572e769 Write binary files to stdout in binary on windows. (#4834) 0b824324b Fix segfault in `SpirvTools::Disassemble` when printing (#4833) 66d88508d Build struct order only for the section needed when unrolling. (#4830) f2dfa53ae Avoid unrolling large loops while fuzzing (#4835) 37d2396ca Fix SplitLoopHeader to handle single block loop (#4829) 3c9fd7577 Avoid if-conversion if both predecessors are the same (#4826) c4ed5157d Fixed crash unrolling loops with residual iterations (#4820) 76fe35219 Fail validation when RelaxedPrecision is applied to a type. (#4823) 845d98d46 Do not check if the binary changed if encoding is different (#4824) 4f321f862 Avoid undefined divide-by-0 (#4821) 2eff41e70 Remove stray output to stdout from tests (#4816) fbcb6cf4c Ability to fold Constant Vector times Matrix and Matrix times vector instructions (#4818) bfc611b03 spirv-val: Label 06807 and 06808 VUID (#4817) 76ebfb989 Avoid replacing access chain with OOB access (#4819) 8f7f5024f Simplify invocation of snprintf (#4815) fad68a755 Fix usage of sprintf. (#4811) 044ff1aab spirv-val: Add support for SPV_AMD_shader_early_and_late_fragment_tests (#4812) c94501352 spirv-val: Optimize struct field decoration lookup (#4809) git-subtree-dir: third_party/SPIRV-Tools git-subtree-split: 54cd5e1963b399e6c6d3c5b70da45583d9f9fed8 Change-Id: I3bf0848ac96c1aee7cf29d9428babe18b52af105
diff --git a/Android.mk b/Android.mk index 6dd1834..cd1d7f8 100644 --- a/Android.mk +++ b/Android.mk
@@ -68,6 +68,7 @@ source/val/validate_logicals.cpp \ source/val/validate_non_uniform.cpp \ source/val/validate_primitives.cpp \ + source/val/validate_ray_query.cpp \ source/val/validate_scopes.cpp \ source/val/validate_small_type_uses.cpp \ source/val/validate_type.cpp
diff --git a/BUILD.bazel b/BUILD.bazel index 914619a..35dfd66 100644 --- a/BUILD.bazel +++ b/BUILD.bazel
@@ -82,7 +82,7 @@ outs = ["generators.inc"], cmd = "$(location generate_registry_tables) --xml=$(location @spirv_headers//:spirv_xml_registry) --generator-output=$(location generators.inc)", cmd_bat = "$(location //:generate_registry_tables) --xml=$(location @spirv_headers//:spirv_xml_registry) --generator-output=$(location generators.inc)", - tools = [":generate_registry_tables"], + exec_tools = [":generate_registry_tables"], ) py_binary( @@ -96,7 +96,7 @@ outs = ["build-version.inc"], cmd = "SOURCE_DATE_EPOCH=0 $(location update_build_version) $(location CHANGES) $(location build-version.inc)", cmd_bat = "set SOURCE_DATE_EPOCH=0 && $(location //:update_build_version) $(location CHANGES) $(location build-version.inc)", - tools = [":update_build_version"], + exec_tools = [":update_build_version"], ) # Libraries
diff --git a/BUILD.gn b/BUILD.gn index 9f96c24..9e9f6e5 100644 --- a/BUILD.gn +++ b/BUILD.gn
@@ -466,7 +466,6 @@ "source/util/make_unique.h", "source/util/parse_number.cpp", "source/util/parse_number.h", - "source/util/pooled_linked_list.h", "source/util/small_vector.h", "source/util/string_utils.cpp", "source/util/string_utils.h", @@ -531,6 +530,7 @@ "source/val/validate_mode_setting.cpp", "source/val/validate_non_uniform.cpp", "source/val/validate_primitives.cpp", + "source/val/validate_ray_query.cpp", "source/val/validate_scopes.cpp", "source/val/validate_scopes.h", "source/val/validate_small_type_uses.cpp",
diff --git a/DEPS b/DEPS index 808ef6c..78c9dd3 100644 --- a/DEPS +++ b/DEPS
@@ -6,7 +6,7 @@ 'effcee_revision': 'ddf5e2bb92957dc8a12c5392f8495333d6844133', 'googletest_revision': '548b13dc3c02b93f60eeff9a0cc6e11c1ea722ca', 're2_revision': '5723bb8950318135ed9cf4fc76bed988a087f536', - 'spirv_headers_revision': '5a121866927a16ab9d49bed4788b532c7fcea766', + 'spirv_headers_revision': 'b2a156e1c0434bc8c99aaebba1c7be98be7ac580', } deps = {
diff --git a/build_defs.bzl b/build_defs.bzl index ef9a829..7189137 100644 --- a/build_defs.bzl +++ b/build_defs.bzl
@@ -76,7 +76,7 @@ "--core-insts-output=$(location {3}) " + "--operand-kinds-output=$(location {4})" ).format(*fmtargs), - tools = [":generate_grammar_tables"], + exec_tools = [":generate_grammar_tables"], visibility = ["//visibility:private"], ) @@ -113,7 +113,7 @@ "--extension-enum-output=$(location {3}) " + "--enum-string-mapping-output=$(location {4})" ).format(*fmtargs), - tools = [":generate_grammar_tables"], + exec_tools = [":generate_grammar_tables"], visibility = ["//visibility:private"], ) @@ -139,7 +139,7 @@ "--extinst-opencl-grammar=$(location {0}) " + "--opencl-insts-output=$(location {1})" ).format(*fmtargs), - tools = [":generate_grammar_tables"], + exec_tools = [":generate_grammar_tables"], visibility = ["//visibility:private"], ) @@ -165,7 +165,7 @@ "--extinst-glsl-grammar=$(location {0}) " + "--glsl-insts-output=$(location {1})" ).format(*fmtargs), - tools = [":generate_grammar_tables"], + exec_tools = [":generate_grammar_tables"], visibility = ["//visibility:private"], ) @@ -193,7 +193,7 @@ "--vendor-insts-output=$(location {1}) " + "--vendor-operand-kind-prefix={2}" ).format(*fmtargs), - tools = [":generate_grammar_tables"], + exec_tools = [":generate_grammar_tables"], visibility = ["//visibility:private"], ) @@ -216,7 +216,7 @@ "--extinst-grammar=$< " + "--extinst-output-path=$(location {0})" ).format(*fmtargs), - tools = [":generate_language_headers"], + exec_tools = [":generate_language_headers"], visibility = ["//visibility:private"], )
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 98559b8..1ceb78f 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt
@@ -228,7 +228,6 @@ ${CMAKE_CURRENT_SOURCE_DIR}/util/hex_float.h ${CMAKE_CURRENT_SOURCE_DIR}/util/make_unique.h ${CMAKE_CURRENT_SOURCE_DIR}/util/parse_number.h - ${CMAKE_CURRENT_SOURCE_DIR}/util/pooled_linked_list.h ${CMAKE_CURRENT_SOURCE_DIR}/util/small_vector.h ${CMAKE_CURRENT_SOURCE_DIR}/util/string_utils.h ${CMAKE_CURRENT_SOURCE_DIR}/util/timer.h @@ -323,6 +322,7 @@ ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_mode_setting.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_non_uniform.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_primitives.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_ray_query.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_scopes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_small_type_uses.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_type.cpp
diff --git a/source/cfa.h b/source/cfa.h index 7cadf55..f55a7bd 100644 --- a/source/cfa.h +++ b/source/cfa.h
@@ -68,6 +68,8 @@ /// CFG following postorder traversal semantics /// @param[in] backedge A function that will be called when a backedge is /// encountered during a traversal + /// @param[in] terminal A function that will be called to determine if the + /// search should stop at the given node. /// NOTE: The @p successor_func and predecessor_func each return a pointer to /// a /// collection such that iterators to that collection remain valid for the @@ -76,7 +78,8 @@ const BB* entry, get_blocks_func successor_func, std::function<void(cbb_ptr)> preorder, std::function<void(cbb_ptr)> postorder, - std::function<void(cbb_ptr, cbb_ptr)> backedge); + std::function<void(cbb_ptr, cbb_ptr)> backedge, + std::function<bool(cbb_ptr)> terminal); /// @brief Calculates dominator edges for a set of blocks /// @@ -138,7 +141,8 @@ const BB* entry, get_blocks_func successor_func, std::function<void(cbb_ptr)> preorder, std::function<void(cbb_ptr)> postorder, - std::function<void(cbb_ptr, cbb_ptr)> backedge) { + std::function<void(cbb_ptr, cbb_ptr)> backedge, + std::function<bool(cbb_ptr)> terminal) { std::unordered_set<uint32_t> processed; /// NOTE: work_list is the sequence of nodes from the root node to the node @@ -152,7 +156,7 @@ while (!work_list.empty()) { block_info& top = work_list.back(); - if (top.iter == end(*successor_func(top.block))) { + if (terminal(top.block) || top.iter == end(*successor_func(top.block))) { postorder(top.block); work_list.pop_back(); } else { @@ -266,11 +270,13 @@ auto mark_visited = [&visited](const BB* b) { visited.insert(b); }; auto ignore_block = [](const BB*) {}; auto ignore_blocks = [](const BB*, const BB*) {}; + auto no_terminal_blocks = [](const BB*) { return false; }; auto traverse_from_root = [&mark_visited, &succ_func, &ignore_block, - &ignore_blocks](const BB* entry) { + &ignore_blocks, + &no_terminal_blocks](const BB* entry) { DepthFirstTraversal(entry, succ_func, mark_visited, ignore_block, - ignore_blocks); + ignore_blocks, no_terminal_blocks); }; std::vector<BB*> result;
diff --git a/source/disassemble.h b/source/disassemble.h index 8eacb10..b520a1e 100644 --- a/source/disassemble.h +++ b/source/disassemble.h
@@ -25,9 +25,10 @@ // Decodes the given SPIR-V instruction binary representation to its assembly // text. The context is inferred from the provided module binary. The options -// parameter is a bit field of spv_binary_to_text_options_t. Decoded text will -// be stored into *text. Any error will be written into *diagnostic if -// diagnostic is non-null. +// parameter is a bit field of spv_binary_to_text_options_t (note: the option +// SPV_BINARY_TO_TEXT_OPTION_PRINT will be ignored). Decoded text will be +// stored into *text. Any error will be written into *diagnostic if diagnostic +// is non-null. std::string spvInstructionBinaryToText(const spv_target_env env, const uint32_t* inst_binary, const size_t inst_word_count,
diff --git a/source/libspirv.cpp b/source/libspirv.cpp index 0bc0935..be76caa 100644 --- a/source/libspirv.cpp +++ b/source/libspirv.cpp
@@ -99,7 +99,9 @@ spv_text spvtext = nullptr; spv_result_t status = spvBinaryToText(impl_->context, binary, binary_size, options, &spvtext, nullptr); - if (status == SPV_SUCCESS) { + if (status == SPV_SUCCESS && + (options & SPV_BINARY_TO_TEXT_OPTION_PRINT) == 0) { + assert(spvtext); text->assign(spvtext->str, spvtext->str + spvtext->length); } spvTextDestroy(spvtext);
diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp index 2486242..ffb499f 100644 --- a/source/opt/aggressive_dead_code_elim_pass.cpp +++ b/source/opt/aggressive_dead_code_elim_pass.cpp
@@ -659,9 +659,14 @@ InitializeModuleScopeLiveInstructions(); - // Process all entry point functions. - ProcessFunction pfn = [this](Function* fp) { return AggressiveDCE(fp); }; - modified |= context()->ProcessReachableCallTree(pfn); + // Run |AggressiveDCE| on the remaining functions. The order does not matter, + // since |AggressiveDCE| is intra-procedural. This can mean that function + // will become dead if all function call to them are removed. These dead + // function will still be in the module after this pass. We expect this to be + // rare. + for (Function& fp : *context()->module()) { + modified |= AggressiveDCE(&fp); + } // If the decoration manager is kept live then the context will try to keep it // up to date. ADCE deals with group decorations by changing the operands in @@ -687,8 +692,9 @@ } // Cleanup all CFG including all unreachable blocks. - ProcessFunction cleanup = [this](Function* f) { return CFGCleanup(f); }; - modified |= context()->ProcessReachableCallTree(cleanup); + for (Function& fp : *context()->module()) { + modified |= CFGCleanup(&fp); + } return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; }
diff --git a/source/opt/cfg.cpp b/source/opt/cfg.cpp index ac0fcc3..66b1aed 100644 --- a/source/opt/cfg.cpp +++ b/source/opt/cfg.cpp
@@ -74,6 +74,12 @@ void CFG::ComputeStructuredOrder(Function* func, BasicBlock* root, std::list<BasicBlock*>* order) { + ComputeStructuredOrder(func, root, nullptr, order); +} + +void CFG::ComputeStructuredOrder(Function* func, BasicBlock* root, + BasicBlock* end, + std::list<BasicBlock*>* order) { assert(module_->context()->get_feature_mgr()->HasCapability( SpvCapabilityShader) && "This only works on structured control flow"); @@ -82,6 +88,8 @@ ComputeStructuredSuccessors(func); auto ignore_block = [](cbb_ptr) {}; auto ignore_edge = [](cbb_ptr, cbb_ptr) {}; + auto terminal = [end](cbb_ptr bb) { return bb == end; }; + auto get_structured_successors = [this](const BasicBlock* b) { return &(block2structured_succs_[b]); }; @@ -92,7 +100,8 @@ order->push_front(const_cast<BasicBlock*>(b)); }; CFA<BasicBlock>::DepthFirstTraversal(root, get_structured_successors, - ignore_block, post_order, ignore_edge); + ignore_block, post_order, ignore_edge, + terminal); } void CFG::ForEachBlockInPostOrder(BasicBlock* bb, @@ -205,7 +214,7 @@ // Find the back edge BasicBlock* latch_block = nullptr; Function::iterator latch_block_iter = header_it; - while (++latch_block_iter != fn->end()) { + for (; latch_block_iter != fn->end(); ++latch_block_iter) { // If blocks are in the proper order, then the only branch that appears // after the header is the latch. if (std::find(pred.begin(), pred.end(), latch_block_iter->id()) != @@ -237,6 +246,15 @@ context->set_instr_block(inst, new_header); }); + // If |bb| was the latch block, the branch back to the header is not in + // |new_header|. + if (latch_block == bb) { + if (new_header->ContinueBlockId() == bb->id()) { + new_header->GetLoopMergeInst()->SetInOperand(1, {new_header_id}); + } + latch_block = new_header; + } + // Adjust the OpPhi instructions as needed. bb->ForEachPhiInst([latch_block, bb, new_header, context](Instruction* phi) { std::vector<uint32_t> preheader_phi_ops;
diff --git a/source/opt/cfg.h b/source/opt/cfg.h index 33412f1..fa4fef2 100644 --- a/source/opt/cfg.h +++ b/source/opt/cfg.h
@@ -66,6 +66,14 @@ void ComputeStructuredOrder(Function* func, BasicBlock* root, std::list<BasicBlock*>* order); + // Compute structured block order into |order| for |func| starting at |root| + // and ending at |end|. This order has the property that dominators come + // before all blocks they dominate, merge blocks come after all blocks that + // are in the control constructs of their header, and continue blocks come + // after all the blocks in the body of their loop. + void ComputeStructuredOrder(Function* func, BasicBlock* root, BasicBlock* end, + std::list<BasicBlock*>* order); + // Applies |f| to all blocks that can be reach from |bb| in post order. void ForEachBlockInPostOrder(BasicBlock* bb, const std::function<void(BasicBlock*)>& f);
diff --git a/source/opt/const_folding_rules.cpp b/source/opt/const_folding_rules.cpp index 249e11e..cb36087 100644 --- a/source/opt/const_folding_rules.cpp +++ b/source/opt/const_folding_rules.cpp
@@ -251,6 +251,193 @@ }; } +ConstantFoldingRule FoldVectorTimesMatrix() { + return [](IRContext* context, Instruction* inst, + const std::vector<const analysis::Constant*>& constants) + -> const analysis::Constant* { + assert(inst->opcode() == SpvOpVectorTimesMatrix); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + analysis::TypeManager* type_mgr = context->get_type_mgr(); + + if (!inst->IsFloatingPointFoldingAllowed()) { + if (HasFloatingPoint(type_mgr->GetType(inst->type_id()))) { + return nullptr; + } + } + + const analysis::Constant* c1 = constants[0]; + const analysis::Constant* c2 = constants[1]; + + if (c1 == nullptr || c2 == nullptr) { + return nullptr; + } + + // Check result type. + const analysis::Type* result_type = type_mgr->GetType(inst->type_id()); + const analysis::Vector* vector_type = result_type->AsVector(); + assert(vector_type != nullptr); + const analysis::Type* element_type = vector_type->element_type(); + assert(element_type != nullptr); + const analysis::Float* float_type = element_type->AsFloat(); + assert(float_type != nullptr); + + // Check types of c1 and c2. + assert(c1->type()->AsVector() == vector_type); + assert(c1->type()->AsVector()->element_type() == element_type && + c2->type()->AsMatrix()->element_type() == vector_type); + + // Get a float vector that is the result of vector-times-matrix. + std::vector<const analysis::Constant*> c1_components = + c1->GetVectorComponents(const_mgr); + std::vector<const analysis::Constant*> c2_components = + c2->AsMatrixConstant()->GetComponents(); + uint32_t resultVectorSize = result_type->AsVector()->element_count(); + + std::vector<uint32_t> ids; + + if ((c1 && c1->IsZero()) || (c2 && c2->IsZero())) { + std::vector<uint32_t> words(float_type->width() / 32, 0); + for (uint32_t i = 0; i < resultVectorSize; ++i) { + const analysis::Constant* new_elem = + const_mgr->GetConstant(float_type, words); + ids.push_back(const_mgr->GetDefiningInstruction(new_elem)->result_id()); + } + return const_mgr->GetConstant(vector_type, ids); + } + + if (float_type->width() == 32) { + for (uint32_t i = 0; i < resultVectorSize; ++i) { + float result_scalar = 0.0f; + const analysis::VectorConstant* c2_vec = + c2_components[i]->AsVectorConstant(); + for (uint32_t j = 0; j < c2_vec->GetComponents().size(); ++j) { + float c1_scalar = c1_components[j]->GetFloat(); + float c2_scalar = c2_vec->GetComponents()[j]->GetFloat(); + result_scalar += c1_scalar * c2_scalar; + } + utils::FloatProxy<float> result(result_scalar); + std::vector<uint32_t> words = result.GetWords(); + const analysis::Constant* new_elem = + const_mgr->GetConstant(float_type, words); + ids.push_back(const_mgr->GetDefiningInstruction(new_elem)->result_id()); + } + return const_mgr->GetConstant(vector_type, ids); + } else if (float_type->width() == 64) { + for (uint32_t i = 0; i < c2_components.size(); ++i) { + double result_scalar = 0.0; + const analysis::VectorConstant* c2_vec = + c2_components[i]->AsVectorConstant(); + for (uint32_t j = 0; j < c2_vec->GetComponents().size(); ++j) { + double c1_scalar = c1_components[j]->GetDouble(); + double c2_scalar = c2_vec->GetComponents()[j]->GetDouble(); + result_scalar += c1_scalar * c2_scalar; + } + utils::FloatProxy<double> result(result_scalar); + std::vector<uint32_t> words = result.GetWords(); + const analysis::Constant* new_elem = + const_mgr->GetConstant(float_type, words); + ids.push_back(const_mgr->GetDefiningInstruction(new_elem)->result_id()); + } + return const_mgr->GetConstant(vector_type, ids); + } + return nullptr; + }; +} + +ConstantFoldingRule FoldMatrixTimesVector() { + return [](IRContext* context, Instruction* inst, + const std::vector<const analysis::Constant*>& constants) + -> const analysis::Constant* { + assert(inst->opcode() == SpvOpMatrixTimesVector); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + analysis::TypeManager* type_mgr = context->get_type_mgr(); + + if (!inst->IsFloatingPointFoldingAllowed()) { + if (HasFloatingPoint(type_mgr->GetType(inst->type_id()))) { + return nullptr; + } + } + + const analysis::Constant* c1 = constants[0]; + const analysis::Constant* c2 = constants[1]; + + if (c1 == nullptr || c2 == nullptr) { + return nullptr; + } + + // Check result type. + const analysis::Type* result_type = type_mgr->GetType(inst->type_id()); + const analysis::Vector* vector_type = result_type->AsVector(); + assert(vector_type != nullptr); + const analysis::Type* element_type = vector_type->element_type(); + assert(element_type != nullptr); + const analysis::Float* float_type = element_type->AsFloat(); + assert(float_type != nullptr); + + // Check types of c1 and c2. + assert(c1->type()->AsMatrix()->element_type() == vector_type); + assert(c2->type()->AsVector()->element_type() == element_type); + + // Get a float vector that is the result of matrix-times-vector. + std::vector<const analysis::Constant*> c1_components = + c1->AsMatrixConstant()->GetComponents(); + std::vector<const analysis::Constant*> c2_components = + c2->GetVectorComponents(const_mgr); + uint32_t resultVectorSize = result_type->AsVector()->element_count(); + + std::vector<uint32_t> ids; + + if ((c1 && c1->IsZero()) || (c2 && c2->IsZero())) { + std::vector<uint32_t> words(float_type->width() / 32, 0); + for (uint32_t i = 0; i < resultVectorSize; ++i) { + const analysis::Constant* new_elem = + const_mgr->GetConstant(float_type, words); + ids.push_back(const_mgr->GetDefiningInstruction(new_elem)->result_id()); + } + return const_mgr->GetConstant(vector_type, ids); + } + + if (float_type->width() == 32) { + for (uint32_t i = 0; i < resultVectorSize; ++i) { + float result_scalar = 0.0f; + for (uint32_t j = 0; j < c1_components.size(); ++j) { + float c1_scalar = c1_components[j] + ->AsVectorConstant() + ->GetComponents()[i] + ->GetFloat(); + float c2_scalar = c2_components[j]->GetFloat(); + result_scalar += c1_scalar * c2_scalar; + } + utils::FloatProxy<float> result(result_scalar); + std::vector<uint32_t> words = result.GetWords(); + const analysis::Constant* new_elem = + const_mgr->GetConstant(float_type, words); + ids.push_back(const_mgr->GetDefiningInstruction(new_elem)->result_id()); + } + return const_mgr->GetConstant(vector_type, ids); + } else if (float_type->width() == 64) { + for (uint32_t i = 0; i < resultVectorSize; ++i) { + double result_scalar = 0.0; + for (uint32_t j = 0; j < c1_components.size(); ++j) { + double c1_scalar = c1_components[j] + ->AsVectorConstant() + ->GetComponents()[i] + ->GetDouble(); + double c2_scalar = c2_components[j]->GetDouble(); + result_scalar += c1_scalar * c2_scalar; + } + utils::FloatProxy<double> result(result_scalar); + std::vector<uint32_t> words = result.GetWords(); + const analysis::Constant* new_elem = + const_mgr->GetConstant(float_type, words); + ids.push_back(const_mgr->GetDefiningInstruction(new_elem)->result_id()); + } + return const_mgr->GetConstant(vector_type, ids); + } + return nullptr; + }; +} + ConstantFoldingRule FoldCompositeWithConstants() { // Folds an OpCompositeConstruct where all of the inputs are constants to a // constant. A new constant is created if necessary. @@ -1288,6 +1475,8 @@ rules_[SpvOpVectorShuffle].push_back(FoldVectorShuffleWithConstants()); rules_[SpvOpVectorTimesScalar].push_back(FoldVectorTimesScalar()); + rules_[SpvOpVectorTimesMatrix].push_back(FoldVectorTimesMatrix()); + rules_[SpvOpMatrixTimesVector].push_back(FoldMatrixTimesVector()); rules_[SpvOpFNegate].push_back(FoldFNegate()); rules_[SpvOpQuantizeToF16].push_back(FoldQuantizeToF16());
diff --git a/source/opt/def_use_manager.cpp b/source/opt/def_use_manager.cpp index e1e441e..d54fdb6 100644 --- a/source/opt/def_use_manager.cpp +++ b/source/opt/def_use_manager.cpp
@@ -13,23 +13,11 @@ // limitations under the License. #include "source/opt/def_use_manager.h" -#include "source/util/make_unique.h" namespace spvtools { namespace opt { namespace analysis { -// Don't compact before we have a reasonable number of ids allocated (~32kb). -static const size_t kCompactThresholdMinTotalIds = (8 * 1024); -// Compact when fewer than this fraction of the storage is used (should be 2^n -// for performance). -static const size_t kCompactThresholdFractionFreeIds = 8; - -DefUseManager::DefUseManager() { - use_pool_ = MakeUnique<UseListPool>(); - used_id_pool_ = MakeUnique<UsedIdListPool>(); -} - void DefUseManager::AnalyzeInstDef(Instruction* inst) { const uint32_t def_id = inst->result_id(); if (def_id != 0) { @@ -46,15 +34,15 @@ } void DefUseManager::AnalyzeInstUse(Instruction* inst) { - // It might have existed before. - EraseUseRecordsOfOperandIds(inst); - // Create entry for the given instruction. Note that the instruction may // not have any in-operands. In such cases, we still need a entry for those // instructions so this manager knows it has seen the instruction later. - UsedIdList& used_ids = - inst_to_used_id_.insert({inst, UsedIdList(used_id_pool_.get())}) - .first->second; + auto* used_ids = &inst_to_used_ids_[inst]; + if (used_ids->size()) { + EraseUseRecordsOfOperandIds(inst); + used_ids = &inst_to_used_ids_[inst]; + } + used_ids->clear(); // It might have existed before. for (uint32_t i = 0; i < inst->NumOperands(); ++i) { switch (inst->GetOperand(i).type) { @@ -66,17 +54,8 @@ uint32_t use_id = inst->GetSingleWordOperand(i); Instruction* def = GetDef(use_id); assert(def && "Definition is not registered."); - - // Add to inst's use records - used_ids.push_back(use_id); - - // Add to the users, taking care to avoid adding duplicates. We know - // the duplicate for this instruction will always be at the tail. - UseList& list = inst_to_users_.insert({def, UseList(use_pool_.get())}) - .first->second; - if (list.empty() || list.back() != inst) { - list.push_back(inst); - } + id_to_users_.insert(UserEntry{def, inst}); + used_ids->push_back(use_id); } break; default: break; @@ -115,6 +94,23 @@ return iter->second; } +DefUseManager::IdToUsersMap::const_iterator DefUseManager::UsersBegin( + const Instruction* def) const { + return id_to_users_.lower_bound( + UserEntry{const_cast<Instruction*>(def), nullptr}); +} + +bool DefUseManager::UsersNotEnd(const IdToUsersMap::const_iterator& iter, + const IdToUsersMap::const_iterator& cached_end, + const Instruction* inst) const { + return (iter != cached_end && iter->def == inst); +} + +bool DefUseManager::UsersNotEnd(const IdToUsersMap::const_iterator& iter, + const Instruction* inst) const { + return UsersNotEnd(iter, id_to_users_.end(), inst); +} + bool DefUseManager::WhileEachUser( const Instruction* def, const std::function<bool(Instruction*)>& f) const { // Ensure that |def| has been registered. @@ -122,11 +118,9 @@ "Definition is not registered."); if (!def->HasResultId()) return true; - auto iter = inst_to_users_.find(def); - if (iter != inst_to_users_.end()) { - for (Instruction* user : iter->second) { - if (!f(user)) return false; - } + auto end = id_to_users_.end(); + for (auto iter = UsersBegin(def); UsersNotEnd(iter, end, def); ++iter) { + if (!f(iter->user)) return false; } return true; } @@ -157,15 +151,14 @@ "Definition is not registered."); if (!def->HasResultId()) return true; - auto iter = inst_to_users_.find(def); - if (iter != inst_to_users_.end()) { - for (Instruction* user : iter->second) { - for (uint32_t idx = 0; idx != user->NumOperands(); ++idx) { - const Operand& op = user->GetOperand(idx); - if (op.type != SPV_OPERAND_TYPE_RESULT_ID && spvIsIdType(op.type)) { - if (def->result_id() == op.words[0]) { - if (!f(user, idx)) return false; - } + auto end = id_to_users_.end(); + for (auto iter = UsersBegin(def); UsersNotEnd(iter, end, def); ++iter) { + Instruction* user = iter->user; + for (uint32_t idx = 0; idx != user->NumOperands(); ++idx) { + const Operand& op = user->GetOperand(idx); + if (op.type != SPV_OPERAND_TYPE_RESULT_ID && spvIsIdType(op.type)) { + if (def->result_id() == op.words[0]) { + if (!f(user, idx)) return false; } } } @@ -237,18 +230,17 @@ } void DefUseManager::ClearInst(Instruction* inst) { - if (inst_to_used_id_.find(inst) != inst_to_used_id_.end()) { + auto iter = inst_to_used_ids_.find(inst); + if (iter != inst_to_used_ids_.end()) { EraseUseRecordsOfOperandIds(inst); - uint32_t const result_id = inst->result_id(); - if (result_id != 0) { - // For each using instruction, remove result_id from their used ids. - auto iter = inst_to_users_.find(inst); - if (iter != inst_to_users_.end()) { - for (Instruction* use : iter->second) { - inst_to_used_id_.at(use).remove_first(result_id); - } - inst_to_users_.erase(iter); + if (inst->result_id() != 0) { + // Remove all uses of this inst. + auto users_begin = UsersBegin(inst); + auto end = id_to_users_.end(); + auto new_end = users_begin; + for (; UsersNotEnd(new_end, end, inst); ++new_end) { } + id_to_users_.erase(users_begin, new_end); id_to_def_.erase(inst->result_id()); } } @@ -257,48 +249,16 @@ void DefUseManager::EraseUseRecordsOfOperandIds(const Instruction* inst) { // Go through all ids used by this instruction, remove this instruction's // uses of them. - auto iter = inst_to_used_id_.find(inst); - if (iter != inst_to_used_id_.end()) { - const UsedIdList& used_ids = iter->second; - for (uint32_t def_id : used_ids) { - auto def_iter = inst_to_users_.find(GetDef(def_id)); - if (def_iter != inst_to_users_.end()) { - def_iter->second.remove_first(const_cast<Instruction*>(inst)); - } + auto iter = inst_to_used_ids_.find(inst); + if (iter != inst_to_used_ids_.end()) { + for (auto use_id : iter->second) { + id_to_users_.erase( + UserEntry{GetDef(use_id), const_cast<Instruction*>(inst)}); } - inst_to_used_id_.erase(inst); - - // If we're using only a fraction of the space in used_ids_, compact storage - // to prevent memory usage from being unbounded. - if (used_id_pool_->total_nodes() > kCompactThresholdMinTotalIds && - used_id_pool_->used_nodes() < - used_id_pool_->total_nodes() / kCompactThresholdFractionFreeIds) { - CompactStorage(); - } + inst_to_used_ids_.erase(iter); } } -void DefUseManager::CompactStorage() { - CompactUseRecords(); - CompactUsedIds(); -} - -void DefUseManager::CompactUseRecords() { - std::unique_ptr<UseListPool> new_pool = MakeUnique<UseListPool>(); - for (auto& iter : inst_to_users_) { - iter.second.move_nodes(new_pool.get()); - } - use_pool_ = std::move(new_pool); -} - -void DefUseManager::CompactUsedIds() { - std::unique_ptr<UsedIdListPool> new_pool = MakeUnique<UsedIdListPool>(); - for (auto& iter : inst_to_used_id_) { - iter.second.move_nodes(new_pool.get()); - } - used_id_pool_ = std::move(new_pool); -} - bool CompareAndPrintDifferences(const DefUseManager& lhs, const DefUseManager& rhs) { bool same = true; @@ -317,52 +277,34 @@ same = false; } - for (const auto& l : lhs.inst_to_used_id_) { - std::set<uint32_t> ul, ur; - lhs.ForEachUse(l.first, - [&ul](Instruction*, uint32_t id) { ul.insert(id); }); - rhs.ForEachUse(l.first, - [&ur](Instruction*, uint32_t id) { ur.insert(id); }); - if (ul.size() != ur.size()) { - printf( - "Diff in inst_to_used_id_: different number of used ids (%zu != %zu)", - ul.size(), ur.size()); - same = false; - } else if (ul != ur) { - printf("Diff in inst_to_used_id_: different used ids\n"); - same = false; + if (lhs.id_to_users_ != rhs.id_to_users_) { + for (auto p : lhs.id_to_users_) { + if (rhs.id_to_users_.count(p) == 0) { + printf("Diff in id_to_users: missing value in rhs\n"); + } } - } - for (const auto& r : rhs.inst_to_used_id_) { - auto iter_l = lhs.inst_to_used_id_.find(r.first); - if (r.second.empty() && - !(iter_l == lhs.inst_to_used_id_.end() || iter_l->second.empty())) { - printf("Diff in inst_to_used_id_: unexpected instr in rhs\n"); - same = false; + for (auto p : rhs.id_to_users_) { + if (lhs.id_to_users_.count(p) == 0) { + printf("Diff in id_to_users: missing value in lhs\n"); + } } + same = false; } - for (const auto& l : lhs.inst_to_users_) { - std::set<Instruction*> ul, ur; - lhs.ForEachUser(l.first, [&ul](Instruction* use) { ul.insert(use); }); - rhs.ForEachUser(l.first, [&ur](Instruction* use) { ur.insert(use); }); - if (ul.size() != ur.size()) { - printf("Diff in inst_to_users_: different number of users (%zu != %zu)", - ul.size(), ur.size()); - same = false; - } else if (ul != ur) { - printf("Diff in inst_to_users_: different users\n"); - same = false; + if (lhs.inst_to_used_ids_ != rhs.inst_to_used_ids_) { + for (auto p : lhs.inst_to_used_ids_) { + if (rhs.inst_to_used_ids_.count(p.first) == 0) { + printf("Diff in inst_to_used_ids: missing value in rhs\n"); + } } - } - for (const auto& r : rhs.inst_to_users_) { - auto iter_l = lhs.inst_to_users_.find(r.first); - if (r.second.empty() && - !(iter_l == lhs.inst_to_users_.end() || iter_l->second.empty())) { - printf("Diff in inst_to_users_: unexpected instr in rhs\n"); - same = false; + for (auto p : rhs.inst_to_used_ids_) { + if (lhs.inst_to_used_ids_.count(p.first) == 0) { + printf("Diff in inst_to_used_ids: missing value in lhs\n"); + } } + same = false; } + return same; }
diff --git a/source/opt/def_use_manager.h b/source/opt/def_use_manager.h index cf6cbdf..a8dbbc6 100644 --- a/source/opt/def_use_manager.h +++ b/source/opt/def_use_manager.h
@@ -21,7 +21,6 @@ #include "source/opt/instruction.h" #include "source/opt/module.h" -#include "source/util/pooled_linked_list.h" #include "spirv-tools/libspirv.hpp" namespace spvtools { @@ -50,6 +49,50 @@ return lhs.operand_index < rhs.operand_index; } +// Definition should never be null. User can be null, however, such an entry +// should be used only for searching (e.g. all users of a particular definition) +// and never stored in a container. +struct UserEntry { + Instruction* def; + Instruction* user; +}; + +inline bool operator==(const UserEntry& lhs, const UserEntry& rhs) { + return lhs.def == rhs.def && lhs.user == rhs.user; +} + +// Orders UserEntry for use in associative containers (i.e. less than ordering). +// +// The definition of an UserEntry is treated as the major key and the users as +// the minor key so that all the users of a particular definition are +// consecutive in a container. +// +// A null user always compares less than a real user. This is done to provide +// easy values to search for the beginning of the users of a particular +// definition (i.e. using {def, nullptr}). +struct UserEntryLess { + bool operator()(const UserEntry& lhs, const UserEntry& rhs) const { + // If lhs.def and rhs.def are both null, fall through to checking the + // second entries. + if (!lhs.def && rhs.def) return true; + if (lhs.def && !rhs.def) return false; + + // If neither definition is null, then compare unique ids. + if (lhs.def && rhs.def) { + if (lhs.def->unique_id() < rhs.def->unique_id()) return true; + if (rhs.def->unique_id() < lhs.def->unique_id()) return false; + } + + // Return false on equality. + if (!lhs.user && !rhs.user) return false; + if (!lhs.user) return true; + if (!rhs.user) return false; + + // If neither user is null then compare unique ids. + return lhs.user->unique_id() < rhs.user->unique_id(); + } +}; + // A class for analyzing and managing defs and uses in an Module. class DefUseManager { public: @@ -59,7 +102,7 @@ // will be communicated to the outside via the given message |consumer|. This // instance only keeps a reference to the |consumer|, so the |consumer| should // outlive this instance. - DefUseManager(Module* module) : DefUseManager() { AnalyzeDefUse(module); } + DefUseManager(Module* module) { AnalyzeDefUse(module); } DefUseManager(const DefUseManager&) = delete; DefUseManager(DefUseManager&&) = delete; @@ -171,36 +214,35 @@ // uses. void UpdateDefUse(Instruction* inst); - // Compacts any internal storage to save memory. - void CompactStorage(); - private: - using UseList = spvtools::utils::PooledLinkedList<Instruction*>; - using UseListPool = spvtools::utils::PooledLinkedListNodes<Instruction*>; - // Stores linked lists of Instructions using a def. - using InstToUsersMap = std::unordered_map<const Instruction*, UseList>; + using IdToUsersMap = std::set<UserEntry, UserEntryLess>; + using InstToUsedIdsMap = + std::unordered_map<const Instruction*, std::vector<uint32_t>>; - using UsedIdList = spvtools::utils::PooledLinkedList<uint32_t>; - using UsedIdListPool = spvtools::utils::PooledLinkedListNodes<uint32_t>; - // Stores mapping from instruction to their UsedIdRange. - using InstToUsedIdMap = std::unordered_map<const Instruction*, UsedIdList>; + // Returns the first location that {|def|, nullptr} could be inserted into the + // users map without violating ordering. + IdToUsersMap::const_iterator UsersBegin(const Instruction* def) const; - DefUseManager(); + // Returns true if |iter| has not reached the end of |def|'s users. + // + // In the first version |iter| is compared against the end of the map for + // validity before other checks. In the second version, |iter| is compared + // against |cached_end| for validity before other checks. This allows caching + // the map's end which is a performance improvement on some platforms. + bool UsersNotEnd(const IdToUsersMap::const_iterator& iter, + const Instruction* def) const; + bool UsersNotEnd(const IdToUsersMap::const_iterator& iter, + const IdToUsersMap::const_iterator& cached_end, + const Instruction* def) const; // Analyzes the defs and uses in the given |module| and populates data // structures in this class. Does nothing if |module| is nullptr. void AnalyzeDefUse(Module* module); - // Removes unused entries in used_records_ and used_ids_. - void CompactUseRecords(); - void CompactUsedIds(); - - IdToDefMap id_to_def_; // Mapping from ids to their definitions - InstToUsersMap inst_to_users_; // Map from def to uses. - std::unique_ptr<UseListPool> use_pool_; - - std::unique_ptr<UsedIdListPool> used_id_pool_; - InstToUsedIdMap inst_to_used_id_; // Map from instruction to used ids. + IdToDefMap id_to_def_; // Mapping from ids to their definitions + IdToUsersMap id_to_users_; // Mapping from ids to their users + // Mapping from instructions to the ids used in the instruction. + InstToUsedIdsMap inst_to_used_ids_; }; } // namespace analysis
diff --git a/source/opt/dominator_tree.cpp b/source/opt/dominator_tree.cpp index d86de15..d6017bb 100644 --- a/source/opt/dominator_tree.cpp +++ b/source/opt/dominator_tree.cpp
@@ -59,7 +59,9 @@ PreLambda pre, PostLambda post) { // Ignore backedge operation. auto nop_backedge = [](const BBType*, const BBType*) {}; - CFA<BBType>::DepthFirstTraversal(bb, successors, pre, post, nop_backedge); + auto no_terminal_blocks = [](const BBType*) { return false; }; + CFA<BBType>::DepthFirstTraversal(bb, successors, pre, post, nop_backedge, + no_terminal_blocks); } // Wrapper around CFA::DepthFirstTraversal to provide an interface to perform
diff --git a/source/opt/folding_rules.cpp b/source/opt/folding_rules.cpp index 0d8f7c8..2d778b9 100644 --- a/source/opt/folding_rules.cpp +++ b/source/opt/folding_rules.cpp
@@ -277,6 +277,11 @@ uint32_t width = c->type()->AsFloat()->width(); assert(width == 32 || width == 64); std::vector<uint32_t> words; + + if (c->IsZero()) { + return 0; + } + if (width == 64) { spvtools::utils::FloatProxy<double> result(1.0 / c->GetDouble()); if (!IsValidResult(result.getAsFloat())) return 0;
diff --git a/source/opt/if_conversion.cpp b/source/opt/if_conversion.cpp index d1debd0..1232796 100644 --- a/source/opt/if_conversion.cpp +++ b/source/opt/if_conversion.cpp
@@ -160,6 +160,11 @@ BasicBlock* inc1 = context()->get_instr_block(preds[1]); if (dominators->Dominates(block, inc1)) return false; + if (inc0 == inc1) { + // If the predecessor blocks are the same, then there is only 1 value for + // the OpPhi. Other transformation should be able to simplify that. + return false; + } // All phis will have the same common dominator, so cache the result // for this block. If there is no common dominator, then we cannot transform // any phi in this basic block.
diff --git a/source/opt/inline_pass.cpp b/source/opt/inline_pass.cpp index 2cc3125..6e73f1c 100644 --- a/source/opt/inline_pass.cpp +++ b/source/opt/inline_pass.cpp
@@ -508,6 +508,37 @@ delete &*loop_merge_itr; } +void InlinePass::UpdateSingleBlockLoopContinueTarget( + uint32_t new_id, std::vector<std::unique_ptr<BasicBlock>>* new_blocks) { + auto& header = new_blocks->front(); + auto* merge_inst = header->GetLoopMergeInst(); + + // The back-edge block is split at the branch to create a new back-edge + // block. The old block is modified to branch to the new block. The loop + // merge instruction is updated to declare the new block as the continue + // target. This has the effect of changing the loop from being a large + // continue construct and an empty loop construct to being a loop with a loop + // construct and a trivial continue construct. This change is made to satisfy + // structural dominance. + + // Add the new basic block. + std::unique_ptr<BasicBlock> new_block = + MakeUnique<BasicBlock>(NewLabel(new_id)); + auto& old_backedge = new_blocks->back(); + auto old_branch = old_backedge->tail(); + + // Move the old back edge into the new block. + std::unique_ptr<Instruction> br(&*old_branch); + new_block->AddInstruction(std::move(br)); + + // Add a branch to the new block from the old back-edge block. + AddBranch(new_id, &old_backedge); + new_blocks->push_back(std::move(new_block)); + + // Update the loop's continue target to the new block. + merge_inst->SetInOperand(1u, {new_id}); +} + bool InlinePass::GenInlineCode( std::vector<std::unique_ptr<BasicBlock>>* new_blocks, std::vector<std::unique_ptr<Instruction>>* new_vars, @@ -639,9 +670,19 @@ // Finalize inline code. new_blocks->push_back(std::move(new_blk_ptr)); - if (caller_is_loop_header && (new_blocks->size() > 1)) + if (caller_is_loop_header && (new_blocks->size() > 1)) { MoveLoopMergeInstToFirstBlock(new_blocks); + // If the loop was a single basic block previously, update it's structure. + auto& header = new_blocks->front(); + auto* merge_inst = header->GetLoopMergeInst(); + if (merge_inst->GetSingleWordInOperand(1u) == header->id()) { + auto new_id = context()->TakeNextId(); + if (new_id == 0) return false; + UpdateSingleBlockLoopContinueTarget(new_id, new_blocks); + } + } + // Update block map given replacement blocks. for (auto& blk : *new_blocks) { id2block_[blk->id()] = &*blk;
diff --git a/source/opt/inline_pass.h b/source/opt/inline_pass.h index 9a5429b..f204395 100644 --- a/source/opt/inline_pass.h +++ b/source/opt/inline_pass.h
@@ -235,6 +235,12 @@ // Move the OpLoopMerge from the last block back to the first. void MoveLoopMergeInstToFirstBlock( std::vector<std::unique_ptr<BasicBlock>>* new_blocks); + + // Update the structure of single block loops so that the inlined code ends + // up in the loop construct and a new continue target is added to satisfy + // structural dominance. + void UpdateSingleBlockLoopContinueTarget( + uint32_t new_id, std::vector<std::unique_ptr<BasicBlock>>* new_blocks); }; } // namespace opt
diff --git a/source/opt/instruction.cpp b/source/opt/instruction.cpp index 418f121..6a8daea 100644 --- a/source/opt/instruction.cpp +++ b/source/opt/instruction.cpp
@@ -693,8 +693,12 @@ return NonSemanticShaderDebugInfo100InstructionsMax; } - return NonSemanticShaderDebugInfo100Instructions( - GetSingleWordInOperand(kExtInstInstructionInIdx)); + uint32_t opcode = GetSingleWordInOperand(kExtInstInstructionInIdx); + if (opcode >= NonSemanticShaderDebugInfo100InstructionsMax) { + return NonSemanticShaderDebugInfo100InstructionsMax; + } + + return NonSemanticShaderDebugInfo100Instructions(opcode); } CommonDebugInfoInstructions Instruction::GetCommonDebugOpcode() const {
diff --git a/source/opt/interface_var_sroa.cpp b/source/opt/interface_var_sroa.cpp index 58ed897..1b2cb36 100644 --- a/source/opt/interface_var_sroa.cpp +++ b/source/opt/interface_var_sroa.cpp
@@ -212,8 +212,12 @@ context()->KillInst(inst); return; } + std::vector<Instruction*> users; context()->get_def_use_mgr()->ForEachUser( - inst, [this](Instruction* user) { KillInstructionAndUsers(user); }); + inst, [&users](Instruction* user) { users.push_back(user); }); + for (auto user : users) { + context()->KillInst(user); + } context()->KillInst(inst); }
diff --git a/source/opt/ir_loader.cpp b/source/opt/ir_loader.cpp index 97db9d8..734ad55 100644 --- a/source/opt/ir_loader.cpp +++ b/source/opt/ir_loader.cpp
@@ -187,6 +187,8 @@ module_->AddExtInstImport(std::move(spv_inst)); } else if (opcode == SpvOpMemoryModel) { module_->SetMemoryModel(std::move(spv_inst)); + } else if (opcode == SpvOpSamplerImageAddressingModeNV) { + module_->SetSampledImageAddressMode(std::move(spv_inst)); } else if (opcode == SpvOpEntryPoint) { module_->AddEntryPoint(std::move(spv_inst)); } else if (opcode == SpvOpExecutionMode ||
diff --git a/source/opt/local_access_chain_convert_pass.cpp b/source/opt/local_access_chain_convert_pass.cpp index da4cac3..9491798 100644 --- a/source/opt/local_access_chain_convert_pass.cpp +++ b/source/opt/local_access_chain_convert_pass.cpp
@@ -237,7 +237,8 @@ } // Rule out variables with nested access chains // TODO(): Convert nested access chains - if (IsNonPtrAccessChain(op) && ptrInst->GetSingleWordInOperand( + bool is_non_ptr_access_chain = IsNonPtrAccessChain(op); + if (is_non_ptr_access_chain && ptrInst->GetSingleWordInOperand( kAccessChainPtrIdInIdx) != varId) { seen_non_target_vars_.insert(varId); seen_target_vars_.erase(varId); @@ -249,6 +250,12 @@ seen_target_vars_.erase(varId); break; } + + if (is_non_ptr_access_chain && AnyIndexIsOutOfBounds(ptrInst)) { + seen_non_target_vars_.insert(varId); + seen_target_vars_.erase(varId); + break; + } } break; default: break; @@ -446,5 +453,42 @@ }); } +bool LocalAccessChainConvertPass::AnyIndexIsOutOfBounds( + const Instruction* access_chain_inst) { + assert(IsNonPtrAccessChain(access_chain_inst->opcode())); + + analysis::TypeManager* type_mgr = context()->get_type_mgr(); + analysis::ConstantManager* const_mgr = context()->get_constant_mgr(); + auto constants = const_mgr->GetOperandConstants(access_chain_inst); + uint32_t base_pointer_id = access_chain_inst->GetSingleWordInOperand(0); + Instruction* base_pointer = get_def_use_mgr()->GetDef(base_pointer_id); + const analysis::Pointer* base_pointer_type = + type_mgr->GetType(base_pointer->type_id())->AsPointer(); + assert(base_pointer_type != nullptr && + "The base of the access chain is not a pointer."); + const analysis::Type* current_type = base_pointer_type->pointee_type(); + for (uint32_t i = 1; i < access_chain_inst->NumInOperands(); ++i) { + if (IsIndexOutOfBounds(constants[i], current_type)) { + return true; + } + + uint32_t index = + (constants[i] + ? static_cast<uint32_t>(constants[i]->GetZeroExtendedValue()) + : 0); + current_type = type_mgr->GetMemberType(current_type, {index}); + } + + return false; +} + +bool LocalAccessChainConvertPass::IsIndexOutOfBounds( + const analysis::Constant* index, const analysis::Type* type) const { + if (index == nullptr) { + return false; + } + return index->GetZeroExtendedValue() >= type->NumberOfComponents(); +} + } // namespace opt } // namespace spvtools
diff --git a/source/opt/local_access_chain_convert_pass.h b/source/opt/local_access_chain_convert_pass.h index 8548e16..eabf864 100644 --- a/source/opt/local_access_chain_convert_pass.h +++ b/source/opt/local_access_chain_convert_pass.h
@@ -111,6 +111,17 @@ // Returns a status to indicate success or failure, and change or no change. Status ConvertLocalAccessChains(Function* func); + // Returns true one of the indexes in the |access_chain_inst| is definitly out + // of bounds. If the size of the type or the value of the index is unknown, + // then it will be considered in-bounds. + bool AnyIndexIsOutOfBounds(const Instruction* access_chain_inst); + + // Returns true if getting element |index| from |type| would be out-of-bounds. + // If |index| is nullptr or the size of the type are unknown, then it will be + // considered in-bounds. + bool IsIndexOutOfBounds(const analysis::Constant* index, + const analysis::Type* type) const; + // Initialize extensions allowlist void InitExtensions();
diff --git a/source/opt/loop_descriptor.cpp b/source/opt/loop_descriptor.cpp index 4feb64e..13982d1 100644 --- a/source/opt/loop_descriptor.cpp +++ b/source/opt/loop_descriptor.cpp
@@ -497,7 +497,8 @@ // 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); + cfg.ComputeStructuredOrder(loop_header_->GetParent(), loop_header_, + loop_merge_, &order); for (BasicBlock* bb : order) { if (bb == GetMergeBlock()) { break;
diff --git a/source/opt/loop_unroller.cpp b/source/opt/loop_unroller.cpp index 28ff072..6f4e6f4 100644 --- a/source/opt/loop_unroller.cpp +++ b/source/opt/loop_unroller.cpp
@@ -384,6 +384,7 @@ std::unique_ptr<Instruction> new_label{new Instruction( context_, SpvOp::SpvOpLabel, 0, context_->TakeNextId(), {})}; std::unique_ptr<BasicBlock> new_exit_bb{new BasicBlock(std::move(new_label))}; + new_exit_bb->SetParent(&function_); // Save the id of the block before we move it. uint32_t new_merge_id = new_exit_bb->id(); @@ -996,6 +997,20 @@ if (!loop_->FindNumberOfIterations(induction, &*condition->ctail(), nullptr)) return false; +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + // ClusterFuzz/OSS-Fuzz is likely to yield examples with very high loop + // iteration counts. This can cause timeouts and memouts during fuzzing that + // are not classed as bugs. To avoid this noise, loop unrolling is not applied + // to loops with large iteration counts when fuzzing. + const size_t kFuzzerIterationLimit = 100; + size_t num_iterations; + loop_->FindNumberOfIterations(induction, &*condition->ctail(), + &num_iterations); + if (num_iterations > kFuzzerIterationLimit) { + return false; + } +#endif + // Make sure the latch block is a unconditional branch to the header // block. const Instruction& branch = *loop_->GetLatchBlock()->ctail();
diff --git a/source/opt/merge_return_pass.h b/source/opt/merge_return_pass.h index a35cf26..d15db2f 100644 --- a/source/opt/merge_return_pass.h +++ b/source/opt/merge_return_pass.h
@@ -118,8 +118,6 @@ StructuredControlState(Instruction* break_merge, Instruction* merge) : break_merge_(break_merge), current_merge_(merge) {} - StructuredControlState(const StructuredControlState&) = default; - bool InBreakable() const { return break_merge_; } bool InStructuredFlow() const { return CurrentMergeId() != 0; }
diff --git a/source/opt/module.cpp b/source/opt/module.cpp index 5983abb..c98af8f 100644 --- a/source/opt/module.cpp +++ b/source/opt/module.cpp
@@ -90,6 +90,8 @@ DELEGATE(extensions_); DELEGATE(ext_inst_imports_); if (memory_model_) memory_model_->ForEachInst(f, run_on_debug_line_insts); + if (sampled_image_address_mode_) + sampled_image_address_mode_->ForEachInst(f, run_on_debug_line_insts); DELEGATE(entry_points_); DELEGATE(execution_modes_); DELEGATE(debugs1_); @@ -114,6 +116,9 @@ if (memory_model_) static_cast<const Instruction*>(memory_model_.get()) ->ForEachInst(f, run_on_debug_line_insts); + if (sampled_image_address_mode_) + static_cast<const Instruction*>(sampled_image_address_mode_.get()) + ->ForEachInst(f, run_on_debug_line_insts); for (auto& i : entry_points_) DELEGATE(i); for (auto& i : execution_modes_) DELEGATE(i); for (auto& i : debugs1_) DELEGATE(i);
diff --git a/source/opt/module.h b/source/opt/module.h index 230be70..7a6be46 100644 --- a/source/opt/module.h +++ b/source/opt/module.h
@@ -83,6 +83,9 @@ // Set the memory model for this module. inline void SetMemoryModel(std::unique_ptr<Instruction> m); + // Set the sampled image addressing mode for this module. + inline void SetSampledImageAddressMode(std::unique_ptr<Instruction> m); + // Appends an entry point instruction to this module. inline void AddEntryPoint(std::unique_ptr<Instruction> e); @@ -158,12 +161,20 @@ inline IteratorRange<inst_iterator> ext_inst_imports(); inline IteratorRange<const_inst_iterator> ext_inst_imports() const; - // Return the memory model instruction contained inthis module. + // Return the memory model instruction contained in this module. inline Instruction* GetMemoryModel() { return memory_model_.get(); } inline const Instruction* GetMemoryModel() const { return memory_model_.get(); } + // Return the sampled image address mode instruction contained in this module. + inline Instruction* GetSampledImageAddressMode() { + return sampled_image_address_mode_.get(); + } + inline const Instruction* GetSampledImageAddressMode() const { + return sampled_image_address_mode_.get(); + } + // There are several kinds of debug instructions, according to where they can // appear in the logical layout of a module: // - Section 7a: OpString, OpSourceExtension, OpSource, OpSourceContinued @@ -288,6 +299,8 @@ InstructionList ext_inst_imports_; // A module only has one memory model instruction. std::unique_ptr<Instruction> memory_model_; + // A module can only have one optional sampled image addressing mode + std::unique_ptr<Instruction> sampled_image_address_mode_; InstructionList entry_points_; InstructionList execution_modes_; InstructionList debugs1_; @@ -326,6 +339,10 @@ memory_model_ = std::move(m); } +inline void Module::SetSampledImageAddressMode(std::unique_ptr<Instruction> m) { + sampled_image_address_mode_ = std::move(m); +} + inline void Module::AddEntryPoint(std::unique_ptr<Instruction> e) { entry_points_.push_back(std::move(e)); }
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp index 2976151..381589b 100644 --- a/source/opt/optimizer.cpp +++ b/source/opt/optimizer.cpp
@@ -623,10 +623,16 @@ assert(optimized_binary_with_nop.size() == original_binary_size && "Binary size unexpectedly changed despite the optimizer saying " "there was no change"); - assert(memcmp(optimized_binary_with_nop.data(), original_binary, - original_binary_size) == 0 && - "Binary content unexpectedly changed despite the optimizer saying " - "there was no change"); + + // Compare the magic number to make sure the binaries were encoded in the + // endianness. If not, the contents of the binaries will be different, so + // do not check the contents. + if (optimized_binary_with_nop[0] == original_binary[0]) { + assert(memcmp(optimized_binary_with_nop.data(), original_binary, + original_binary_size) == 0 && + "Binary content unexpectedly changed despite the optimizer saying " + "there was no change"); + } } #endif // !NDEBUG
diff --git a/source/opt/reduce_load_size.cpp b/source/opt/reduce_load_size.cpp index e9b8087..56491b2 100644 --- a/source/opt/reduce_load_size.cpp +++ b/source/opt/reduce_load_size.cpp
@@ -161,8 +161,15 @@ case analysis::Type::kArray: { const analysis::Constant* size_const = const_mgr->FindDeclaredConstant(load_type->AsArray()->LengthId()); - assert(size_const->AsIntConstant()); - total_size = size_const->GetU32(); + + if (size_const) { + assert(size_const->AsIntConstant()); + total_size = size_const->GetU32(); + } else { + // The size is spec constant, so it is unknown at this time. Assume + // it is very large. + total_size = UINT32_MAX; + } } break; case analysis::Type::kStruct: total_size = static_cast<uint32_t>(
diff --git a/source/opt/scalar_replacement_pass.h b/source/opt/scalar_replacement_pass.h index 76afc26..6a66dfb 100644 --- a/source/opt/scalar_replacement_pass.h +++ b/source/opt/scalar_replacement_pass.h
@@ -15,6 +15,7 @@ #ifndef SOURCE_OPT_SCALAR_REPLACEMENT_PASS_H_ #define SOURCE_OPT_SCALAR_REPLACEMENT_PASS_H_ +#include <cassert> #include <cstdio> #include <memory> #include <queue> @@ -37,9 +38,20 @@ public: ScalarReplacementPass(uint32_t limit = kDefaultLimit) : max_num_elements_(limit) { - name_[0] = '\0'; - strcat(name_, "scalar-replacement="); - sprintf(&name_[strlen(name_)], "%d", max_num_elements_); + const auto num_to_write = snprintf( + name_, sizeof(name_), "scalar-replacement=%u", max_num_elements_); + assert(size_t(num_to_write) < sizeof(name_)); + (void)num_to_write; // Mark as unused + +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + // ClusterFuzz/OSS-Fuzz is likely to yield examples with very large arrays. + // This can cause timeouts and memouts during fuzzing that + // are not classed as bugs. To avoid this noise, we set the + // max_num_elements_ to a smaller value for fuzzing. + max_num_elements_ = + (max_num_elements_ > 0 && max_num_elements_ < 100 ? max_num_elements_ + : 100); +#endif } const char* name() const override { return name_; } @@ -253,7 +265,10 @@ // Limit on the number of members in an object that will be replaced. // 0 means there is no limit. uint32_t max_num_elements_; - char name_[55]; + // This has to be big enough to fit "scalar-replacement=" followed by a + // uint32_t number written in decimal (so 10 digits), and then a + // terminating nul. + char name_[30]; }; } // namespace opt
diff --git a/source/opt/types.cpp b/source/opt/types.cpp index ebbdc36..056aceb 100644 --- a/source/opt/types.cpp +++ b/source/opt/types.cpp
@@ -16,6 +16,7 @@ #include <algorithm> #include <cassert> +#include <climits> #include <cstdint> #include <sstream> #include <string> @@ -246,6 +247,35 @@ return ComputeHashValue(0, &seen); } +uint64_t Type::NumberOfComponents() const { + switch (kind()) { + case kVector: + return AsVector()->element_count(); + case kMatrix: + return AsMatrix()->element_count(); + case kArray: { + Array::LengthInfo length_info = AsArray()->length_info(); + if (length_info.words[0] != Array::LengthInfo::kConstant) { + return UINT64_MAX; + } + assert(length_info.words.size() <= 3 && + "The size of the array could not fit size_t."); + uint64_t length = 0; + length |= length_info.words[1]; + if (length_info.words.size() > 2) { + length |= static_cast<uint64_t>(length_info.words[2]) << 32; + } + return length; + } + case kRuntimeArray: + return UINT64_MAX; + case kStruct: + return AsStruct()->element_types().size(); + default: + return 0; + } +} + bool Integer::IsSameImpl(const Type* that, IsSameCache*) const { const Integer* it = that->AsInteger(); return it && width_ == it->width_ && signed_ == it->signed_ &&
diff --git a/source/opt/types.h b/source/opt/types.h index f5a4a6b..a92669e 100644 --- a/source/opt/types.h +++ b/source/opt/types.h
@@ -160,6 +160,10 @@ size_t ComputeHashValue(size_t hash, SeenTypes* seen) const; + // Returns the number of components in a composite type. Returns 0 for a + // non-composite type. + uint64_t NumberOfComponents() const; + // A bunch of methods for casting this type to a given type. Returns this if the // cast can be done, nullptr otherwise. // clang-format off
diff --git a/source/text_handler.cpp b/source/text_handler.cpp index fe12a26..15c1741 100644 --- a/source/text_handler.cpp +++ b/source/text_handler.cpp
@@ -62,28 +62,29 @@ // parameters, its the users responsibility to ensure these are non null. spv_result_t advance(spv_text text, spv_position position) { // NOTE: Consume white space, otherwise don't advance. - if (position->index >= text->length) return SPV_END_OF_STREAM; - switch (text->str[position->index]) { - case '\0': - return SPV_END_OF_STREAM; - case ';': - if (spv_result_t error = advanceLine(text, position)) return error; - return advance(text, position); - case ' ': - case '\t': - case '\r': - position->column++; - position->index++; - return advance(text, position); - case '\n': - position->column = 0; - position->line++; - position->index++; - return advance(text, position); - default: - break; + while (true) { + if (position->index >= text->length) return SPV_END_OF_STREAM; + switch (text->str[position->index]) { + case '\0': + return SPV_END_OF_STREAM; + case ';': + if (spv_result_t error = advanceLine(text, position)) return error; + continue; + case ' ': + case '\t': + case '\r': + position->column++; + position->index++; + continue; + case '\n': + position->column = 0; + position->line++; + position->index++; + continue; + default: + return SPV_SUCCESS; + } } - return SPV_SUCCESS; } // Fetches the next word from the given text stream starting from the given
diff --git a/source/util/hex_float.h b/source/util/hex_float.h index 903b628..06e3c57 100644 --- a/source/util/hex_float.h +++ b/source/util/hex_float.h
@@ -209,9 +209,10 @@ // be the default for any non-specialized type. template <typename T> struct HexFloatTraits { - // Integer type that can store this hex-float. + // Integer type that can store the bit representation of this hex-float. using uint_type = void; - // Signed integer type that can store this hex-float. + // Signed integer type that can store the bit representation of this + // hex-float. using int_type = void; // The numerical type that this HexFloat represents. using underlying_type = void; @@ -958,9 +959,15 @@ // This "looks" like a hex-float so treat it as one. bool seen_p = false; bool seen_dot = false; + + // The mantissa bits, without the most significant 1 bit, and with the + // the most recently read bits in the least significant positions. + uint_type fraction = 0; + // The number of mantissa bits that have been read, including the leading 1 + // bit that is not written into 'fraction'. uint_type fraction_index = 0; - uint_type fraction = 0; + // TODO(dneto): handle overflow and underflow int_type exponent = HF::exponent_bias; // Strip off leading zeros so we don't have to special-case them later. @@ -968,11 +975,13 @@ is.get(); } - bool is_denorm = - true; // Assume denorm "representation" until we hear otherwise. - // NB: This does not mean the value is actually denorm, - // it just means that it was written 0. + // Does the mantissa, as written, have non-zero digits to the left of + // the decimal point. Assume no until proven otherwise. + bool has_integer_part = false; bool bits_written = false; // Stays false until we write a bit. + + // Scan the mantissa hex digits until we see a '.' or the 'p' that + // starts the exponent. while (!seen_p && !seen_dot) { // Handle characters that are left of the fractional part. if (next_char == '.') { @@ -980,9 +989,8 @@ } else if (next_char == 'p') { seen_p = true; } else if (::isxdigit(next_char)) { - // We know this is not denormalized since we have stripped all leading - // zeroes and we are not a ".". - is_denorm = false; + // We have stripped all leading zeroes and we have not yet seen a ".". + has_integer_part = true; int number = get_nibble_from_character(next_char); for (int i = 0; i < 4; ++i, number <<= 1) { uint_type write_bit = (number & 0x8) ? 0x1 : 0x0; @@ -993,8 +1001,12 @@ fraction | static_cast<uint_type>( write_bit << (HF::top_bit_left_shift - fraction_index++))); + // TODO(dneto): Avoid overflow. Testing would require + // parameterization. exponent = static_cast<int_type>(exponent + 1); } + // Since this updated after setting fraction bits, this effectively + // drops the leading 1 bit. bits_written |= write_bit != 0; } } else { @@ -1018,10 +1030,12 @@ for (int i = 0; i < 4; ++i, number <<= 1) { uint_type write_bit = (number & 0x8) ? 0x01 : 0x00; bits_written |= write_bit != 0; - if (is_denorm && !bits_written) { + if ((!has_integer_part) && !bits_written) { // Handle modifying the exponent here this way we can handle // an arbitrary number of hex values without overflowing our // integer. + // TODO(dneto): Handle underflow. Testing would require extra + // parameterization. exponent = static_cast<int_type>(exponent - 1); } else { fraction = static_cast<uint_type>( @@ -1043,25 +1057,40 @@ // Finished reading the part preceding 'p'. // In hex floats syntax, the binary exponent is required. - bool seen_sign = false; + bool seen_exponent_sign = false; int8_t exponent_sign = 1; bool seen_written_exponent_digits = false; + // The magnitude of the exponent, as written, or the sentinel value to signal + // overflow. int_type written_exponent = 0; + // A sentinel value signalling overflow of the magnitude of the written + // exponent. We'll assume that -written_exponent_overflow is valid for the + // type. Later we may add 1 or subtract 1 from the adjusted exponent, so leave + // room for an extra 1. + const int_type written_exponent_overflow = + std::numeric_limits<int_type>::max() - 1; while (true) { if (!seen_written_exponent_digits && (next_char == '-' || next_char == '+')) { - if (seen_sign) { + if (seen_exponent_sign) { is.setstate(std::ios::failbit); return is; } - seen_sign = true; + seen_exponent_sign = true; exponent_sign = (next_char == '-') ? -1 : 1; } else if (::isdigit(next_char)) { seen_written_exponent_digits = true; // Hex-floats express their exponent as decimal. - written_exponent = static_cast<int_type>(written_exponent * 10); - written_exponent = - static_cast<int_type>(written_exponent + (next_char - '0')); + int_type digit = + static_cast<int_type>(static_cast<int_type>(next_char) - '0'); + if (written_exponent >= (written_exponent_overflow - digit) / 10) { + // The exponent is very big. Saturate rather than overflow the exponent. + // signed integer, which would be undefined behaviour. + written_exponent = written_exponent_overflow; + } else { + written_exponent = static_cast<int_type>( + static_cast<int_type>(written_exponent * 10) + digit); + } } else { break; } @@ -1075,10 +1104,29 @@ } written_exponent = static_cast<int_type>(written_exponent * exponent_sign); - exponent = static_cast<int_type>(exponent + written_exponent); + // Now fold in the exponent bias into the written exponent, updating exponent. + // But avoid undefined behaviour that would result from overflowing int_type. + if (written_exponent >= 0 && exponent >= 0) { + // Saturate up to written_exponent_overflow. + if (written_exponent_overflow - exponent > written_exponent) { + exponent = static_cast<int_type>(written_exponent + exponent); + } else { + exponent = written_exponent_overflow; + } + } else if (written_exponent < 0 && exponent < 0) { + // Saturate down to -written_exponent_overflow. + if (written_exponent_overflow + exponent > -written_exponent) { + exponent = static_cast<int_type>(written_exponent + exponent); + } else { + exponent = static_cast<int_type>(-written_exponent_overflow); + } + } else { + // They're of opposing sign, so it's safe to add. + exponent = static_cast<int_type>(written_exponent + exponent); + } - bool is_zero = is_denorm && (fraction == 0); - if (is_denorm && !is_zero) { + bool is_zero = (!has_integer_part) && (fraction == 0); + if ((!has_integer_part) && !is_zero) { fraction = static_cast<uint_type>(fraction << 1); exponent = static_cast<int_type>(exponent - 1); } else if (is_zero) { @@ -1095,7 +1143,7 @@ const int_type max_exponent = SetBits<uint_type, 0, HF::num_exponent_bits>::get; - // Handle actual denorm numbers + // Handle denorm numbers while (exponent < 0 && !is_zero) { fraction = static_cast<uint_type>(fraction >> 1); exponent = static_cast<int_type>(exponent + 1);
diff --git a/source/util/pooled_linked_list.h b/source/util/pooled_linked_list.h deleted file mode 100644 index faaa4c4..0000000 --- a/source/util/pooled_linked_list.h +++ /dev/null
@@ -1,236 +0,0 @@ -// Copyright (c) 2021 The Khronos Group Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef SOURCE_UTIL_POOLED_LINKED_LIST_H_ -#define SOURCE_UTIL_POOLED_LINKED_LIST_H_ - -#include <cstdint> -#include <vector> - -namespace spvtools { -namespace utils { - -// Shared storage of nodes for PooledLinkedList. -template <typename T> -class PooledLinkedListNodes { - public: - struct Node { - Node(T e, int32_t n = -1) : element(e), next(n) {} - - T element = {}; - int32_t next = -1; - }; - - PooledLinkedListNodes() = default; - PooledLinkedListNodes(const PooledLinkedListNodes&) = delete; - PooledLinkedListNodes& operator=(const PooledLinkedListNodes&) = delete; - - PooledLinkedListNodes(PooledLinkedListNodes&& that) { - *this = std::move(that); - } - - PooledLinkedListNodes& operator=(PooledLinkedListNodes&& that) { - vec_ = std::move(that.vec_); - free_nodes_ = that.free_nodes_; - return *this; - } - - size_t total_nodes() { return vec_.size(); } - size_t free_nodes() { return free_nodes_; } - size_t used_nodes() { return total_nodes() - free_nodes(); } - - private: - template <typename ListT> - friend class PooledLinkedList; - - Node& at(int32_t index) { return vec_[index]; } - const Node& at(int32_t index) const { return vec_[index]; } - - int32_t insert(T element) { - int32_t index = int32_t(vec_.size()); - vec_.emplace_back(element); - return index; - } - - std::vector<Node> vec_; - size_t free_nodes_ = 0; -}; - -// Implements a linked-list where list nodes come from a shared pool. This is -// meant to be used in scenarios where it is desirable to avoid many small -// allocations. -// -// Instead of pointers, the list uses indices to allow the underlying storage -// to be modified without needing to modify the list. When removing elements -// from the list, nodes are not deleted or recycled: to reclaim unused space, -// perform a sequence of |move_nodes| operations into a temporary pool, which -// then is moved into the old pool. -// -// This does *not* attempt to implement a full stl-compatible interface. -template <typename T> -class PooledLinkedList { - public: - using NodePool = PooledLinkedListNodes<T>; - using Node = typename NodePool::Node; - - PooledLinkedList() = delete; - PooledLinkedList(NodePool* nodes) : nodes_(nodes) {} - - // Shared iterator implementation (for iterator and const_iterator). - template <typename ElementT, typename PoolT> - class iterator_base { - public: - iterator_base(const iterator_base& i) - : nodes_(i.nodes_), index_(i.index_) {} - - iterator_base& operator++() { - index_ = nodes_->at(index_).next; - return *this; - } - - iterator_base& operator=(const iterator_base& i) { - nodes_ = i.nodes_; - index_ = i.index_; - return *this; - } - - ElementT& operator*() const { return nodes_->at(index_).element; } - ElementT* operator->() const { return &nodes_->at(index_).element; } - - friend inline bool operator==(const iterator_base& lhs, - const iterator_base& rhs) { - return lhs.nodes_ == rhs.nodes_ && lhs.index_ == rhs.index_; - } - friend inline bool operator!=(const iterator_base& lhs, - const iterator_base& rhs) { - return lhs.nodes_ != rhs.nodes_ || lhs.index_ != rhs.index_; - } - - // Define standard iterator types needs so this class can be - // used with <algorithms>. - using iterator_category = std::forward_iterator_tag; - using difference_type = std::ptrdiff_t; - using value_type = ElementT; - using pointer = ElementT*; - using const_pointer = const ElementT*; - using reference = ElementT&; - using const_reference = const ElementT&; - using size_type = size_t; - - private: - friend PooledLinkedList; - - iterator_base(PoolT* pool, int32_t index) : nodes_(pool), index_(index) {} - - PoolT* nodes_; - int32_t index_ = -1; - }; - - using iterator = iterator_base<T, std::vector<Node>>; - using const_iterator = iterator_base<const T, const std::vector<Node>>; - - bool empty() const { return head_ == -1; } - - T& front() { return nodes_->at(head_).element; } - T& back() { return nodes_->at(tail_).element; } - const T& front() const { return nodes_->at(head_).element; } - const T& back() const { return nodes_->at(tail_).element; } - - iterator begin() { return iterator(&nodes_->vec_, head_); } - iterator end() { return iterator(&nodes_->vec_, -1); } - const_iterator begin() const { return const_iterator(&nodes_->vec_, head_); } - const_iterator end() const { return const_iterator(&nodes_->vec_, -1); } - - // Inserts |element| at the back of the list. - void push_back(T element) { - int32_t new_tail = nodes_->insert(element); - if (head_ == -1) { - head_ = new_tail; - tail_ = new_tail; - } else { - nodes_->at(tail_).next = new_tail; - tail_ = new_tail; - } - } - - // Removes the first occurrence of |element| from the list. - // Returns if |element| was removed. - bool remove_first(T element) { - int32_t* prev_next = &head_; - for (int32_t prev_index = -1, index = head_; index != -1; /**/) { - auto& node = nodes_->at(index); - if (node.element == element) { - // Snip from of the list, optionally fixing up tail pointer. - if (tail_ == index) { - assert(node.next == -1); - tail_ = prev_index; - } - *prev_next = node.next; - nodes_->free_nodes_++; - return true; - } else { - prev_next = &node.next; - } - prev_index = index; - index = node.next; - } - return false; - } - - // Returns the PooledLinkedListNodes that owns this list's nodes. - NodePool* pool() { return nodes_; } - - // Moves the nodes in this list into |new_pool|, providing a way to compact - // storage and reclaim unused space. - // - // Upon completing a sequence of |move_nodes| calls, you must ensure you - // retain ownership of the new storage your lists point to. Example usage: - // - // unique_ptr<NodePool> new_pool = ...; - // for (PooledLinkedList& list : lists) { - // list.move_to(new_pool); - // } - // my_pool_ = std::move(new_pool); - void move_nodes(NodePool* new_pool) { - // Be sure to construct the list in the same order, instead of simply - // doing a sequence of push_backs. - int32_t prev_entry = -1; - int32_t nodes_freed = 0; - for (int32_t index = head_; index != -1; nodes_freed++) { - const auto& node = nodes_->at(index); - int32_t this_entry = new_pool->insert(node.element); - index = node.next; - if (prev_entry == -1) { - head_ = this_entry; - } else { - new_pool->at(prev_entry).next = this_entry; - } - prev_entry = this_entry; - } - tail_ = prev_entry; - // Update our old pool's free count, now we're a member of the new pool. - nodes_->free_nodes_ += nodes_freed; - nodes_ = new_pool; - } - - private: - NodePool* nodes_; - int32_t head_ = -1; - int32_t tail_ = -1; -}; - -} // namespace utils -} // namespace spvtools - -#endif // SOURCE_UTIL_POOLED_LINKED_LIST_H_ \ No newline at end of file
diff --git a/source/val/basic_block.cpp b/source/val/basic_block.cpp index b2a8793..da05db3 100644 --- a/source/val/basic_block.cpp +++ b/source/val/basic_block.cpp
@@ -24,11 +24,13 @@ BasicBlock::BasicBlock(uint32_t label_id) : id_(label_id), immediate_dominator_(nullptr), - immediate_post_dominator_(nullptr), + immediate_structural_dominator_(nullptr), + immediate_structural_post_dominator_(nullptr), predecessors_(), successors_(), type_(0), reachable_(false), + structurally_reachable_(false), label_(nullptr), terminator_(nullptr) {} @@ -36,21 +38,32 @@ immediate_dominator_ = dom_block; } -void BasicBlock::SetImmediatePostDominator(BasicBlock* pdom_block) { - immediate_post_dominator_ = pdom_block; +void BasicBlock::SetImmediateStructuralDominator(BasicBlock* dom_block) { + immediate_structural_dominator_ = dom_block; +} + +void BasicBlock::SetImmediateStructuralPostDominator(BasicBlock* pdom_block) { + immediate_structural_post_dominator_ = pdom_block; } const BasicBlock* BasicBlock::immediate_dominator() const { return immediate_dominator_; } -const BasicBlock* BasicBlock::immediate_post_dominator() const { - return immediate_post_dominator_; +const BasicBlock* BasicBlock::immediate_structural_dominator() const { + return immediate_structural_dominator_; +} + +const BasicBlock* BasicBlock::immediate_structural_post_dominator() const { + return immediate_structural_post_dominator_; } BasicBlock* BasicBlock::immediate_dominator() { return immediate_dominator_; } -BasicBlock* BasicBlock::immediate_post_dominator() { - return immediate_post_dominator_; +BasicBlock* BasicBlock::immediate_structural_dominator() { + return immediate_structural_dominator_; +} +BasicBlock* BasicBlock::immediate_structural_post_dominator() { + return immediate_structural_post_dominator_; } void BasicBlock::RegisterSuccessors( @@ -58,6 +71,10 @@ for (auto& block : next_blocks) { block->predecessors_.push_back(this); successors_.push_back(block); + + // Register structural successors/predecessors too. + block->structural_predecessors_.push_back(this); + structural_successors_.push_back(block); } } @@ -67,10 +84,16 @@ std::find(other.dom_begin(), other.dom_end(), this)); } -bool BasicBlock::postdominates(const BasicBlock& other) const { - return (this == &other) || - !(other.pdom_end() == - std::find(other.pdom_begin(), other.pdom_end(), this)); +bool BasicBlock::structurally_dominates(const BasicBlock& other) const { + return (this == &other) || !(other.structural_dom_end() == + std::find(other.structural_dom_begin(), + other.structural_dom_end(), this)); +} + +bool BasicBlock::structurally_postdominates(const BasicBlock& other) const { + return (this == &other) || !(other.structural_pdom_end() == + std::find(other.structural_pdom_begin(), + other.structural_pdom_end(), this)); } BasicBlock::DominatorIterator::DominatorIterator() : current_(nullptr) {} @@ -107,21 +130,43 @@ return DominatorIterator(); } -const BasicBlock::DominatorIterator BasicBlock::pdom_begin() const { - return DominatorIterator( - this, [](const BasicBlock* b) { return b->immediate_post_dominator(); }); +const BasicBlock::DominatorIterator BasicBlock::structural_dom_begin() const { + return DominatorIterator(this, [](const BasicBlock* b) { + return b->immediate_structural_dominator(); + }); } -BasicBlock::DominatorIterator BasicBlock::pdom_begin() { - return DominatorIterator( - this, [](const BasicBlock* b) { return b->immediate_post_dominator(); }); +BasicBlock::DominatorIterator BasicBlock::structural_dom_begin() { + return DominatorIterator(this, [](const BasicBlock* b) { + return b->immediate_structural_dominator(); + }); } -const BasicBlock::DominatorIterator BasicBlock::pdom_end() const { +const BasicBlock::DominatorIterator BasicBlock::structural_dom_end() const { return DominatorIterator(); } -BasicBlock::DominatorIterator BasicBlock::pdom_end() { +BasicBlock::DominatorIterator BasicBlock::structural_dom_end() { + return DominatorIterator(); +} + +const BasicBlock::DominatorIterator BasicBlock::structural_pdom_begin() const { + return DominatorIterator(this, [](const BasicBlock* b) { + return b->immediate_structural_post_dominator(); + }); +} + +BasicBlock::DominatorIterator BasicBlock::structural_pdom_begin() { + return DominatorIterator(this, [](const BasicBlock* b) { + return b->immediate_structural_post_dominator(); + }); +} + +const BasicBlock::DominatorIterator BasicBlock::structural_pdom_end() const { + return DominatorIterator(); +} + +BasicBlock::DominatorIterator BasicBlock::structural_pdom_end() { return DominatorIterator(); }
diff --git a/source/val/basic_block.h b/source/val/basic_block.h index 47cd06d..be5657e 100644 --- a/source/val/basic_block.h +++ b/source/val/basic_block.h
@@ -64,9 +64,32 @@ /// Returns the successors of the BasicBlock std::vector<BasicBlock*>* successors() { return &successors_; } - /// Returns true if the block is reachable in the CFG + /// Returns the structural successors of the BasicBlock + std::vector<BasicBlock*>* structural_predecessors() { + return &structural_predecessors_; + } + + /// Returns the structural predecessors of the BasicBlock + const std::vector<BasicBlock*>* structural_predecessors() const { + return &structural_predecessors_; + } + + /// Returns the structural successors of the BasicBlock + std::vector<BasicBlock*>* structural_successors() { + return &structural_successors_; + } + + /// Returns the structural predecessors of the BasicBlock + const std::vector<BasicBlock*>* structural_successors() const { + return &structural_successors_; + } + + /// Returns true if the block is reachable in the CFG. bool reachable() const { return reachable_; } + /// Returns true if the block is structurally reachable in the CFG. + bool structurally_reachable() const { return structurally_reachable_; } + /// Returns true if BasicBlock is of the given type bool is_type(BlockType type) const { if (type == kBlockTypeUndefined) return type_.none(); @@ -76,6 +99,11 @@ /// Sets the reachability of the basic block in the CFG void set_reachable(bool reachability) { reachable_ = reachability; } + /// Sets the structural reachability of the basic block in the CFG + void set_structurally_reachable(bool reachability) { + structurally_reachable_ = reachability; + } + /// Sets the type of the BasicBlock void set_type(BlockType type) { if (type == kBlockTypeUndefined) @@ -89,10 +117,15 @@ /// @param[in] dom_block The dominator block void SetImmediateDominator(BasicBlock* dom_block); + /// Sets the immediate dominator of this basic block + /// + /// @param[in] dom_block The dominator block + void SetImmediateStructuralDominator(BasicBlock* dom_block); + /// Sets the immediate post dominator of this basic block /// /// @param[in] pdom_block The post dominator block - void SetImmediatePostDominator(BasicBlock* pdom_block); + void SetImmediateStructuralPostDominator(BasicBlock* pdom_block); /// Returns the immediate dominator of this basic block BasicBlock* immediate_dominator(); @@ -100,11 +133,17 @@ /// Returns the immediate dominator of this basic block const BasicBlock* immediate_dominator() const; - /// Returns the immediate post dominator of this basic block - BasicBlock* immediate_post_dominator(); + /// Returns the immediate dominator of this basic block + BasicBlock* immediate_structural_dominator(); + + /// Returns the immediate dominator of this basic block + const BasicBlock* immediate_structural_dominator() const; /// Returns the immediate post dominator of this basic block - const BasicBlock* immediate_post_dominator() const; + BasicBlock* immediate_structural_post_dominator(); + + /// Returns the immediate post dominator of this basic block + const BasicBlock* immediate_structural_post_dominator() const; /// Returns the label instruction for the block, or nullptr if not set. const Instruction* label() const { return label_; } @@ -132,9 +171,18 @@ /// Assumes dominators have been computed. bool dominates(const BasicBlock& other) const; - /// Returns true if this block postdominates the other block. - /// Assumes dominators have been computed. - bool postdominates(const BasicBlock& other) const; + /// Returns true if this block structurally dominates the other block. + /// Assumes structural dominators have been computed. + bool structurally_dominates(const BasicBlock& other) const; + + /// Returns true if this block structurally postdominates the other block. + /// Assumes structural dominators have been computed. + bool structurally_postdominates(const BasicBlock& other) const; + + void RegisterStructuralSuccessor(BasicBlock* block) { + block->structural_predecessors_.push_back(this); + structural_successors_.push_back(block); + } /// @brief A BasicBlock dominator iterator class /// @@ -191,18 +239,32 @@ /// block DominatorIterator dom_end(); + /// Returns a dominator iterator which points to the current block + const DominatorIterator structural_dom_begin() const; + + /// Returns a dominator iterator which points to the current block + DominatorIterator structural_dom_begin(); + + /// Returns a dominator iterator which points to one element past the first + /// block + const DominatorIterator structural_dom_end() const; + + /// Returns a dominator iterator which points to one element past the first + /// block + DominatorIterator structural_dom_end(); + /// Returns a post dominator iterator which points to the current block - const DominatorIterator pdom_begin() const; + const DominatorIterator structural_pdom_begin() const; /// Returns a post dominator iterator which points to the current block - DominatorIterator pdom_begin(); + DominatorIterator structural_pdom_begin(); /// Returns a post dominator iterator which points to one element past the /// last block - const DominatorIterator pdom_end() const; + const DominatorIterator structural_pdom_end() const; /// Returns a post dominator iterator which points to one element past the /// last block - DominatorIterator pdom_end(); + DominatorIterator structural_pdom_end(); private: /// Id of the BasicBlock @@ -211,8 +273,11 @@ /// Pointer to the immediate dominator of the BasicBlock BasicBlock* immediate_dominator_; - /// Pointer to the immediate dominator of the BasicBlock - BasicBlock* immediate_post_dominator_; + /// Pointer to the immediate structural dominator of the BasicBlock + BasicBlock* immediate_structural_dominator_; + + /// Pointer to the immediate structural post dominator of the BasicBlock + BasicBlock* immediate_structural_post_dominator_; /// The set of predecessors of the BasicBlock std::vector<BasicBlock*> predecessors_; @@ -226,11 +291,17 @@ /// True if the block is reachable in the CFG bool reachable_; + /// True if the block is structurally reachable in the CFG + bool structurally_reachable_; + /// label of this block, if any. const Instruction* label_; /// Terminator of this block. const Instruction* terminator_; + + std::vector<BasicBlock*> structural_predecessors_; + std::vector<BasicBlock*> structural_successors_; }; /// @brief Returns true if the iterators point to the same element or if both
diff --git a/source/val/construct.cpp b/source/val/construct.cpp index 251e2bb..52e61d5 100644 --- a/source/val/construct.cpp +++ b/source/val/construct.cpp
@@ -70,60 +70,45 @@ void Construct::set_exit(BasicBlock* block) { exit_block_ = block; } -Construct::ConstructBlockSet Construct::blocks(Function* function) const { - auto header = entry_block(); - auto merge = exit_block(); - assert(header); - int header_depth = function->GetBlockDepth(const_cast<BasicBlock*>(header)); - ConstructBlockSet construct_blocks; - std::unordered_set<BasicBlock*> corresponding_headers; - for (auto& other : corresponding_constructs()) { - // The corresponding header can be the same block as this construct's - // header for loops with no loop construct. In those cases, don't add the - // loop header as it prevents finding any blocks in the construct. - if (type() != ConstructType::kContinue || other->entry_block() != header) { - corresponding_headers.insert(other->entry_block()); - } +Construct::ConstructBlockSet Construct::blocks(Function* /*function*/) const { + const auto header = entry_block(); + const auto exit = exit_block(); + const bool is_continue = type() == ConstructType::kContinue; + const bool is_loop = type() == ConstructType::kLoop; + const BasicBlock* continue_header = nullptr; + if (is_loop) { + // The only corresponding construct for a loop is the continue. + continue_header = (*corresponding_constructs().begin())->entry_block(); } std::vector<BasicBlock*> stack; stack.push_back(const_cast<BasicBlock*>(header)); + ConstructBlockSet construct_blocks; while (!stack.empty()) { - BasicBlock* block = stack.back(); + auto* block = stack.back(); stack.pop_back(); - if (merge == block && ExitBlockIsMergeBlock()) { - // Merge block is not part of the construct. - continue; - } + if (header->structurally_dominates(*block)) { + bool include = false; + if (is_continue && exit->structurally_postdominates(*block)) { + // Continue construct include blocks dominated by the continue target + // and post-dominated by the back-edge block. + include = true; + } else if (!exit->structurally_dominates(*block)) { + // Selection and loop constructs include blocks dominated by the header + // and not dominated by the merge. + include = true; + if (is_loop && continue_header->structurally_dominates(*block)) { + // Loop constructs have an additional constraint that they do not + // include blocks dominated by the continue construct. Since all + // blocks in the continue construct are dominated by the continue + // target, we just test for dominance by continue target. + include = false; + } + } + if (include) { + if (!construct_blocks.insert(block).second) continue; - if (corresponding_headers.count(block)) { - // Entered a corresponding construct. - continue; - } - - int block_depth = function->GetBlockDepth(block); - if (block_depth < header_depth) { - // Broke to outer construct. - continue; - } - - // In a loop, the continue target is at a depth of the loop construct + 1. - // A selection construct nested directly within the loop construct is also - // at the same depth. It is valid, however, to branch directly to the - // continue target from within the selection construct. - if (block != header && block_depth == header_depth && - type() == ConstructType::kSelection && - block->is_type(kBlockTypeContinue)) { - // Continued to outer construct. - continue; - } - - if (!construct_blocks.insert(block).second) continue; - - if (merge != block) { - for (auto succ : *block->successors()) { - // All blocks in the construct must be dominated by the header. - if (header->dominates(*succ)) { + for (auto succ : *block->structural_successors()) { stack.push_back(succ); } } @@ -181,11 +166,12 @@ for (auto& use : block->label()->uses()) { if ((use.first->opcode() == SpvOpLoopMerge || use.first->opcode() == SpvOpSelectionMerge) && - use.second == 1 && use.first->block()->dominates(*block)) { + use.second == 1 && + use.first->block()->structurally_dominates(*block)) { return use.first->block(); } } - return block->immediate_dominator(); + return block->immediate_structural_dominator(); }; bool seen_switch = false; @@ -201,7 +187,7 @@ terminator->opcode() == SpvOpSwitch)) { auto merge_target = merge_inst->GetOperandAs<uint32_t>(0u); auto merge_block = merge_inst->function()->GetBlock(merge_target).first; - if (merge_block->dominates(*header)) { + if (merge_block->structurally_dominates(*header)) { block = NextBlock(block); continue; }
diff --git a/source/val/decoration.h b/source/val/decoration.h index ed3320f..4f53f20 100644 --- a/source/val/decoration.h +++ b/source/val/decoration.h
@@ -69,6 +69,15 @@ std::vector<uint32_t>& params() { return params_; } const std::vector<uint32_t>& params() const { return params_; } + inline bool operator<(const Decoration& rhs) const { + // Note: Sort by struct_member_index_ first, then type, so look up can be + // efficient using lower_bound() and upper_bound(). + if (struct_member_index_ < rhs.struct_member_index_) return true; + if (rhs.struct_member_index_ < struct_member_index_) return false; + if (dec_type_ < rhs.dec_type_) return true; + if (rhs.dec_type_ < dec_type_) return false; + return params_ < rhs.params_; + } inline bool operator==(const Decoration& rhs) const { return (dec_type_ == rhs.dec_type_ && params_ == rhs.params_ && struct_member_index_ == rhs.struct_member_index_);
diff --git a/source/val/function.cpp b/source/val/function.cpp index f3292b0..fc7ccd0 100644 --- a/source/val/function.cpp +++ b/source/val/function.cpp
@@ -73,6 +73,8 @@ BasicBlock& continue_target_block = blocks_.at(continue_id); assert(current_block_ && "RegisterLoopMerge must be called when called within a block"); + current_block_->RegisterStructuralSuccessor(&merge_block); + current_block_->RegisterStructuralSuccessor(&continue_target_block); current_block_->set_type(kBlockTypeLoop); merge_block.set_type(kBlockTypeMerge); @@ -101,6 +103,7 @@ current_block_->set_type(kBlockTypeSelection); merge_block.set_type(kBlockTypeMerge); merge_block_header_[&merge_block] = current_block_; + current_block_->RegisterStructuralSuccessor(&merge_block); AddConstruct({ConstructType::kSelection, current_block(), &merge_block}); @@ -251,16 +254,6 @@ }; } -Function::GetBlocksFunction -Function::AugmentedCFGSuccessorsFunctionIncludingHeaderToContinueEdge() const { - return [this](const BasicBlock* block) { - auto where = loop_header_successors_plus_continue_target_map_.find(block); - return where == loop_header_successors_plus_continue_target_map_.end() - ? AugmentedCFGSuccessorsFunction()(block) - : &(*where).second; - }; -} - Function::GetBlocksFunction Function::AugmentedCFGPredecessorsFunction() const { return [this](const BasicBlock* block) { auto where = augmented_predecessors_map_.find(block); @@ -269,11 +262,35 @@ }; } +Function::GetBlocksFunction Function::AugmentedStructuralCFGSuccessorsFunction() + const { + return [this](const BasicBlock* block) { + auto where = augmented_successors_map_.find(block); + return where == augmented_successors_map_.end() + ? block->structural_successors() + : &(*where).second; + }; +} + +Function::GetBlocksFunction +Function::AugmentedStructuralCFGPredecessorsFunction() const { + return [this](const BasicBlock* block) { + auto where = augmented_predecessors_map_.find(block); + return where == augmented_predecessors_map_.end() + ? block->structural_predecessors() + : &(*where).second; + }; +} + void Function::ComputeAugmentedCFG() { // Compute the successors of the pseudo-entry block, and // the predecessors of the pseudo exit block. - auto succ_func = [](const BasicBlock* b) { return b->successors(); }; - auto pred_func = [](const BasicBlock* b) { return b->predecessors(); }; + auto succ_func = [](const BasicBlock* b) { + return b->structural_successors(); + }; + auto pred_func = [](const BasicBlock* b) { + return b->structural_predecessors(); + }; CFA<BasicBlock>::ComputeAugmentedCFG( ordered_blocks_, &pseudo_entry_block_, &pseudo_exit_block_, &augmented_successors_map_, &augmented_predecessors_map_, succ_func,
diff --git a/source/val/function.h b/source/val/function.h index 2fe30bd..126b1dc 100644 --- a/source/val/function.h +++ b/source/val/function.h
@@ -184,12 +184,12 @@ std::function<const std::vector<BasicBlock*>*(const BasicBlock*)>; /// Returns the block successors function for the augmented CFG. GetBlocksFunction AugmentedCFGSuccessorsFunction() const; - /// Like AugmentedCFGSuccessorsFunction, but also includes a forward edge from - /// a loop header block to its continue target, if they are different blocks. - GetBlocksFunction - AugmentedCFGSuccessorsFunctionIncludingHeaderToContinueEdge() const; /// Returns the block predecessors function for the augmented CFG. GetBlocksFunction AugmentedCFGPredecessorsFunction() const; + /// Returns the block structural successors function for the augmented CFG. + GetBlocksFunction AugmentedStructuralCFGSuccessorsFunction() const; + /// Returns the block structural predecessors function for the augmented CFG. + GetBlocksFunction AugmentedStructuralCFGPredecessorsFunction() const; /// Returns the control flow nesting depth of the given basic block. /// This function only works when you have structured control flow.
diff --git a/source/val/validate.cpp b/source/val/validate.cpp index ecc9fdb..55e9fd2 100644 --- a/source/val/validate.cpp +++ b/source/val/validate.cpp
@@ -293,6 +293,11 @@ return vstate->diag(SPV_ERROR_INVALID_LAYOUT, nullptr) << "Missing OpFunctionEnd at end of module."; + if (vstate->HasCapability(SpvCapabilityBindlessTextureNV) && + !vstate->has_samplerimage_variable_address_mode_specified()) + return vstate->diag(SPV_ERROR_INVALID_LAYOUT, nullptr) + << "Missing required OpSamplerImageAddressingModeNV instruction."; + // Catch undefined forward references before performing further checks. if (auto error = ValidateForwardDecls(*vstate)) return error; @@ -345,6 +350,7 @@ if (auto error = NonUniformPass(*vstate, &instruction)) return error; if (auto error = LiteralsPass(*vstate, &instruction)) return error; + if (auto error = RayQueryPass(*vstate, &instruction)) return error; } // Validate the preconditions involving adjacent instructions. e.g. SpvOpPhi
diff --git a/source/val/validate.h b/source/val/validate.h index cb1d05a..97d4683 100644 --- a/source/val/validate.h +++ b/source/val/validate.h
@@ -197,6 +197,9 @@ /// Validates correctness of miscellaneous instructions. spv_result_t MiscPass(ValidationState_t& _, const Instruction* inst); +/// Validates correctness of ray query instructions. +spv_result_t RayQueryPass(ValidationState_t& _, const Instruction* inst); + /// Calculates the reachability of basic blocks. void ReachabilityPass(ValidationState_t& _);
diff --git a/source/val/validate_annotation.cpp b/source/val/validate_annotation.cpp index 40f2118..c27c799 100644 --- a/source/val/validate_annotation.cpp +++ b/source/val/validate_annotation.cpp
@@ -581,7 +581,7 @@ // Word 1 is the group <id>. All subsequent words are target <id>s that // are going to be decorated with the decorations. const uint32_t decoration_group_id = inst->word(1); - std::vector<Decoration>& group_decorations = + std::set<Decoration>& group_decorations = _.id_decorations(decoration_group_id); for (size_t i = 2; i < inst->words().size(); ++i) { const uint32_t target_id = inst->word(i); @@ -595,7 +595,7 @@ // pairs. All decorations of the group should be applied to all the struct // members that are specified in the instructions. const uint32_t decoration_group_id = inst->word(1); - std::vector<Decoration>& group_decorations = + std::set<Decoration>& group_decorations = _.id_decorations(decoration_group_id); // Grammar checks ensures that the number of arguments to this instruction // is an odd number: 1 decoration group + (id,literal) pairs.
diff --git a/source/val/validate_cfg.cpp b/source/val/validate_cfg.cpp index dd605d2..0220fcd 100644 --- a/source/val/validate_cfg.cpp +++ b/source/val/validate_cfg.cpp
@@ -66,7 +66,8 @@ assert(type_inst); const SpvOp type_opcode = type_inst->opcode(); - if (!_.options()->before_hlsl_legalization) { + if (!_.options()->before_hlsl_legalization && + !_.HasCapability(SpvCapabilityBindlessTextureNV)) { if (type_opcode == SpvOpTypeSampledImage || (_.HasCapability(SpvCapabilityShader) && (type_opcode == SpvOpTypeImage || type_opcode == SpvOpTypeSampler))) { @@ -466,7 +467,7 @@ std::vector<BasicBlock*> stack; stack.push_back(target_block); std::unordered_set<const BasicBlock*> visited; - bool target_reachable = target_block->reachable(); + bool target_reachable = target_block->structurally_reachable(); int target_depth = function->GetBlockDepth(target_block); while (!stack.empty()) { auto block = stack.back(); @@ -476,8 +477,8 @@ if (!visited.insert(block).second) continue; - if (target_reachable && block->reachable() && - target_block->dominates(*block)) { + if (target_reachable && block->structurally_reachable() && + target_block->structurally_dominates(*block)) { // Still in the case construct. for (auto successor : *block->successors()) { stack.push_back(successor); @@ -549,11 +550,12 @@ if (seen_iter == seen_to_fall_through.end()) { const auto target_block = function->GetBlock(target).first; // OpSwitch must dominate all its case constructs. - if (header->reachable() && target_block->reachable() && - !header->dominates(*target_block)) { + if (header->structurally_reachable() && + target_block->structurally_reachable() && + !header->structurally_dominates(*target_block)) { return _.diag(SPV_ERROR_INVALID_CFG, header->label()) << "Selection header " << _.getIdName(header->id()) - << " does not dominate its case construct " + << " does not structurally dominate its case construct " << _.getIdName(target); } @@ -653,7 +655,7 @@ } // Skip unreachable blocks. - if (!block->reachable()) continue; + if (!block->structurally_reachable()) continue; if (terminator->opcode() == SpvOpBranchConditional) { const auto true_label = terminator->GetOperandAs<uint32_t>(1); @@ -708,7 +710,7 @@ // Check the loop headers have exactly one back-edge branching to it for (BasicBlock* loop_header : function->ordered_blocks()) { - if (!loop_header->reachable()) continue; + if (!loop_header->structurally_reachable()) continue; if (!loop_header->is_type(kBlockTypeLoop)) continue; auto loop_header_id = loop_header->id(); auto num_latch_blocks = loop_latch_blocks[loop_header_id].size(); @@ -723,9 +725,10 @@ // Check construct rules for (const Construct& construct : function->constructs()) { auto header = construct.entry_block(); + if (!header->structurally_reachable()) continue; auto merge = construct.exit_block(); - if (header->reachable() && !merge) { + if (!merge) { std::string construct_name, header_name, exit_name; std::tie(construct_name, header_name, exit_name) = ConstructNames(construct.type()); @@ -735,32 +738,31 @@ exit_name + ". This may be a bug in the validator."; } - // If the exit block is reachable then it's dominated by the - // header. - if (merge && merge->reachable()) { - if (!header->dominates(*merge)) { - return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id())) - << ConstructErrorString(construct, _.getIdName(header->id()), - _.getIdName(merge->id()), - "does not dominate"); - } - // If it's really a merge block for a selection or loop, then it must be - // *strictly* dominated by the header. - if (construct.ExitBlockIsMergeBlock() && (header == merge)) { - return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id())) - << ConstructErrorString(construct, _.getIdName(header->id()), - _.getIdName(merge->id()), - "does not strictly dominate"); - } + // If the header is reachable, the merge is guaranteed to be structurally + // reachable. + if (!header->structurally_dominates(*merge)) { + return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id())) + << ConstructErrorString(construct, _.getIdName(header->id()), + _.getIdName(merge->id()), + "does not structurally dominate"); } + // If it's really a merge block for a selection or loop, then it must be + // *strictly* structrually dominated by the header. + if (construct.ExitBlockIsMergeBlock() && (header == merge)) { + return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id())) + << ConstructErrorString(construct, _.getIdName(header->id()), + _.getIdName(merge->id()), + "does not strictly structurally dominate"); + } + // Check post-dominance for continue constructs. But dominance and // post-dominance only make sense when the construct is reachable. - if (header->reachable() && construct.type() == ConstructType::kContinue) { - if (!merge->postdominates(*header)) { + if (construct.type() == ConstructType::kContinue) { + if (!merge->structurally_postdominates(*header)) { return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id())) << ConstructErrorString(construct, _.getIdName(header->id()), _.getIdName(merge->id()), - "is not post dominated by"); + "is not structurally post dominated by"); } } @@ -771,7 +773,7 @@ for (auto block : construct_blocks) { // Check that all exits from the construct are via structured exits. for (auto succ : *block->successors()) { - if (block->reachable() && !construct_blocks.count(succ) && + if (!construct_blocks.count(succ) && !construct.IsStructuredExit(_, succ)) { return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id())) << "block <ID> " << _.getIdName(block->id()) << " exits the " @@ -784,7 +786,7 @@ // Check that for all non-header blocks, all predecessors are within this // construct. for (auto pred : *block->predecessors()) { - if (pred->reachable() && !construct_blocks.count(pred)) { + if (pred->structurally_reachable() && !construct_blocks.count(pred)) { return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(pred->id())) << "block <ID> " << pred->id() << " branches to the " << construct_name << " construct, but not to the " @@ -800,7 +802,7 @@ merge_inst.opcode() == SpvOpLoopMerge) { uint32_t merge_id = merge_inst.GetOperandAs<uint32_t>(0); auto merge_block = function->GetBlock(merge_id).first; - if (merge_block->reachable() && + if (merge_block->structurally_reachable() && !construct_blocks.count(merge_block)) { return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id())) << "Header block " << _.getIdName(block->id()) @@ -813,6 +815,43 @@ } } + if (construct.type() == ConstructType::kLoop) { + // If the continue target differs from the loop header, then check that + // all edges into the continue construct come from within the loop. + const auto index = header->terminator() - &_.ordered_instructions()[0]; + const auto& merge_inst = _.ordered_instructions()[index - 1]; + const auto continue_id = merge_inst.GetOperandAs<uint32_t>(1); + const auto* continue_inst = _.FindDef(continue_id); + // OpLabel instructions aren't stored as part of the basic block for + // legacy reaasons. Grab the next instruction and use it's block pointer + // instead. + const auto next_index = + (continue_inst - &_.ordered_instructions()[0]) + 1; + const auto& next_inst = _.ordered_instructions()[next_index]; + const auto* continue_target = next_inst.block(); + if (header->id() != continue_id) { + for (auto pred : *continue_target->predecessors()) { + // Ignore back-edges from within the continue construct. + bool is_back_edge = false; + for (auto back_edge : back_edges) { + uint32_t back_edge_block; + uint32_t header_block; + std::tie(back_edge_block, header_block) = back_edge; + if (header_block == continue_id && back_edge_block == pred->id()) + is_back_edge = true; + } + if (!construct_blocks.count(pred) && !is_back_edge) { + return _.diag(SPV_ERROR_INVALID_CFG, pred->terminator()) + << "Block " << _.getIdName(pred->id()) + << " branches to the loop continue target " + << _.getIdName(continue_id) + << ", but is not contained in the associated loop construct " + << _.getIdName(header->id()); + } + } + } + } + // Checks rules for case constructs. if (construct.type() == ConstructType::kSelection && header->terminator()->opcode() == SpvOpSwitch) { @@ -850,52 +889,28 @@ << _.getIdName(function.id()); } - // Set each block's immediate dominator and immediate postdominator, - // and find all back-edges. + // Set each block's immediate dominator. // // We want to analyze all the blocks in the function, even in degenerate // control flow cases including unreachable blocks. So use the augmented // CFG to ensure we cover all the blocks. std::vector<const BasicBlock*> postorder; - std::vector<const BasicBlock*> postdom_postorder; - std::vector<std::pair<uint32_t, uint32_t>> back_edges; auto ignore_block = [](const BasicBlock*) {}; auto ignore_edge = [](const BasicBlock*, const BasicBlock*) {}; + auto no_terminal_blocks = [](const BasicBlock*) { return false; }; if (!function.ordered_blocks().empty()) { /// calculate dominators CFA<BasicBlock>::DepthFirstTraversal( function.first_block(), function.AugmentedCFGSuccessorsFunction(), ignore_block, [&](const BasicBlock* b) { postorder.push_back(b); }, - ignore_edge); + ignore_edge, no_terminal_blocks); auto edges = CFA<BasicBlock>::CalculateDominators( postorder, function.AugmentedCFGPredecessorsFunction()); for (auto edge : edges) { if (edge.first != edge.second) edge.first->SetImmediateDominator(edge.second); } - - /// calculate post dominators - CFA<BasicBlock>::DepthFirstTraversal( - function.pseudo_exit_block(), - function.AugmentedCFGPredecessorsFunction(), ignore_block, - [&](const BasicBlock* b) { postdom_postorder.push_back(b); }, - ignore_edge); - auto postdom_edges = CFA<BasicBlock>::CalculateDominators( - postdom_postorder, function.AugmentedCFGSuccessorsFunction()); - for (auto edge : postdom_edges) { - edge.first->SetImmediatePostDominator(edge.second); - } - /// calculate back edges. - CFA<BasicBlock>::DepthFirstTraversal( - function.pseudo_entry_block(), - function - .AugmentedCFGSuccessorsFunctionIncludingHeaderToContinueEdge(), - ignore_block, ignore_block, - [&](const BasicBlock* from, const BasicBlock* to) { - back_edges.emplace_back(from->id(), to->id()); - }); } - UpdateContinueConstructExitBlocks(function, back_edges); auto& blocks = function.ordered_blocks(); if (!blocks.empty()) { @@ -929,6 +944,52 @@ /// Structured control flow checks are only required for shader capabilities if (_.HasCapability(SpvCapabilityShader)) { + // Calculate structural dominance. + postorder.clear(); + std::vector<const BasicBlock*> postdom_postorder; + std::vector<std::pair<uint32_t, uint32_t>> back_edges; + if (!function.ordered_blocks().empty()) { + /// calculate dominators + CFA<BasicBlock>::DepthFirstTraversal( + function.first_block(), + function.AugmentedStructuralCFGSuccessorsFunction(), ignore_block, + [&](const BasicBlock* b) { postorder.push_back(b); }, ignore_edge, + no_terminal_blocks); + auto edges = CFA<BasicBlock>::CalculateDominators( + postorder, function.AugmentedStructuralCFGPredecessorsFunction()); + for (auto edge : edges) { + if (edge.first != edge.second) + edge.first->SetImmediateStructuralDominator(edge.second); + } + + /// calculate post dominators + CFA<BasicBlock>::DepthFirstTraversal( + function.pseudo_exit_block(), + function.AugmentedStructuralCFGPredecessorsFunction(), ignore_block, + [&](const BasicBlock* b) { postdom_postorder.push_back(b); }, + ignore_edge, no_terminal_blocks); + auto postdom_edges = CFA<BasicBlock>::CalculateDominators( + postdom_postorder, + function.AugmentedStructuralCFGSuccessorsFunction()); + for (auto edge : postdom_edges) { + edge.first->SetImmediateStructuralPostDominator(edge.second); + } + /// calculate back edges. + CFA<BasicBlock>::DepthFirstTraversal( + function.pseudo_entry_block(), + function.AugmentedStructuralCFGSuccessorsFunction(), ignore_block, + ignore_block, + [&](const BasicBlock* from, const BasicBlock* to) { + // A back edge must be a real edge. Since the augmented successors + // contain structural edges, filter those from consideration. + for (const auto* succ : *(from->successors())) { + if (succ == to) back_edges.emplace_back(from->id(), to->id()); + } + }, + no_terminal_blocks); + } + UpdateContinueConstructExitBlocks(function, back_edges); + if (auto error = StructuredControlFlowChecks(_, &function, back_edges, postorder)) return error; @@ -1054,6 +1115,26 @@ } } } + + // Repeat for structural reachability. + for (auto& f : _.functions()) { + std::vector<BasicBlock*> stack; + auto entry = f.first_block(); + // Skip function declarations. + if (entry) stack.push_back(entry); + + while (!stack.empty()) { + auto block = stack.back(); + stack.pop_back(); + + if (block->structurally_reachable()) continue; + + block->set_structurally_reachable(true); + for (auto succ : *block->structural_successors()) { + stack.push_back(succ); + } + } + } } spv_result_t ControlFlowPass(ValidationState_t& _, const Instruction* inst) {
diff --git a/source/val/validate_conversion.cpp b/source/val/validate_conversion.cpp index b4e39cf..dc6b151 100644 --- a/source/val/validate_conversion.cpp +++ b/source/val/validate_conversion.cpp
@@ -534,6 +534,24 @@ break; } + case SpvOpConvertUToAccelerationStructureKHR: { + if (!_.IsAccelerationStructureType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Expected Result Type to be a Acceleration Structure: " + << spvOpcodeString(opcode); + } + + const uint32_t input_type = _.GetOperandTypeId(inst, 2); + if (!input_type || !_.IsUnsigned64BitHandle(input_type)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Expected 64-bit uint scalar or 2-component 32-bit uint " + "vector as input: " + << spvOpcodeString(opcode); + } + + break; + } + default: break; }
diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp index 73d512a..4e4f108 100644 --- a/source/val/validate_decorations.cpp +++ b/source/val/validate_decorations.cpp
@@ -190,6 +190,13 @@ // Minimal alignment is byte-aligned. uint32_t baseAlignment = 1; switch (inst->opcode()) { + case SpvOpTypeSampledImage: + case SpvOpTypeSampler: + case SpvOpTypeImage: + if (vstate.HasCapability(SpvCapabilityBindlessTextureNV)) + return baseAlignment = vstate.samplerimage_variable_address_mode() / 8; + assert(0); + return 0; case SpvOpTypeInt: case SpvOpTypeFloat: baseAlignment = words[2] / 8; @@ -219,6 +226,7 @@ baseAlignment = componentAlignment * (num_columns == 3 ? 4 : num_columns); } + if (roundUp) baseAlignment = align(baseAlignment, 16u); } break; case SpvOpTypeArray: case SpvOpTypeRuntimeArray: @@ -256,6 +264,13 @@ const auto inst = vstate.FindDef(type_id); const auto& words = inst->words(); switch (inst->opcode()) { + case SpvOpTypeSampledImage: + case SpvOpTypeSampler: + case SpvOpTypeImage: + if (vstate.HasCapability(SpvCapabilityBindlessTextureNV)) + return vstate.samplerimage_variable_address_mode() / 8; + assert(0); + return 0; case SpvOpTypeInt: case SpvOpTypeFloat: return words[2] / 8; @@ -296,6 +311,13 @@ const auto inst = vstate.FindDef(member_id); const auto& words = inst->words(); switch (inst->opcode()) { + case SpvOpTypeSampledImage: + case SpvOpTypeSampler: + case SpvOpTypeImage: + if (vstate.HasCapability(SpvCapabilityBindlessTextureNV)) + return vstate.samplerimage_variable_address_mode() / 8; + assert(0); + return 0; case SpvOpTypeInt: case SpvOpTypeFloat: return words[2] / 8; @@ -346,10 +368,13 @@ const auto& lastMember = members.back(); uint32_t offset = 0xffffffff; // Find the offset of the last element and add the size. - for (auto& decoration : vstate.id_decorations(member_id)) { - if (SpvDecorationOffset == decoration.dec_type() && - decoration.struct_member_index() == (int)lastIdx) { - offset = decoration.params()[0]; + auto member_decorations = + vstate.id_member_decorations(member_id, lastIdx); + for (auto decoration = member_decorations.begin; + decoration != member_decorations.end; ++decoration) { + assert(decoration->struct_member_index() == (int)lastIdx); + if (SpvDecorationOffset == decoration->dec_type()) { + offset = decoration->params()[0]; } } // This check depends on the fact that all members have offsets. This @@ -445,15 +470,17 @@ for (uint32_t memberIdx = 0, numMembers = uint32_t(members.size()); memberIdx < numMembers; memberIdx++) { uint32_t offset = 0xffffffff; - for (auto& decoration : vstate.id_decorations(struct_id)) { - if (decoration.struct_member_index() == (int)memberIdx) { - switch (decoration.dec_type()) { - case SpvDecorationOffset: - offset = decoration.params()[0]; - break; - default: - break; - } + auto member_decorations = + vstate.id_member_decorations(struct_id, memberIdx); + for (auto decoration = member_decorations.begin; + decoration != member_decorations.end; ++decoration) { + assert(decoration->struct_member_index() == (int)memberIdx); + switch (decoration->dec_type()) { + case SpvDecorationOffset: + offset = decoration->params()[0]; + break; + default: + break; } } member_offsets.push_back( @@ -633,7 +660,8 @@ } // Returns true if all ids of given type have a specified decoration. -bool checkForRequiredDecoration(uint32_t struct_id, SpvDecoration decoration, +bool checkForRequiredDecoration(uint32_t struct_id, + std::function<bool(SpvDecoration)> checker, SpvOp type, ValidationState_t& vstate) { const auto& members = getStructMembers(struct_id, vstate); for (size_t memberIdx = 0; memberIdx < members.size(); memberIdx++) { @@ -641,10 +669,10 @@ if (type != vstate.FindDef(id)->opcode()) continue; bool found = false; for (auto& dec : vstate.id_decorations(id)) { - if (decoration == dec.dec_type()) found = true; + if (checker(dec.dec_type())) found = true; } for (auto& dec : vstate.id_decorations(struct_id)) { - if (decoration == dec.dec_type() && + if (checker(dec.dec_type()) && (int)memberIdx == dec.struct_member_index()) { found = true; } @@ -654,7 +682,7 @@ } } for (auto id : getStructMembers(struct_id, SpvOpTypeStruct, vstate)) { - if (!checkForRequiredDecoration(id, decoration, type, vstate)) { + if (!checkForRequiredDecoration(id, checker, type, vstate)) { return false; } } @@ -806,6 +834,56 @@ ++num_workgroup_variables_with_aliased; } } + + if (spvIsVulkanEnv(vstate.context()->target_env)) { + const auto* models = vstate.GetExecutionModels(entry_point); + const bool has_frag = + models->find(SpvExecutionModelFragment) != models->end(); + const bool has_vert = + models->find(SpvExecutionModelVertex) != models->end(); + for (const auto& decoration : + vstate.id_decorations(var_instr->id())) { + if (decoration == SpvDecorationFlat || + decoration == SpvDecorationNoPerspective || + decoration == SpvDecorationSample || + decoration == SpvDecorationCentroid) { + // VUID 04670 already validates these decorations are input/output + if (storage_class == SpvStorageClassInput && + (models->size() > 1 || has_vert)) { + return vstate.diag(SPV_ERROR_INVALID_ID, var_instr) + << vstate.VkErrorID(6202) + << "OpEntryPoint interfaces variable must not be vertex " + "execution model with an input storage class for " + "Entry Point id " + << entry_point << "."; + } else if (storage_class == SpvStorageClassOutput && + (models->size() > 1 || has_frag)) { + return vstate.diag(SPV_ERROR_INVALID_ID, var_instr) + << vstate.VkErrorID(6201) + << "OpEntryPoint interfaces variable must not be " + "fragment " + "execution model with an output storage class for " + "Entry Point id " + << entry_point << "."; + } + } + } + + const bool has_flat = + hasDecoration(var_instr->id(), SpvDecorationFlat, vstate); + if (has_frag && storage_class == SpvStorageClassInput && !has_flat && + ((vstate.IsFloatScalarType(type_id) && + vstate.GetBitWidth(type_id) == 64) || + vstate.IsIntScalarOrVectorType(type_id))) { + return vstate.diag(SPV_ERROR_INVALID_ID, var_instr) + << vstate.VkErrorID(4744) + << "Fragment OpEntryPoint operand " + << interface << " with Input interfaces with integer or " + "float type must have a Flat decoration " + "for Entry Point id " + << entry_point << "."; + } + } } if (num_builtin_block_inputs > 1 || num_builtin_block_outputs > 1) { return vstate.diag(SPV_ERROR_INVALID_BINARY, @@ -878,21 +956,23 @@ LayoutConstraints& constraint = (*constraints)[std::make_pair(struct_id, memberIdx)]; constraint = inherited; - for (auto& decoration : vstate.id_decorations(struct_id)) { - if (decoration.struct_member_index() == (int)memberIdx) { - switch (decoration.dec_type()) { - case SpvDecorationRowMajor: - constraint.majorness = kRowMajor; - break; - case SpvDecorationColMajor: - constraint.majorness = kColumnMajor; - break; - case SpvDecorationMatrixStride: - constraint.matrix_stride = decoration.params()[0]; - break; - default: - break; - } + auto member_decorations = + vstate.id_member_decorations(struct_id, memberIdx); + for (auto decoration = member_decorations.begin; + decoration != member_decorations.end; ++decoration) { + assert(decoration->struct_member_index() == (int)memberIdx); + switch (decoration->dec_type()) { + case SpvDecorationRowMajor: + constraint.majorness = kRowMajor; + break; + case SpvDecorationColMajor: + constraint.majorness = kColumnMajor; + break; + case SpvDecorationMatrixStride: + constraint.matrix_stride = decoration->params()[0]; + break; + default: + break; } } @@ -1144,30 +1224,48 @@ return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id)) << "Structure id " << id << " decorated as " << deco_str << " must not use GLSLPacked decoration."; - } else if (!checkForRequiredDecoration(id, SpvDecorationArrayStride, - SpvOpTypeArray, vstate)) { + } else if (!checkForRequiredDecoration( + id, + [](SpvDecoration d) { + return d == SpvDecorationArrayStride; + }, + SpvOpTypeArray, vstate)) { return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id)) << "Structure id " << id << " decorated as " << deco_str << " must be explicitly laid out with ArrayStride " "decorations."; - } else if (!checkForRequiredDecoration(id, - SpvDecorationMatrixStride, - SpvOpTypeMatrix, vstate)) { + } else if (!checkForRequiredDecoration( + id, + [](SpvDecoration d) { + return d == SpvDecorationMatrixStride; + }, + SpvOpTypeMatrix, vstate)) { return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id)) << "Structure id " << id << " decorated as " << deco_str << " must be explicitly laid out with MatrixStride " "decorations."; + } else if (!checkForRequiredDecoration( + id, + [](SpvDecoration d) { + return d == SpvDecorationRowMajor || + d == SpvDecorationColMajor; + }, + SpvOpTypeMatrix, vstate)) { + return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id)) + << "Structure id " << id << " decorated as " << deco_str + << " must be explicitly laid out with RowMajor or " + "ColMajor decorations."; } else if (blockRules && - (SPV_SUCCESS != (recursive_status = checkLayout( - id, sc_str, deco_str, true, - scalar_block_layout, 0, - constraints, vstate)))) { + (SPV_SUCCESS != + (recursive_status = checkLayout( + id, sc_str, deco_str, true, scalar_block_layout, 0, + constraints, vstate)))) { return recursive_status; } else if (bufferRules && - (SPV_SUCCESS != (recursive_status = checkLayout( - id, sc_str, deco_str, false, - scalar_block_layout, 0, - constraints, vstate)))) { + (SPV_SUCCESS != + (recursive_status = checkLayout( + id, sc_str, deco_str, false, scalar_block_layout, + 0, constraints, vstate)))) { return recursive_status; } } @@ -1654,6 +1752,24 @@ "of a structure type"; } +spv_result_t CheckRelaxPrecisionDecoration(ValidationState_t& vstate, + const Instruction& inst, + const Decoration& decoration) { + // This is not the most precise check, but the rules for RelaxPrecision are + // very general, and it will be difficult to implement precisely. For now, + // I will only check for the cases that cause problems for the optimizer. + if (!spvOpcodeGeneratesType(inst.opcode())) { + return SPV_SUCCESS; + } + + if (decoration.struct_member_index() != Decoration::kInvalidMember && + inst.opcode() == SpvOpTypeStruct) { + return SPV_SUCCESS; + } + return vstate.diag(SPV_ERROR_INVALID_ID, &inst) + << "RelaxPrecision decoration cannot be applied to a type"; +} + #define PASS_OR_BAIL_AT_LINE(X, LINE) \ { \ spv_result_t e##LINE = (X); \ @@ -1708,6 +1824,10 @@ case SpvDecorationLocation: PASS_OR_BAIL(CheckLocationDecoration(vstate, *inst, decoration)); break; + case SpvDecorationRelaxedPrecision: + PASS_OR_BAIL( + CheckRelaxPrecisionDecoration(vstate, *inst, decoration)); + break; default: break; }
diff --git a/source/val/validate_image.cpp b/source/val/validate_image.cpp index f6d7d10..2d5e2c7 100644 --- a/source/val/validate_image.cpp +++ b/source/val/validate_image.cpp
@@ -927,7 +927,7 @@ return SPV_SUCCESS; } -bool IsAllowedSampledImageOperand(SpvOp opcode) { +bool IsAllowedSampledImageOperand(SpvOp opcode, ValidationState_t& _) { switch (opcode) { case SpvOpSampledImage: case SpvOpImageSampleImplicitLod: @@ -950,6 +950,9 @@ case SpvOpImageSparseDrefGather: case SpvOpCopyObject: return true; + case SpvOpStore: + if (_.HasCapability(SpvCapabilityBindlessTextureNV)) return true; + return false; default: return false; } @@ -1035,7 +1038,7 @@ << _.getIdName(consumer_instr->id()) << "'."; } - if (!IsAllowedSampledImageOperand(consumer_opcode)) { + if (!IsAllowedSampledImageOperand(consumer_opcode, _)) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "Result <id> from OpSampledImage instruction must not appear " "as operand for Op"
diff --git a/source/val/validate_instruction.cpp b/source/val/validate_instruction.cpp index 3edf163..767c0ce 100644 --- a/source/val/validate_instruction.cpp +++ b/source/val/validate_instruction.cpp
@@ -483,6 +483,22 @@ if (auto error = LimitCheckNumVars(_, inst->id(), storage_class)) { return error; } + } else if (opcode == SpvOpSamplerImageAddressingModeNV) { + if (!_.HasCapability(SpvCapabilityBindlessTextureNV)) { + return _.diag(SPV_ERROR_MISSING_EXTENSION, inst) + << "OpSamplerImageAddressingModeNV supported only with extension " + "SPV_NV_bindless_texture"; + } + uint32_t bitwidth = inst->GetOperandAs<uint32_t>(0); + if (_.samplerimage_variable_address_mode() != 0) { + return _.diag(SPV_ERROR_INVALID_LAYOUT, inst) + << "OpSamplerImageAddressingModeNV should only be provided once"; + } + if (bitwidth != 32 && bitwidth != 64) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "OpSamplerImageAddressingModeNV bitwidth should be 64 or 32"; + } + _.set_samplerimage_variable_address_mode(bitwidth); } if (auto error = ReservedCheck(_, inst)) return error;
diff --git a/source/val/validate_layout.cpp b/source/val/validate_layout.cpp index d582321..6f95135 100644 --- a/source/val/validate_layout.cpp +++ b/source/val/validate_layout.cpp
@@ -363,6 +363,7 @@ case kLayoutExtensions: case kLayoutExtInstImport: case kLayoutMemoryModel: + case kLayoutSamplerImageAddressMode: case kLayoutEntryPoint: case kLayoutExecutionMode: case kLayoutDebug1:
diff --git a/source/val/validate_logicals.cpp b/source/val/validate_logicals.cpp index 5307988..ec1e207 100644 --- a/source/val/validate_logicals.cpp +++ b/source/val/validate_logicals.cpp
@@ -170,6 +170,16 @@ break; } + case SpvOpTypeSampledImage: + case SpvOpTypeImage: + case SpvOpTypeSampler: { + if (!_.HasCapability(SpvCapabilityBindlessTextureNV)) + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Using image/sampler with OpSelect requires capability " + << "BindlessTextureNV"; + break; + } + case SpvOpTypeVector: { dimension = type_inst->word(3); break;
diff --git a/source/val/validate_memory.cpp b/source/val/validate_memory.cpp index af9da67..425a8d3 100644 --- a/source/val/validate_memory.cpp +++ b/source/val/validate_memory.cpp
@@ -35,8 +35,8 @@ const Instruction*); bool HaveSameLayoutDecorations(ValidationState_t&, const Instruction*, const Instruction*); -bool HasConflictingMemberOffsets(const std::vector<Decoration>&, - const std::vector<Decoration>&); +bool HasConflictingMemberOffsets(const std::set<Decoration>&, + const std::set<Decoration>&); bool IsAllowedTypeOrArrayOfSame(ValidationState_t& _, const Instruction* type, std::initializer_list<uint32_t> allowed) { @@ -105,10 +105,8 @@ "type1 must be an OpTypeStruct instruction."); assert(type2->opcode() == SpvOpTypeStruct && "type2 must be an OpTypeStruct instruction."); - const std::vector<Decoration>& type1_decorations = - _.id_decorations(type1->id()); - const std::vector<Decoration>& type2_decorations = - _.id_decorations(type2->id()); + const std::set<Decoration>& type1_decorations = _.id_decorations(type1->id()); + const std::set<Decoration>& type2_decorations = _.id_decorations(type2->id()); // TODO: Will have to add other check for arrays an matricies if we want to // handle them. @@ -120,8 +118,8 @@ } bool HasConflictingMemberOffsets( - const std::vector<Decoration>& type1_decorations, - const std::vector<Decoration>& type2_decorations) { + const std::set<Decoration>& type1_decorations, + const std::set<Decoration>& type2_decorations) { { // We are interested in conflicting decoration. If a decoration is in one // list but not the other, then we will assume the code is correct. We are @@ -526,8 +524,8 @@ if (storage_class == SpvStorageClassPushConstant) { if (pointee->opcode() != SpvOpTypeStruct) { return _.diag(SPV_ERROR_INVALID_ID, inst) - << "PushConstant OpVariable <id> '" << _.getIdName(inst->id()) - << "' has illegal type.\n" + << _.VkErrorID(6808) << "PushConstant OpVariable <id> '" + << _.getIdName(inst->id()) << "' has illegal type.\n" << "From Vulkan spec, Push Constant Interface section:\n" << "Such variables must be typed as OpTypeStruct"; } @@ -554,9 +552,9 @@ if (storage_class == SpvStorageClassUniform) { if (!IsAllowedTypeOrArrayOfSame(_, pointee, {SpvOpTypeStruct})) { return _.diag(SPV_ERROR_INVALID_ID, inst) - << "Uniform OpVariable <id> '" << _.getIdName(inst->id()) - << "' has illegal type.\n" - << "From Vulkan spec, section 14.5.2:\n" + << _.VkErrorID(6807) << "Uniform OpVariable <id> '" + << _.getIdName(inst->id()) << "' has illegal type.\n" + << "From Vulkan spec:\n" << "Variables identified with the Uniform storage class are " << "used to access transparent buffer backed resources. Such " << "variables must be typed as OpTypeStruct, or an array of " @@ -567,9 +565,9 @@ if (storage_class == SpvStorageClassStorageBuffer) { if (!IsAllowedTypeOrArrayOfSame(_, pointee, {SpvOpTypeStruct})) { return _.diag(SPV_ERROR_INVALID_ID, inst) - << "StorageBuffer OpVariable <id> '" << _.getIdName(inst->id()) - << "' has illegal type.\n" - << "From Vulkan spec, section 14.5.2:\n" + << _.VkErrorID(6807) << "StorageBuffer OpVariable <id> '" + << _.getIdName(inst->id()) << "' has illegal type.\n" + << "From Vulkan spec:\n" << "Variables identified with the StorageBuffer storage class " "are used to access transparent buffer backed resources. " "Such variables must be typed as OpTypeStruct, or an array " @@ -982,6 +980,7 @@ } if (_.HasDecoration(base_type->id(), SpvDecorationBlock)) { return _.diag(SPV_ERROR_INVALID_ID, inst) + << _.VkErrorID(6925) << "In the Vulkan environment, cannot store to Uniform Blocks"; } }
diff --git a/source/val/validate_misc.cpp b/source/val/validate_misc.cpp index 3bc15ca..5acc21e 100644 --- a/source/val/validate_misc.cpp +++ b/source/val/validate_misc.cpp
@@ -59,10 +59,7 @@ // a vector of two - components of 32 - // bit unsigned integer type const uint32_t result_type = inst->type_id(); - if (!(_.IsUnsignedIntScalarType(result_type) && - _.GetBitWidth(result_type) == 64) && - !(_.IsUnsignedIntVectorType(result_type) && - _.GetDimension(result_type) == 2 && _.GetBitWidth(result_type) == 32)) { + if (!_.IsUnsigned64BitHandle(result_type)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Expected Value to be a " "vector of two components" " of unsigned integer"
diff --git a/source/val/validate_mode_setting.cpp b/source/val/validate_mode_setting.cpp index 9635268..67b10c5 100644 --- a/source/val/validate_mode_setting.cpp +++ b/source/val/validate_mode_setting.cpp
@@ -112,6 +112,44 @@ << "Fragment execution model entry points can specify at most " "one fragment shader interlock execution mode."; } + if (execution_modes && + 1 < std::count_if( + execution_modes->begin(), execution_modes->end(), + [](const SpvExecutionMode& mode) { + switch (mode) { + case SpvExecutionModeStencilRefUnchangedFrontAMD: + case SpvExecutionModeStencilRefLessFrontAMD: + case SpvExecutionModeStencilRefGreaterFrontAMD: + return true; + default: + return false; + } + })) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Fragment execution model entry points can specify at most " + "one of StencilRefUnchangedFrontAMD, " + "StencilRefLessFrontAMD or StencilRefGreaterFrontAMD " + "execution modes."; + } + if (execution_modes && + 1 < std::count_if( + execution_modes->begin(), execution_modes->end(), + [](const SpvExecutionMode& mode) { + switch (mode) { + case SpvExecutionModeStencilRefUnchangedBackAMD: + case SpvExecutionModeStencilRefLessBackAMD: + case SpvExecutionModeStencilRefGreaterBackAMD: + return true; + default: + return false; + } + })) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Fragment execution model entry points can specify at most " + "one of StencilRefUnchangedBackAMD, " + "StencilRefLessBackAMD or StencilRefGreaterBackAMD " + "execution modes."; + } break; case SpvExecutionModelTessellationControl: case SpvExecutionModelTessellationEvaluation: @@ -412,6 +450,13 @@ case SpvExecutionModeSampleInterlockUnorderedEXT: case SpvExecutionModeShadingRateInterlockOrderedEXT: case SpvExecutionModeShadingRateInterlockUnorderedEXT: + case SpvExecutionModeEarlyAndLateFragmentTestsAMD: + case SpvExecutionModeStencilRefUnchangedFrontAMD: + case SpvExecutionModeStencilRefGreaterFrontAMD: + case SpvExecutionModeStencilRefLessFrontAMD: + case SpvExecutionModeStencilRefUnchangedBackAMD: + case SpvExecutionModeStencilRefGreaterBackAMD: + case SpvExecutionModeStencilRefLessBackAMD: if (!std::all_of(models->begin(), models->end(), [](const SpvExecutionModel& model) { return model == SpvExecutionModelFragment;
diff --git a/source/val/validate_ray_query.cpp b/source/val/validate_ray_query.cpp new file mode 100644 index 0000000..f92bf01 --- /dev/null +++ b/source/val/validate_ray_query.cpp
@@ -0,0 +1,271 @@ +// Copyright (c) 2022 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validates ray query instructions from SPV_KHR_ray_query + +#include "source/opcode.h" +#include "source/val/instruction.h" +#include "source/val/validate.h" +#include "source/val/validation_state.h" + +namespace spvtools { +namespace val { +namespace { + +spv_result_t ValidateRayQueryPointer(ValidationState_t& _, + const Instruction* inst, + uint32_t ray_query_index) { + const uint32_t ray_query_id = inst->GetOperandAs<uint32_t>(ray_query_index); + auto variable = _.FindDef(ray_query_id); + if (!variable || (variable->opcode() != SpvOpVariable && + variable->opcode() != SpvOpFunctionParameter)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Ray Query must be a memory object declaration"; + } + auto pointer = _.FindDef(variable->GetOperandAs<uint32_t>(0)); + if (!pointer || pointer->opcode() != SpvOpTypePointer) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Ray Query must be a pointer"; + } + auto type = _.FindDef(pointer->GetOperandAs<uint32_t>(2)); + if (!type || type->opcode() != SpvOpTypeRayQueryKHR) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Ray Query must be a pointer to OpTypeRayQueryKHR"; + } + return SPV_SUCCESS; +} + +spv_result_t ValidateIntersectionId(ValidationState_t& _, + const Instruction* inst, + uint32_t intersection_index) { + const uint32_t intersection_id = + inst->GetOperandAs<uint32_t>(intersection_index); + const uint32_t intersection_type = _.GetTypeId(intersection_id); + const SpvOp intersection_opcode = _.GetIdOpcode(intersection_id); + if (!_.IsIntScalarType(intersection_type) || + _.GetBitWidth(intersection_type) != 32 || + !spvOpcodeIsConstant(intersection_opcode)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "expected Intersection ID to be a constant 32-bit int scalar"; + } + + return SPV_SUCCESS; +} + +} // namespace + +spv_result_t RayQueryPass(ValidationState_t& _, const Instruction* inst) { + const SpvOp opcode = inst->opcode(); + const uint32_t result_type = inst->type_id(); + + switch (opcode) { + case SpvOpRayQueryInitializeKHR: { + if (auto error = ValidateRayQueryPointer(_, inst, 0)) return error; + + if (_.GetIdOpcode(_.GetOperandTypeId(inst, 1)) != + SpvOpTypeAccelerationStructureKHR) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Expected Acceleration Structure to be of type " + "OpTypeAccelerationStructureKHR"; + } + + const uint32_t ray_flags = _.GetOperandTypeId(inst, 2); + if (!_.IsIntScalarType(ray_flags) || _.GetBitWidth(ray_flags) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Ray Flags must be a 32-bit int scalar"; + } + + const uint32_t cull_mask = _.GetOperandTypeId(inst, 3); + if (!_.IsIntScalarType(cull_mask) || _.GetBitWidth(cull_mask) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Cull Mask must be a 32-bit int scalar"; + } + + const uint32_t ray_origin = _.GetOperandTypeId(inst, 4); + if (!_.IsFloatVectorType(ray_origin) || _.GetDimension(ray_origin) != 3 || + _.GetBitWidth(ray_origin) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Ray Origin must be a 32-bit float 3-component vector"; + } + + const uint32_t ray_tmin = _.GetOperandTypeId(inst, 5); + if (!_.IsFloatScalarType(ray_tmin) || _.GetBitWidth(ray_tmin) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Ray TMin must be a 32-bit float scalar"; + } + + const uint32_t ray_direction = _.GetOperandTypeId(inst, 6); + if (!_.IsFloatVectorType(ray_direction) || + _.GetDimension(ray_direction) != 3 || + _.GetBitWidth(ray_direction) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Ray Direction must be a 32-bit float 3-component vector"; + } + + const uint32_t ray_tmax = _.GetOperandTypeId(inst, 7); + if (!_.IsFloatScalarType(ray_tmax) || _.GetBitWidth(ray_tmax) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Ray TMax must be a 32-bit float scalar"; + } + break; + } + + case SpvOpRayQueryTerminateKHR: + case SpvOpRayQueryConfirmIntersectionKHR: { + if (auto error = ValidateRayQueryPointer(_, inst, 0)) return error; + break; + } + + case SpvOpRayQueryGenerateIntersectionKHR: { + if (auto error = ValidateRayQueryPointer(_, inst, 0)) return error; + + const uint32_t hit_t_id = _.GetOperandTypeId(inst, 1); + if (!_.IsFloatScalarType(hit_t_id) || _.GetBitWidth(hit_t_id) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Hit T must be a 32-bit float scalar"; + } + + break; + } + + case SpvOpRayQueryGetIntersectionFrontFaceKHR: + case SpvOpRayQueryProceedKHR: + case SpvOpRayQueryGetIntersectionCandidateAABBOpaqueKHR: { + if (auto error = ValidateRayQueryPointer(_, inst, 2)) return error; + + if (!_.IsBoolScalarType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "expected Result Type to be bool scalar type"; + } + + if (opcode == SpvOpRayQueryGetIntersectionFrontFaceKHR) { + if (auto error = ValidateIntersectionId(_, inst, 3)) return error; + } + + break; + } + + case SpvOpRayQueryGetIntersectionTKHR: + case SpvOpRayQueryGetRayTMinKHR: { + if (auto error = ValidateRayQueryPointer(_, inst, 2)) return error; + + if (!_.IsFloatScalarType(result_type) || + _.GetBitWidth(result_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "expected Result Type to be 32-bit float scalar type"; + } + + if (opcode == SpvOpRayQueryGetIntersectionTKHR) { + if (auto error = ValidateIntersectionId(_, inst, 3)) return error; + } + + break; + } + + case SpvOpRayQueryGetIntersectionTypeKHR: + case SpvOpRayQueryGetIntersectionInstanceCustomIndexKHR: + case SpvOpRayQueryGetIntersectionInstanceIdKHR: + case SpvOpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR: + case SpvOpRayQueryGetIntersectionGeometryIndexKHR: + case SpvOpRayQueryGetIntersectionPrimitiveIndexKHR: + case SpvOpRayQueryGetRayFlagsKHR: { + if (auto error = ValidateRayQueryPointer(_, inst, 2)) return error; + + if (!_.IsIntScalarType(result_type) || _.GetBitWidth(result_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "expected Result Type to be 32-bit int scalar type"; + } + + if (opcode != SpvOpRayQueryGetRayFlagsKHR) { + if (auto error = ValidateIntersectionId(_, inst, 3)) return error; + } + + break; + } + + case SpvOpRayQueryGetIntersectionObjectRayDirectionKHR: + case SpvOpRayQueryGetIntersectionObjectRayOriginKHR: + case SpvOpRayQueryGetWorldRayDirectionKHR: + case SpvOpRayQueryGetWorldRayOriginKHR: { + if (auto error = ValidateRayQueryPointer(_, inst, 2)) return error; + + if (!_.IsFloatVectorType(result_type) || + _.GetDimension(result_type) != 3 || + _.GetBitWidth(result_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "expected Result Type to be 32-bit float 3-component " + "vector type"; + } + + if (opcode == SpvOpRayQueryGetIntersectionObjectRayDirectionKHR || + opcode == SpvOpRayQueryGetIntersectionObjectRayOriginKHR) { + if (auto error = ValidateIntersectionId(_, inst, 3)) return error; + } + + break; + } + + case SpvOpRayQueryGetIntersectionBarycentricsKHR: { + if (auto error = ValidateRayQueryPointer(_, inst, 2)) return error; + if (auto error = ValidateIntersectionId(_, inst, 3)) return error; + + if (!_.IsFloatVectorType(result_type) || + _.GetDimension(result_type) != 2 || + _.GetBitWidth(result_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "expected Result Type to be 32-bit float 2-component " + "vector type"; + } + + break; + } + + case SpvOpRayQueryGetIntersectionObjectToWorldKHR: + case SpvOpRayQueryGetIntersectionWorldToObjectKHR: { + if (auto error = ValidateRayQueryPointer(_, inst, 2)) return error; + if (auto error = ValidateIntersectionId(_, inst, 3)) return error; + + uint32_t num_rows = 0; + uint32_t num_cols = 0; + uint32_t col_type = 0; + uint32_t component_type = 0; + if (!_.GetMatrixTypeInfo(result_type, &num_rows, &num_cols, &col_type, + &component_type)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "expected matrix type as Result Type"; + } + + if (num_cols != 4) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "expected Result Type matrix to have a Column Count of 4"; + } + + if (!_.IsFloatScalarType(component_type) || + _.GetBitWidth(result_type) != 32 || num_rows != 3) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "expected Result Type matrix to have a Column Type of " + "3-component 32-bit float vectors"; + } + break; + } + + default: + break; + } + + return SPV_SUCCESS; +} + +} // namespace val +} // namespace spvtools
diff --git a/source/val/validate_scopes.cpp b/source/val/validate_scopes.cpp index 1c5f70a..887e8d1 100644 --- a/source/val/validate_scopes.cpp +++ b/source/val/validate_scopes.cpp
@@ -220,30 +220,23 @@ // Vulkan Specific rules if (spvIsVulkanEnv(_.context()->target_env)) { - if (value == SpvScopeCrossDevice) { - return _.diag(SPV_ERROR_INVALID_DATA, inst) - << _.VkErrorID(4638) << spvOpcodeString(opcode) - << ": in Vulkan environment, Memory Scope cannot be CrossDevice"; - } - // Vulkan 1.0 specific rules - if (_.context()->target_env == SPV_ENV_VULKAN_1_0 && - value != SpvScopeDevice && value != SpvScopeWorkgroup && - value != SpvScopeInvocation) { - return _.diag(SPV_ERROR_INVALID_DATA, inst) - << _.VkErrorID(4638) << spvOpcodeString(opcode) - << ": in Vulkan 1.0 environment Memory Scope is limited to " - << "Device, Workgroup and Invocation"; - } - // Vulkan 1.1 specific rules - if ((_.context()->target_env == SPV_ENV_VULKAN_1_1 || - _.context()->target_env == SPV_ENV_VULKAN_1_2) && - value != SpvScopeDevice && value != SpvScopeWorkgroup && + if (value != SpvScopeDevice && value != SpvScopeWorkgroup && value != SpvScopeSubgroup && value != SpvScopeInvocation && - value != SpvScopeShaderCallKHR) { + value != SpvScopeShaderCallKHR && value != SpvScopeQueueFamily) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << _.VkErrorID(4638) << spvOpcodeString(opcode) - << ": in Vulkan 1.1 and 1.2 environment Memory Scope is limited " - << "to Device, Workgroup, Invocation, and ShaderCall"; + << ": in Vulkan environment Memory Scope is limited to Device, " + "QueueFamily, Workgroup, ShaderCallKHR, Subgroup, or " + "Invocation"; + } else if (_.context()->target_env == SPV_ENV_VULKAN_1_0 && + value == SpvScopeSubgroup && + !_.HasCapability(SpvCapabilitySubgroupBallotKHR) && + !_.HasCapability(SpvCapabilitySubgroupVoteKHR)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << spvOpcodeString(opcode) + << ": in Vulkan 1.0 environment Memory Scope is can not be " + "Subgroup without SubgroupBallotKHR or SubgroupVoteKHR " + "declared"; } if (value == SpvScopeShaderCallKHR) {
diff --git a/source/val/validate_type.cpp b/source/val/validate_type.cpp index 2aded61..b0b6079 100644 --- a/source/val/validate_type.cpp +++ b/source/val/validate_type.cpp
@@ -306,35 +306,6 @@ return SPV_SUCCESS; } -bool ContainsOpaqueType(ValidationState_t& _, const Instruction* str) { - const size_t elem_type_index = 1; - uint32_t elem_type_id; - Instruction* elem_type; - - if (spvOpcodeIsBaseOpaqueType(str->opcode())) { - return true; - } - - switch (str->opcode()) { - case SpvOpTypeArray: - case SpvOpTypeRuntimeArray: - elem_type_id = str->GetOperandAs<uint32_t>(elem_type_index); - elem_type = _.FindDef(elem_type_id); - return ContainsOpaqueType(_, elem_type); - case SpvOpTypeStruct: - for (size_t member_type_index = 1; - member_type_index < str->operands().size(); ++member_type_index) { - auto member_type_id = str->GetOperandAs<uint32_t>(member_type_index); - auto member_type = _.FindDef(member_type_id); - if (ContainsOpaqueType(_, member_type)) return true; - } - break; - default: - break; - } - return false; -} - spv_result_t ValidateTypeStruct(ValidationState_t& _, const Instruction* inst) { const uint32_t struct_id = inst->GetOperandAs<uint32_t>(0); for (size_t member_type_index = 1; @@ -425,8 +396,21 @@ _.RegisterStructTypeWithBuiltInMember(struct_id); } + const auto isOpaqueType = [&_](const Instruction* opaque_inst) { + auto opcode = opaque_inst->opcode(); + if (_.HasCapability(SpvCapabilityBindlessTextureNV) && + (opcode == SpvOpTypeImage || opcode == SpvOpTypeSampler || + opcode == SpvOpTypeSampledImage)) { + return false; + } else if (spvOpcodeIsBaseOpaqueType(opcode)) { + return true; + } + return false; + }; + if (spvIsVulkanEnv(_.context()->target_env) && - !_.options()->before_hlsl_legalization && ContainsOpaqueType(_, inst)) { + !_.options()->before_hlsl_legalization && + _.ContainsType(inst->id(), isOpaqueType)) { return _.diag(SPV_ERROR_INVALID_ID, inst) << _.VkErrorID(4667) << "In " << spvLogStringForEnv(_.context()->target_env)
diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp index d9422b2..adfe75b 100644 --- a/source/val/validation_state.cpp +++ b/source/val/validation_state.cpp
@@ -90,6 +90,8 @@ if (current_section == kLayoutFunctionDeclarations) return kLayoutFunctionDeclarations; return kLayoutFunctionDefinitions; + case SpvOpSamplerImageAddressingModeNV: + return kLayoutSamplerImageAddressMode; default: break; } @@ -161,6 +163,7 @@ addressing_model_(SpvAddressingModelMax), memory_model_(SpvMemoryModelMax), pointer_size_and_alignment_(0), + sampler_image_addressing_mode_(0), in_function_(false), num_of_warnings_(0), max_num_of_warnings_(max_warnings) { @@ -473,6 +476,15 @@ SpvMemoryModel ValidationState_t::memory_model() const { return memory_model_; } +void ValidationState_t::set_samplerimage_variable_address_mode( + uint32_t bit_width) { + sampler_image_addressing_mode_ = bit_width; +} + +uint32_t ValidationState_t::samplerimage_variable_address_mode() const { + return sampler_image_addressing_mode_; +} + spv_result_t ValidationState_t::RegisterFunction( uint32_t id, uint32_t ret_type_id, SpvFunctionControlMask function_control, uint32_t function_type_id) { @@ -965,6 +977,11 @@ return true; } +bool ValidationState_t::IsAccelerationStructureType(uint32_t id) const { + const Instruction* inst = FindDef(id); + return inst && inst->opcode() == SpvOpTypeAccelerationStructureKHR; +} + bool ValidationState_t::IsCooperativeMatrixType(uint32_t id) const { const Instruction* inst = FindDef(id); return inst && inst->opcode() == SpvOpTypeCooperativeMatrixNV; @@ -985,6 +1002,13 @@ return IsUnsignedIntScalarType(FindDef(id)->word(2)); } +// Either a 32 bit 2-component uint vector or a 64 bit uint scalar +bool ValidationState_t::IsUnsigned64BitHandle(uint32_t id) const { + return ((IsUnsignedIntScalarType(id) && GetBitWidth(id) == 64) || + (IsUnsignedIntVectorType(id) && GetDimension(id) == 2 && + GetBitWidth(id) == 32)); +} + spv_result_t ValidationState_t::CooperativeMatrixShapesMatch( const Instruction* inst, uint32_t m1, uint32_t m2) { const auto m1_type = FindDef(m1); @@ -1878,7 +1902,7 @@ case 4677: return VUID_WRAP(VUID-StandaloneSpirv-Invariant-04677); case 4680: - return VUID_WRAP( VUID-StandaloneSpirv-OpTypeRuntimeArray-04680); + return VUID_WRAP(VUID-StandaloneSpirv-OpTypeRuntimeArray-04680); case 4682: return VUID_WRAP(VUID-StandaloneSpirv-OpControlBarrier-04682); case 6426: @@ -1903,6 +1927,8 @@ return VUID_WRAP(VUID-StandaloneSpirv-OpMemoryBarrier-04733); case 4734: return VUID_WRAP(VUID-StandaloneSpirv-OpVariable-04734); + case 4744: + return VUID_WRAP(VUID-StandaloneSpirv-Flat-04744); case 4777: return VUID_WRAP(VUID-StandaloneSpirv-OpImage-04777); case 4780: @@ -1919,6 +1945,10 @@ return VUID_WRAP(VUID-StandaloneSpirv-Location-04918); case 4919: return VUID_WRAP(VUID-StandaloneSpirv-Location-04919); + case 6201: + return VUID_WRAP(VUID-StandaloneSpirv-Flat-06201); + case 6202: + return VUID_WRAP(VUID-StandaloneSpirv-Flat-06202); case 6214: return VUID_WRAP(VUID-StandaloneSpirv-OpTypeImage-06214); case 6491: @@ -1941,6 +1971,12 @@ return VUID_WRAP(VUID-StandaloneSpirv-PerVertexKHR-06777); case 6778: return VUID_WRAP(VUID-StandaloneSpirv-Input-06778); + case 6807: + return VUID_WRAP(VUID-StandaloneSpirv-Uniform-06807); + case 6808: + return VUID_WRAP(VUID-StandaloneSpirv-PushConstant-06808); + case 6925: + return VUID_WRAP(VUID-StandaloneSpirv-Uniform-06925); default: return ""; // unknown id }
diff --git a/source/val/validation_state.h b/source/val/validation_state.h index 4888840..b4d343d 100644 --- a/source/val/validation_state.h +++ b/source/val/validation_state.h
@@ -44,19 +44,20 @@ /// of the SPIRV spec for additional details of the order. The enumerant values /// are in the same order as the vector returned by GetModuleOrder enum ModuleLayoutSection { - kLayoutCapabilities, /// < Section 2.4 #1 - kLayoutExtensions, /// < Section 2.4 #2 - kLayoutExtInstImport, /// < Section 2.4 #3 - kLayoutMemoryModel, /// < Section 2.4 #4 - kLayoutEntryPoint, /// < Section 2.4 #5 - kLayoutExecutionMode, /// < Section 2.4 #6 - kLayoutDebug1, /// < Section 2.4 #7 > 1 - kLayoutDebug2, /// < Section 2.4 #7 > 2 - kLayoutDebug3, /// < Section 2.4 #7 > 3 - kLayoutAnnotations, /// < Section 2.4 #8 - kLayoutTypes, /// < Section 2.4 #9 - kLayoutFunctionDeclarations, /// < Section 2.4 #10 - kLayoutFunctionDefinitions /// < Section 2.4 #11 + kLayoutCapabilities, /// < Section 2.4 #1 + kLayoutExtensions, /// < Section 2.4 #2 + kLayoutExtInstImport, /// < Section 2.4 #3 + kLayoutMemoryModel, /// < Section 2.4 #4 + kLayoutSamplerImageAddressMode, /// < Section 2.4 #5 + kLayoutEntryPoint, /// < Section 2.4 #6 + kLayoutExecutionMode, /// < Section 2.4 #7 + kLayoutDebug1, /// < Section 2.4 #8 > 1 + kLayoutDebug2, /// < Section 2.4 #8 > 2 + kLayoutDebug3, /// < Section 2.4 #8 > 3 + kLayoutAnnotations, /// < Section 2.4 #9 + kLayoutTypes, /// < Section 2.4 #10 + kLayoutFunctionDeclarations, /// < Section 2.4 #11 + kLayoutFunctionDefinitions /// < Section 2.4 #12 }; /// This class manages the state of the SPIR-V validation as it is being parsed. @@ -360,6 +361,20 @@ /// Returns the memory model of this module, or Simple if uninitialized. SpvMemoryModel memory_model() const; + /// Sets the bit width for sampler/image type variables. If not set, they are + /// considered opaque + void set_samplerimage_variable_address_mode(uint32_t bit_width); + + /// Get the addressing mode currently set. If 0, it means addressing mode is + /// invalid Sampler/Image type variables must be considered opaque This mode + /// is only valid after the instruction has been read + uint32_t samplerimage_variable_address_mode() const; + + /// Returns true if the OpSamplerImageAddressingModeNV was found. + bool has_samplerimage_variable_address_mode_specified() const { + return sampler_image_addressing_mode_ != 0; + } + const AssemblyGrammar& grammar() const { return grammar_; } /// Inserts the instruction into the list of ordered instructions in the file. @@ -375,17 +390,14 @@ /// Registers the decoration for the given <id> void RegisterDecorationForId(uint32_t id, const Decoration& dec) { auto& dec_list = id_decorations_[id]; - auto lb = std::find(dec_list.begin(), dec_list.end(), dec); - if (lb == dec_list.end()) { - dec_list.push_back(dec); - } + dec_list.insert(dec); } /// Registers the list of decorations for the given <id> template <class InputIt> void RegisterDecorationsForId(uint32_t id, InputIt begin, InputIt end) { - std::vector<Decoration>& cur_decs = id_decorations_[id]; - cur_decs.insert(cur_decs.end(), begin, end); + std::set<Decoration>& cur_decs = id_decorations_[id]; + cur_decs.insert(begin, end); } /// Registers the list of decorations for the given member of the given @@ -394,21 +406,44 @@ void RegisterDecorationsForStructMember(uint32_t struct_id, uint32_t member_index, InputIt begin, InputIt end) { - RegisterDecorationsForId(struct_id, begin, end); - for (auto& decoration : id_decorations_[struct_id]) { - decoration.set_struct_member_index(member_index); + std::set<Decoration>& cur_decs = id_decorations_[struct_id]; + for (InputIt iter = begin; iter != end; ++iter) { + Decoration dec = *iter; + dec.set_struct_member_index(member_index); + cur_decs.insert(dec); } } /// Returns all the decorations for the given <id>. If no decorations exist - /// for the <id>, it registers an empty vector for it in the map and - /// returns the empty vector. - std::vector<Decoration>& id_decorations(uint32_t id) { + /// for the <id>, it registers an empty set for it in the map and + /// returns the empty set. + std::set<Decoration>& id_decorations(uint32_t id) { return id_decorations_[id]; } + /// Returns the range of decorations for the given field of the given <id>. + struct FieldDecorationsIter { + std::set<Decoration>::const_iterator begin; + std::set<Decoration>::const_iterator end; + }; + FieldDecorationsIter id_member_decorations(uint32_t id, + uint32_t member_index) { + const auto& decorations = id_decorations_[id]; + + // The decorations are sorted by member_index, so this look up will give the + // exact range of decorations for this member index. + Decoration min_decoration((SpvDecoration)0, {}, member_index); + Decoration max_decoration(SpvDecorationMax, {}, member_index); + + FieldDecorationsIter result; + result.begin = decorations.lower_bound(min_decoration); + result.end = decorations.upper_bound(max_decoration); + + return result; + } + // Returns const pointer to the internal decoration container. - const std::map<uint32_t, std::vector<Decoration>>& id_decorations() const { + const std::map<uint32_t, std::set<Decoration>>& id_decorations() const { return id_decorations_; } @@ -572,10 +607,12 @@ bool IsBoolVectorType(uint32_t id) const; bool IsBoolScalarOrVectorType(uint32_t id) const; bool IsPointerType(uint32_t id) const; + bool IsAccelerationStructureType(uint32_t id) const; bool IsCooperativeMatrixType(uint32_t id) const; bool IsFloatCooperativeMatrixType(uint32_t id) const; bool IsIntCooperativeMatrixType(uint32_t id) const; bool IsUnsignedIntCooperativeMatrixType(uint32_t id) const; + bool IsUnsigned64BitHandle(uint32_t id) const; // Returns true if |id| is a type id that contains |type| (or integer or // floating point type) of |width| bits. @@ -826,7 +863,7 @@ struct_has_nested_blockorbufferblock_struct_; /// Stores the list of decorations for a given <id> - std::map<uint32_t, std::vector<Decoration>> id_decorations_; + std::map<uint32_t, std::set<Decoration>> id_decorations_; /// Stores type declarations which need to be unique (i.e. non-aggregates), /// in the form [opcode, operand words], result_id is not stored. @@ -842,7 +879,10 @@ // have the same pointer size (for physical pointer types). uint32_t pointer_size_and_alignment_; - /// NOTE: See corresponding getter functions + /// bit width of sampler/image type variables. Valid values are 32 and 64 + uint32_t sampler_image_addressing_mode_; + + /// NOTE: See correspoding getter functions bool in_function_; /// The state of optional features. These are determined by capabilities
diff --git a/test/binary_to_text_test.cpp b/test/binary_to_text_test.cpp index df703e5..44705f2 100644 --- a/test/binary_to_text_test.cpp +++ b/test/binary_to_text_test.cpp
@@ -101,6 +101,17 @@ spvTextDestroy(text); } +TEST_F(BinaryToText, Print) { + spv_text text = nullptr; + spv_diagnostic diagnostic = nullptr; + ASSERT_EQ( + SPV_SUCCESS, + spvBinaryToText(context, binary->code, binary->wordCount, + SPV_BINARY_TO_TEXT_OPTION_PRINT, &text, &diagnostic)); + ASSERT_EQ(text, nullptr); + spvTextDestroy(text); +} + TEST_F(BinaryToText, MissingModule) { spv_text text; spv_diagnostic diagnostic = nullptr;
diff --git a/test/hex_float_test.cpp b/test/hex_float_test.cpp index 7edfd43..25d3c70 100644 --- a/test/hex_float_test.cpp +++ b/test/hex_float_test.cpp
@@ -1395,6 +1395,47 @@ {"0x1.0p1+", true, "+", 2.0f}, {"0x1.0p1-", true, "-", 2.0f}})); +INSTANTIATE_TEST_SUITE_P( + HexFloatPositiveExponentOverflow, FloatStreamParseTest, + ::testing::ValuesIn(std::vector<StreamParseCase<float>>{ + // Positive exponents + {"0x1.0p1", true, "", 2.0f}, // fine, a normal number + {"0x1.0p15", true, "", 32768.0f}, // fine, a normal number + {"0x1.0p127", true, "", float(ldexp(1.0f, 127))}, // good large number + {"0x0.8p128", true, "", float(ldexp(1.0f, 127))}, // good large number + {"0x0.1p131", true, "", float(ldexp(1.0f, 127))}, // good large number + {"0x0.01p135", true, "", float(ldexp(1.0f, 127))}, // good large number + {"0x1.0p128", true, "", float(ldexp(1.0f, 128))}, // infinity + {"0x1.0p4294967295", true, "", float(ldexp(1.0f, 128))}, // infinity + {"0x1.0p5000000000", true, "", float(ldexp(1.0f, 128))}, // infinity + {"0x0.0p5000000000", true, "", 0.0f}, // zero mantissa, zero result + })); + +INSTANTIATE_TEST_SUITE_P( + HexFloatNegativeExponentOverflow, FloatStreamParseTest, + ::testing::ValuesIn(std::vector<StreamParseCase<float>>{ + // Positive results, digits before '.' + {"0x1.0p-126", true, "", + float(ldexp(1.0f, -126))}, // fine, a small normal number + {"0x1.0p-127", true, "", float(ldexp(1.0f, -127))}, // denorm number + {"0x1.0p-149", true, "", + float(ldexp(1.0f, -149))}, // smallest positive denormal + {"0x0.8p-148", true, "", + float(ldexp(1.0f, -149))}, // smallest positive denormal + {"0x0.1p-145", true, "", + float(ldexp(1.0f, -149))}, // smallest positive denormal + {"0x0.01p-141", true, "", + float(ldexp(1.0f, -149))}, // smallest positive denormal + + // underflow rounds down to zero + {"0x1.0p-150", true, "", 0.0f}, + {"0x1.0p-4294967296", true, "", + 0.0f}, // avoid exponent overflow in parser + {"0x1.0p-5000000000", true, "", + 0.0f}, // avoid exponent overflow in parser + {"0x0.0p-5000000000", true, "", 0.0f}, // zero mantissa, zero result + })); + // TODO(awoloszyn): Add fp16 tests and HexFloatTraits. } // namespace } // namespace utils
diff --git a/test/opt/aggressive_dead_code_elim_test.cpp b/test/opt/aggressive_dead_code_elim_test.cpp index 25f8541..89cb56f 100644 --- a/test/opt/aggressive_dead_code_elim_test.cpp +++ b/test/opt/aggressive_dead_code_elim_test.cpp
@@ -3541,9 +3541,11 @@ OpBranch %16 %16 = OpLabel OpSelectionMerge %18 None -OpSwitch %13 %18 0 %17 1 %15 +OpSwitch %13 %18 0 %17 1 %19 %17 = OpLabel OpStore %3 %uint_1 +OpBranch %19 +%19 = OpLabel OpBranch %15 %15 = OpLabel OpBranch %12 @@ -7626,6 +7628,55 @@ SinglePassRunAndCheck<AggressiveDCEPass>(text, text, false); } +TEST_F(AggressiveDCETest, FunctionBecomesUnreachableAfterDCE) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 320 + %void = OpTypeVoid + %4 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %7 = OpTypeFunction %int + %int_n1 = OpConstant %int -1 + %2 = OpFunction %void None %4 + %9 = OpLabel + OpKill + %10 = OpLabel + %11 = OpFunctionCall %int %12 + OpReturn + OpFunctionEnd +; CHECK: {{%\w+}} = OpFunction %int DontInline|Pure + %12 = OpFunction %int DontInline|Pure %7 +; CHECK-NEXT: {{%\w+}} = OpLabel + %13 = OpLabel + %14 = OpVariable %_ptr_Function_int Function +; CHECK-NEXT: OpBranch [[header:%\w+]] + OpBranch %15 +; CHECK-NEXT: [[header]] = OpLabel +; CHECK-NEXT: OpBranch [[merge:%\w+]] + %15 = OpLabel + OpLoopMerge %16 %17 None + OpBranch %18 + %18 = OpLabel + %19 = OpLoad %int %14 + OpBranch %17 + %17 = OpLabel + OpBranch %15 +; CHECK-NEXT: [[merge]] = OpLabel + %16 = OpLabel +; CHECK-NEXT: OpReturnValue %int_n1 + OpReturnValue %int_n1 +; CHECK-NEXT: OpFunctionEnd + OpFunctionEnd +)"; + + SinglePassRunAndMatch<AggressiveDCEPass>(text, true); +} + } // namespace } // namespace opt } // namespace spvtools
diff --git a/test/opt/cfg_test.cpp b/test/opt/cfg_test.cpp index 2cfc9f3..7dfd2bc 100644 --- a/test/opt/cfg_test.cpp +++ b/test/opt/cfg_test.cpp
@@ -200,6 +200,125 @@ ContainerEq(expected_result2))); } +TEST_F(CFGTest, SplitLoopHeaderForSingleBlockLoop) { + const std::string test = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + %void = OpTypeVoid + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %6 = OpTypeFunction %void + %2 = OpFunction %void None %6 + %7 = OpLabel + OpBranch %8 + %8 = OpLabel + %9 = OpPhi %uint %uint_0 %7 %9 %8 + OpLoopMerge %10 %8 None + OpBranch %8 + %10 = OpLabel + OpUnreachable + OpFunctionEnd +)"; + + const std::string expected_result = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%6 = OpTypeFunction %void +%2 = OpFunction %void None %6 +%7 = OpLabel +OpBranch %8 +%8 = OpLabel +OpBranch %11 +%11 = OpLabel +%9 = OpPhi %uint %9 %11 %uint_0 %8 +OpLoopMerge %10 %11 None +OpBranch %11 +%10 = OpLabel +OpUnreachable +OpFunctionEnd +)"; + + std::unique_ptr<IRContext> context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, test, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ASSERT_NE(nullptr, context); + + BasicBlock* loop_header = context->get_instr_block(8); + ASSERT_TRUE(loop_header->GetLoopMergeInst() != nullptr); + + CFG* cfg = context->cfg(); + cfg->SplitLoopHeader(loop_header); + + std::vector<uint32_t> binary; + bool skip_nop = false; + context->module()->ToBinary(&binary, skip_nop); + + std::string optimized_asm; + SpirvTools tools(SPV_ENV_UNIVERSAL_1_1); + EXPECT_TRUE(tools.Disassemble(binary, &optimized_asm, + SpirvTools::kDefaultDisassembleOption)) + << "Disassembling failed for shader\n" + << std::endl; + + EXPECT_EQ(optimized_asm, expected_result); +} + +TEST_F(CFGTest, ComputeStructedOrderForLoop) { + const std::string test = R"( +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %main "main" +OpName %main "main" +%bool = OpTypeBool +%true = OpConstantTrue %bool +%void = OpTypeVoid +%4 = OpTypeFunction %void +%uint = OpTypeInt 32 0 +%5 = OpConstant %uint 5 +%main = OpFunction %void None %4 +%8 = OpLabel +OpBranch %9 +%9 = OpLabel +OpLoopMerge %11 %10 None +OpBranchConditional %true %11 %10 +%10 = OpLabel +OpBranch %9 +%11 = OpLabel +OpBranch %12 +%12 = OpLabel +OpReturn +OpFunctionEnd +)"; + + std::unique_ptr<IRContext> context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, test, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ASSERT_NE(nullptr, context); + + CFG* cfg = context->cfg(); + Module* module = context->module(); + Function* function = &*module->begin(); + std::list<BasicBlock*> order; + cfg->ComputeStructuredOrder(function, context->get_instr_block(9), + context->get_instr_block(11), &order); + + // Order should contain the loop header, the continue target, and the merge + // node. + std::list<BasicBlock*> expected_result = {context->get_instr_block(9), + context->get_instr_block(10), + context->get_instr_block(11)}; + EXPECT_THAT(order, ContainerEq(expected_result)); +} + } // namespace } // namespace opt } // namespace spvtools
diff --git a/test/opt/dead_branch_elim_test.cpp b/test/opt/dead_branch_elim_test.cpp index b04c8f5..1095d3b 100644 --- a/test/opt/dead_branch_elim_test.cpp +++ b/test/opt/dead_branch_elim_test.cpp
@@ -2570,9 +2570,8 @@ TEST_F(DeadBranchElimTest, SelectionMergeWithExitToLoop3) { // Checks that if a selection merge construct contains a conditional branch - // to the merge of a surrounding loop, the selection merge, and another block - // inside the selection merge, then we must keep the OpSelectionMerge - // instruction on that branch. + // to the selection merge, and another block inside the selection merge, + // then we must keep the OpSelectionMerge instruction on that branch. const std::string predefs = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" @@ -2586,6 +2585,7 @@ %true = OpConstantTrue %bool %uint = OpTypeInt 32 0 %undef_int = OpUndef %uint +%undef_bool = OpUndef %bool )"; const std::string body = @@ -2596,7 +2596,7 @@ ; CHECK-NEXT: OpBranch [[bb2:%\w+]] ; CHECK: [[bb2]] = OpLabel ; CHECK-NEXT: OpSelectionMerge [[sel_merge:%\w+]] None -; CHECK-NEXT: OpSwitch {{%\w+}} [[sel_merge]] 0 [[loop_merge]] 1 [[bb3:%\w+]] +; CHECK-NEXT: OpBranchConditional {{%\w+}} [[sel_merge]] [[bb3:%\w+]] ; CHECK: [[bb3]] = OpLabel ; CHECK-NEXT: OpBranch [[sel_merge]] ; CHECK: [[sel_merge]] = OpLabel @@ -2613,7 +2613,8 @@ OpSelectionMerge %sel_merge None OpBranchConditional %true %bb2 %bb4 %bb2 = OpLabel -OpSwitch %undef_int %sel_merge 0 %loop_merge 1 %bb3 +;OpSwitch %undef_int %sel_merge 0 %loop_merge 1 %bb3 +OpBranchConditional %undef_bool %sel_merge %bb3 %bb3 = OpLabel OpBranch %sel_merge %bb4 = OpLabel @@ -2632,9 +2633,8 @@ TEST_F(DeadBranchElimTest, SelectionMergeWithExitToLoopContinue3) { // Checks that if a selection merge construct contains a conditional branch - // to the merge of a surrounding loop, the selection merge, and another block - // inside the selection merge, then we must keep the OpSelectionMerge - // instruction on that branch. + // the selection merge, and another block inside the selection merge, then we + // must keep the OpSelectionMerge instruction on that branch. const std::string predefs = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" @@ -2648,6 +2648,7 @@ %true = OpConstantTrue %bool %uint = OpTypeInt 32 0 %undef_int = OpUndef %uint +%undef_bool = OpUndef %bool )"; const std::string body = @@ -2660,7 +2661,7 @@ ; CHECK-NEXT: OpBranch [[bb2:%\w+]] ; CHECK: [[bb2]] = OpLabel ; CHECK-NEXT: OpSelectionMerge [[sel_merge:%\w+]] None -; CHECK-NEXT: OpSwitch {{%\w+}} [[sel_merge]] 0 [[loop_continue]] 1 [[bb3:%\w+]] +; CHECK-NEXT: OpBranchConditional {{%\w+}} [[sel_merge]] [[bb3:%\w+]] ; CHECK: [[bb3]] = OpLabel ; CHECK-NEXT: OpBranch [[sel_merge]] ; CHECK: [[sel_merge]] = OpLabel @@ -2679,131 +2680,7 @@ OpSelectionMerge %sel_merge None OpBranchConditional %true %bb2 %bb4 %bb2 = OpLabel -OpSwitch %undef_int %sel_merge 0 %cont 1 %bb3 -%bb3 = OpLabel -OpBranch %sel_merge -%bb4 = OpLabel -OpBranch %sel_merge -%sel_merge = OpLabel -OpBranch %loop_merge -%cont = OpLabel -OpBranch %loop_header -%loop_merge = OpLabel -OpReturn -OpFunctionEnd -)"; - - SinglePassRunAndMatch<DeadBranchElimPass>(predefs + body, true); -} - -TEST_F(DeadBranchElimTest, SelectionMergeWithExitToLoop4) { - // Same as |SelectionMergeWithExitToLoop|, except the branch in the selection - // construct is an |OpSwitch| instead of an |OpConditionalBranch|. The - // OpSelectionMerge instruction is not needed in this case either. - const std::string predefs = R"( -OpCapability Shader -%1 = OpExtInstImport "GLSL.std.450" -OpMemoryModel Logical GLSL450 -OpEntryPoint Fragment %main "main" -OpExecutionMode %main OriginUpperLeft -OpSource GLSL 140 -%void = OpTypeVoid -%func_type = OpTypeFunction %void -%bool = OpTypeBool -%true = OpConstantTrue %bool -%uint = OpTypeInt 32 0 -%undef_int = OpUndef %uint -)"; - - const std::string body = - R"( -; CHECK: OpLoopMerge [[loop_merge:%\w+]] -; CHECK-NEXT: OpBranch [[bb1:%\w+]] -; CHECK: [[bb1]] = OpLabel -; CHECK-NEXT: OpBranch [[bb2:%\w+]] -; CHECK: [[bb2]] = OpLabel -; CHECK-NEXT: OpSelectionMerge -; CHECK-NEXT: OpSwitch {{%\w+}} [[bb3:%\w+]] 0 [[loop_merge]] 1 [[bb3:%\w+]] -; CHECK: [[bb3]] = OpLabel -; CHECK-NEXT: OpBranch [[sel_merge:%\w+]] -; CHECK: [[sel_merge]] = OpLabel -; CHECK-NEXT: OpBranch [[loop_merge]] -; CHECK: [[loop_merge]] = OpLabel -; CHECK-NEXT: OpReturn -%main = OpFunction %void None %func_type -%entry_bb = OpLabel -OpBranch %loop_header -%loop_header = OpLabel -OpLoopMerge %loop_merge %cont None -OpBranch %bb1 -%bb1 = OpLabel -OpSelectionMerge %sel_merge None -OpBranchConditional %true %bb2 %bb4 -%bb2 = OpLabel -OpSelectionMerge %bb3 None -OpSwitch %undef_int %bb3 0 %loop_merge 1 %bb3 -%bb3 = OpLabel -OpBranch %sel_merge -%bb4 = OpLabel -OpBranch %sel_merge -%sel_merge = OpLabel -OpBranch %loop_merge -%cont = OpLabel -OpBranch %loop_header -%loop_merge = OpLabel -OpReturn -OpFunctionEnd -)"; - - SinglePassRunAndMatch<DeadBranchElimPass>(predefs + body, true); -} - -TEST_F(DeadBranchElimTest, SelectionMergeWithExitToLoopContinue4) { - // Same as |SelectionMergeWithExitToLoopContinue|, except the branch in the - // selection construct is an |OpSwitch| instead of an |OpConditionalBranch|. - // The OpSelectionMerge instruction is not needed in this case either. - const std::string predefs = R"( -OpCapability Shader -%1 = OpExtInstImport "GLSL.std.450" -OpMemoryModel Logical GLSL450 -OpEntryPoint Fragment %main "main" -OpExecutionMode %main OriginUpperLeft -OpSource GLSL 140 -%void = OpTypeVoid -%func_type = OpTypeFunction %void -%bool = OpTypeBool -%true = OpConstantTrue %bool -%uint = OpTypeInt 32 0 -%undef_int = OpUndef %uint -)"; - - const std::string body = - R"( -; CHECK: OpLoopMerge [[loop_merge:%\w+]] [[loop_cont:%\w+]] -; CHECK-NEXT: OpBranch [[bb1:%\w+]] -; CHECK: [[bb1]] = OpLabel -; CHECK-NEXT: OpBranch [[bb2:%\w+]] -; CHECK: [[bb2]] = OpLabel -; CHECK-NEXT: OpSelectionMerge -; CHECK-NEXT: OpSwitch {{%\w+}} [[bb3:%\w+]] 0 [[loop_cont]] 1 [[bb3:%\w+]] -; CHECK: [[bb3]] = OpLabel -; CHECK-NEXT: OpBranch [[sel_merge:%\w+]] -; CHECK: [[sel_merge]] = OpLabel -; CHECK-NEXT: OpBranch [[loop_merge]] -; CHECK: [[loop_merge]] = OpLabel -; CHECK-NEXT: OpReturn -%main = OpFunction %void None %func_type -%entry_bb = OpLabel -OpBranch %loop_header -%loop_header = OpLabel -OpLoopMerge %loop_merge %cont None -OpBranch %bb1 -%bb1 = OpLabel -OpSelectionMerge %sel_merge None -OpBranchConditional %true %bb2 %bb4 -%bb2 = OpLabel -OpSelectionMerge %bb3 None -OpSwitch %undef_int %bb3 0 %cont 1 %bb3 +OpBranchConditional %undef_bool %sel_merge %bb3 %bb3 = OpLabel OpBranch %sel_merge %bb4 = OpLabel @@ -3036,9 +2913,11 @@ ; CHECK-NEXT: OpLoopMerge [[outer_merge:%\w+]] [[outer_cont:%\w+]] None ; CHECK-NEXT: OpBranch [[inner:%\w+]] ; CHECK: [[inner]] = OpLabel -; CHECK: OpLoopMerge [[outer_cont]] [[inner_cont:%\w+]] None +; CHECK: OpLoopMerge [[inner_merge:%\w+]] [[inner_cont:%\w+]] None ; CHECK: [[inner_cont]] = OpLabel ; CHECK-NEXT: OpBranch [[inner]] +; CHECK: [[inner_merge]] = OpLabel +; CHECK-NEXT: OpUnreachable ; CHECK: [[outer_cont]] = OpLabel ; CHECK-NEXT: OpBranch [[outer]] ; CHECK: [[outer_merge]] = OpLabel @@ -3058,7 +2937,7 @@ OpLoopMerge %outer_merge %outer_continue None OpBranch %inner_loop %inner_loop = OpLabel -OpLoopMerge %outer_continue %inner_continue None +OpLoopMerge %inner_merge %inner_continue None OpBranch %inner_body %inner_body = OpLabel OpSelectionMerge %inner_continue None @@ -3066,7 +2945,9 @@ %ret = OpLabel OpReturn %inner_continue = OpLabel -OpBranchConditional %true %outer_continue %inner_loop +OpBranchConditional %true %inner_merge %inner_loop +%inner_merge = OpLabel +OpBranch %outer_continue %outer_continue = OpLabel OpBranchConditional %true %outer_merge %outer_loop %outer_merge = OpLabel
diff --git a/test/opt/def_use_test.cpp b/test/opt/def_use_test.cpp index 48a485e..0210095 100644 --- a/test/opt/def_use_test.cpp +++ b/test/opt/def_use_test.cpp
@@ -950,369 +950,6 @@ ); // clang-format on -// We re-use the same replace usecases, we need to similarly exercise the -// DefUseManager by replacing instructions and uses. -using CompactIdempotence = ::testing::TestWithParam<ReplaceUseCase>; - -TEST_P(CompactIdempotence, Case) { - const auto& tc = GetParam(); - - // Build module. - const std::vector<const char*> text = {tc.before}; - std::unique_ptr<IRContext> context = - BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, JoinAllInsts(text), - SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); - ASSERT_NE(nullptr, context); - - // Force a re-build of def-use manager. - context->InvalidateAnalyses(IRContext::Analysis::kAnalysisDefUse); - (void)context->get_def_use_mgr(); - - // Do the substitution. - for (const auto& candidate : tc.candidates) { - context->ReplaceAllUsesWith(candidate.first, candidate.second); - } - - // Ensure new/uncompacted managers produce the same result - EXPECT_TRUE(CompareAndPrintDifferences(*context->get_def_use_mgr(), - DefUseManager(context->module()))); - - EXPECT_EQ(tc.after, DisassembleModule(context->module())); - CheckDef(tc.du, context->get_def_use_mgr()->id_to_defs()); - CheckUse(tc.du, context->get_def_use_mgr(), context->module()->IdBound()); - - // Compare again after compacting the defuse manager's storage - context->get_def_use_mgr()->CompactStorage(); - - CheckDef(tc.du, context->get_def_use_mgr()->id_to_defs()); - CheckUse(tc.du, context->get_def_use_mgr(), context->module()->IdBound()); - - EXPECT_TRUE(CompareAndPrintDifferences(*context->get_def_use_mgr(), - DefUseManager(context->module()))); -} - -// clang-format off -INSTANTIATE_TEST_SUITE_P( - TestCase, CompactIdempotence, - ::testing::ValuesIn(std::vector<ReplaceUseCase>{ - { // no use, no replace request - "", {}, "", {}, - }, - { // replace one use - "%1 = OpTypeBool " - "%2 = OpTypeVector %1 3 " - "%3 = OpTypeInt 32 0 ", - {{1, 3}}, - "%1 = OpTypeBool\n" - "%2 = OpTypeVector %3 3\n" - "%3 = OpTypeInt 32 0", - { - { // defs - {1, "%1 = OpTypeBool"}, - {2, "%2 = OpTypeVector %3 3"}, - {3, "%3 = OpTypeInt 32 0"}, - }, - { // uses - {3, {"%2 = OpTypeVector %3 3"}}, - }, - }, - }, - { // replace and then replace back - "%1 = OpTypeBool " - "%2 = OpTypeVector %1 3 " - "%3 = OpTypeInt 32 0", - {{1, 3}, {3, 1}}, - "%1 = OpTypeBool\n" - "%2 = OpTypeVector %1 3\n" - "%3 = OpTypeInt 32 0", - { - { // defs - {1, "%1 = OpTypeBool"}, - {2, "%2 = OpTypeVector %1 3"}, - {3, "%3 = OpTypeInt 32 0"}, - }, - { // uses - {1, {"%2 = OpTypeVector %1 3"}}, - }, - }, - }, - { // replace with the same id - "%1 = OpTypeBool " - "%2 = OpTypeVector %1 3", - {{1, 1}, {2, 2}, {3, 3}}, - "%1 = OpTypeBool\n" - "%2 = OpTypeVector %1 3", - { - { // defs - {1, "%1 = OpTypeBool"}, - {2, "%2 = OpTypeVector %1 3"}, - }, - { // uses - {1, {"%2 = OpTypeVector %1 3"}}, - }, - }, - }, - { // replace in sequence - "%1 = OpTypeBool " - "%2 = OpTypeVector %1 3 " - "%3 = OpTypeInt 32 0 " - "%4 = OpTypeInt 32 1 ", - {{1, 3}, {3, 4}}, - "%1 = OpTypeBool\n" - "%2 = OpTypeVector %4 3\n" - "%3 = OpTypeInt 32 0\n" - "%4 = OpTypeInt 32 1", - { - { // defs - {1, "%1 = OpTypeBool"}, - {2, "%2 = OpTypeVector %4 3"}, - {3, "%3 = OpTypeInt 32 0"}, - {4, "%4 = OpTypeInt 32 1"}, - }, - { // uses - {4, {"%2 = OpTypeVector %4 3"}}, - }, - }, - }, - { // replace multiple uses - "%1 = OpTypeBool " - "%2 = OpTypeVector %1 2 " - "%3 = OpTypeVector %1 3 " - "%4 = OpTypeVector %1 4 " - "%5 = OpTypeMatrix %2 2 " - "%6 = OpTypeMatrix %3 3 " - "%7 = OpTypeMatrix %4 4 " - "%8 = OpTypeInt 32 0 " - "%9 = OpTypeInt 32 1 " - "%10 = OpTypeInt 64 0", - {{1, 8}, {2, 9}, {4, 10}}, - "%1 = OpTypeBool\n" - "%2 = OpTypeVector %8 2\n" - "%3 = OpTypeVector %8 3\n" - "%4 = OpTypeVector %8 4\n" - "%5 = OpTypeMatrix %9 2\n" - "%6 = OpTypeMatrix %3 3\n" - "%7 = OpTypeMatrix %10 4\n" - "%8 = OpTypeInt 32 0\n" - "%9 = OpTypeInt 32 1\n" - "%10 = OpTypeInt 64 0", - { - { // defs - {1, "%1 = OpTypeBool"}, - {2, "%2 = OpTypeVector %8 2"}, - {3, "%3 = OpTypeVector %8 3"}, - {4, "%4 = OpTypeVector %8 4"}, - {5, "%5 = OpTypeMatrix %9 2"}, - {6, "%6 = OpTypeMatrix %3 3"}, - {7, "%7 = OpTypeMatrix %10 4"}, - {8, "%8 = OpTypeInt 32 0"}, - {9, "%9 = OpTypeInt 32 1"}, - {10, "%10 = OpTypeInt 64 0"}, - }, - { // uses - {8, - { - "%2 = OpTypeVector %8 2", - "%3 = OpTypeVector %8 3", - "%4 = OpTypeVector %8 4", - } - }, - {9, {"%5 = OpTypeMatrix %9 2"}}, - {3, {"%6 = OpTypeMatrix %3 3"}}, - {10, {"%7 = OpTypeMatrix %10 4"}}, - }, - }, - }, - { // OpPhi. - kOpPhiTestFunction, - // replace one id used by OpPhi, replace one id generated by OpPhi - {{9, 13}, {11, 9}}, - "%1 = OpTypeVoid\n" - "%6 = OpTypeInt 32 0\n" - "%10 = OpTypeFloat 32\n" - "%16 = OpTypeBool\n" - "%3 = OpTypeFunction %1\n" - "%8 = OpConstant %6 0\n" - "%18 = OpConstant %6 1\n" - "%12 = OpConstant %10 1\n" - "%2 = OpFunction %1 None %3\n" - "%4 = OpLabel\n" - "OpBranch %5\n" - - "%5 = OpLabel\n" - "%7 = OpPhi %6 %8 %4 %13 %5\n" // %9 -> %13 - "%11 = OpPhi %10 %12 %4 %13 %5\n" - "%9 = OpIAdd %6 %7 %8\n" - "%13 = OpFAdd %10 %9 %12\n" // %11 -> %9 - "%17 = OpSLessThan %16 %7 %18\n" - "OpLoopMerge %19 %5 None\n" - "OpBranchConditional %17 %5 %19\n" - - "%19 = OpLabel\n" - "OpReturn\n" - "OpFunctionEnd", - { - { // defs. - {1, "%1 = OpTypeVoid"}, - {2, "%2 = OpFunction %1 None %3"}, - {3, "%3 = OpTypeFunction %1"}, - {4, "%4 = OpLabel"}, - {5, "%5 = OpLabel"}, - {6, "%6 = OpTypeInt 32 0"}, - {7, "%7 = OpPhi %6 %8 %4 %13 %5"}, - {8, "%8 = OpConstant %6 0"}, - {9, "%9 = OpIAdd %6 %7 %8"}, - {10, "%10 = OpTypeFloat 32"}, - {11, "%11 = OpPhi %10 %12 %4 %13 %5"}, - {12, "%12 = OpConstant %10 1.0"}, - {13, "%13 = OpFAdd %10 %9 %12"}, - {16, "%16 = OpTypeBool"}, - {17, "%17 = OpSLessThan %16 %7 %18"}, - {18, "%18 = OpConstant %6 1"}, - {19, "%19 = OpLabel"}, - }, - { // uses - {1, - { - "%2 = OpFunction %1 None %3", - "%3 = OpTypeFunction %1", - } - }, - {3, {"%2 = OpFunction %1 None %3"}}, - {4, - { - "%7 = OpPhi %6 %8 %4 %13 %5", - "%11 = OpPhi %10 %12 %4 %13 %5", - } - }, - {5, - { - "OpBranch %5", - "%7 = OpPhi %6 %8 %4 %13 %5", - "%11 = OpPhi %10 %12 %4 %13 %5", - "OpLoopMerge %19 %5 None", - "OpBranchConditional %17 %5 %19", - } - }, - {6, - { - // Can't properly check constants - // "%8 = OpConstant %6 0", - // "%18 = OpConstant %6 1", - "%7 = OpPhi %6 %8 %4 %13 %5", - "%9 = OpIAdd %6 %7 %8" - } - }, - {7, - { - "%9 = OpIAdd %6 %7 %8", - "%17 = OpSLessThan %16 %7 %18", - } - }, - {8, - { - "%7 = OpPhi %6 %8 %4 %13 %5", - "%9 = OpIAdd %6 %7 %8", - } - }, - {9, {"%13 = OpFAdd %10 %9 %12"}}, // uses of %9 changed from %7 to %13 - {10, - { - "%11 = OpPhi %10 %12 %4 %13 %5", - // "%12 = OpConstant %10 1", - "%13 = OpFAdd %10 %9 %12" - } - }, - // no more uses of %11 - {12, - { - "%11 = OpPhi %10 %12 %4 %13 %5", - "%13 = OpFAdd %10 %9 %12" - } - }, - {13, { - "%7 = OpPhi %6 %8 %4 %13 %5", - "%11 = OpPhi %10 %12 %4 %13 %5", - } - }, - {16, {"%17 = OpSLessThan %16 %7 %18"}}, - {17, {"OpBranchConditional %17 %5 %19"}}, - {18, {"%17 = OpSLessThan %16 %7 %18"}}, - {19, - { - "OpLoopMerge %19 %5 None", - "OpBranchConditional %17 %5 %19", - } - }, - }, - }, - }, - { // OpPhi defining and referencing the same id. - "%1 = OpTypeBool " - "%3 = OpTypeFunction %1 " - "%2 = OpConstantTrue %1 " - - "%4 = OpFunction %3 None %1 " - "%6 = OpLabel " - " OpBranch %7 " - "%7 = OpLabel " - "%8 = OpPhi %1 %8 %7 %2 %6 " // both defines and uses %8 - " OpBranch %7 " - " OpFunctionEnd", - {{8, 2}}, - "%1 = OpTypeBool\n" - "%3 = OpTypeFunction %1\n" - "%2 = OpConstantTrue %1\n" - - "%4 = OpFunction %3 None %1\n" - "%6 = OpLabel\n" - "OpBranch %7\n" - "%7 = OpLabel\n" - "%8 = OpPhi %1 %2 %7 %2 %6\n" // use of %8 changed to %2 - "OpBranch %7\n" - "OpFunctionEnd", - { - { // defs - {1, "%1 = OpTypeBool"}, - {2, "%2 = OpConstantTrue %1"}, - {3, "%3 = OpTypeFunction %1"}, - {4, "%4 = OpFunction %3 None %1"}, - {6, "%6 = OpLabel"}, - {7, "%7 = OpLabel"}, - {8, "%8 = OpPhi %1 %2 %7 %2 %6"}, - }, - { // uses - {1, - { - "%2 = OpConstantTrue %1", - "%3 = OpTypeFunction %1", - "%4 = OpFunction %3 None %1", - "%8 = OpPhi %1 %2 %7 %2 %6", - } - }, - {2, - { - // Only checking users - "%8 = OpPhi %1 %2 %7 %2 %6", - } - }, - {3, {"%4 = OpFunction %3 None %1"}}, - {6, {"%8 = OpPhi %1 %2 %7 %2 %6"}}, - {7, - { - "OpBranch %7", - "%8 = OpPhi %1 %2 %7 %2 %6", - "OpBranch %7", - } - }, - // {8, {"%8 = OpPhi %1 %8 %7 %2 %6"}}, - }, - }, - }, - }) -); -// clang-format on - struct KillDefCase { const char* before; std::vector<uint32_t> ids_to_kill;
diff --git a/test/opt/fold_test.cpp b/test/opt/fold_test.cpp index 8ee14a9..f6c94ca 100644 --- a/test/opt/fold_test.cpp +++ b/test/opt/fold_test.cpp
@@ -148,6 +148,8 @@ %v2half = OpTypeVector %half 2 %v2bool = OpTypeVector %bool 2 %m2x2int = OpTypeMatrix %v2int 2 +%mat4v4float = OpTypeMatrix %v4float 4 +%mat4v4double = OpTypeMatrix %v4double 4 %struct_v2int_int_int = OpTypeStruct %v2int %int %int %_ptr_int = OpTypePointer Function %int %_ptr_uint = OpTypePointer Function %uint @@ -276,13 +278,20 @@ %v4float_0_0_0_1 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_1 %v4float_0_1_0_0 = OpConstantComposite %v4float %float_0 %float_1 %float_null %float_0 %v4float_1_1_1_1 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +%v4float_1_2_3_4 = OpConstantComposite %v4float %float_1 %float_2 %float_3 %float_4 +%v4float_null = OpConstantNull %v4float +%mat4v4float_null = OpConstantComposite %mat4v4float %v4float_null %v4float_null %v4float_null %v4float_null +%mat4v4float_1_2_3_4 = OpConstantComposite %mat4v4float %v4float_1_2_3_4 %v4float_1_2_3_4 %v4float_1_2_3_4 %v4float_1_2_3_4 %107 = OpConstantComposite %v4double %double_0 %double_0 %double_0 %double_0 %v4double_0_0_0_0 = OpConstantComposite %v4double %double_0 %double_0 %double_0 %double_0 %v4double_0_0_0_1 = OpConstantComposite %v4double %double_0 %double_0 %double_0 %double_1 %v4double_0_1_0_0 = OpConstantComposite %v4double %double_0 %double_1 %double_null %double_0 %v4double_1_1_1_1 = OpConstantComposite %v4double %double_1 %double_1 %double_1 %double_1 +%v4double_1_2_3_4 = OpConstantComposite %v4double %double_1 %double_2 %double_3 %double_4 %v4double_1_1_1_0p5 = OpConstantComposite %v4double %double_1 %double_1 %double_1 %double_0p5 %v4double_null = OpConstantNull %v4double +%mat4v4double_null = OpConstantComposite %mat4v4double %v4double_null %v4double_null %v4double_null %v4double_null +%mat4v4double_1_2_3_4 = OpConstantComposite %mat4v4double %v4double_1_2_3_4 %v4double_1_2_3_4 %v4double_1_2_3_4 %v4double_1_2_3_4 %v4float_n1_2_1_3 = OpConstantComposite %v4float %float_n1 %float_2 %float_1 %float_3 %uint_0x3f800000 = OpConstant %uint 0x3f800000 %uint_0xbf800000 = OpConstant %uint 0xbf800000 @@ -912,7 +921,61 @@ "%2 = OpBitcast %v2double %v4int_0x3FF00000_0x00000000_0xC05FD666_0x66666666\n" + "OpReturn\n" + "OpFunctionEnd", - 2, {1.0,-127.35}) + 2, {1.0,-127.35}), + // Test case 1: OpVectorTimesMatrix Non-Zero Zero {{0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}} {1.0, 2.0, 3.0, 4.0} {0.0, 0.0, 0.0, 0.0} + InstructionFoldingCase<std::vector<double>>( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpVectorTimesMatrix %v4double %v4double_1_2_3_4 %mat4v4double_null\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, {0.0,0.0,0.0,0.0}), + // Test case 2: OpVectorTimesMatrix Zero Non-Zero {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {0.0, 0.0, 0.0, 0.0} {0.0, 0.0, 0.0, 0.0} + InstructionFoldingCase<std::vector<double>>( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpVectorTimesMatrix %v4double %v4double_null %mat4v4double_1_2_3_4\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, {0.0,0.0,0.0,0.0}), + // Test case 3: OpVectorTimesMatrix Non-Zero Non-Zero {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {1.0, 2.0, 3.0, 4.0} {30.0, 30.0, 30.0, 30.0} + InstructionFoldingCase<std::vector<double>>( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpVectorTimesMatrix %v4double %v4double_1_2_3_4 %mat4v4double_1_2_3_4\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, {30.0,30.0,30.0,30.0}), + // Test case 4: OpMatrixTimesVector Zero Non-Zero {1.0, 2.0, 3.0, 4.0} {{0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}} {0.0, 0.0, 0.0, 0.0} + InstructionFoldingCase<std::vector<double>>( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpMatrixTimesVector %v4double %mat4v4double_null %v4double_1_2_3_4\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, {0.0,0.0,0.0,0.0}), + // Test case 5: OpMatrixTimesVector Non-Zero Zero {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {0.0, 0.0, 0.0, 0.0} {0.0, 0.0, 0.0, 0.0} + InstructionFoldingCase<std::vector<double>>( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpMatrixTimesVector %v4double %mat4v4double_1_2_3_4 %v4double_null\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, {0.0,0.0,0.0,0.0}), + // Test case 6: OpMatrixTimesVector Non-Zero Non-Zero {1.0, 2.0, 3.0, 4.0} {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {10.0, 20.0, 30.0, 40.0} + InstructionFoldingCase<std::vector<double>>( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpMatrixTimesVector %v4double %mat4v4double_1_2_3_4 %v4double_1_2_3_4\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, {10.0,20.0,30.0,40.0}) )); using FloatVectorInstructionFoldingTest = @@ -981,7 +1044,61 @@ "%2 = OpBitcast %v2float %long_0xbf8000003f800000\n" + "OpReturn\n" + "OpFunctionEnd", - 2, {1.0f,-1.0f}) + 2, {1.0f,-1.0f}), + // Test case 3: OpVectorTimesMatrix Non-Zero Zero {{0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}} {1.0, 2.0, 3.0, 4.0} {0.0, 0.0, 0.0, 0.0} + InstructionFoldingCase<std::vector<float>>( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpVectorTimesMatrix %v4float %v4float_1_2_3_4 %mat4v4float_null\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, {0.0f,0.0f,0.0f,0.0f}), + // Test case 4: OpVectorTimesMatrix Zero Non-Zero {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {0.0, 0.0, 0.0, 0.0} {0.0, 0.0, 0.0, 0.0} + InstructionFoldingCase<std::vector<float>>( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpVectorTimesMatrix %v4float %v4float_null %mat4v4float_1_2_3_4\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, {0.0f,0.0f,0.0f,0.0f}), + // Test case 5: OpVectorTimesMatrix Non-Zero Non-Zero {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {1.0, 2.0, 3.0, 4.0} {30.0, 30.0, 30.0, 30.0} + InstructionFoldingCase<std::vector<float>>( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpVectorTimesMatrix %v4float %v4float_1_2_3_4 %mat4v4float_1_2_3_4\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, {30.0f,30.0f,30.0f,30.0f}), + // Test case 6: OpMatrixTimesVector Zero Non-Zero {1.0, 2.0, 3.0, 4.0} {{0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}} {0.0, 0.0, 0.0, 0.0} + InstructionFoldingCase<std::vector<float>>( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpMatrixTimesVector %v4float %mat4v4float_null %v4float_1_2_3_4\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, {0.0f,0.0f,0.0f,0.0f}), + // Test case 7: OpMatrixTimesVector Non-Zero Zero {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {0.0, 0.0, 0.0, 0.0} {0.0, 0.0, 0.0, 0.0} + InstructionFoldingCase<std::vector<float>>( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpMatrixTimesVector %v4float %mat4v4float_1_2_3_4 %v4float_null\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, {0.0f,0.0f,0.0f,0.0f}), + // Test case 8: OpMatrixTimesVector Non-Zero Non-Zero {1.0, 2.0, 3.0, 4.0} {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {10.0, 20.0, 30.0, 40.0} + InstructionFoldingCase<std::vector<float>>( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpMatrixTimesVector %v4float %mat4v4float_1_2_3_4 %v4float_1_2_3_4\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, {10.0f,20.0f,30.0f,40.0f}) )); // clang-format on using BooleanInstructionFoldingTest =
diff --git a/test/opt/if_conversion_test.cpp b/test/opt/if_conversion_test.cpp index 81e9bb2..dc7f831 100644 --- a/test/opt/if_conversion_test.cpp +++ b/test/opt/if_conversion_test.cpp
@@ -591,6 +591,33 @@ SinglePassRunAndMatch<IfConversion>(text, true); } +TEST_F(IfConversionTest, MultipleEdgesFromSameBlock) { + // If a block has two out going edges that go to the same block, then there + // can be an OpPhi instruction with fewer entries than the number of incoming + // edges. This must be handled. + const std::string text = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +%void = OpTypeVoid +%4 = OpTypeFunction %void +%bool = OpTypeBool +%true = OpConstantTrue %bool +%true_0 = OpConstantTrue %bool +%2 = OpFunction %void None %4 +%8 = OpLabel +OpSelectionMerge %9 None +OpBranchConditional %true_0 %9 %9 +%9 = OpLabel +%10 = OpPhi %bool %true %8 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck<IfConversion>(text, text, true, true); +} + } // namespace } // namespace opt } // namespace spvtools
diff --git a/test/opt/inline_test.cpp b/test/opt/inline_test.cpp index cefd8e5..d804f7c 100644 --- a/test/opt/inline_test.cpp +++ b/test/opt/inline_test.cpp
@@ -1533,9 +1533,11 @@ %9 = OpLabel OpBranch %10 %10 = OpLabel -OpLoopMerge %12 %10 None +OpLoopMerge %12 %15 None OpBranch %14 %14 = OpLabel +OpBranch %15 +%15 = OpLabel OpBranchConditional %true %10 %12 %12 = OpLabel OpReturn @@ -1694,7 +1696,7 @@ OpBranch %13 %13 = OpLabel %14 = OpCopyObject %bool %false -OpLoopMerge %16 %13 None +OpLoopMerge %16 %22 None OpBranch %17 %17 = OpLabel %19 = OpCopyObject %bool %true @@ -1702,6 +1704,8 @@ OpBranchConditional %true %20 %20 %20 = OpLabel %21 = OpPhi %bool %19 %17 +OpBranch %22 +%22 = OpLabel OpBranchConditional %true %13 %16 %16 = OpLabel OpReturn
diff --git a/test/opt/inst_bindless_check_test.cpp b/test/opt/inst_bindless_check_test.cpp index 4c271de..c8eb6c1 100644 --- a/test/opt/inst_bindless_check_test.cpp +++ b/test/opt/inst_bindless_check_test.cpp
@@ -8897,6 +8897,7 @@ )"; SetTargetEnv(SPV_ENV_VULKAN_1_2); + ValidatorOptions()->uniform_buffer_standard_layout = true; SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); SinglePassRunAndMatch<InstBindlessCheckPass>(text, true, 7u, 23u, false, false, true, false, true);
diff --git a/test/opt/instruction_test.cpp b/test/opt/instruction_test.cpp index 2a48134..dd749ab 100644 --- a/test/opt/instruction_test.cpp +++ b/test/opt/instruction_test.cpp
@@ -1525,6 +1525,45 @@ EXPECT_EQ(false, inst->IsVulkanStorageTexelBuffer()); } +TEST_F(DescriptorTypeTest, GetShader100DebugOpcode) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" + %2 = OpString "ps.hlsl" + %3 = OpString "#line 1 \"ps.hlsl\"" + %void = OpTypeVoid + %5 = OpExtInst %void %1 DebugExpression + %6 = OpExtInst %void %1 DebugSource %2 %3 +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + std::unique_ptr<IRContext> context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + Instruction* debug_expression = context->get_def_use_mgr()->GetDef(5); + EXPECT_EQ(debug_expression->GetShader100DebugOpcode(), + NonSemanticShaderDebugInfo100DebugExpression); + Instruction* debug_source = context->get_def_use_mgr()->GetDef(6); + EXPECT_EQ(debug_source->GetShader100DebugOpcode(), + NonSemanticShaderDebugInfo100DebugSource); + + // Test that an opcode larger than the max will return Max. This instruction + // cannot be in the assembly above because the assembler expects the string + // for the opcode, so we cannot use an arbitrary number. However, a binary + // file could have an arbitrary number. + std::unique_ptr<Instruction> past_max(debug_expression->Clone(context.get())); + const uint32_t kExtInstOpcodeInIndex = 1; + uint32_t large_opcode = NonSemanticShaderDebugInfo100InstructionsMax + 2; + past_max->SetInOperand(kExtInstOpcodeInIndex, {large_opcode}); + EXPECT_EQ(past_max->GetShader100DebugOpcode(), + NonSemanticShaderDebugInfo100InstructionsMax); + + // Test that an opcode without a value in the enum, but less than Max returns + // the same value. + uint32_t opcode = NonSemanticShaderDebugInfo100InstructionsMax - 2; + past_max->SetInOperand(kExtInstOpcodeInIndex, {opcode}); + EXPECT_EQ(past_max->GetShader100DebugOpcode(), opcode); +} + } // namespace } // namespace opt } // namespace spvtools
diff --git a/test/opt/local_access_chain_convert_test.cpp b/test/opt/local_access_chain_convert_test.cpp index 2b3231c..6f5021c 100644 --- a/test/opt/local_access_chain_convert_test.cpp +++ b/test/opt/local_access_chain_convert_test.cpp
@@ -1252,6 +1252,70 @@ true); } +TEST_F(LocalAccessChainConvertTest, OutOfBoundsAccess) { + // The access chain indexes element 12 in an array of size 10. Nothing should + // be done. + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" %3 +OpExecutionMode %2 OriginUpperLeft +%void = OpTypeVoid +%5 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%int_10 = OpConstant %int 10 +%_arr_int_int_10 = OpTypeArray %int %int_10 +%_ptr_Function_int = OpTypePointer Function %int +%int_12 = OpConstant %int 12 +%_ptr_Output_int = OpTypePointer Output %int +%3 = OpVariable %_ptr_Output_int Output +%_ptr_Function__arr_int_int_10 = OpTypePointer Function %_arr_int_int_10 +%2 = OpFunction %void None %5 +%13 = OpLabel +%14 = OpVariable %_ptr_Function__arr_int_int_10 Function +%15 = OpAccessChain %_ptr_Function_int %14 %int_12 +%16 = OpLoad %int %15 +OpStore %3 %16 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck<LocalAccessChainConvertPass>(assembly, assembly, false, + true); +} + +TEST_F(LocalAccessChainConvertTest, OutOfBoundsAccessAtBoundary) { + // The access chain indexes element 10 in an array of size 10. Nothing should + // be done. + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" %3 +OpExecutionMode %2 OriginUpperLeft +%void = OpTypeVoid +%5 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%int_10 = OpConstant %int 10 +%_arr_int_int_10 = OpTypeArray %int %int_10 +%_ptr_Function_int = OpTypePointer Function %int +%_ptr_Output_int = OpTypePointer Output %int +%3 = OpVariable %_ptr_Output_int Output +%_ptr_Function__arr_int_int_10 = OpTypePointer Function %_arr_int_int_10 +%2 = OpFunction %void None %5 +%12 = OpLabel +%13 = OpVariable %_ptr_Function__arr_int_int_10 Function +%14 = OpAccessChain %_ptr_Function_int %13 %int_10 +%15 = OpLoad %int %14 +OpStore %3 %15 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck<LocalAccessChainConvertPass>(assembly, assembly, false, + true); +} // TODO(greg-lunarg): Add tests to verify handling of these cases: // // Assorted vector and matrix types
diff --git a/test/opt/pass_merge_return_test.cpp b/test/opt/pass_merge_return_test.cpp index 21960d1..04bd5d9 100644 --- a/test/opt/pass_merge_return_test.cpp +++ b/test/opt/pass_merge_return_test.cpp
@@ -2231,7 +2231,7 @@ TEST_F(MergeReturnPassTest, UnreachableMergeAndContinue) { // Make sure that the pass can handle a single block that is both a merge and - // a continue. + // a continue. Note that this is invalid SPIR-V. const std::string text = R"( OpCapability Shader @@ -2265,7 +2265,7 @@ )"; SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); - auto result = SinglePassRunAndDisassemble<MergeReturnPass>(text, true, true); + auto result = SinglePassRunAndDisassemble<MergeReturnPass>(text, true, false); // Not looking for any particular output. Other tests do that. // Just want to make sure the check for unreachable blocks does not emit an
diff --git a/test/opt/reduce_load_size_test.cpp b/test/opt/reduce_load_size_test.cpp index abb5cde..4546750 100644 --- a/test/opt/reduce_load_size_test.cpp +++ b/test/opt/reduce_load_size_test.cpp
@@ -498,6 +498,45 @@ SinglePassRunAndMatch<ReduceLoadSize>(test, false, 1.1); } +TEST_F(ReduceLoadSizeTest, replace_array_with_spec_constant_size) { + const std::string test = + R"( + OpCapability ClipDistance + OpExtension " " + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %1 " " + OpExecutionMode %1 OriginUpperLeft + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %uint = OpTypeInt 32 0 + %6 = OpSpecConstant %uint 538976288 + %_arr_int_6 = OpTypeArray %int %6 + %_struct_8 = OpTypeStruct %_arr_int_6 + %_struct_9 = OpTypeStruct %_struct_8 +%_ptr_Uniform__struct_9 = OpTypePointer Uniform %_struct_9 +; CHECK: [[var:%\w+]] = OpVariable %_ptr_Uniform__struct_9 Uniform + %11 = OpVariable %_ptr_Uniform__struct_9 Uniform + %int_0 = OpConstant %int 0 +%_ptr_Uniform__arr_int_6 = OpTypePointer Uniform %_arr_int_6 + %1 = OpFunction %void None %3 + %14 = OpLabel +; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_Uniform__arr_int_6 [[var]] %int_0 %int_0 +; CHECK: [[new_ac:%\w+]] = OpAccessChain %_ptr_Uniform_int [[ac]] %uint_538976288 +; CHECK: [[ld:%\w+]] = OpLoad %int [[new_ac]] +; CHECK: %18 = OpIAdd %int [[ld]] [[ld]] + %15 = OpAccessChain %_ptr_Uniform__arr_int_6 %11 %int_0 %int_0 + %16 = OpLoad %_arr_int_6 %15 + %17 = OpCompositeExtract %int %16 538976288 + %18 = OpIAdd %int %17 %17 + OpUnreachable + OpFunctionEnd +)"; + + SinglePassRunAndMatch<ReduceLoadSize>(test, false, + kDefaultLoadReductionThreshold); +} + } // namespace } // namespace opt } // namespace spvtools
diff --git a/test/opt/scalar_analysis.cpp b/test/opt/scalar_analysis.cpp index 598d8c7..df2aa8f 100644 --- a/test/opt/scalar_analysis.cpp +++ b/test/opt/scalar_analysis.cpp
@@ -1202,7 +1202,6 @@ EXPECT_EQ(phis.size(), 2u); SENode* phi_node_1 = analysis.AnalyzeInstruction(phis[0]); SENode* phi_node_2 = analysis.AnalyzeInstruction(phis[1]); - phi_node_1->DumpDot(std::cout, true); EXPECT_NE(phi_node_1, nullptr); EXPECT_NE(phi_node_2, nullptr);
diff --git a/test/opt/scalar_replacement_test.cpp b/test/opt/scalar_replacement_test.cpp index 7db997d..0c97c80 100644 --- a/test/opt/scalar_replacement_test.cpp +++ b/test/opt/scalar_replacement_test.cpp
@@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "source/opt/scalar_replacement_pass.h" + #include <string> #include "gmock/gmock.h" @@ -23,6 +25,18 @@ namespace opt { namespace { +using ScalarReplacementPassName = ::testing::Test; + +TEST_F(ScalarReplacementPassName, Default) { + auto srp = ScalarReplacementPass(); + EXPECT_STREQ(srp.name(), "scalar-replacement=100"); +} + +TEST_F(ScalarReplacementPassName, Large) { + auto srp = ScalarReplacementPass(0xffffffffu); + EXPECT_STREQ(srp.name(), "scalar-replacement=4294967295"); +} + using ScalarReplacementTest = PassTest<::testing::Test>; TEST_F(ScalarReplacementTest, SimpleStruct) {
diff --git a/test/opt/types_test.cpp b/test/opt/types_test.cpp index 82e4040..552ad97 100644 --- a/test/opt/types_test.cpp +++ b/test/opt/types_test.cpp
@@ -266,6 +266,67 @@ } } +TEST(Types, TestNumberOfComponentsOnArrays) { + Float f32(32); + EXPECT_EQ(f32.NumberOfComponents(), 0); + + Array array_size_42( + &f32, Array::LengthInfo{99u, {Array::LengthInfo::kConstant, 42u}}); + EXPECT_EQ(array_size_42.NumberOfComponents(), 42); + + Array array_size_0xDEADBEEF00C0FFEE( + &f32, Array::LengthInfo{ + 99u, {Array::LengthInfo::kConstant, 0xC0FFEE, 0xDEADBEEF}}); + EXPECT_EQ(array_size_0xDEADBEEF00C0FFEE.NumberOfComponents(), + 0xDEADBEEF00C0FFEEull); + + Array array_size_unknown( + &f32, + Array::LengthInfo{99u, {Array::LengthInfo::kConstantWithSpecId, 10}}); + EXPECT_EQ(array_size_unknown.NumberOfComponents(), UINT64_MAX); + + RuntimeArray runtime_array(&f32); + EXPECT_EQ(runtime_array.NumberOfComponents(), UINT64_MAX); +} + +TEST(Types, TestNumberOfComponentsOnVectors) { + Float f32(32); + EXPECT_EQ(f32.NumberOfComponents(), 0); + + for (uint32_t vector_size = 1; vector_size < 4; ++vector_size) { + Vector vector(&f32, vector_size); + EXPECT_EQ(vector.NumberOfComponents(), vector_size); + } +} + +TEST(Types, TestNumberOfComponentsOnMatrices) { + Float f32(32); + Vector vector(&f32, 2); + + for (uint32_t number_of_columns = 1; number_of_columns < 4; + ++number_of_columns) { + Matrix matrix(&vector, number_of_columns); + EXPECT_EQ(matrix.NumberOfComponents(), number_of_columns); + } +} + +TEST(Types, TestNumberOfComponentsOnStructs) { + Float f32(32); + Vector vector(&f32, 2); + + Struct empty_struct({}); + EXPECT_EQ(empty_struct.NumberOfComponents(), 0); + + Struct struct_f32({&f32}); + EXPECT_EQ(struct_f32.NumberOfComponents(), 1); + + Struct struct_f32_vec({&f32, &vector}); + EXPECT_EQ(struct_f32_vec.NumberOfComponents(), 2); + + Struct struct_100xf32(std::vector<const Type*>(100, &f32)); + EXPECT_EQ(struct_100xf32.NumberOfComponents(), 100); +} + TEST(Types, IntSignedness) { std::vector<bool> signednesses = {true, false, false, true}; std::vector<std::unique_ptr<Integer>> types;
diff --git a/test/reduce/structured_loop_to_selection_test.cpp b/test/reduce/structured_loop_to_selection_test.cpp index 0cfcfdf..d203f3e 100644 --- a/test/reduce/structured_loop_to_selection_test.cpp +++ b/test/reduce/structured_loop_to_selection_test.cpp
@@ -2957,7 +2957,7 @@ OpLoopMerge %12 %13 None OpBranch %12 %13 = OpLabel - OpBranchConditional %6 %9 %11 + OpBranch %11 %12 = OpLabel OpBranch %10 %10 = OpLabel @@ -2999,7 +2999,7 @@ OpLoopMerge %12 %13 None OpBranch %12 %13 = OpLabel - OpBranchConditional %6 %9 %11 + OpBranch %11 %12 = OpLabel OpBranch %9 %10 = OpLabel @@ -3036,7 +3036,7 @@ OpSelectionMerge %12 None OpBranchConditional %6 %12 %12 %13 = OpLabel - OpBranchConditional %6 %9 %11 + OpBranch %11 %12 = OpLabel OpBranch %9 %10 = OpLabel @@ -3050,8 +3050,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, UnreachableInnerLoopContinueBranchingToOuterLoopMerge2) { - // In this test, the branch to the outer loop merge from the inner loop's - // continue is part of a structured selection. + // In this test, the unreachable continue is composed of multiple blocks. std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" @@ -3073,8 +3072,7 @@ OpLoopMerge %12 %13 None OpBranch %12 %13 = OpLabel - OpSelectionMerge %14 None - OpBranchConditional %6 %9 %14 + OpBranch %14 %14 = OpLabel OpBranch %11 %12 = OpLabel @@ -3118,8 +3116,7 @@ OpLoopMerge %12 %13 None OpBranch %12 %13 = OpLabel - OpSelectionMerge %14 None - OpBranchConditional %6 %9 %14 + OpBranch %14 %14 = OpLabel OpBranch %11 %12 = OpLabel @@ -3158,8 +3155,7 @@ OpSelectionMerge %12 None OpBranchConditional %6 %12 %12 %13 = OpLabel - OpSelectionMerge %14 None - OpBranchConditional %6 %9 %14 + OpBranch %14 %14 = OpLabel OpBranch %11 %12 = OpLabel
diff --git a/test/text_advance_test.cpp b/test/text_advance_test.cpp index 9de77a8..0d23ab1 100644 --- a/test/text_advance_test.cpp +++ b/test/text_advance_test.cpp
@@ -130,5 +130,14 @@ EXPECT_EQ(2u, pos.line); EXPECT_EQ(4u, pos.index); } + +TEST(TextAdvance, HandleLotsOfWhitespace) { + std::string lots_of_spaces(10000, ' '); + lots_of_spaces += "Word"; + const auto pos = PositionAfterAdvance(lots_of_spaces.c_str()); + EXPECT_EQ(10000u, pos.column); + EXPECT_EQ(0u, pos.line); + EXPECT_EQ(10000u, pos.index); +} } // namespace } // namespace spvtools
diff --git a/test/util/CMakeLists.txt b/test/util/CMakeLists.txt index 6808783..20038f7 100644 --- a/test/util/CMakeLists.txt +++ b/test/util/CMakeLists.txt
@@ -17,7 +17,6 @@ bit_vector_test.cpp bitutils_test.cpp hash_combine_test.cpp - pooled_linked_list_test.cpp small_vector_test.cpp LIBS SPIRV-Tools-opt )
diff --git a/test/util/pooled_linked_list_test.cpp b/test/util/pooled_linked_list_test.cpp deleted file mode 100644 index 82fb4ac..0000000 --- a/test/util/pooled_linked_list_test.cpp +++ /dev/null
@@ -1,185 +0,0 @@ -// Copyright (c) 2021 The Khronos Group Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <algorithm> -#include <list> -#include <utility> -#include <vector> - -#include "gmock/gmock.h" -#include "source/util/pooled_linked_list.h" - -namespace spvtools { -namespace utils { -namespace { - -using PooledLinkedListTest = ::testing::Test; - -template <typename T> -static std::vector<T> ToVector(const PooledLinkedList<T>& list) { - std::vector<T> vec; - for (auto it = list.begin(); it != list.end(); ++it) { - vec.push_back(*it); - } - return vec; -} - -template <typename T> -static void AppendVector(PooledLinkedList<T>& list, const std::vector<T>& vec) { - for (const T& t : vec) { - list.push_back(t); - } -} - -TEST(PooledLinkedListTest, Empty) { - PooledLinkedListNodes<uint32_t> pool; - PooledLinkedList<uint32_t> ll(&pool); - EXPECT_TRUE(ll.empty()); - - ll.push_back(1u); - EXPECT_TRUE(!ll.empty()); -} - -TEST(PooledLinkedListTest, Iterator) { - PooledLinkedListNodes<uint32_t> pool; - PooledLinkedList<uint32_t> ll(&pool); - - EXPECT_EQ(ll.begin(), ll.end()); - - ll.push_back(1); - EXPECT_NE(ll.begin(), ll.end()); - - auto it = ll.begin(); - EXPECT_EQ(*it, 1); - ++it; - EXPECT_EQ(it, ll.end()); -} - -TEST(PooledLinkedListTest, Iterator_algorithms) { - PooledLinkedListNodes<uint32_t> pool; - PooledLinkedList<uint32_t> ll(&pool); - - AppendVector(ll, {3, 2, 0, 1}); - EXPECT_EQ(std::distance(ll.begin(), ll.end()), 4); - EXPECT_EQ(*std::min_element(ll.begin(), ll.end()), 0); - EXPECT_EQ(*std::max_element(ll.begin(), ll.end()), 3); -} - -TEST(PooledLinkedListTest, FrontBack) { - PooledLinkedListNodes<uint32_t> pool; - PooledLinkedList<uint32_t> ll(&pool); - - ll.push_back(1); - EXPECT_EQ(ll.front(), 1); - EXPECT_EQ(ll.back(), 1); - - ll.push_back(2); - EXPECT_EQ(ll.front(), 1); - EXPECT_EQ(ll.back(), 2); -} - -TEST(PooledLinkedListTest, PushBack) { - const std::vector<uint32_t> vec = {1, 2, 3, 4, 5, 6}; - - PooledLinkedListNodes<uint32_t> pool; - PooledLinkedList<uint32_t> ll(&pool); - - AppendVector(ll, vec); - EXPECT_EQ(vec, ToVector(ll)); -} - -TEST(PooledLinkedListTest, RemoveFirst) { - const std::vector<uint32_t> vec = {1, 2, 3, 4, 5, 6}; - - PooledLinkedListNodes<uint32_t> pool; - PooledLinkedList<uint32_t> ll(&pool); - - EXPECT_FALSE(ll.remove_first(0)); - AppendVector(ll, vec); - EXPECT_FALSE(ll.remove_first(0)); - - std::vector<uint32_t> tmp = vec; - while (!tmp.empty()) { - size_t mid = tmp.size() / 2; - uint32_t elt = tmp[mid]; - tmp.erase(tmp.begin() + mid); - - EXPECT_TRUE(ll.remove_first(elt)); - EXPECT_FALSE(ll.remove_first(elt)); - EXPECT_EQ(tmp, ToVector(ll)); - } - EXPECT_TRUE(ll.empty()); -} - -TEST(PooledLinkedListTest, RemoveFirst_Duplicates) { - const std::vector<uint32_t> vec = {3, 1, 2, 3, 3, 3, 3, 4, 3, 5, 3, 6, 3}; - - PooledLinkedListNodes<uint32_t> pool; - PooledLinkedList<uint32_t> ll(&pool); - AppendVector(ll, vec); - - std::vector<uint32_t> tmp = vec; - while (!tmp.empty()) { - size_t mid = tmp.size() / 2; - uint32_t elt = tmp[mid]; - tmp.erase(std::find(tmp.begin(), tmp.end(), elt)); - - EXPECT_TRUE(ll.remove_first(elt)); - EXPECT_EQ(tmp, ToVector(ll)); - } - EXPECT_TRUE(ll.empty()); -} - -TEST(PooledLinkedList, MoveTo) { - const std::vector<uint32_t> vec = {1, 2, 3, 4, 5, 6}; - - PooledLinkedListNodes<uint32_t> pool; - PooledLinkedList<uint32_t> ll1(&pool); - PooledLinkedList<uint32_t> ll2(&pool); - PooledLinkedList<uint32_t> ll3(&pool); - - AppendVector(ll1, vec); - AppendVector(ll2, vec); - AppendVector(ll3, vec); - EXPECT_EQ(pool.total_nodes(), vec.size() * 3); - EXPECT_EQ(pool.total_nodes(), vec.size() * 3); - EXPECT_EQ(pool.free_nodes(), 0); - - // Move two lists to the new pool - PooledLinkedListNodes<uint32_t> pool_new; - ll1.move_nodes(&pool_new); - ll2.move_nodes(&pool_new); - - // Moved nodes should belong to new pool - EXPECT_EQ(ll1.pool(), &pool_new); - EXPECT_EQ(ll2.pool(), &pool_new); - - // Old pool should be smaller & have free nodes. - EXPECT_EQ(pool.used_nodes(), vec.size()); - EXPECT_EQ(pool.free_nodes(), vec.size() * 2); - - // New pool should be sized exactly and no free nodes. - EXPECT_EQ(pool_new.total_nodes(), vec.size() * 2); - EXPECT_EQ(pool_new.used_nodes(), vec.size() * 2); - EXPECT_EQ(pool_new.free_nodes(), 0); - - // All lists should be preserved - EXPECT_EQ(ToVector(ll1), vec); - EXPECT_EQ(ToVector(ll2), vec); - EXPECT_EQ(ToVector(ll3), vec); -} - -} // namespace -} // namespace utils -} // namespace spvtools
diff --git a/test/val/CMakeLists.txt b/test/val/CMakeLists.txt index 65f2791..d02807a 100644 --- a/test/val/CMakeLists.txt +++ b/test/val/CMakeLists.txt
@@ -88,8 +88,9 @@ PCH_FILE pch_test_val ) -add_spvtools_unittest(TARGET val_stuvw +add_spvtools_unittest(TARGET val_rstuvw SRCS + val_ray_query.cpp val_small_type_uses_test.cpp val_ssa_test.cpp val_state_test.cpp
diff --git a/test/val/val_barriers_test.cpp b/test/val/val_barriers_test.cpp index 1178ca0..f27e467 100644 --- a/test/val/val_barriers_test.cpp +++ b/test/val/val_barriers_test.cpp
@@ -359,12 +359,25 @@ CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); - EXPECT_THAT(getDiagnosticString(), - AnyVUID("VUID-StandaloneSpirv-None-04638")); EXPECT_THAT( getDiagnosticString(), - HasSubstr("ControlBarrier: in Vulkan 1.0 environment Memory Scope is " - "limited to Device, Workgroup and Invocation")); + HasSubstr( + "ControlBarrier: in Vulkan 1.0 environment Memory Scope is can not " + "be Subgroup without SubgroupBallotKHR or SubgroupVoteKHR declared")); +} + +TEST_F(ValidateBarriers, OpControlBarrierVulkanMemoryScopeSubgroupVoteKHR) { + const std::string capabilities = R"( +OpCapability SubgroupVoteKHR +OpExtension "SPV_KHR_subgroup_vote" +)"; + const std::string body = R"( +OpControlBarrier %subgroup %subgroup %none +)"; + + CompileSuccessfully(GenerateShaderCode(body, capabilities), + SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); } TEST_F(ValidateBarriers, OpControlBarrierVulkan1p1MemoryScopeSubgroup) { @@ -386,8 +399,9 @@ EXPECT_THAT(getDiagnosticString(), AnyVUID("VUID-StandaloneSpirv-None-04638")); EXPECT_THAT(getDiagnosticString(), - HasSubstr("ControlBarrier: in Vulkan environment, Memory Scope " - "cannot be CrossDevice")); + HasSubstr("ControlBarrier: in Vulkan environment Memory Scope is " + "limited to Device, QueueFamily, Workgroup, " + "ShaderCallKHR, Subgroup, or Invocation")); } TEST_F(ValidateBarriers, @@ -751,12 +765,11 @@ CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); - EXPECT_THAT(getDiagnosticString(), - AnyVUID("VUID-StandaloneSpirv-None-04638")); EXPECT_THAT( getDiagnosticString(), - HasSubstr("MemoryBarrier: in Vulkan 1.0 environment Memory Scope is " - "limited to Device, Workgroup and Invocation")); + HasSubstr( + "MemoryBarrier: in Vulkan 1.0 environment Memory Scope is can not be " + "Subgroup without SubgroupBallotKHR or SubgroupVoteKHR declared")); } TEST_F(ValidateBarriers, OpMemoryBarrierVulkan1p1MemoryScopeSubgroup) {
diff --git a/test/val/val_builtins_test.cpp b/test/val/val_builtins_test.cpp index b76c163..2cbe9a8 100644 --- a/test/val/val_builtins_test.cpp +++ b/test/val/val_builtins_test.cpp
@@ -394,6 +394,11 @@ generator.before_types_ = "OpDecorate %built_in_var BuiltIn "; generator.before_types_ += built_in; generator.before_types_ += "\n"; + if ((0 == std::strcmp(storage_class, "Input")) && + (0 == std::strcmp(execution_model, "Fragment"))) { + // ensure any needed input types that might require Flat + generator.before_types_ += "OpDecorate %built_in_var Flat\n"; + } std::ostringstream after_types; if (InitializerRequired(storage_class)) {
diff --git a/test/val/val_cfg_test.cpp b/test/val/val_cfg_test.cpp index 7647746..ede51a9 100644 --- a/test/val/val_cfg_test.cpp +++ b/test/val/val_cfg_test.cpp
@@ -637,41 +637,6 @@ " %Main = OpFunction %void None %9\n")); } -TEST_P(ValidateCFG, HeaderDoesntDominatesMergeBad) { - bool is_shader = GetParam() == SpvCapabilityShader; - Block entry("entry"); - Block head("head", SpvOpBranchConditional); - Block f("f"); - Block merge("merge", SpvOpReturn); - - head.SetBody("%cond = OpSLessThan %boolt %one %two\n"); - - if (is_shader) head.AppendBody("OpSelectionMerge %merge None\n"); - - std::string str = GetDefaultHeader(GetParam()) + - nameOps("head", "merge", std::make_pair("func", "Main")) + - types_consts() + - "%func = OpFunction %voidt None %funct\n"; - - str += entry >> merge; - str += head >> std::vector<Block>({merge, f}); - str += f >> merge; - str += merge; - str += "OpFunctionEnd\n"; - - CompileSuccessfully(str); - if (is_shader) { - ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); - EXPECT_THAT( - getDiagnosticString(), - MatchesRegex("The selection construct with the selection header " - ".\\[%head\\] does not dominate the merge block " - ".\\[%merge\\]\n %merge = OpLabel\n")); - } else { - ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); - } -} - TEST_P(ValidateCFG, HeaderDoesntStrictlyDominateMergeBad) { // If a merge block is reachable, then it must be strictly dominated by // its header block. @@ -698,7 +663,8 @@ EXPECT_THAT( getDiagnosticString(), MatchesRegex("The selection construct with the selection header " - ".\\[%head\\] does not strictly dominate the merge block " + ".\\[%head\\] does not strictly structurally dominate the " + "merge block " ".\\[%head\\]\n %head = OpLabel\n")); } else { ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << str; @@ -907,16 +873,7 @@ TEST_P(ValidateCFG, UnreachableContinueUnreachableLoopInst) { CompileSuccessfully(GetUnreachableContinueUnreachableLoopInst(GetParam())); - if (GetParam() == SpvCapabilityShader) { - // Shader causes additional structured CFG checks that cause a failure. - ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); - EXPECT_THAT(getDiagnosticString(), - HasSubstr("Back-edges (1[%branch] -> 3[%target]) can only be " - "formed between a block and a loop header.")); - - } else { - ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); - } + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); } std::string GetUnreachableMergeWithComplexBody(SpvCapability cap) { @@ -1070,12 +1027,10 @@ std::string header = GetDefaultHeader(cap); Block entry("entry"); - Block foo("foo", SpvOpBranch); Block branch("branch", SpvOpBranch); Block merge("merge", SpvOpReturn); Block target("target", SpvOpBranch); - foo >> target; target >> branch; entry.AppendBody("%placeholder = OpVariable %intptrt Function\n"); @@ -1092,7 +1047,6 @@ str += branch >> std::vector<Block>({merge}); str += merge; str += target; - str += foo; str += "OpFunctionEnd\n"; return str; @@ -1156,6 +1110,7 @@ Block body("body", SpvOpBranchConditional); Block t("t", SpvOpReturn); Block f("f", SpvOpReturn); + Block pre_target("pre_target", SpvOpBranch); target >> branch; body.SetBody("%cond = OpSLessThan %boolt %one %two\n"); @@ -1163,10 +1118,10 @@ std::string str = header; if (cap == SpvCapabilityShader) { branch.AppendBody("OpLoopMerge %merge %target None\n"); - body.AppendBody("OpSelectionMerge %target None\n"); + body.AppendBody("OpSelectionMerge %pre_target None\n"); } - str += nameOps("branch", "merge", "target", "body", "t", "f", + str += nameOps("branch", "merge", "pre_target", "target", "body", "t", "f", std::make_pair("func", "Main")); str += types_consts(); str += "%func = OpFunction %voidt None %funct\n"; @@ -1176,6 +1131,7 @@ str += t; str += f; str += merge; + str += pre_target >> target; str += target; str += "OpFunctionEnd\n"; @@ -1296,9 +1252,10 @@ loop2.SetBody("OpLoopMerge %loop2_merge %loop2 None\n"); } - std::string str = GetDefaultHeader(GetParam()) + - nameOps("loop2", "loop2_merge") + types_consts() + - "%func = OpFunction %voidt None %funct\n"; + std::string str = + GetDefaultHeader(GetParam()) + + nameOps("loop1", "loop1_cont_break_block", "loop2", "loop2_merge") + + types_consts() + "%func = OpFunction %voidt None %funct\n"; str += entry >> loop1; str += loop1 >> loop1_cont_break_block; @@ -1389,11 +1346,13 @@ CompileSuccessfully(str); if (GetParam() == SpvCapabilityShader) { ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); - EXPECT_THAT(getDiagnosticString(), - MatchesRegex("The continue construct with the continue target " - ".\\[%loop1_cont\\] is not post dominated by the " - "back-edge block .\\[%be_block\\]\n" - " %be_block = OpLabel\n")); + EXPECT_THAT( + getDiagnosticString(), + MatchesRegex( + "The continue construct with the continue target " + ".\\[%loop1_cont\\] is not structurally post dominated by the " + "back-edge block .\\[%be_block\\]\n" + " %be_block = OpLabel\n")); } else { ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); } @@ -1529,10 +1488,11 @@ if (is_shader) { ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); EXPECT_THAT(getDiagnosticString(), - MatchesRegex("The continue construct with the continue target " - ".\\[%cheader\\] is not post dominated by the " - "back-edge block .\\[%be_block\\]\n" - " %be_block = OpLabel\n")); + MatchesRegex( + "The continue construct with the continue target " + ".\\[%cheader\\] is not structurally post dominated by the " + "back-edge block .\\[%be_block\\]\n" + " %be_block = OpLabel\n")); } else { ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); } @@ -1561,11 +1521,12 @@ CompileSuccessfully(str); if (is_shader) { ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); - EXPECT_THAT(getDiagnosticString(), - MatchesRegex("The continue construct with the continue target " - ".\\[%loop\\] is not post dominated by the " - "back-edge block .\\[%cont\\]\n" - " %cont = OpLabel\n")) + EXPECT_THAT( + getDiagnosticString(), + MatchesRegex("The continue construct with the continue target " + ".\\[%loop\\] is not structurally post dominated by the " + "back-edge block .\\[%cont\\]\n" + " %cont = OpLabel\n")) << str; } else { ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); @@ -1597,11 +1558,12 @@ CompileSuccessfully(str); if (is_shader) { ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); - EXPECT_THAT(getDiagnosticString(), - MatchesRegex("The continue construct with the continue target " - ".\\[%loop\\] is not post dominated by the " - "back-edge block .\\[%cont\\]\n" - " %cont = OpLabel\n")); + EXPECT_THAT( + getDiagnosticString(), + MatchesRegex("The continue construct with the continue target " + ".\\[%loop\\] is not structurally post dominated by the " + "back-edge block .\\[%cont\\]\n" + " %cont = OpLabel\n")); } else { ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); } @@ -1824,40 +1786,6 @@ << str << getDiagnosticString(); } -TEST_P(ValidateCFG, SingleLatchBlockHeaderContinueTargetIsItselfGood) { - // This test case ensures we don't count a Continue Target from a loop - // header to itself as a self-loop when computing back edges. - // Also, it detects that there is an edge from %latch to the pseudo-exit - // node, rather than from %loop. In particular, it detects that we - // have used the *reverse* textual order of blocks when computing - // predecessor traversal roots. - bool is_shader = GetParam() == SpvCapabilityShader; - Block entry("entry"); - Block loop("loop"); - Block latch("latch"); - Block merge("merge", SpvOpReturn); - - entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); - if (is_shader) { - loop.SetBody("OpLoopMerge %merge %loop None\n"); - } - - std::string str = GetDefaultHeader(GetParam()) + - nameOps("entry", "loop", "latch", "merge") + - types_consts() + - "%func = OpFunction %voidt None %funct\n"; - - str += entry >> loop; - str += loop >> latch; - str += latch >> loop; - str += merge; - str += "OpFunctionEnd"; - - CompileSuccessfully(str); - EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) - << str << getDiagnosticString(); -} - // Unit test to check the case where a basic block is the entry block of 2 // different constructs. In this case, the basic block is the entry block of a // continue construct as well as a selection construct. See issue# 517 for more @@ -2872,8 +2800,8 @@ CompileSuccessfully(text); EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); EXPECT_THAT(getDiagnosticString(), - HasSubstr("block <ID> 9 branches to the loop construct, but not " - "to the loop header <ID> 7")); + HasSubstr("Back-edges (10[%10] -> 9[%9]) can only be formed " + "between a block and a loop header")); } TEST_F(ValidateCFG, LoopMergeMergeBlockNotLabel) { @@ -3275,9 +3203,10 @@ CompileSuccessfully(text); EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); - EXPECT_THAT(getDiagnosticString(), - HasSubstr("block <ID> 13[%13] exits the selection headed by <ID> " - "9[%9], but not via a structured exit")); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("The continue construct with the continue target 9[%9] is not " + "structurally post dominated by the back-edge block 13[%13]")); } TEST_F(ValidateCFG, BreakFromSwitch) { @@ -4285,9 +4214,11 @@ CompileSuccessfully(text); EXPECT_NE(SPV_SUCCESS, ValidateInstructions()); - EXPECT_THAT(getDiagnosticString(), - HasSubstr("The selection construct with the selection header " - "8[%8] does not dominate the merge block 10[%10]\n")); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "The selection construct with the selection header " + "8[%8] does not structurally dominate the merge block 10[%10]\n")); } TEST_F(ValidateCFG, UnreachableIsStaticallyReachable) { @@ -4624,6 +4555,36 @@ "must be different labels")); } +TEST_F(ValidateCFG, BadBackEdgeUnreachableContinue) { + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%3 = OpFunction %1 None %2 +%4 = OpLabel +OpBranch %5 +%5 = OpLabel +OpLoopMerge %6 %7 None +OpBranch %8 +%8 = OpLabel +OpBranch %5 +%7 = OpLabel +OpUnreachable +%6 = OpLabel +OpUnreachable +OpFunctionEnd +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("The continue construct with the continue target 7[%7] " + "does not structurally dominate the back-edge block 8[%8]")); +} + } // namespace } // namespace val } // namespace spvtools
diff --git a/test/val/val_conversion_test.cpp b/test/val/val_conversion_test.cpp index 94bd27d..f6f37b3 100644 --- a/test/val/val_conversion_test.cpp +++ b/test/val/val_conversion_test.cpp
@@ -1641,6 +1641,131 @@ "integer type to have a 64-bit width for Vulkan environment.")); } +TEST_F(ValidateConversion, ConvertUToAccelerationStructureU32Vec2) { + const std::string extensions = R"( +OpCapability RayQueryKHR +OpExtension "SPV_KHR_ray_query" +)"; + const std::string types = R"( +%u32vec2ptr_func = OpTypePointer Function %u32vec2 +%typeAS = OpTypeAccelerationStructureKHR +)"; + const std::string body = R"( +%asHandle = OpVariable %u32vec2ptr_func Function +%load = OpLoad %u32vec2 %asHandle +%val = OpConvertUToAccelerationStructureKHR %typeAS %load +)"; + + CompileSuccessfully(GenerateShaderCode(body, extensions, "", types).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateConversion, ConvertUToAccelerationStructureSuccessU64) { + const std::string extensions = R"( +OpCapability RayQueryKHR +OpExtension "SPV_KHR_ray_query" +)"; + const std::string types = R"( +%u64_func = OpTypePointer Function %u64 +%typeAS = OpTypeAccelerationStructureKHR +)"; + const std::string body = R"( +%asHandle = OpVariable %u64_func Function +%load = OpLoad %u64 %asHandle +%val = OpConvertUToAccelerationStructureKHR %typeAS %load +)"; + + CompileSuccessfully(GenerateShaderCode(body, extensions, "", types).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateConversion, ConvertUToAccelerationStructureResult) { + const std::string extensions = R"( +OpCapability RayQueryKHR +OpExtension "SPV_KHR_ray_query" +)"; + const std::string types = R"( +%u32vec2ptr_func = OpTypePointer Function %u32vec2 +%typeRQ = OpTypeRayQueryKHR +)"; + const std::string body = R"( +%asHandle = OpVariable %u32vec2ptr_func Function +%load = OpLoad %u32vec2 %asHandle +%val = OpConvertUToAccelerationStructureKHR %typeRQ %load +)"; + + CompileSuccessfully(GenerateShaderCode(body, extensions, "", types).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to be a Acceleration Structure")); +} + +TEST_F(ValidateConversion, ConvertUToAccelerationStructureU32) { + const std::string extensions = R"( +OpCapability RayQueryKHR +OpExtension "SPV_KHR_ray_query" +)"; + const std::string types = R"( +%u32ptr_func = OpTypePointer Function %u32 +%typeAS = OpTypeAccelerationStructureKHR +)"; + const std::string body = R"( +%asHandle = OpVariable %u32ptr_func Function +%load = OpLoad %u32 %asHandle +%val = OpConvertUToAccelerationStructureKHR %typeAS %load +)"; + + CompileSuccessfully(GenerateShaderCode(body, extensions, "", types).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected 64-bit uint scalar or 2-component 32-bit " + "uint vector as input")); +} + +TEST_F(ValidateConversion, ConvertUToAccelerationStructureS64) { + const std::string extensions = R"( +OpCapability RayQueryKHR +OpExtension "SPV_KHR_ray_query" +)"; + const std::string types = R"( +%s64ptr_func = OpTypePointer Function %s64 +%typeAS = OpTypeAccelerationStructureKHR +)"; + const std::string body = R"( +%asHandle = OpVariable %s64ptr_func Function +%load = OpLoad %s64 %asHandle +%val = OpConvertUToAccelerationStructureKHR %typeAS %load +)"; + + CompileSuccessfully(GenerateShaderCode(body, extensions, "", types).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected 64-bit uint scalar or 2-component 32-bit " + "uint vector as input")); +} + +TEST_F(ValidateConversion, ConvertUToAccelerationStructureS32Vec2) { + const std::string extensions = R"( +OpCapability RayQueryKHR +OpExtension "SPV_KHR_ray_query" +)"; + const std::string types = R"( +%s32vec2ptr_func = OpTypePointer Function %s32vec2 +%typeAS = OpTypeAccelerationStructureKHR +)"; + const std::string body = R"( +%asHandle = OpVariable %s32vec2ptr_func Function +%load = OpLoad %s32vec2 %asHandle +%val = OpConvertUToAccelerationStructureKHR %typeAS %load +)"; + + CompileSuccessfully(GenerateShaderCode(body, extensions, "", types).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected 64-bit uint scalar or 2-component 32-bit " + "uint vector as input")); +} + using ValidateSmallConversions = spvtest::ValidateBase<std::string>; CodeGenerator GetSmallConversionsCodeGenerator() {
diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp index e7ecb61..77526bf 100644 --- a/test/val/val_decoration_test.cpp +++ b/test/val/val_decoration_test.cpp
@@ -42,6 +42,7 @@ }; using ValidateDecorations = spvtest::ValidateBase<bool>; +using ValidateDecorationString = spvtest::ValidateBase<std::string>; using ValidateVulkanCombineDecorationResult = spvtest::ValidateBase<std::tuple<const char*, const char*, TestResult>>; @@ -50,20 +51,20 @@ OpCapability Shader OpCapability Linkage OpMemoryModel Logical GLSL450 - OpDecorate %1 ArrayStride 4 - OpDecorate %1 RelaxedPrecision + OpDecorate %1 Location 4 + OpDecorate %1 Centroid %2 = OpTypeFloat 32 - %1 = OpTypeRuntimeArray %2 + %3 = OpTypePointer Output %2 + %1 = OpVariable %3 Output ; Since %1 is used first in Decoration, it gets id 1. )"; const uint32_t id = 1; CompileSuccessfully(spirv); EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); // Must have 2 decorations. - EXPECT_THAT( - vstate_->id_decorations(id), - Eq(std::vector<Decoration>{Decoration(SpvDecorationArrayStride, {4}), - Decoration(SpvDecorationRelaxedPrecision)})); + EXPECT_THAT(vstate_->id_decorations(id), + Eq(std::set<Decoration>{Decoration(SpvDecorationLocation, {4}), + Decoration(SpvDecorationCentroid)})); } TEST_F(ValidateDecorations, ValidateOpMemberDecorateRegistration) { @@ -88,15 +89,15 @@ const uint32_t arr_id = 1; EXPECT_THAT( vstate_->id_decorations(arr_id), - Eq(std::vector<Decoration>{Decoration(SpvDecorationArrayStride, {4})})); + Eq(std::set<Decoration>{Decoration(SpvDecorationArrayStride, {4})})); // The struct must have 3 decorations. const uint32_t struct_id = 2; EXPECT_THAT( vstate_->id_decorations(struct_id), - Eq(std::vector<Decoration>{Decoration(SpvDecorationNonReadable, {}, 2), - Decoration(SpvDecorationOffset, {2}, 2), - Decoration(SpvDecorationBufferBlock)})); + Eq(std::set<Decoration>{Decoration(SpvDecorationNonReadable, {}, 2), + Decoration(SpvDecorationOffset, {2}, 2), + Decoration(SpvDecorationBufferBlock)})); } TEST_F(ValidateDecorations, ValidateOpMemberDecorateOutOfBound) { @@ -151,9 +152,9 @@ // Decoration group has 3 decorations. auto expected_decorations = - std::vector<Decoration>{Decoration(SpvDecorationDescriptorSet, {0}), - Decoration(SpvDecorationRelaxedPrecision), - Decoration(SpvDecorationRestrict)}; + std::set<Decoration>{Decoration(SpvDecorationDescriptorSet, {0}), + Decoration(SpvDecorationRelaxedPrecision), + Decoration(SpvDecorationRestrict)}; // Decoration group is applied to id 1, 2, 3, and 4. Note that id 1 (which is // the decoration group id) also has all the decorations. @@ -181,7 +182,7 @@ EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); // Decoration group has 1 decoration. auto expected_decorations = - std::vector<Decoration>{Decoration(SpvDecorationOffset, {3}, 3)}; + std::set<Decoration>{Decoration(SpvDecorationOffset, {3}, 3)}; // Decoration group is applied to id 2, 3, and 4. EXPECT_THAT(vstate_->id_decorations(2), Eq(expected_decorations)); @@ -8022,6 +8023,7 @@ OpDecorate %block Block OpMemberDecorate %block 0 Offset 0 OpMemberDecorate %block 0 MatrixStride 3 +OpMemberDecorate %block 0 ColMajor OpDecorate %var DescriptorSet 0 OpDecorate %var Binding 0 %void = OpTypeVoid @@ -8058,6 +8060,7 @@ OpDecorate %block Block OpMemberDecorate %block 0 Offset 0 OpMemberDecorate %block 0 MatrixStride 3 +OpMemberDecorate %block 0 ColMajor OpDecorate %var DescriptorSet 0 OpDecorate %var Binding 0 %void = OpTypeVoid @@ -8093,6 +8096,7 @@ OpDecorate %block Block OpMemberDecorate %block 0 Offset 0 OpMemberDecorate %block 0 MatrixStride 3 +OpMemberDecorate %block 0 ColMajor OpDecorate %var DescriptorSet 0 OpDecorate %var Binding 0 %void = OpTypeVoid @@ -8129,6 +8133,7 @@ OpDecorate %block Block OpMemberDecorate %block 0 Offset 0 OpMemberDecorate %block 0 MatrixStride 3 +OpMemberDecorate %block 0 RowMajor OpDecorate %var DescriptorSet 0 OpDecorate %var Binding 0 %void = OpTypeVoid @@ -8363,6 +8368,642 @@ HasSubstr("PerVertexKHR must be declared as arrays")); } +TEST_F(ValidateDecorations, RelaxedPrecisionDecorationOnNumericTypeBad) { + const spv_target_env env = SPV_ENV_VULKAN_1_0; + std::string spirv = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + OpDecorate %float RelaxedPrecision + %void = OpTypeVoid + %voidfn = OpTypeFunction %void + %float = OpTypeFloat 32 + %main = OpFunction %void None %voidfn + %label = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(spirv, env); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState(env)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("RelaxPrecision decoration cannot be applied to a type")); +} + +TEST_F(ValidateDecorations, RelaxedPrecisionDecorationOnStructMember) { + const spv_target_env env = SPV_ENV_VULKAN_1_0; + std::string spirv = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + OpMemberDecorate %struct 0 RelaxedPrecision + %void = OpTypeVoid + %voidfn = OpTypeFunction %void + %float = OpTypeFloat 32 + %struct = OpTypeStruct %float + %main = OpFunction %void None %voidfn + %label = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(spirv, env); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(env)); +} + +TEST_F(ValidateDecorations, VulkanFlatMultipleInterfaceGood) { + std::string spirv = R"( + OpCapability Shader + OpCapability Geometry + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %layer %gl_Layer + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpDecorate %layer Location 0 + OpDecorate %gl_Layer Flat + OpDecorate %gl_Layer BuiltIn Layer + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Output_int = OpTypePointer Output %int + %layer = OpVariable %_ptr_Output_int Output +%_ptr_Input_int = OpTypePointer Input %int + %gl_Layer = OpVariable %_ptr_Input_int Input + %main = OpFunction %void None %3 + %5 = OpLabel + %11 = OpLoad %int %gl_Layer + OpStore %layer %11 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_SUCCESS, + ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0)); +} + +TEST_F(ValidateDecorations, VulkanFlatMultipleInterfaceBad) { + std::string spirv = R"( + OpCapability Shader + OpCapability Geometry + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %layer %gl_Layer + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpDecorate %layer Location 0 + OpDecorate %gl_Layer BuiltIn Layer + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Output_int = OpTypePointer Output %int + %layer = OpVariable %_ptr_Output_int Output +%_ptr_Input_int = OpTypePointer Input %int + %gl_Layer = OpVariable %_ptr_Input_int Input + %main = OpFunction %void None %3 + %5 = OpLabel + %11 = OpLoad %int %gl_Layer + OpStore %layer %11 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, + ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-Flat-04744")); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Fragment OpEntryPoint operand 4 with Input interfaces with integer " + "or float type must have a Flat decoration for Entry Point id 2.")); +} + +TEST_F(ValidateDecorations, VulkanNoFlatFloat32) { + std::string spirv = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %in + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpDecorate %in Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%_ptr_Input_float = OpTypePointer Input %float + %in = OpVariable %_ptr_Input_float Input + %main = OpFunction %void None %3 + %5 = OpLabel + %b = OpVariable %_ptr_Function_float Function + %11 = OpLoad %float %in + OpStore %b %11 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_SUCCESS, + ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0)); +} + +TEST_F(ValidateDecorations, VulkanNoFlatFloat64) { + std::string spirv = R"( + OpCapability Shader + OpCapability Float64 + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %in + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpDecorate %in Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %double = OpTypeFloat 64 +%_ptr_Function_double = OpTypePointer Function %double +%_ptr_Input_double = OpTypePointer Input %double + %in = OpVariable %_ptr_Input_double Input + %main = OpFunction %void None %3 + %5 = OpLabel + %b = OpVariable %_ptr_Function_double Function + %11 = OpLoad %double %in + OpStore %b %11 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, + ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-Flat-04744")); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Fragment OpEntryPoint operand 3 with Input interfaces with integer " + "or float type must have a Flat decoration for Entry Point id 2.")); +} + +TEST_F(ValidateDecorations, VulkanNoFlatVectorFloat64) { + std::string spirv = R"( + OpCapability Shader + OpCapability Float64 + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %in + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpDecorate %in Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %double = OpTypeFloat 64 + %v2double = OpTypeVector %double 2 +%_ptr_Function_v2double = OpTypePointer Function %v2double +%_ptr_Input_v2double = OpTypePointer Input %v2double + %in = OpVariable %_ptr_Input_v2double Input + %main = OpFunction %void None %3 + %5 = OpLabel + %b = OpVariable %_ptr_Function_v2double Function + %11 = OpLoad %v2double %in + OpStore %b %11 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_SUCCESS, + ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0)); +} + +TEST_F(ValidateDecorations, VulkanNoFlatIntVector) { + std::string spirv = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %in + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpDecorate %in Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %v2int = OpTypeVector %int 2 +%_ptr_Function_v2int = OpTypePointer Function %v2int +%_ptr_Input_v2int = OpTypePointer Input %v2int + %in = OpVariable %_ptr_Input_v2int Input + %main = OpFunction %void None %3 + %5 = OpLabel + %b = OpVariable %_ptr_Function_v2int Function + %12 = OpLoad %v2int %in + OpStore %b %12 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, + ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-Flat-04744")); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Fragment OpEntryPoint operand 3 with Input interfaces with integer " + "or float type must have a Flat decoration for Entry Point id 2.")); +} + +TEST_P(ValidateDecorationString, VulkanOutputInvalidInterface) { + const std::string decoration = GetParam(); + std::stringstream ss; + ss << R"( + OpCapability Shader + OpCapability SampleRateShading + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %out + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpDecorate %out )" + << decoration << R"( + OpDecorate %out Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Output_int = OpTypePointer Output %int + %out = OpVariable %_ptr_Output_int Output + %int_1 = OpConstant %int 1 + %main = OpFunction %void None %3 + %5 = OpLabel + OpStore %out %int_1 + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(ss.str(), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-Flat-06201")); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpEntryPoint interfaces variable must not be fragment execution " + "model with an output storage class for Entry Point id 2.")); +} + +TEST_P(ValidateDecorationString, VulkanVertexInputInvalidInterface) { + const std::string decoration = GetParam(); + std::stringstream ss; + ss << R"( + OpCapability Shader + OpCapability SampleRateShading + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" %out %in + OpSource GLSL 450 + OpDecorate %in )" + << decoration << R"( + OpDecorate %out Location 0 + OpDecorate %in Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Output_int = OpTypePointer Output %int + %out = OpVariable %_ptr_Output_int Output +%_ptr_Input_int = OpTypePointer Input %int + %in = OpVariable %_ptr_Input_int Input + %main = OpFunction %void None %3 + %5 = OpLabel + %11 = OpLoad %int %in + OpStore %out %11 + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(ss.str(), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-Flat-06202")); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpEntryPoint interfaces variable must not be vertex execution " + "model with an input storage class for Entry Point id 2.")); +} + +INSTANTIATE_TEST_SUITE_P(FragmentInputInterface, ValidateDecorationString, + ::testing::Values("Flat", "NoPerspective", "Sample", + "Centroid")); + +TEST_F(ValidateDecorations, NVBindlessSamplerArrayInBlock) { + const std::string spirv = R"( + OpCapability Shader + OpCapability BindlessTextureNV + OpExtension "SPV_NV_bindless_texture" + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpSamplerImageAddressingModeNV 64 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpName %main "main" + OpName %UBO "UBO" + OpMemberName %UBO 0 "uboSampler" + OpName %_ "" + OpDecorate %array ArrayStride 16 + OpMemberDecorate %UBO 0 Offset 0 + OpDecorate %UBO Block + OpDecorate %_ DescriptorSet 0 + OpDecorate %_ Binding 2 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %7 = OpTypeImage %float 2D 0 0 0 1 Unknown + %8 = OpTypeSampledImage %7 + %uint = OpTypeInt 32 0 + %uint_3 = OpConstant %uint 3 + %array = OpTypeArray %8 %uint_3 + %UBO = OpTypeStruct %array + %pointer = OpTypePointer Uniform %UBO + %_ = OpVariable %pointer Uniform + %main = OpFunction %void None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); +} + +TEST_F(ValidateDecorations, Std140ColMajorMat2x2) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpMemberDecorate %block 0 ColMajor +OpMemberDecorate %block 0 MatrixStride 8 +OpDecorate %var DescriptorSet 0 +OpDecorate %var Binding 0 +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%float = OpTypeFloat 32 +%float2 = OpTypeVector %float 2 +%matrix = OpTypeMatrix %float2 2 +%block = OpTypeStruct %matrix +%ptr_block = OpTypePointer Uniform %block +%var = OpVariable %ptr_block Uniform +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "member 0 is a matrix with stride 8 not satisfying alignment to 16")); +} + +TEST_F(ValidateDecorations, Std140RowMajorMat2x2) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpMemberDecorate %block 0 RowMajor +OpMemberDecorate %block 0 MatrixStride 8 +OpDecorate %var DescriptorSet 0 +OpDecorate %var Binding 0 +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%float = OpTypeFloat 32 +%float2 = OpTypeVector %float 2 +%matrix = OpTypeMatrix %float2 2 +%block = OpTypeStruct %matrix +%ptr_block = OpTypePointer Uniform %block +%var = OpVariable %ptr_block Uniform +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "member 0 is a matrix with stride 8 not satisfying alignment to 16")); +} + +TEST_F(ValidateDecorations, Std140ColMajorMat4x2) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpMemberDecorate %block 0 ColMajor +OpMemberDecorate %block 0 MatrixStride 8 +OpDecorate %var DescriptorSet 0 +OpDecorate %var Binding 0 +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%float = OpTypeFloat 32 +%float2 = OpTypeVector %float 2 +%matrix = OpTypeMatrix %float2 4 +%block = OpTypeStruct %matrix +%ptr_block = OpTypePointer Uniform %block +%var = OpVariable %ptr_block Uniform +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "member 0 is a matrix with stride 8 not satisfying alignment to 16")); +} + +TEST_F(ValidateDecorations, Std140ColMajorMat2x3) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpMemberDecorate %block 0 ColMajor +OpMemberDecorate %block 0 MatrixStride 12 +OpDecorate %var DescriptorSet 0 +OpDecorate %var Binding 0 +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%float = OpTypeFloat 32 +%float3 = OpTypeVector %float 3 +%matrix = OpTypeMatrix %float3 2 +%block = OpTypeStruct %matrix +%ptr_block = OpTypePointer Uniform %block +%var = OpVariable %ptr_block Uniform +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("member 0 is a matrix with stride 12 not satisfying " + "alignment to 16")); +} + +TEST_F(ValidateDecorations, MatrixMissingMajornessUniform) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpMemberDecorate %block 0 MatrixStride 16 +OpDecorate %var DescriptorSet 0 +OpDecorate %var Binding 0 +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%float = OpTypeFloat 32 +%float2 = OpTypeVector %float 2 +%matrix = OpTypeMatrix %float2 2 +%block = OpTypeStruct %matrix +%ptr_block = OpTypePointer Uniform %block +%var = OpVariable %ptr_block Uniform +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "must be explicitly laid out with RowMajor or ColMajor decorations")); +} + +TEST_F(ValidateDecorations, MatrixMissingMajornessStorageBuffer) { + const std::string spirv = R"( +OpCapability Shader +OpExtension "SPV_KHR_storage_buffer_storage_class" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpMemberDecorate %block 0 MatrixStride 16 +OpDecorate %var DescriptorSet 0 +OpDecorate %var Binding 0 +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%float = OpTypeFloat 32 +%float2 = OpTypeVector %float 2 +%matrix = OpTypeMatrix %float2 2 +%block = OpTypeStruct %matrix +%ptr_block = OpTypePointer StorageBuffer %block +%var = OpVariable %ptr_block StorageBuffer +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "must be explicitly laid out with RowMajor or ColMajor decorations")); +} + +TEST_F(ValidateDecorations, MatrixMissingMajornessPushConstant) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpMemberDecorate %block 0 MatrixStride 16 +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%float = OpTypeFloat 32 +%float2 = OpTypeVector %float 2 +%matrix = OpTypeMatrix %float2 2 +%block = OpTypeStruct %matrix +%ptr_block = OpTypePointer PushConstant %block +%var = OpVariable %ptr_block PushConstant +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "must be explicitly laid out with RowMajor or ColMajor decorations")); +} + +TEST_F(ValidateDecorations, StructWithRowAndColMajor) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpMemberDecorate %block 0 MatrixStride 16 +OpMemberDecorate %block 0 ColMajor +OpMemberDecorate %block 1 Offset 32 +OpMemberDecorate %block 1 MatrixStride 16 +OpMemberDecorate %block 1 RowMajor +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%float = OpTypeFloat 32 +%float2 = OpTypeVector %float 2 +%matrix = OpTypeMatrix %float2 2 +%block = OpTypeStruct %matrix %matrix +%ptr_block = OpTypePointer PushConstant %block +%var = OpVariable %ptr_block PushConstant +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); +} + } // namespace } // namespace val } // namespace spvtools
diff --git a/test/val/val_id_test.cpp b/test/val/val_id_test.cpp index 69257a5..b7e5042 100644 --- a/test/val/val_id_test.cpp +++ b/test/val/val_id_test.cpp
@@ -6570,6 +6570,35 @@ "Operand 3[%_ptr_Uniform__struct_2] requires a previous definition")); } +TEST_F(ValidateIdWithMessage, NVBindlessSamplerInStruct) { + std::string spirv = R"( + OpCapability Shader + OpCapability BindlessTextureNV + OpExtension "SPV_NV_bindless_texture" + OpMemoryModel Logical GLSL450 + OpSamplerImageAddressingModeNV 64 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %7 = OpTypeImage %float 2D 0 0 0 1 Unknown + %8 = OpTypeSampledImage %7 + %9 = OpTypeImage %float 2D 0 0 0 2 Rgba32f + %10 = OpTypeSampler + %UBO = OpTypeStruct %8 %9 %10 +%_ptr_Uniform_UBO = OpTypePointer Uniform %UBO + %_ = OpVariable %_ptr_Uniform_UBO Uniform + %main = OpFunction %void None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); +} + } // namespace } // namespace val } // namespace spvtools
diff --git a/test/val/val_image_test.cpp b/test/val/val_image_test.cpp index 76af29c..c4de60a 100644 --- a/test/val/val_image_test.cpp +++ b/test/val/val_image_test.cpp
@@ -6248,6 +6248,138 @@ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_6)); } +TEST_F(ValidateImage, NVBindlessSamplerBuiltins) { + const std::string text = R"( + OpCapability Shader + OpCapability Int64 + OpCapability Image1D + OpCapability BindlessTextureNV + OpExtension "SPV_NV_bindless_texture" + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpSamplerImageAddressingModeNV 64 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpName %main "main" + OpName %s2D "s2D" + OpName %textureHandle "textureHandle" + OpName %i1D "i1D" + OpName %s "s" + OpName %temp "temp" + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %7 = OpTypeImage %float 2D 0 0 0 1 Unknown + %8 = OpTypeSampledImage %7 +%_ptr_Function_8 = OpTypePointer Function %8 + %ulong = OpTypeInt 64 0 +%_ptr_Private_ulong = OpTypePointer Private %ulong +%textureHandle = OpVariable %_ptr_Private_ulong Private + %16 = OpTypeImage %float 1D 0 0 0 2 Rgba32f +%_ptr_Function_16 = OpTypePointer Function %16 + %21 = OpTypeSampler +%_ptr_Function_21 = OpTypePointer Function %21 +%_ptr_Function_ulong = OpTypePointer Function %ulong + %main = OpFunction %void None %3 + %5 = OpLabel + %s2D = OpVariable %_ptr_Function_8 Function + %i1D = OpVariable %_ptr_Function_16 Function + %s = OpVariable %_ptr_Function_21 Function + %temp = OpVariable %_ptr_Function_ulong Function + %14 = OpLoad %ulong %textureHandle + %15 = OpConvertUToSampledImageNV %8 %14 + OpStore %s2D %15 + %19 = OpLoad %ulong %textureHandle + %20 = OpConvertUToImageNV %16 %19 + OpStore %i1D %20 + %24 = OpLoad %ulong %textureHandle + %25 = OpConvertUToSamplerNV %21 %24 + OpStore %s %25 + %28 = OpLoad %8 %s2D + %29 = OpConvertSampledImageToUNV %ulong %28 + OpStore %temp %29 + %30 = OpLoad %16 %i1D + %31 = OpConvertImageToUNV %ulong %30 + OpStore %temp %31 + %32 = OpLoad %21 %s + %33 = OpConvertSamplerToUNV %ulong %32 + OpStore %temp %33 + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); +} + +TEST_F(ValidateImage, NVBindlessAddressingMode64) { + std::string text = R"( + OpCapability Shader + OpCapability BindlessTextureNV + OpExtension "SPV_NV_bindless_texture" + OpMemoryModel Logical GLSL450 + OpSamplerImageAddressingModeNV 64 + OpEntryPoint GLCompute %func "main" +%voidt = OpTypeVoid +%uintt = OpTypeInt 32 0 +%funct = OpTypeFunction %voidt +%func = OpFunction %voidt None %funct +%entry = OpLabel +%udef = OpUndef %uintt + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); +} + +TEST_F(ValidateImage, NVBindlessAddressingMode32) { + std::string text = R"( + OpCapability Shader + OpCapability BindlessTextureNV + OpExtension "SPV_NV_bindless_texture" + OpMemoryModel Logical GLSL450 + OpSamplerImageAddressingModeNV 32 + OpEntryPoint GLCompute %func "main" +%voidt = OpTypeVoid +%uintt = OpTypeInt 32 0 +%funct = OpTypeFunction %voidt +%func = OpFunction %voidt None %funct +%entry = OpLabel +%udef = OpUndef %uintt + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); +} + +TEST_F(ValidateImage, NVBindlessInvalidAddressingMode) { + std::string text = R"( + OpCapability Shader + OpCapability BindlessTextureNV + OpExtension "SPV_NV_bindless_texture" + OpMemoryModel Logical GLSL450 + OpSamplerImageAddressingModeNV 0 + OpEntryPoint GLCompute %func "main" +%voidt = OpTypeVoid +%uintt = OpTypeInt 32 0 +%funct = OpTypeFunction %voidt +%func = OpFunction %voidt None %funct +%entry = OpLabel +%udef = OpUndef %uintt + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpSamplerImageAddressingModeNV bitwidth should be 64 or 32")); +} + } // namespace } // namespace val } // namespace spvtools
diff --git a/test/val/val_interfaces_test.cpp b/test/val/val_interfaces_test.cpp index bec8d02..d9c3748 100644 --- a/test/val/val_interfaces_test.cpp +++ b/test/val/val_interfaces_test.cpp
@@ -711,7 +711,9 @@ OpEntryPoint Fragment %main "main" %var1 %var2 OpExecutionMode %main OriginUpperLeft OpDecorate %var1 Location 0 +OpDecorate %var1 Flat OpDecorate %var2 Location 1 +OpDecorate %var2 Flat %void = OpTypeVoid %void_fn = OpTypeFunction %void %float = OpTypeFloat 32
diff --git a/test/val/val_layout_test.cpp b/test/val/val_layout_test.cpp index 7ebd7c0..8cca96f 100644 --- a/test/val/val_layout_test.cpp +++ b/test/val/val_layout_test.cpp
@@ -667,6 +667,98 @@ // TODO(umar): Test optional instructions +TEST_F(ValidateLayout, ValidNVBindlessTexturelayout) { + std::string str = R"( + OpCapability Shader + OpCapability BindlessTextureNV + OpExtension "SPV_NV_bindless_texture" + OpMemoryModel Logical GLSL450 + OpSamplerImageAddressingModeNV 64 + OpEntryPoint GLCompute %func "main" +%voidt = OpTypeVoid +%uintt = OpTypeInt 32 0 +%funct = OpTypeFunction %voidt +%func = OpFunction %voidt None %funct +%entry = OpLabel +%udef = OpUndef %uintt + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateLayout, InvalidValidNVBindlessTexturelayout) { + std::string str = R"( + OpCapability Shader + OpCapability BindlessTextureNV + OpExtension "SPV_NV_bindless_texture" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %func "main" + OpSamplerImageAddressingModeNV 64 +%voidt = OpTypeVoid +%uintt = OpTypeInt 32 0 +%funct = OpTypeFunction %voidt +%func = OpFunction %voidt None %funct +%entry = OpLabel +%udef = OpUndef %uintt + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "SamplerImageAddressingModeNV is in an invalid layout section")); +} + +TEST_F(ValidateLayout, MissingNVBindlessAddressModeFromLayout) { + std::string str = R"( + OpCapability Shader + OpCapability BindlessTextureNV + OpExtension "SPV_NV_bindless_texture" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %func "main" +%voidt = OpTypeVoid +%uintt = OpTypeInt 32 0 +%funct = OpTypeFunction %voidt +%func = OpFunction %voidt None %funct +%entry = OpLabel +%udef = OpUndef %uintt + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Missing required OpSamplerImageAddressingModeNV instruction")); +} + +TEST_F(ValidateLayout, NVBindlessAddressModeFromLayoutSpecifiedTwice) { + std::string str = R"( + OpCapability Shader + OpCapability BindlessTextureNV + OpExtension "SPV_NV_bindless_texture" + OpMemoryModel Logical GLSL450 + OpSamplerImageAddressingModeNV 64 + OpSamplerImageAddressingModeNV 64 +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpSamplerImageAddressingModeNV should only be provided once")); +} + } // namespace } // namespace val } // namespace spvtools
diff --git a/test/val/val_logicals_test.cpp b/test/val/val_logicals_test.cpp index 1b76c85..c140672 100644 --- a/test/val/val_logicals_test.cpp +++ b/test/val/val_logicals_test.cpp
@@ -1159,6 +1159,61 @@ "condition to be equal: Select")); } +TEST_F(ValidateLogicals, SelectNVBindlessSamplers) { + const std::string spirv = R"( + OpCapability Shader + OpCapability BindlessTextureNV + OpExtension "SPV_NV_bindless_texture" + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpSamplerImageAddressingModeNV 64 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpSourceExtension "GL_NV_bindless_texture" + OpName %main "main" + OpName %s2D "s2D" + OpName %pickhandle "pickhandle" + OpName %Sampler1 "Sampler1" + OpName %Sampler2 "Sampler2" + OpDecorate %pickhandle Flat + OpDecorate %Sampler1 DescriptorSet 0 + OpDecorate %Sampler1 Binding 0 + OpDecorate %Sampler1 BindlessSamplerNV + OpDecorate %Sampler2 DescriptorSet 0 + OpDecorate %Sampler2 Binding 1 + OpDecorate %Sampler2 BindlessSamplerNV + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %7 = OpTypeImage %float 2D 0 0 0 1 Unknown + %8 = OpTypeSampledImage %7 +%_ptr_Function_8 = OpTypePointer Function %8 + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_0 = OpConstant %int 0 + %bool = OpTypeBool +%_ptr_UniformConstant_8 = OpTypePointer UniformConstant %8 + %Sampler1 = OpVariable %_ptr_UniformConstant_8 UniformConstant + %Sampler2 = OpVariable %_ptr_UniformConstant_8 UniformConstant + %main = OpFunction %void None %3 + %5 = OpLabel + %s2D = OpVariable %_ptr_Function_8 Function + %pickhandle = OpVariable %_ptr_Function_int Function + %14 = OpLoad %int %pickhandle + %17 = OpIEqual %bool %14 %int_0 + %20 = OpLoad %8 %Sampler1 + %22 = OpLoad %8 %Sampler2 + %23 = OpSelect %8 %17 %20 %22 + OpStore %s2D %23 + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); +} + } // namespace } // namespace val } // namespace spvtools
diff --git a/test/val/val_memory_test.cpp b/test/val/val_memory_test.cpp index 5fb43f7..ec1a000 100644 --- a/test/val/val_memory_test.cpp +++ b/test/val/val_memory_test.cpp
@@ -203,9 +203,11 @@ )"; CompileSuccessfully(src, SPV_ENV_VULKAN_1_1); ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-Uniform-06807")); EXPECT_THAT( getDiagnosticString(), - HasSubstr("From Vulkan spec, section 14.5.2:\n" + HasSubstr("From Vulkan spec:\n" "Variables identified with the Uniform storage class are used " "to access transparent buffer backed resources. Such variables " "must be typed as OpTypeStruct, or an array of this type")); @@ -277,9 +279,11 @@ )"; CompileSuccessfully(src, SPV_ENV_VULKAN_1_1); ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-Uniform-06807")); EXPECT_THAT( getDiagnosticString(), - HasSubstr("From Vulkan spec, section 14.5.2:\n" + HasSubstr("From Vulkan spec:\n" "Variables identified with the Uniform storage class are used " "to access transparent buffer backed resources. Such variables " "must be typed as OpTypeStruct, or an array of this type")); @@ -318,9 +322,11 @@ )"; CompileSuccessfully(src, SPV_ENV_VULKAN_1_1); ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-Uniform-06807")); EXPECT_THAT( getDiagnosticString(), - HasSubstr("From Vulkan spec, section 14.5.2:\n" + HasSubstr("From Vulkan spec:\n" "Variables identified with the Uniform storage class are used " "to access transparent buffer backed resources. Such variables " "must be typed as OpTypeStruct, or an array of this type")); @@ -833,6 +839,8 @@ )"; CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-PushConstant-06808")); EXPECT_THAT( getDiagnosticString(), HasSubstr("PushConstant OpVariable <id> '6[%6]' has illegal " @@ -867,6 +875,8 @@ )"; CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-PushConstant-06808")); EXPECT_THAT( getDiagnosticString(), HasSubstr("PushConstant OpVariable <id> '10[%10]' has illegal " @@ -3490,6 +3500,8 @@ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-Uniform-06925")); EXPECT_THAT( getDiagnosticString(), HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks")); @@ -3531,6 +3543,8 @@ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-Uniform-06925")); EXPECT_THAT( getDiagnosticString(), HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks")); @@ -3603,6 +3617,8 @@ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-Uniform-06925")); EXPECT_THAT( getDiagnosticString(), HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks")); @@ -3647,6 +3663,8 @@ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-Uniform-06925")); EXPECT_THAT( getDiagnosticString(), HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks")); @@ -3687,6 +3705,8 @@ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-Uniform-06925")); EXPECT_THAT( getDiagnosticString(), HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks")); @@ -4212,9 +4232,11 @@ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-Uniform-06807")); EXPECT_THAT( getDiagnosticString(), - HasSubstr("From Vulkan spec, section 14.5.2:\nVariables identified with " + HasSubstr("From Vulkan spec:\nVariables identified with " "the StorageBuffer storage class are used to access " "transparent buffer backed resources. Such variables must be " "typed as OpTypeStruct, or an array of this type")); @@ -4243,9 +4265,11 @@ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-Uniform-06807")); EXPECT_THAT( getDiagnosticString(), - HasSubstr("From Vulkan spec, section 14.5.2:\nVariables identified with " + HasSubstr("From Vulkan spec:\nVariables identified with " "the StorageBuffer storage class are used to access " "transparent buffer backed resources. Such variables must be " "typed as OpTypeStruct, or an array of this type")); @@ -4273,9 +4297,11 @@ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-Uniform-06807")); EXPECT_THAT( getDiagnosticString(), - HasSubstr("From Vulkan spec, section 14.5.2:\nVariables identified with " + HasSubstr("From Vulkan spec:\nVariables identified with " "the StorageBuffer storage class are used to access " "transparent buffer backed resources. Such variables must be " "typed as OpTypeStruct, or an array of this type"));
diff --git a/test/val/val_modes_test.cpp b/test/val/val_modes_test.cpp index a37989b..689f0ba 100644 --- a/test/val/val_modes_test.cpp +++ b/test/val/val_modes_test.cpp
@@ -1101,6 +1101,89 @@ EXPECT_THAT(SPV_SUCCESS, ValidateInstructions()); } + +TEST_F(ValidateMode, FragmentShaderStencilRefFrontTooManyModesBad) { + const std::string spirv = R"( +OpCapability Shader +OpCapability StencilExportEXT +OpExtension "SPV_AMD_shader_early_and_late_fragment_tests" +OpExtension "SPV_EXT_shader_stencil_export" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpExecutionMode %main EarlyAndLateFragmentTestsAMD +OpExecutionMode %main StencilRefLessFrontAMD +OpExecutionMode %main StencilRefGreaterFrontAMD +)" + kVoidFunction; + + CompileSuccessfully(spirv); + EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Fragment execution model entry points can specify at most " + "one of StencilRefUnchangedFrontAMD, " + "StencilRefLessFrontAMD or StencilRefGreaterFrontAMD " + "execution modes.")); +} + +TEST_F(ValidateMode, FragmentShaderStencilRefBackTooManyModesBad) { + const std::string spirv = R"( +OpCapability Shader +OpCapability StencilExportEXT +OpExtension "SPV_AMD_shader_early_and_late_fragment_tests" +OpExtension "SPV_EXT_shader_stencil_export" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpExecutionMode %main EarlyAndLateFragmentTestsAMD +OpExecutionMode %main StencilRefLessBackAMD +OpExecutionMode %main StencilRefGreaterBackAMD +)" + kVoidFunction; + + CompileSuccessfully(spirv); + EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Fragment execution model entry points can specify at most " + "one of StencilRefUnchangedBackAMD, " + "StencilRefLessBackAMD or StencilRefGreaterBackAMD " + "execution modes.")); +} + +TEST_F(ValidateMode, FragmentShaderStencilRefFrontGood) { + const std::string spirv = R"( +OpCapability Shader +OpCapability StencilExportEXT +OpExtension "SPV_AMD_shader_early_and_late_fragment_tests" +OpExtension "SPV_EXT_shader_stencil_export" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpExecutionMode %main EarlyAndLateFragmentTestsAMD +OpExecutionMode %main StencilRefLessFrontAMD +)" + kVoidFunction; + + CompileSuccessfully(spirv); + EXPECT_THAT(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateMode, FragmentShaderStencilRefBackGood) { + const std::string spirv = R"( +OpCapability Shader +OpCapability StencilExportEXT +OpExtension "SPV_AMD_shader_early_and_late_fragment_tests" +OpExtension "SPV_EXT_shader_stencil_export" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpExecutionMode %main EarlyAndLateFragmentTestsAMD +OpExecutionMode %main StencilRefLessBackAMD +)" + kVoidFunction; + + CompileSuccessfully(spirv); + EXPECT_THAT(SPV_SUCCESS, ValidateInstructions()); +} + TEST_F(ValidateMode, FragmentShaderDemoteVertexBad) { const std::string spirv = R"( OpCapability Shader
diff --git a/test/val/val_ray_query.cpp b/test/val/val_ray_query.cpp new file mode 100644 index 0000000..e9b9696 --- /dev/null +++ b/test/val/val_ray_query.cpp
@@ -0,0 +1,578 @@ +// Copyright (c) 2022 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Tests ray query instructions from SPV_KHR_ray_query. + +#include <sstream> +#include <string> + +#include "gmock/gmock.h" +#include "test/val/val_fixtures.h" + +namespace spvtools { +namespace val { +namespace { + +using ::testing::HasSubstr; +using ::testing::Values; + +using ValidateRayQuery = spvtest::ValidateBase<bool>; + +std::string GenerateShaderCode( + const std::string& body, + const std::string& capabilities_and_extensions = "", + const std::string& declarations = "") { + std::ostringstream ss; + ss << R"( +OpCapability Shader +OpCapability Int64 +OpCapability Float64 +OpCapability RayQueryKHR +OpExtension "SPV_KHR_ray_query" +)"; + + ss << capabilities_and_extensions; + + ss << R"( +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 + +OpDecorate %top_level_as DescriptorSet 0 +OpDecorate %top_level_as Binding 0 + +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f32 = OpTypeFloat 32 +%f64 = OpTypeFloat 64 +%u32 = OpTypeInt 32 0 +%s32 = OpTypeInt 32 1 +%u64 = OpTypeInt 64 0 +%s64 = OpTypeInt 64 1 +%type_rq = OpTypeRayQueryKHR +%type_as = OpTypeAccelerationStructureKHR + +%s32vec2 = OpTypeVector %s32 2 +%u32vec2 = OpTypeVector %u32 2 +%f32vec2 = OpTypeVector %f32 2 +%u32vec3 = OpTypeVector %u32 3 +%s32vec3 = OpTypeVector %s32 3 +%f32vec3 = OpTypeVector %f32 3 +%u32vec4 = OpTypeVector %u32 4 +%s32vec4 = OpTypeVector %s32 4 +%f32vec4 = OpTypeVector %f32 4 + +%mat4x3 = OpTypeMatrix %f32vec3 4 + +%f32_0 = OpConstant %f32 0 +%f64_0 = OpConstant %f64 0 +%s32_0 = OpConstant %s32 0 +%u32_0 = OpConstant %u32 0 +%u64_0 = OpConstant %u64 0 + +%u32vec3_0 = OpConstantComposite %u32vec3 %u32_0 %u32_0 %u32_0 +%f32vec3_0 = OpConstantComposite %f32vec3 %f32_0 %f32_0 %f32_0 +%f32vec4_0 = OpConstantComposite %f32vec4 %f32_0 %f32_0 %f32_0 %f32_0 + +%ptr_rq = OpTypePointer Private %type_rq +%ray_query = OpVariable %ptr_rq Private + +%ptr_as = OpTypePointer UniformConstant %type_as +%top_level_as = OpVariable %ptr_as UniformConstant + +%ptr_function_u32 = OpTypePointer Function %u32 +%ptr_function_f32 = OpTypePointer Function %f32 +%ptr_function_f32vec3 = OpTypePointer Function %f32vec3 +)"; + + ss << declarations; + + ss << R"( +%main = OpFunction %void None %func +%main_entry = OpLabel +)"; + + ss << body; + + ss << R"( +OpReturn +OpFunctionEnd)"; + return ss.str(); +} + +std::string RayQueryResult(std::string opcode) { + if (opcode.compare("OpRayQueryProceedKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionTypeKHR") == 0 || + opcode.compare("OpRayQueryGetRayTMinKHR") == 0 || + opcode.compare("OpRayQueryGetRayFlagsKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionTKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionInstanceCustomIndexKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionInstanceIdKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionInstanceShaderBindingTableRecord" + "OffsetKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionGeometryIndexKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionPrimitiveIndexKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionBarycentricsKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionFrontFaceKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionCandidateAABBOpaqueKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionObjectRayDirectionKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionObjectRayOriginKHR") == 0 || + opcode.compare("OpRayQueryGetWorldRayDirectionKHR") == 0 || + opcode.compare("OpRayQueryGetWorldRayOriginKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionObjectToWorldKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionWorldToObjectKHR") == 0) { + return "%result ="; + } + return ""; +} + +std::string RayQueryResultType(std::string opcode, bool valid) { + if (opcode.compare("OpRayQueryGetIntersectionTypeKHR") == 0 || + opcode.compare("OpRayQueryGetRayFlagsKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionInstanceCustomIndexKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionInstanceIdKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionInstanceShaderBindingTableRecord" + "OffsetKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionGeometryIndexKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionPrimitiveIndexKHR") == 0) { + return valid ? "%u32" : "%f64"; + } + + if (opcode.compare("OpRayQueryGetRayTMinKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionTKHR") == 0) { + return valid ? "%f32" : "%f64"; + } + + if (opcode.compare("OpRayQueryGetIntersectionBarycentricsKHR") == 0) { + return valid ? "%f32vec2" : "%f64"; + } + + if (opcode.compare("OpRayQueryGetIntersectionObjectRayDirectionKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionObjectRayOriginKHR") == 0 || + opcode.compare("OpRayQueryGetWorldRayDirectionKHR") == 0 || + opcode.compare("OpRayQueryGetWorldRayOriginKHR") == 0) { + return valid ? "%f32vec3" : "%f64"; + } + + if (opcode.compare("OpRayQueryProceedKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionFrontFaceKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionCandidateAABBOpaqueKHR") == 0) { + return valid ? "%bool" : "%f64"; + } + + if (opcode.compare("OpRayQueryGetIntersectionObjectToWorldKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionWorldToObjectKHR") == 0) { + return valid ? "%mat4x3" : "%f64"; + } + return ""; +} + +std::string RayQueryIntersection(std::string opcode, bool valid) { + if (opcode.compare("OpRayQueryGetIntersectionTypeKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionTKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionInstanceCustomIndexKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionInstanceIdKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionInstanceShaderBindingTableRecord" + "OffsetKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionGeometryIndexKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionPrimitiveIndexKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionBarycentricsKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionFrontFaceKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionObjectRayDirectionKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionObjectRayOriginKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionObjectToWorldKHR") == 0 || + opcode.compare("OpRayQueryGetIntersectionWorldToObjectKHR") == 0) { + return valid ? "%s32_0" : "%f32_0"; + } + return ""; +} + +using RayQueryCommon = spvtest::ValidateBase<std::string>; + +TEST_P(RayQueryCommon, Success) { + std::string opcode = GetParam(); + std::ostringstream ss; + ss << RayQueryResult(opcode); + ss << " " << opcode << " "; + ss << RayQueryResultType(opcode, true); + ss << " %ray_query "; + ss << RayQueryIntersection(opcode, true); + CompileSuccessfully(GenerateShaderCode(ss.str()).c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(RayQueryCommon, BadQuery) { + std::string opcode = GetParam(); + std::ostringstream ss; + ss << RayQueryResult(opcode); + ss << " " << opcode << " "; + ss << RayQueryResultType(opcode, true); + ss << " %top_level_as "; + ss << RayQueryIntersection(opcode, true); + CompileSuccessfully(GenerateShaderCode(ss.str()).c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Ray Query must be a pointer to OpTypeRayQueryKHR")); +} + +TEST_P(RayQueryCommon, BadResult) { + std::string opcode = GetParam(); + std::string result_type = RayQueryResultType(opcode, false); + if (!result_type.empty()) { + std::ostringstream ss; + ss << RayQueryResult(opcode); + ss << " " << opcode << " "; + ss << result_type; + ss << " %ray_query "; + ss << RayQueryIntersection(opcode, true); + CompileSuccessfully(GenerateShaderCode(ss.str()).c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + + std::string correct_result_type = RayQueryResultType(opcode, true); + if (correct_result_type.compare("%u32") == 0) { + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("expected Result Type to be 32-bit int scalar type")); + } else if (correct_result_type.compare("%f32") == 0) { + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("expected Result Type to be 32-bit float scalar type")); + } else if (correct_result_type.compare("%f32vec2") == 0) { + EXPECT_THAT(getDiagnosticString(), + HasSubstr("expected Result Type to be 32-bit float " + "2-component vector type")); + } else if (correct_result_type.compare("%f32vec3") == 0) { + EXPECT_THAT(getDiagnosticString(), + HasSubstr("expected Result Type to be 32-bit float " + "3-component vector type")); + } else if (correct_result_type.compare("%bool") == 0) { + EXPECT_THAT(getDiagnosticString(), + HasSubstr("expected Result Type to be bool scalar type")); + } else if (correct_result_type.compare("%mat4x3") == 0) { + EXPECT_THAT(getDiagnosticString(), + HasSubstr("expected matrix type as Result Type")); + } + } +} + +TEST_P(RayQueryCommon, BadIntersection) { + std::string opcode = GetParam(); + std::string intersection = RayQueryIntersection(opcode, false); + if (!intersection.empty()) { + std::ostringstream ss; + ss << RayQueryResult(opcode); + ss << " " << opcode << " "; + ss << RayQueryResultType(opcode, true); + ss << " %ray_query "; + ss << intersection; + CompileSuccessfully(GenerateShaderCode(ss.str()).c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "expected Intersection ID to be a constant 32-bit int scalar")); + } +} + +INSTANTIATE_TEST_SUITE_P( + ValidateRayQueryCommon, RayQueryCommon, + Values("OpRayQueryTerminateKHR", "OpRayQueryConfirmIntersectionKHR", + "OpRayQueryProceedKHR", "OpRayQueryGetIntersectionTypeKHR", + "OpRayQueryGetRayTMinKHR", "OpRayQueryGetRayFlagsKHR", + "OpRayQueryGetWorldRayDirectionKHR", + "OpRayQueryGetWorldRayOriginKHR", "OpRayQueryGetIntersectionTKHR", + "OpRayQueryGetIntersectionInstanceCustomIndexKHR", + "OpRayQueryGetIntersectionInstanceIdKHR", + "OpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR", + "OpRayQueryGetIntersectionGeometryIndexKHR", + "OpRayQueryGetIntersectionPrimitiveIndexKHR", + "OpRayQueryGetIntersectionBarycentricsKHR", + "OpRayQueryGetIntersectionFrontFaceKHR", + "OpRayQueryGetIntersectionCandidateAABBOpaqueKHR", + "OpRayQueryGetIntersectionObjectRayDirectionKHR", + "OpRayQueryGetIntersectionObjectRayOriginKHR", + "OpRayQueryGetIntersectionObjectToWorldKHR", + "OpRayQueryGetIntersectionWorldToObjectKHR")); + +// tests various Intersection operand types +TEST_F(ValidateRayQuery, IntersectionSuccess) { + const std::string body = R"( +%result_1 = OpRayQueryGetIntersectionFrontFaceKHR %bool %ray_query %s32_0 +%result_2 = OpRayQueryGetIntersectionFrontFaceKHR %bool %ray_query %u32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateRayQuery, IntersectionVector) { + const std::string body = R"( +%result = OpRayQueryGetIntersectionFrontFaceKHR %bool %ray_query %u32vec3_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("expected Intersection ID to be a constant 32-bit int scalar")); +} + +TEST_F(ValidateRayQuery, IntersectionNonConstantVariable) { + const std::string body = R"( +%var = OpVariable %ptr_function_u32 Function +%result = OpRayQueryGetIntersectionFrontFaceKHR %bool %ray_query %var +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("expected Intersection ID to be a constant 32-bit int scalar")); +} + +TEST_F(ValidateRayQuery, IntersectionNonConstantLoad) { + const std::string body = R"( +%var = OpVariable %ptr_function_u32 Function +%load = OpLoad %u32 %var +%result = OpRayQueryGetIntersectionFrontFaceKHR %bool %ray_query %load +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("expected Intersection ID to be a constant 32-bit int scalar")); +} + +TEST_F(ValidateRayQuery, InitializeSuccess) { + const std::string body = R"( +%var_u32 = OpVariable %ptr_function_u32 Function +%var_f32 = OpVariable %ptr_function_f32 Function +%var_f32vec3 = OpVariable %ptr_function_f32vec3 Function + +%as = OpLoad %type_as %top_level_as +OpRayQueryInitializeKHR %ray_query %as %u32_0 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0 + +%_u32 = OpLoad %u32 %var_u32 +%_f32 = OpLoad %f32 %var_f32 +%_f32vec3 = OpLoad %f32vec3 %var_f32vec3 +OpRayQueryInitializeKHR %ray_query %as %_u32 %_u32 %_f32vec3 %_f32 %_f32vec3 %_f32 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateRayQuery, InitializeFunctionSuccess) { + const std::string declaration = R"( +%rq_ptr = OpTypePointer Private %type_rq +%rq_func_type = OpTypeFunction %void %rq_ptr +%rq_var_1 = OpVariable %rq_ptr Private +%rq_var_2 = OpVariable %rq_ptr Private +)"; + + const std::string body = R"( +%fcall_1 = OpFunctionCall %void %rq_func %rq_var_1 +%as_1 = OpLoad %type_as %top_level_as +OpRayQueryInitializeKHR %rq_var_1 %as_1 %u32_0 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0 +%fcall_2 = OpFunctionCall %void %rq_func %rq_var_2 +OpReturn +OpFunctionEnd +%rq_func = OpFunction %void None %rq_func_type +%rq_param = OpFunctionParameter %rq_ptr +%label = OpLabel +%as_2 = OpLoad %type_as %top_level_as +OpRayQueryInitializeKHR %rq_param %as_2 %u32_0 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body, "", declaration).c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateRayQuery, InitializeBadRayQuery) { + const std::string body = R"( +%load = OpLoad %type_as %top_level_as +OpRayQueryInitializeKHR %top_level_as %load %u32_0 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Ray Query must be a pointer to OpTypeRayQueryKHR")); +} + +TEST_F(ValidateRayQuery, InitializeBadAS) { + const std::string body = R"( +OpRayQueryInitializeKHR %ray_query %ray_query %u32_0 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Acceleration Structure to be of type " + "OpTypeAccelerationStructureKHR")); +} + +TEST_F(ValidateRayQuery, InitializeBadRayFlags64) { + const std::string body = R"( +%load = OpLoad %type_as %top_level_as +OpRayQueryInitializeKHR %ray_query %load %u64_0 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Ray Flags must be a 32-bit int scalar")); +} + +TEST_F(ValidateRayQuery, InitializeBadRayFlagsVector) { + const std::string body = R"( +%load = OpLoad %type_as %top_level_as +OpRayQueryInitializeKHR %ray_query %load %u32vec2 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Operand 15[%v2uint] cannot be a type")); +} + +TEST_F(ValidateRayQuery, InitializeBadCullMask) { + const std::string body = R"( +%load = OpLoad %type_as %top_level_as +OpRayQueryInitializeKHR %ray_query %load %u32_0 %f32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Cull Mask must be a 32-bit int scalar")); +} + +TEST_F(ValidateRayQuery, InitializeBadRayOriginVec4) { + const std::string body = R"( +%load = OpLoad %type_as %top_level_as +OpRayQueryInitializeKHR %ray_query %load %u32_0 %u32_0 %f32vec4_0 %f32_0 %f32vec3_0 %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Ray Origin must be a 32-bit float 3-component vector")); +} + +TEST_F(ValidateRayQuery, InitializeBadRayOriginFloat) { + const std::string body = R"( +%var_f32 = OpVariable %ptr_function_f32 Function +%_f32 = OpLoad %f32 %var_f32 +%load = OpLoad %type_as %top_level_as +OpRayQueryInitializeKHR %ray_query %load %u32_0 %u32_0 %_f32 %f32_0 %f32vec3_0 %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Ray Origin must be a 32-bit float 3-component vector")); +} + +TEST_F(ValidateRayQuery, InitializeBadRayOriginInt) { + const std::string body = R"( +%load = OpLoad %type_as %top_level_as +OpRayQueryInitializeKHR %ray_query %load %u32_0 %u32_0 %u32vec3_0 %f32_0 %f32vec3_0 %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Ray Origin must be a 32-bit float 3-component vector")); +} + +TEST_F(ValidateRayQuery, InitializeBadRayTMin) { + const std::string body = R"( +%load = OpLoad %type_as %top_level_as +OpRayQueryInitializeKHR %ray_query %load %u32_0 %u32_0 %f32vec3_0 %u32_0 %f32vec3_0 %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Ray TMin must be a 32-bit float scalar")); +} + +TEST_F(ValidateRayQuery, InitializeBadRayDirection) { + const std::string body = R"( +%load = OpLoad %type_as %top_level_as +OpRayQueryInitializeKHR %ray_query %load %u32_0 %u32_0 %f32vec3_0 %f32_0 %f32vec4_0 %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Ray Direction must be a 32-bit float 3-component vector")); +} + +TEST_F(ValidateRayQuery, InitializeBadRayTMax) { + const std::string body = R"( +%load = OpLoad %type_as %top_level_as +OpRayQueryInitializeKHR %ray_query %load %u32_0 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f64_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Ray TMax must be a 32-bit float scalar")); +} + +TEST_F(ValidateRayQuery, GenerateIntersectionSuccess) { + const std::string body = R"( +%var = OpVariable %ptr_function_f32 Function +%load = OpLoad %f32 %var +OpRayQueryGenerateIntersectionKHR %ray_query %f32_0 +OpRayQueryGenerateIntersectionKHR %ray_query %load +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateRayQuery, GenerateIntersectionBadRayQuery) { + const std::string body = R"( +OpRayQueryGenerateIntersectionKHR %top_level_as %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Ray Query must be a pointer to OpTypeRayQueryKHR")); +} + +TEST_F(ValidateRayQuery, GenerateIntersectionBadHitT) { + const std::string body = R"( +OpRayQueryGenerateIntersectionKHR %ray_query %u32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Hit T must be a 32-bit float scalar")); +} + +} // namespace +} // namespace val +} // namespace spvtools
diff --git a/tools/io.h b/tools/io.h index 83a85c1..9dc834e 100644 --- a/tools/io.h +++ b/tools/io.h
@@ -26,9 +26,15 @@ #define SET_STDIN_TO_BINARY_MODE() _setmode(_fileno(stdin), O_BINARY); #define SET_STDIN_TO_TEXT_MODE() _setmode(_fileno(stdin), O_TEXT); +#define SET_STDOUT_TO_BINARY_MODE() _setmode(_fileno(stdout), O_BINARY); +#define SET_STDOUT_TO_TEXT_MODE() _setmode(_fileno(stdout), O_TEXT); +#define SET_STDOUT_MODE(mode) _setmode(_fileno(stdout), mode); #else #define SET_STDIN_TO_BINARY_MODE() #define SET_STDIN_TO_TEXT_MODE() +#define SET_STDOUT_TO_BINARY_MODE() 0 +#define SET_STDOUT_TO_TEXT_MODE() 0 +#define SET_STDOUT_MODE(mode) #endif // Appends the contents of the |file| to |data|, assuming each element in the @@ -115,6 +121,44 @@ return succeeded; } +namespace { +// A class to create and manage a file for outputting data. +class OutputFile { + public: + // Opens |filename| in the given mode. If |filename| is nullptr, the empty + // string or "-", stdout will be set to the given mode. + OutputFile(const char* filename, const char* mode) { + const bool use_stdout = + !filename || (filename[0] == '-' && filename[1] == '\0'); + if (use_stdout) { + if (strchr(mode, 'b')) { + old_mode_ = SET_STDOUT_TO_BINARY_MODE(); + } else { + old_mode_ = SET_STDOUT_TO_TEXT_MODE(); + } + fp_ = stdout; + } else { + fp_ = fopen(filename, mode); + } + } + + ~OutputFile() { + if (fp_ == stdout) { + SET_STDOUT_MODE(old_mode_); + } else if (fp_ != nullptr) { + fclose(fp_); + } + } + + // Returns a file handle to the file. + FILE* GetFileHandle() const { return fp_; } + + private: + FILE* fp_; + int old_mode_; +}; +} // namespace + // Writes the given |data| into the file named as |filename| using the given // |mode|, assuming |data| is an array of |count| elements of type |T|. If // |filename| is nullptr or "-", writes to standard output. If any error occurs, @@ -122,20 +166,19 @@ template <typename T> bool WriteFile(const char* filename, const char* mode, const T* data, size_t count) { - const bool use_stdout = - !filename || (filename[0] == '-' && filename[1] == '\0'); - if (FILE* fp = (use_stdout ? stdout : fopen(filename, mode))) { - size_t written = fwrite(data, sizeof(T), count, fp); - if (count != written) { - fprintf(stderr, "error: could not write to file '%s'\n", filename); - if (!use_stdout) fclose(fp); - return false; - } - if (!use_stdout) fclose(fp); - } else { + OutputFile file(filename, mode); + FILE* fp = file.GetFileHandle(); + if (fp == nullptr) { fprintf(stderr, "error: could not open file '%s'\n", filename); return false; } + + size_t written = fwrite(data, sizeof(T), count, fp); + if (count != written) { + fprintf(stderr, "error: could not write to file '%s'\n", filename); + return false; + } + return true; }