Updated spirv-tools.

This commit is contained in:
Бранимир Караџић 2019-03-30 21:02:17 -07:00
parent 2ddefa50b7
commit 2d6c2cebeb
35 changed files with 775 additions and 269 deletions

View File

@ -1 +1 @@
"v2019.2", "SPIRV-Tools v2019.2 027e592038bb74c2ffe6b07f9cc4e2ab1b3fbb84"
"v2019.2", "SPIRV-Tools v2019.2 2ddefa50b7e00747527827ee9aeddb9741bd6c2e"

View File

@ -557,14 +557,17 @@ SPIRV_TOOLS_EXPORT spv_reducer_options spvReducerOptionsCreate();
// Destroys the given reducer options object.
SPIRV_TOOLS_EXPORT void spvReducerOptionsDestroy(spv_reducer_options options);
// Records the maximum number of reduction steps that should run before the
// reducer gives up.
// Sets the maximum number of reduction steps that should run before the reducer
// gives up.
SPIRV_TOOLS_EXPORT void spvReducerOptionsSetStepLimit(
spv_reducer_options options, uint32_t step_limit);
// Sets seed for random number generation.
SPIRV_TOOLS_EXPORT void spvReducerOptionsSetSeed(spv_reducer_options options,
uint32_t seed);
// Sets the fail-on-validation-error option; if true, the reducer will return
// kStateInvalid if a reduction step yields a state that fails SPIR-V
// validation. Otherwise, an invalid state is treated as uninteresting and the
// reduction backtracks and continues.
SPIRV_TOOLS_EXPORT void spvReducerOptionsSetFailOnValidationError(
spv_reducer_options options, bool fail_on_validation_error);
// Encodes the given SPIR-V assembly text to its binary representation. The
// length parameter specifies the number of bytes for text. Encoded binary will

View File

@ -151,16 +151,20 @@ class ReducerOptions {
~ReducerOptions() { spvReducerOptionsDestroy(options_); }
// Allow implicit conversion to the underlying object.
operator spv_reducer_options() const { return options_; }
operator spv_reducer_options() const { // NOLINT(google-explicit-constructor)
return options_;
}
// Records the maximum number of reduction steps that should
// run before the reducer gives up.
// See spvReducerOptionsSetStepLimit.
void set_step_limit(uint32_t step_limit) {
spvReducerOptionsSetStepLimit(options_, step_limit);
}
// Sets a seed to be used for random number generation.
void set_seed(uint32_t seed) { spvReducerOptionsSetSeed(options_, seed); }
// See spvReducerOptionsSetFailOnValidationError.
void set_fail_on_validation_error(bool fail_on_validation_error) {
spvReducerOptionsSetFailOnValidationError(options_,
fail_on_validation_error);
}
private:
spv_reducer_options options_;

View File

@ -199,6 +199,9 @@ class Optimizer {
// |out| output stream.
Optimizer& SetTimeReport(std::ostream* out);
// Sets the option to validate the module after each pass.
Optimizer& SetValidateAfterAll(bool validate);
private:
struct Impl; // Opaque struct for holding internal data.
std::unique_ptr<Impl> impl_; // Unique pointer to internal data.

View File

@ -184,6 +184,12 @@ uint32_t BasicBlock::MergeBlockIdIfAny() const {
return mbid;
}
uint32_t BasicBlock::MergeBlockId() const {
uint32_t mbid = MergeBlockIdIfAny();
assert(mbid && "Expected block to have a corresponding merge block");
return mbid;
}
uint32_t BasicBlock::ContinueBlockIdIfAny() const {
auto merge_ii = cend();
--merge_ii;
@ -197,6 +203,12 @@ uint32_t BasicBlock::ContinueBlockIdIfAny() const {
return cbid;
}
uint32_t BasicBlock::ContinueBlockId() const {
uint32_t cbid = ContinueBlockIdIfAny();
assert(cbid && "Expected block to have a corresponding continue target");
return cbid;
}
std::ostream& operator<<(std::ostream& str, const BasicBlock& block) {
str << block.PrettyPrint();
return str;

View File

@ -183,10 +183,16 @@ class BasicBlock {
// block, if any. If none, returns zero.
uint32_t MergeBlockIdIfAny() const;
// Returns MergeBlockIdIfAny() and asserts that it is non-zero.
uint32_t MergeBlockId() const;
// Returns the ID of the continue block declared by a merge instruction in
// this block, if any. If none, returns zero.
uint32_t ContinueBlockIdIfAny() const;
// Returns ContinueBlockIdIfAny() and asserts that it is non-zero.
uint32_t ContinueBlockId() const;
// Returns the terminator instruction. Assumes the terminator exists.
Instruction* terminator() { return &*tail(); }
const Instruction* terminator() const { return &*ctail(); }

View File

@ -507,6 +507,8 @@ bool Optimizer::Run(const uint32_t* original_binary,
context->set_max_id_bound(opt_options->max_id_bound_);
impl_->pass_manager.SetValidatorOptions(&opt_options->val_options_);
impl_->pass_manager.SetTargetEnv(impl_->target_env);
auto status = impl_->pass_manager.Run(context.get());
if (status == opt::Pass::Status::SuccessWithChange ||
(status == opt::Pass::Status::SuccessWithoutChange &&
@ -529,6 +531,11 @@ Optimizer& Optimizer::SetTimeReport(std::ostream* out) {
return *this;
}
Optimizer& Optimizer::SetValidateAfterAll(bool validate) {
impl_->pass_manager.SetValidateAfterAll(validate);
return *this;
}
Optimizer::PassToken CreateNullPass() {
return MakeUnique<Optimizer::PassToken::Impl>(MakeUnique<opt::NullPass>());
}

View File

@ -51,6 +51,20 @@ Pass::Status PassManager::Run(IRContext* context) {
if (one_status == Pass::Status::Failure) return one_status;
if (one_status == Pass::Status::SuccessWithChange) status = one_status;
if (validate_after_all_) {
spvtools::SpirvTools tools(target_env_);
tools.SetMessageConsumer(consumer());
std::vector<uint32_t> binary;
context->module()->ToBinary(&binary, true);
if (!tools.Validate(binary.data(), binary.size(), val_options_)) {
std::string msg = "Validation failed after pass ";
msg += pass->name();
spv_position_t null_pos{0, 0, 0};
consumer()(SPV_MSG_INTERNAL_ERROR, "", null_pos, msg.c_str());
return Pass::Status::Failure;
}
}
// Reset the pass to free any memory used by the pass.
pass.reset(nullptr);
}

View File

@ -43,7 +43,10 @@ class PassManager {
PassManager()
: consumer_(nullptr),
print_all_stream_(nullptr),
time_report_stream_(nullptr) {}
time_report_stream_(nullptr),
target_env_(SPV_ENV_UNIVERSAL_1_2),
val_options_(nullptr),
validate_after_all_(false) {}
// Sets the message consumer to the given |consumer|.
void SetMessageConsumer(MessageConsumer c) { consumer_ = std::move(c); }
@ -89,6 +92,24 @@ class PassManager {
return *this;
}
// Sets the target environment for validation.
PassManager& SetTargetEnv(spv_target_env env) {
target_env_ = env;
return *this;
}
// Sets the validation options.
PassManager& SetValidatorOptions(spv_validator_options options) {
val_options_ = options;
return *this;
}
// Sets the option to validate after each pass.
PassManager& SetValidateAfterAll(bool validate) {
validate_after_all_ = validate;
return *this;
}
private:
// Consumer for messages.
MessageConsumer consumer_;
@ -100,6 +121,12 @@ class PassManager {
// The output stream to write the resource utilization of each pass. If this
// is null, no output is generated.
std::ostream* time_report_stream_;
// The target environment.
spv_target_env target_env_;
// The validator options (used when validating each pass).
spv_validator_options val_options_;
// Controls whether validation occurs after every pass.
bool validate_after_all_;
};
inline void PassManager::AddPass(std::unique_ptr<Pass> pass) {

View File

@ -19,7 +19,7 @@
namespace {
const uint32_t kMergeNodeIndex = 0;
const uint32_t kContinueNodeIndex = 1;
}
} // namespace
namespace spvtools {
namespace opt {
@ -37,6 +37,8 @@ StructuredCFGAnalysis::StructuredCFGAnalysis(IRContext* ctx) : context_(ctx) {
}
void StructuredCFGAnalysis::AddBlocksInFunction(Function* func) {
if (func->begin() == func->end()) return;
std::list<BasicBlock*> order;
context_->cfg()->ComputeStructuredOrder(func, &*func->begin(), &order);

View File

@ -15,10 +15,17 @@
#include <cassert>
#include <sstream>
#include "source/reduce/merge_blocks_reduction_opportunity_finder.h"
#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
#include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h"
#include "source/reduce/operand_to_undef_reduction_opportunity_finder.h"
#include "source/reduce/remove_function_reduction_opportunity_finder.h"
#include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
#include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
#include "source/spirv_reducer_options.h"
#include "reducer.h"
#include "reduction_pass.h"
namespace spvtools {
namespace reduce {
@ -105,13 +112,15 @@ Reducer::ReductionResultStatus Reducer::Run(
do {
auto maybe_result = pass->TryApplyReduction(current_binary);
if (maybe_result.empty()) {
// This pass did not have any impact, so move on to the next pass.
// For this round, the pass has no more opportunities (chunks) to
// apply, so move on to the next pass.
impl_->consumer(
SPV_MSG_INFO, nullptr, {},
("Pass " + pass->GetName() + " did not make a reduction step.")
.c_str());
break;
}
bool interesting = false;
std::stringstream stringstream;
reductions_applied++;
stringstream << "Pass " << pass->GetName() << " made reduction step "
@ -125,6 +134,9 @@ Reducer::ReductionResultStatus Reducer::Run(
// invalid binary from being regarded as interesting.
impl_->consumer(SPV_MSG_INFO, nullptr, {},
"Reduction step produced an invalid binary.");
if (options->fail_on_validation_error) {
return Reducer::ReductionResultStatus::kStateInvalid;
}
} else if (impl_->interestingness_function(maybe_result,
reductions_applied)) {
// Success! The binary produced by this reduction step is
@ -133,8 +145,11 @@ Reducer::ReductionResultStatus Reducer::Run(
impl_->consumer(SPV_MSG_INFO, nullptr, {},
"Reduction step succeeded.");
current_binary = std::move(maybe_result);
interesting = true;
another_round_worthwhile = true;
}
// We must call this before the next call to TryApplyReduction.
pass->NotifyInteresting(interesting);
// Bail out if the reduction step limit has been reached.
} while (!impl_->ReachedStepLimit(reductions_applied, options));
}
@ -153,6 +168,25 @@ Reducer::ReductionResultStatus Reducer::Run(
return Reducer::ReductionResultStatus::kComplete;
}
void Reducer::AddDefaultReductionPasses() {
AddReductionPass(spvtools::MakeUnique<
RemoveOpNameInstructionReductionOpportunityFinder>());
AddReductionPass(
spvtools::MakeUnique<OperandToUndefReductionOpportunityFinder>());
AddReductionPass(
spvtools::MakeUnique<OperandToConstReductionOpportunityFinder>());
AddReductionPass(
spvtools::MakeUnique<OperandToDominatingIdReductionOpportunityFinder>());
AddReductionPass(spvtools::MakeUnique<
RemoveUnreferencedInstructionReductionOpportunityFinder>());
AddReductionPass(spvtools::MakeUnique<
StructuredLoopToSelectionReductionOpportunityFinder>());
AddReductionPass(
spvtools::MakeUnique<MergeBlocksReductionOpportunityFinder>());
AddReductionPass(
spvtools::MakeUnique<RemoveFunctionReductionOpportunityFinder>());
}
void Reducer::AddReductionPass(
std::unique_ptr<ReductionOpportunityFinder>&& finder) {
impl_->passes.push_back(spvtools::MakeUnique<ReductionPass>(

View File

@ -34,7 +34,11 @@ class Reducer {
kInitialStateNotInteresting,
kReachedStepLimit,
kComplete,
kInitialStateInvalid
kInitialStateInvalid,
// Returned when the fail-on-validation-error option is set and a
// reduction step yields a state that fails validation.
kStateInvalid,
};
// The type for a function that will take a binary and return true if and
@ -76,6 +80,9 @@ class Reducer {
void SetInterestingnessFunction(
InterestingnessFunction interestingness_function);
// Adds all default reduction passes.
void AddDefaultReductionPasses();
// Adds a reduction pass based on the given finder to the sequence of passes
// that will be iterated over.
void AddReductionPass(std::unique_ptr<ReductionOpportunityFinder>&& finder);

View File

@ -36,20 +36,19 @@ std::vector<uint32_t> ReductionPass::TryApplyReduction(
std::vector<std::unique_ptr<ReductionOpportunity>> opportunities =
finder_->GetAvailableOpportunities(context.get());
if (!is_initialized_) {
is_initialized_ = true;
index_ = 0;
granularity_ = (uint32_t)opportunities.size();
}
if (opportunities.empty()) {
granularity_ = 1;
return std::vector<uint32_t>();
// There is no point in having a granularity larger than the number of
// opportunities, so reduce the granularity in this case.
if (granularity_ > opportunities.size()) {
granularity_ = std::max((uint32_t)1, (uint32_t)opportunities.size());
}
assert(granularity_ > 0);
if (index_ >= opportunities.size()) {
// We have reached the end of the available opportunities and, therefore,
// the end of the round for this pass, so reset the index and decrease the
// granularity for the next round. Return an empty vector to signal the end
// of the round.
index_ = 0;
granularity_ = std::max((uint32_t)1, granularity_ / 2);
return std::vector<uint32_t>();
@ -61,8 +60,6 @@ std::vector<uint32_t> ReductionPass::TryApplyReduction(
opportunities[i]->TryToApply();
}
index_ += granularity_;
std::vector<uint32_t> result;
context->module()->ToBinary(&result, false);
return result;
@ -73,16 +70,17 @@ void ReductionPass::SetMessageConsumer(MessageConsumer consumer) {
}
bool ReductionPass::ReachedMinimumGranularity() const {
if (!is_initialized_) {
// Conceptually we can think that if the pass has not yet been initialized,
// it is operating at unbounded granularity.
return false;
}
assert(granularity_ != 0);
return granularity_ == 1;
}
std::string ReductionPass::GetName() const { return finder_->GetName(); }
void ReductionPass::NotifyInteresting(bool interesting) {
if (!interesting) {
index_ += granularity_;
}
}
} // namespace reduce
} // namespace spvtools

View File

@ -15,10 +15,11 @@
#ifndef SOURCE_REDUCE_REDUCTION_PASS_H_
#define SOURCE_REDUCE_REDUCTION_PASS_H_
#include "spirv-tools/libspirv.hpp"
#include <limits>
#include "reduction_opportunity_finder.h"
#include "source/opt/ir_context.h"
#include "source/reduce/reduction_opportunity_finder.h"
#include "spirv-tools/libspirv.hpp"
namespace spvtools {
namespace reduce {
@ -33,17 +34,28 @@ namespace reduce {
class ReductionPass {
public:
// Constructs a reduction pass with a given target environment, |target_env|,
// and a given finder of reduction opportunities, |finder|. Initially the
// pass is uninitialized.
// and a given finder of reduction opportunities, |finder|.
explicit ReductionPass(const spv_target_env target_env,
std::unique_ptr<ReductionOpportunityFinder> finder)
: target_env_(target_env),
finder_(std::move(finder)),
is_initialized_(false) {}
index_(0),
granularity_(std::numeric_limits<uint32_t>::max()) {}
// Applies the reduction pass to the given binary.
// Applies the reduction pass to the given binary by applying a "chunk" of
// reduction opportunities. Returns the new binary if a chunk was applied; in
// this case, before the next call the caller must invoke
// NotifyInteresting(...) to indicate whether the new binary is interesting.
// Returns an empty vector if there are no more chunks left to apply; in this
// case, the index will be reset and the granularity lowered for the next
// round.
std::vector<uint32_t> TryApplyReduction(const std::vector<uint32_t>& binary);
// Notifies the reduction pass whether the binary returned from
// TryApplyReduction is interesting, so that the next call to
// TryApplyReduction will avoid applying the same chunk of opportunities.
void NotifyInteresting(bool interesting);
// Sets a consumer to which relevant messages will be directed.
void SetMessageConsumer(MessageConsumer consumer);
@ -59,7 +71,6 @@ class ReductionPass {
const spv_target_env target_env_;
const std::unique_ptr<ReductionOpportunityFinder> finder_;
MessageConsumer consumer_;
bool is_initialized_;
uint32_t index_;
uint32_t granularity_;
};

View File

@ -23,7 +23,6 @@ namespace reduce {
namespace {
const uint32_t kMergeNodeIndex = 0;
const uint32_t kContinueNodeIndex = 1;
} // namespace
bool StructuredLoopToSelectionReductionOpportunity::PreconditionHolds() {
@ -43,15 +42,11 @@ void StructuredLoopToSelectionReductionOpportunity::Apply() {
// (1) Redirect edges that point to the loop's continue target to their
// closest merge block.
RedirectToClosestMergeBlock(
loop_construct_header_->GetLoopMergeInst()->GetSingleWordOperand(
kContinueNodeIndex));
RedirectToClosestMergeBlock(loop_construct_header_->ContinueBlockId());
// (2) Redirect edges that point to the loop's merge block to their closest
// merge block (which might be that of an enclosing selection, for instance).
RedirectToClosestMergeBlock(
loop_construct_header_->GetLoopMergeInst()->GetSingleWordOperand(
kMergeNodeIndex));
RedirectToClosestMergeBlock(loop_construct_header_->MergeBlockId());
// (3) Turn the loop construct header into a selection.
ChangeLoopToSelection();
@ -127,12 +122,8 @@ void StructuredLoopToSelectionReductionOpportunity::RedirectEdge(
// original_target_id must either be the merge target or continue construct
// for the loop being operated on.
assert(original_target_id ==
loop_construct_header_->GetMergeInst()->GetSingleWordOperand(
kMergeNodeIndex) ||
original_target_id ==
loop_construct_header_->GetMergeInst()->GetSingleWordOperand(
kContinueNodeIndex));
assert(original_target_id == loop_construct_header_->MergeBlockId() ||
original_target_id == loop_construct_header_->ContinueBlockId());
auto terminator = context_->cfg()->block(source_id)->terminator();
@ -221,7 +212,7 @@ void StructuredLoopToSelectionReductionOpportunity::ChangeLoopToSelection() {
const analysis::Bool* bool_type =
context_->get_type_mgr()->GetRegisteredType(&temp)->AsBool();
auto const_mgr = context_->get_constant_mgr();
auto true_const = const_mgr->GetConstant(bool_type, {true});
auto true_const = const_mgr->GetConstant(bool_type, {1});
auto true_const_result_id =
const_mgr->GetDefiningInstruction(true_const)->result_id();
auto original_branch_id = terminator->GetSingleWordOperand(0);
@ -250,6 +241,10 @@ void StructuredLoopToSelectionReductionOpportunity::FixNonDominatedIdUses() {
context_->get_def_use_mgr()->ForEachUse(&def, [this, &block, &def](
Instruction* use,
uint32_t index) {
// Ignore uses outside of blocks, such as in OpDecorate.
if (context_->get_instr_block(use) == nullptr) {
return;
}
// If a use is not appropriately dominated by its definition,
// replace the use with an OpUndef, unless the definition is an
// access chain, in which case replace it with some (possibly fresh)

View File

@ -33,10 +33,9 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities(
std::set<uint32_t> merge_block_ids;
for (auto& function : *context->module()) {
for (auto& block : function) {
auto merge_inst = block.GetMergeInst();
if (merge_inst) {
merge_block_ids.insert(
merge_inst->GetSingleWordOperand(kMergeNodeIndex));
auto merge_block_id = block.MergeBlockIdIfAny();
if (merge_block_id) {
merge_block_ids.insert(merge_block_id);
}
}
}
@ -50,11 +49,19 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities(
continue;
}
uint32_t continue_block_id =
loop_merge_inst->GetSingleWordOperand(kContinueNodeIndex);
// Check whether the loop construct's continue target is the merge block
// of some structured control flow construct. If it is, we cautiously do
// not consider applying a transformation.
if (merge_block_ids.find(loop_merge_inst->GetSingleWordOperand(
kContinueNodeIndex)) != merge_block_ids.end()) {
if (merge_block_ids.find(continue_block_id) != merge_block_ids.end()) {
continue;
}
// Check whether the loop header block is also the continue target. If it
// is, we cautiously do not consider applying a transformation.
if (block.id() == continue_block_id) {
continue;
}

View File

@ -29,3 +29,8 @@ SPIRV_TOOLS_EXPORT void spvReducerOptionsSetStepLimit(
spv_reducer_options options, uint32_t step_limit) {
options->step_limit = step_limit;
}
SPIRV_TOOLS_EXPORT void spvReducerOptionsSetFailOnValidationError(
spv_reducer_options options, bool fail_on_validation_error) {
options->fail_on_validation_error = fail_on_validation_error;
}

View File

@ -26,10 +26,14 @@ const uint32_t kDefaultStepLimit = 250;
// Manages command line options passed to the SPIR-V Reducer. New struct
// members may be added for any new option.
struct spv_reducer_options_t {
spv_reducer_options_t() : step_limit(kDefaultStepLimit) {}
spv_reducer_options_t()
: step_limit(kDefaultStepLimit), fail_on_validation_error(false) {}
// The number of steps the reducer will run for before giving up.
// See spvReducerOptionsSetStepLimit.
uint32_t step_limit;
// See spvReducerOptionsSetFailOnValidationError.
bool fail_on_validation_error;
};
#endif // SOURCE_SPIRV_REDUCER_OPTIONS_H_

View File

@ -725,6 +725,36 @@ spv_result_t PerformWebGPUCfgChecks(ValidationState_t& _, Function* function) {
return SPV_SUCCESS;
}
// Checks that there are no OpUnreachable instructions reachable from the entry
// block of |function|.
spv_result_t ValidateUnreachableBlocks(ValidationState_t& _,
Function& function) {
auto* entry_block = function.first_block();
std::vector<BasicBlock*> stack;
std::unordered_set<BasicBlock*> seen;
if (entry_block) stack.push_back(entry_block);
while (!stack.empty()) {
auto* block = stack.back();
stack.pop_back();
if (!seen.insert(block).second) continue;
auto* terminator = block->terminator();
if (terminator->opcode() == SpvOpUnreachable) {
return _.diag(SPV_ERROR_INVALID_CFG, block->label())
<< "Statically reachable blocks cannot be terminated by "
"OpUnreachable";
}
for (auto* succ : *block->successors()) {
stack.push_back(succ);
}
}
return SPV_SUCCESS;
}
spv_result_t PerformCfgChecks(ValidationState_t& _) {
for (auto& function : _.functions()) {
// Check all referenced blocks are defined within a function
@ -827,6 +857,8 @@ spv_result_t PerformCfgChecks(ValidationState_t& _) {
}
}
if (auto error = ValidateUnreachableBlocks(_, function)) return error;
/// Structured control flow checks are only required for shader capabilities
if (_.HasCapability(SpvCapabilityShader)) {
if (auto error = StructuredControlFlowChecks(_, &function, back_edges))

View File

@ -748,6 +748,33 @@ spv_result_t ValidateTypeSampledImage(ValidationState_t& _,
return SPV_SUCCESS;
}
bool IsAllowedSampledImageOperand(SpvOp opcode) {
switch (opcode) {
case SpvOpSampledImage:
case SpvOpImageSampleImplicitLod:
case SpvOpImageSampleExplicitLod:
case SpvOpImageSampleDrefImplicitLod:
case SpvOpImageSampleDrefExplicitLod:
case SpvOpImageSampleProjImplicitLod:
case SpvOpImageSampleProjExplicitLod:
case SpvOpImageSampleProjDrefImplicitLod:
case SpvOpImageSampleProjDrefExplicitLod:
case SpvOpImageGather:
case SpvOpImageDrefGather:
case SpvOpImage:
case SpvOpImageQueryLod:
case SpvOpImageSparseSampleImplicitLod:
case SpvOpImageSparseSampleExplicitLod:
case SpvOpImageSparseSampleDrefImplicitLod:
case SpvOpImageSparseSampleDrefExplicitLod:
case SpvOpImageSparseGather:
case SpvOpImageSparseDrefGather:
return true;
default:
return false;
}
}
spv_result_t ValidateSampledImage(ValidationState_t& _,
const Instruction* inst) {
if (_.GetIdOpcode(inst->type_id()) != SpvOpTypeSampledImage) {
@ -815,11 +842,7 @@ spv_result_t ValidateSampledImage(ValidationState_t& _,
"block. The consumer instruction <id> is '"
<< _.getIdName(consumer_instr->id()) << "'.";
}
// TODO: The following check is incomplete. We should also check that the
// Sampled Image is not used by instructions that should not take
// SampledImage as an argument. We could find the list of valid
// instructions by scanning for "Sampled Image" in the operand description
// field in the grammar file.
if (consumer_opcode == SpvOpPhi || consumer_opcode == SpvOpSelect) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Result <id> from OpSampledImage instruction must not appear "
@ -830,6 +853,18 @@ spv_result_t ValidateSampledImage(ValidationState_t& _,
<< "' as an operand of <id> '"
<< _.getIdName(consumer_instr->id()) << "'.";
}
if (!IsAllowedSampledImageOperand(consumer_opcode)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Result <id> from OpSampledImage instruction must not appear "
"as operand for Op"
<< spvOpcodeString(static_cast<SpvOp>(consumer_opcode))
<< ", since it is not specificed as taking an "
<< "OpTypeSampledImage."
<< " Found result <id> '" << _.getIdName(inst->id())
<< "' as an operand of <id> '"
<< _.getIdName(consumer_instr->id()) << "'.";
}
}
}
return SPV_SUCCESS;

View File

@ -590,7 +590,7 @@ OpUnreachable
OpFunctionEnd
)";
SinglePassRunAndMatch<BlockMergePass>(text, true);
SinglePassRunAndMatch<BlockMergePass>(text, false);
}
TEST_F(BlockMergeTest, DontMergeReturn) {

View File

@ -2421,7 +2421,8 @@ OpBranchConditional %true %44 %43
OpStore %38 %float_1
OpBranch %40
%43 = OpLabel
OpUnreachable
OpStore %38 %float_1
OpBranch %40
%41 = OpLabel
OpBranchConditional %false %39 %40
%40 = OpLabel
@ -2446,7 +2447,7 @@ OpBranchConditional %true %36 %35
%36 = OpLabel
OpReturnValue %float_1
%35 = OpLabel
OpUnreachable
OpReturnValue %float_1
OpFunctionEnd
)";
@ -3058,6 +3059,7 @@ OpDecorate %2 DescriptorSet 439418829
%4 = OpTypeFunction %void
%float = OpTypeFloat 32
%_struct_6 = OpTypeStruct %float %float
%15 = OpConstantNull %_struct_6
%7 = OpTypeFunction %_struct_6
%1 = OpFunction %void Pure|Const %4
%8 = OpLabel
@ -3067,10 +3069,11 @@ OpFunctionEnd
%9 = OpFunction %_struct_6 None %7
%10 = OpLabel
%11 = OpFunctionCall %_struct_6 %9
OpUnreachable
OpReturnValue %15
OpFunctionEnd
)";
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SinglePassRunAndCheck<InlineExhaustivePass>(test, test, false, true);
}
@ -3086,6 +3089,7 @@ OpDecorate %2 DescriptorSet 439418829
%4 = OpTypeFunction %void
%float = OpTypeFloat 32
%_struct_6 = OpTypeStruct %float %float
%15 = OpConstantNull %_struct_6
%7 = OpTypeFunction %_struct_6
%1 = OpFunction %void Pure|Const %4
%8 = OpLabel
@ -3095,15 +3099,16 @@ OpFunctionEnd
%9 = OpFunction %_struct_6 None %7
%10 = OpLabel
%11 = OpFunctionCall %_struct_6 %12
OpUnreachable
OpReturnValue %15
OpFunctionEnd
%12 = OpFunction %_struct_6 None %7
%13 = OpLabel
%14 = OpFunctionCall %_struct_6 %9
OpUnreachable
OpReturnValue %15
OpFunctionEnd
)";
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SinglePassRunAndCheck<InlineExhaustivePass>(test, test, false, true);
}

View File

@ -461,6 +461,26 @@ OpFunctionEnd
}
}
TEST_F(StructCFGAnalysisTest, EmptyFunctionTest) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpDecorate %func LinkageAttributes "x" Import
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
OpFunctionEnd
)";
std::unique_ptr<IRContext> context =
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
// #2451: This segfaulted on empty functions.
StructuredCFGAnalysis analysis(context.get());
}
} // namespace
} // namespace opt
} // namespace spvtools

View File

@ -14,6 +14,8 @@
#include "reduce_test_util.h"
#include <iostream>
namespace spvtools {
namespace reduce {
@ -68,5 +70,27 @@ void NopDiagnostic(spv_message_level_t /*level*/, const char* /*source*/,
const spv_position_t& /*position*/,
const char* /*message*/) {}
void CLIMessageConsumer(spv_message_level_t level, const char*,
const spv_position_t& position, const char* message) {
switch (level) {
case SPV_MSG_FATAL:
case SPV_MSG_INTERNAL_ERROR:
case SPV_MSG_ERROR:
std::cerr << "error: line " << position.index << ": " << message
<< std::endl;
break;
case SPV_MSG_WARNING:
std::cout << "warning: line " << position.index << ": " << message
<< std::endl;
break;
case SPV_MSG_INFO:
std::cout << "info: line " << position.index << ": " << message
<< std::endl;
break;
default:
break;
}
}
} // namespace reduce
} // namespace spvtools

View File

@ -59,6 +59,10 @@ const uint32_t kReduceDisassembleOption =
void NopDiagnostic(spv_message_level_t /*level*/, const char* /*source*/,
const spv_position_t& /*position*/, const char* /*message*/);
// Prints reducer messages (for debugging).
void CLIMessageConsumer(spv_message_level_t level, const char*,
const spv_position_t& position, const char* message);
} // namespace reduce
} // namespace spvtools

View File

@ -229,6 +229,7 @@ TEST(ReducerTest, ExprToConstantAndRemoveUnreferenced) {
std::vector<uint32_t> binary_out;
spvtools::ReducerOptions reducer_options;
reducer_options.set_step_limit(500);
reducer_options.set_fail_on_validation_error(true);
spvtools::ValidatorOptions validator_options;
Reducer::ReductionResultStatus status = reducer.Run(
@ -304,6 +305,7 @@ TEST(ReducerTest, RemoveOpnameAndRemoveUnreferenced) {
std::vector<uint32_t> binary_out;
spvtools::ReducerOptions reducer_options;
reducer_options.set_step_limit(500);
reducer_options.set_fail_on_validation_error(true);
spvtools::ValidatorOptions validator_options;
Reducer::ReductionResultStatus status = reducer.Run(

View File

@ -65,12 +65,16 @@ TEST(RemoveUnreferencedInstructionReductionPassTest, RemoveStores) {
OpStore %14 %15
)" + epilogue;
const std::string expected = prologue + R"(
const std::string expected_after_2 = prologue + R"(
OpStore %12 %13
%15 = OpLoad %6 %8
OpStore %14 %15
)" + epilogue;
const std::string expected_after_4 = prologue + R"(
%15 = OpLoad %6 %8
)" + epilogue;
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context =
@ -83,149 +87,14 @@ TEST(RemoveUnreferencedInstructionReductionPassTest, RemoveStores) {
ASSERT_TRUE(ops[1]->PreconditionHolds());
ops[1]->TryToApply();
CheckEqual(env, expected, context.get());
}
CheckEqual(env, expected_after_2, context.get());
TEST(RemoveUnreferencedInstructionReductionPassTest, ApplyReduction) {
const std::string prologue = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %8 "a"
OpName %10 "b"
OpName %12 "c"
OpName %14 "d"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypePointer Function %6
%9 = OpConstant %6 10
%11 = OpConstant %6 20
%13 = OpConstant %6 30
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
%10 = OpVariable %7 Function
%12 = OpVariable %7 Function
%14 = OpVariable %7 Function
)";
ASSERT_TRUE(ops[2]->PreconditionHolds());
ops[2]->TryToApply();
ASSERT_TRUE(ops[3]->PreconditionHolds());
ops[3]->TryToApply();
const std::string epilogue = R"(
OpReturn
OpFunctionEnd
)";
const std::string original = prologue + R"(
OpStore %8 %9
OpStore %10 %11
OpStore %12 %13
%15 = OpLoad %6 %8
OpStore %14 %15
)" + epilogue;
const auto env = SPV_ENV_UNIVERSAL_1_3;
std::vector<uint32_t> binary;
SpirvTools t(env);
ASSERT_TRUE(t.Assemble(original, &binary, kReduceAssembleOption));
ReductionPass pass(
env, spvtools::MakeUnique<
RemoveUnreferencedInstructionReductionOpportunityFinder>());
{
// Attempt 1 should remove everything removable.
const std::string expected_reduced = prologue + R"(
%15 = OpLoad %6 %8
)" + epilogue;
auto reduced_binary = pass.TryApplyReduction(binary);
CheckEqual(env, expected_reduced, reduced_binary);
}
// Attempt 2 should fail as pass with granularity 4 got to end.
ASSERT_EQ(0, pass.TryApplyReduction(binary).size());
{
// Attempt 3 should remove first two removable statements.
const std::string expected_reduced = prologue + R"(
OpStore %12 %13
%15 = OpLoad %6 %8
OpStore %14 %15
)" + epilogue;
auto reduced_binary = pass.TryApplyReduction(binary);
CheckEqual(env, expected_reduced, reduced_binary);
}
{
// Attempt 4 should remove last two removable statements.
const std::string expected_reduced = prologue + R"(
OpStore %8 %9
OpStore %10 %11
%15 = OpLoad %6 %8
)" + epilogue;
auto reduced_binary = pass.TryApplyReduction(binary);
CheckEqual(env, expected_reduced, reduced_binary);
}
// Attempt 5 should fail as pass with granularity 2 got to end.
ASSERT_EQ(0, pass.TryApplyReduction(binary).size());
{
// Attempt 6 should remove first removable statement.
const std::string expected_reduced = prologue + R"(
OpStore %10 %11
OpStore %12 %13
%15 = OpLoad %6 %8
OpStore %14 %15
)" + epilogue;
auto reduced_binary = pass.TryApplyReduction(binary);
CheckEqual(env, expected_reduced, reduced_binary);
}
{
// Attempt 7 should remove second removable statement.
const std::string expected_reduced = prologue + R"(
OpStore %8 %9
OpStore %12 %13
%15 = OpLoad %6 %8
OpStore %14 %15
)" + epilogue;
auto reduced_binary = pass.TryApplyReduction(binary);
CheckEqual(env, expected_reduced, reduced_binary);
}
{
// Attempt 8 should remove third removable statement.
const std::string expected_reduced = prologue + R"(
OpStore %8 %9
OpStore %10 %11
%15 = OpLoad %6 %8
OpStore %14 %15
)" + epilogue;
auto reduced_binary = pass.TryApplyReduction(binary);
CheckEqual(env, expected_reduced, reduced_binary);
}
{
// Attempt 9 should remove fourth removable statement.
const std::string expected_reduced = prologue + R"(
OpStore %8 %9
OpStore %10 %11
OpStore %12 %13
%15 = OpLoad %6 %8
)" + epilogue;
auto reduced_binary = pass.TryApplyReduction(binary);
CheckEqual(env, expected_reduced, reduced_binary);
}
// Attempt 10 should fail as pass with granularity 1 got to end.
ASSERT_EQ(0, pass.TryApplyReduction(binary).size());
ASSERT_TRUE(pass.ReachedMinimumGranularity());
CheckEqual(env, expected_after_4, context.get());
}
} // namespace

View File

@ -3436,6 +3436,191 @@ TEST(StructuredLoopToSelectionReductionPassTest, LongAccessChains) {
// CheckEqual(env, expected, context.get());
}
TEST(StructuredLoopToSelectionReductionPassTest, LoopyShaderWithOpDecorate) {
// A shader containing a function that contains a loop and some definitions
// that are "used" in OpDecorate instructions (outside the function). These
// "uses" were causing segfaults because we try to calculate their dominance
// information, which doesn't make sense.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main" %9
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %9 "_GLF_color"
OpName %14 "buf0"
OpMemberName %14 0 "a"
OpName %16 ""
OpDecorate %9 RelaxedPrecision
OpDecorate %9 Location 0
OpMemberDecorate %14 0 RelaxedPrecision
OpMemberDecorate %14 0 Offset 0
OpDecorate %14 Block
OpDecorate %16 DescriptorSet 0
OpDecorate %16 Binding 0
OpDecorate %21 RelaxedPrecision
OpDecorate %35 RelaxedPrecision
OpDecorate %36 RelaxedPrecision
OpDecorate %39 RelaxedPrecision
OpDecorate %40 RelaxedPrecision
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypeVector %6 4
%8 = OpTypePointer Output %7
%9 = OpVariable %8 Output
%10 = OpConstant %6 1
%11 = OpConstantComposite %7 %10 %10 %10 %10
%14 = OpTypeStruct %6
%15 = OpTypePointer Uniform %14
%16 = OpVariable %15 Uniform
%17 = OpTypeInt 32 1
%18 = OpConstant %17 0
%19 = OpTypePointer Uniform %6
%28 = OpConstant %6 2
%29 = OpTypeBool
%31 = OpTypeInt 32 0
%32 = OpConstant %31 0
%33 = OpTypePointer Output %6
%4 = OpFunction %2 None %3
%5 = OpLabel
OpStore %9 %11
%20 = OpAccessChain %19 %16 %18
%21 = OpLoad %6 %20
OpBranch %22
%22 = OpLabel
%40 = OpPhi %6 %21 %5 %39 %23
%30 = OpFOrdLessThan %29 %40 %28
OpLoopMerge %24 %23 None
OpBranchConditional %30 %23 %24
%23 = OpLabel
%34 = OpAccessChain %33 %9 %32
%35 = OpLoad %6 %34
%36 = OpFAdd %6 %35 %10
OpStore %34 %36
%39 = OpFAdd %6 %40 %10
OpBranch %22
%24 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(1, ops.size());
ASSERT_TRUE(ops[0]->PreconditionHolds());
ops[0]->TryToApply();
CheckValid(env, context.get());
std::string after_op_0 = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main" %9
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %9 "_GLF_color"
OpName %14 "buf0"
OpMemberName %14 0 "a"
OpName %16 ""
OpDecorate %9 RelaxedPrecision
OpDecorate %9 Location 0
OpMemberDecorate %14 0 RelaxedPrecision
OpMemberDecorate %14 0 Offset 0
OpDecorate %14 Block
OpDecorate %16 DescriptorSet 0
OpDecorate %16 Binding 0
OpDecorate %21 RelaxedPrecision
OpDecorate %35 RelaxedPrecision
OpDecorate %36 RelaxedPrecision
OpDecorate %39 RelaxedPrecision
OpDecorate %40 RelaxedPrecision
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypeVector %6 4
%8 = OpTypePointer Output %7
%9 = OpVariable %8 Output
%10 = OpConstant %6 1
%11 = OpConstantComposite %7 %10 %10 %10 %10
%14 = OpTypeStruct %6
%15 = OpTypePointer Uniform %14
%16 = OpVariable %15 Uniform
%17 = OpTypeInt 32 1
%18 = OpConstant %17 0
%19 = OpTypePointer Uniform %6
%28 = OpConstant %6 2
%29 = OpTypeBool
%31 = OpTypeInt 32 0
%32 = OpConstant %31 0
%33 = OpTypePointer Output %6
%41 = OpUndef %6 ; Added
%4 = OpFunction %2 None %3
%5 = OpLabel
OpStore %9 %11
%20 = OpAccessChain %19 %16 %18
%21 = OpLoad %6 %20
OpBranch %22
%22 = OpLabel
%40 = OpPhi %6 %21 %5 %41 %23 ; Changed
%30 = OpFOrdLessThan %29 %40 %28
OpSelectionMerge %24 None ; Changed
OpBranchConditional %30 %24 %24
%23 = OpLabel
%34 = OpAccessChain %33 %9 %32
%35 = OpLoad %6 %34
%36 = OpFAdd %6 %35 %10
OpStore %34 %36
%39 = OpFAdd %6 %41 %10 ; Changed
OpBranch %22
%24 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(env, after_op_0, context.get());
}
TEST(StructuredLoopToSelectionReductionPassTest,
LoopWithCombinedHeaderAndContinue) {
// A shader containing a loop where the header is also the continue target.
// For now, we don't simplify such loops.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeBool
%30 = OpConstantFalse %6
%4 = OpFunction %2 None %3
%5 = OpLabel
OpBranch %10
%10 = OpLabel ; loop header and continue target
OpLoopMerge %12 %10 None
OpBranchConditional %30 %10 %12
%12 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(0, ops.size());
}
} // namespace
} // namespace reduce
} // namespace spvtools

View File

@ -113,7 +113,9 @@ class OpVariableDuplicatorReductionOpportunityFinder
TEST(ValidationDuringReductionTest, CheckInvalidPassMakesNoProgress) {
// A module whose global values are all referenced, so that any application of
// MakeModuleInvalidPass will make the module invalid.
// MakeModuleInvalidPass will make the module invalid. Check that the reducer
// makes no progress, as every step will be invalid and treated as
// uninteresting.
std::string original = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
@ -219,6 +221,8 @@ TEST(ValidationDuringReductionTest, CheckInvalidPassMakesNoProgress) {
std::vector<uint32_t> binary_out;
spvtools::ReducerOptions reducer_options;
reducer_options.set_step_limit(500);
// Don't fail on a validation error; just treat it as uninteresting.
reducer_options.set_fail_on_validation_error(false);
spvtools::ValidatorOptions validator_options;
Reducer::ReductionResultStatus status = reducer.Run(
@ -428,6 +432,8 @@ TEST(ValidationDuringReductionTest, CheckNotAlwaysInvalidCanMakeProgress) {
std::vector<uint32_t> binary_out;
spvtools::ReducerOptions reducer_options;
reducer_options.set_step_limit(500);
// Don't fail on a validation error; just treat it as uninteresting.
reducer_options.set_fail_on_validation_error(false);
spvtools::ValidatorOptions validator_options;
Reducer::ReductionResultStatus status = reducer.Run(
@ -518,6 +524,7 @@ TEST(ValidationDuringReductionTest, CheckValidationOptions) {
spvtools::ValidatorOptions validator_options;
reducer_options.set_step_limit(3);
reducer_options.set_fail_on_validation_error(true);
// Reduction should fail because the initial state is invalid without the
// "skip-block-layout" validator option. Note that the interestingness test
@ -553,8 +560,9 @@ TEST(ValidationDuringReductionTest, CheckValidationOptions) {
validator_options.SetUniversalLimit(spv_validator_limit_max_local_variables,
2);
// Reduction should "complete"; after one step, a local variable is added and
// the module becomes "invalid" given the validator limits.
// Reduction should now fail due to reaching an invalid state; after one step,
// a local variable is added and the module becomes "invalid" given the
// validator limits.
{
Reducer reducer(env);
setupReducerForCheckValidationOptions(&reducer);
@ -563,7 +571,7 @@ TEST(ValidationDuringReductionTest, CheckValidationOptions) {
reducer.Run(std::vector<uint32_t>(binary_in), &binary_out,
reducer_options, validator_options);
ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
ASSERT_EQ(status, Reducer::ReductionResultStatus::kStateInvalid);
}
}

View File

@ -1203,7 +1203,11 @@ std::string GetUnreachableMergeWithBranchUse(SpvCapability cap,
TEST_P(ValidateCFG, UnreachableMergeWithBranchUse) {
CompileSuccessfully(
GetUnreachableMergeWithBranchUse(GetParam(), SPV_ENV_UNIVERSAL_1_0));
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Statically reachable blocks cannot be terminated by OpUnreachable"));
}
TEST_F(ValidateCFG, WebGPUUnreachableMergeWithBranchUse) {
@ -1938,7 +1942,10 @@ OpDecorate %id BuiltIn GlobalInvocationId
str += "OpFunctionEnd";
CompileSuccessfully(str);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Statically reachable blocks cannot be terminated by "
"OpUnreachable"));
}
TEST_F(ValidateCFG, LoopWithZeroBackEdgesBad) {
@ -2852,6 +2859,134 @@ TEST_F(ValidateCFG, BranchTargetMustBeLabel) {
"be the ID of an OpLabel instruction"));
}
TEST_F(ValidateCFG, ReachableOpUnreachableOneBlock) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpUnreachable
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Statically reachable blocks cannot be terminated by OpUnreachable"));
}
TEST_F(ValidateCFG, ReachableOpUnreachableOpBranch) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpBranch %block
%block = OpLabel
OpUnreachable
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Statically reachable blocks cannot be terminated by OpUnreachable"));
}
TEST_F(ValidateCFG, ReachableOpUnreachableOpBranchConditional) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%bool = OpTypeBool
%undef = OpUndef %bool
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpBranchConditional %undef %block %unreachable
%block = OpLabel
OpReturn
%unreachable = OpLabel
OpUnreachable
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Statically reachable blocks cannot be terminated by OpUnreachable"));
}
TEST_F(ValidateCFG, ReachableOpUnreachableOpSwitch) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%int = OpTypeInt 32 0
%undef = OpUndef %int
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpSwitch %undef %block1 0 %unreachable 1 %block2
%block1 = OpLabel
OpReturn
%unreachable = OpLabel
OpUnreachable
%block2 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Statically reachable blocks cannot be terminated by OpUnreachable"));
}
TEST_F(ValidateCFG, ReachableOpUnreachableLoop) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%bool = OpTypeBool
%undef = OpUndef %bool
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpBranch %loop
%loop = OpLabel
OpLoopMerge %unreachable %loop None
OpBranchConditional %undef %loop %unreachable
%unreachable = OpLabel
OpUnreachable
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Statically reachable blocks cannot be terminated by OpUnreachable"));
}
TEST_F(ValidateCFG, OneContinueTwoBackedges) {
const std::string text = R"(
OpCapability Shader

View File

@ -6203,20 +6203,22 @@ TEST_F(ValidateIdWithMessage, IdDefInUnreachableBlock1) {
%4 = OpTypeFunction %3
%5 = OpFunction %1 None %2
%6 = OpLabel
%7 = OpFunctionCall %3 %8
OpReturn
%7 = OpLabel
%8 = OpFunctionCall %3 %9
OpUnreachable
OpFunctionEnd
%8 = OpFunction %3 None %4
%9 = OpLabel
OpReturnValue %7
%9 = OpFunction %3 None %4
%10 = OpLabel
OpReturnValue %8
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("ID 7[%7] defined in block 6[%6] does not dominate its "
"use in block 9[%9]\n %9 = OpLabel"));
HasSubstr("ID 8[%8] defined in block 7[%7] does not dominate its "
"use in block 10[%10]\n %10 = OpLabel"));
}
TEST_F(ValidateIdWithMessage, IdDefInUnreachableBlock2) {

View File

@ -2283,7 +2283,7 @@ TEST_F(ValidateImage, FetchNotImage) {
%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
%sampler = OpLoad %type_sampler %uniform_sampler
%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
%res1 = OpImageFetch %f32vec4 %simg %u32vec2_01
%res1 = OpImageFetch %f32vec4 %sampler %u32vec2_01
)";
CompileSuccessfully(GenerateShaderCode(body).c_str());
@ -2292,6 +2292,21 @@ TEST_F(ValidateImage, FetchNotImage) {
HasSubstr("Expected Image to be of type OpTypeImage"));
}
TEST_F(ValidateImage, FetchSampledImageDirectly) {
const std::string body = R"(
%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
%sampler = OpLoad %type_sampler %uniform_sampler
%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
%res1 = OpImageFetch %f32vec4 %simg %u32vec2_01
)";
CompileSuccessfully(GenerateShaderCode(body).c_str());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("OpSampledImage instruction must not appear as operand "
"for OpImageFetch"));
}
TEST_F(ValidateImage, FetchNotSampled) {
const std::string body = R"(
%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
@ -3191,7 +3206,7 @@ TEST_F(ValidateImage, QueryFormatNotImage) {
%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
%sampler = OpLoad %type_sampler %uniform_sampler
%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
%res1 = OpImageQueryFormat %u32 %simg
%res1 = OpImageQueryFormat %u32 %sampler
)";
CompileSuccessfully(GenerateKernelCode(body).c_str());
@ -3227,7 +3242,7 @@ TEST_F(ValidateImage, QueryOrderNotImage) {
%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
%sampler = OpLoad %type_sampler %uniform_sampler
%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
%res1 = OpImageQueryOrder %u32 %simg
%res1 = OpImageQueryOrder %u32 %sampler
)";
CompileSuccessfully(GenerateKernelCode(body).c_str());
@ -3276,7 +3291,7 @@ TEST_F(ValidateImage, QuerySizeLodNotImage) {
%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
%sampler = OpLoad %type_sampler %uniform_sampler
%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
%res1 = OpImageQuerySizeLod %u32vec2 %simg %u32_1
%res1 = OpImageQuerySizeLod %u32vec2 %sampler %u32_1
)";
CompileSuccessfully(GenerateKernelCode(body).c_str());
@ -3285,6 +3300,21 @@ TEST_F(ValidateImage, QuerySizeLodNotImage) {
HasSubstr("Expected Image to be of type OpTypeImage"));
}
TEST_F(ValidateImage, QuerySizeLodSampledImageDirectly) {
const std::string body = R"(
%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
%sampler = OpLoad %type_sampler %uniform_sampler
%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
%res1 = OpImageQuerySizeLod %u32vec2 %simg %u32_1
)";
CompileSuccessfully(GenerateShaderCode(body).c_str());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("OpSampledImage instruction must not appear as operand "
"for OpImageQuerySizeLod"));
}
TEST_F(ValidateImage, QuerySizeLodWrongImageDim) {
const std::string body = R"(
%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001
@ -3348,7 +3378,7 @@ TEST_F(ValidateImage, QuerySizeNotImage) {
%img = OpLoad %type_image_f32_2d_0010 %uniform_image_f32_2d_0010
%sampler = OpLoad %type_sampler %uniform_sampler
%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
%res1 = OpImageQuerySize %u32vec2 %simg
%res1 = OpImageQuerySize %u32vec2 %sampler
)";
CompileSuccessfully(GenerateKernelCode(body).c_str());
@ -3357,6 +3387,21 @@ TEST_F(ValidateImage, QuerySizeNotImage) {
HasSubstr("Expected Image to be of type OpTypeImage"));
}
TEST_F(ValidateImage, QuerySizeSampledImageDirectly) {
const std::string body = R"(
%img = OpLoad %type_image_f32_2d_0010 %uniform_image_f32_2d_0010
%sampler = OpLoad %type_sampler %uniform_sampler
%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
%res1 = OpImageQuerySize %u32vec2 %simg
)";
CompileSuccessfully(GenerateShaderCode(body).c_str());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("OpSampledImage instruction must not appear as operand "
"for OpImageQuerySize"));
}
TEST_F(ValidateImage, QuerySizeDimSubpassDataBad) {
const std::string body = R"(
%img = OpLoad %type_image_f32_spd_0002 %uniform_image_f32_spd_0002
@ -3531,7 +3576,7 @@ TEST_F(ValidateImage, QueryLevelsNotImage) {
%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
%sampler = OpLoad %type_sampler %uniform_sampler
%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
%res1 = OpImageQueryLevels %u32 %simg
%res1 = OpImageQueryLevels %u32 %sampler
)";
CompileSuccessfully(GenerateKernelCode(body).c_str());
@ -3540,6 +3585,21 @@ TEST_F(ValidateImage, QueryLevelsNotImage) {
HasSubstr("Expected Image to be of type OpTypeImage"));
}
TEST_F(ValidateImage, QueryLevelsSampledImageDirectly) {
const std::string body = R"(
%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
%sampler = OpLoad %type_sampler %uniform_sampler
%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
%res1 = OpImageQueryLevels %u32 %simg
)";
CompileSuccessfully(GenerateShaderCode(body).c_str());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("OpSampledImage instruction must not appear as operand "
"for OpImageQueryLevels"));
}
TEST_F(ValidateImage, QueryLevelsWrongDim) {
const std::string body = R"(
%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001
@ -4481,7 +4541,10 @@ TEST_F(ValidateImage, Issue2463NoSegFault) {
)";
CompileSuccessfully(spirv);
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("OpSampledImage instruction must not appear as operand "
"for OpReturnValue"));
}
} // namespace

View File

@ -56,15 +56,16 @@ OpExecutionMode %1 OriginUpperLeft
%4 = OpTypeFunction %void
%float = OpTypeFloat 32
%_struct_6 = OpTypeStruct %float %float
%null = OpConstantNull %_struct_6
%7 = OpTypeFunction %_struct_6
%12 = OpFunction %_struct_6 None %7
%13 = OpLabel
OpUnreachable
OpReturnValue %null
OpFunctionEnd
%9 = OpFunction %_struct_6 None %7
%10 = OpLabel
%11 = OpFunctionCall %_struct_6 %12
OpUnreachable
OpReturnValue %null
OpFunctionEnd
%1 = OpFunction %void Pure|Const %4
%8 = OpLabel
@ -89,7 +90,7 @@ OpFunctionEnd
%1 = OpFunction %void Pure|Const %4
%8 = OpLabel
%2 = OpFunctionCall %_struct_6 %9
OpUnreachable
OpReturn
OpFunctionEnd
)";
@ -100,16 +101,17 @@ OpExecutionMode %1 OriginUpperLeft
%4 = OpTypeFunction %void
%float = OpTypeFloat 32
%_struct_6 = OpTypeStruct %float %float
%null = OpConstantNull %_struct_6
%7 = OpTypeFunction %_struct_6
%9 = OpFunction %_struct_6 None %7
%10 = OpLabel
%11 = OpFunctionCall %_struct_6 %12
OpUnreachable
OpReturnValue %null
OpFunctionEnd
%12 = OpFunction %_struct_6 None %7
%13 = OpLabel
%14 = OpFunctionCall %_struct_6 %9
OpUnreachable
OpReturnValue %null
OpFunctionEnd
%1 = OpFunction %void Pure|Const %4
%8 = OpLabel
@ -303,7 +305,7 @@ TEST_F(ValidationStateTest, CheckWebGPUIndirectlyRecursiveBodyBad) {
ValidateAndRetrieveValidationState(SPV_ENV_WEBGPU_0));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("For WebGPU, functions need to be defined before being "
"called.\n %9 = OpFunctionCall %_struct_5 %10\n"));
"called.\n %10 = OpFunctionCall %_struct_5 %11\n"));
}
TEST_F(ValidationStateTest,

View File

@ -384,6 +384,8 @@ Options (in lexicographical order):
Current workarounds: Avoid OpUnreachable in loops.
--unify-const
Remove the duplicated constants.
--validate-after-all
Validate the module after each pass is performed.
-h, --help
Print this help.
--version
@ -628,6 +630,8 @@ OptStatus ParseFlags(int argc, const char** argv,
optimizer->SetTargetEnv(SPV_ENV_WEBGPU_0);
optimizer->RegisterWebGPUPasses();
} else if (0 == strcmp(cur_arg, "--validate-after-all")) {
optimizer->SetValidateAfterAll(true);
} else {
// Some passes used to accept the form '--pass arg', canonicalize them
// to '--pass=arg'.

View File

@ -18,21 +18,10 @@
#include <functional>
#include "source/opt/build_module.h"
#include "source/opt/ir_context.h"
#include "source/opt/log.h"
#include "source/reduce/merge_blocks_reduction_opportunity_finder.h"
#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
#include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h"
#include "source/reduce/operand_to_undef_reduction_opportunity_finder.h"
#include "source/reduce/reducer.h"
#include "source/reduce/remove_function_reduction_opportunity_finder.h"
#include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
#include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
#include "source/spirv_reducer_options.h"
#include "source/util/make_unique.h"
#include "source/util/string_utils.h"
#include "spirv-tools/libspirv.hpp"
#include "tools/io.h"
#include "tools/util/cli_consumer.h"
@ -103,6 +92,10 @@ should be the path to a script.
NOTE: The reducer is a work in progress.
Options (in lexicographical order):
--fail-on-validation-error
Stop reduction with an error if any reduction step produces a
SPIR-V module that fails to validate.
-h, --help
Print this help.
--step-limit
@ -172,6 +165,8 @@ ReduceStatus ParseFlags(int argc, const char** argv, const char** in_file,
assert(!*interestingness_test);
*interestingness_test = cur_arg;
positional_arg_index++;
} else if (0 == strcmp(cur_arg, "--fail-on-validation-error")) {
reducer_options->set_fail_on_validation_error(true);
} else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) {
validator_options->SetRelaxLogicalPointer(true);
} else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
@ -246,25 +241,7 @@ int main(int argc, const char** argv) {
return ExecuteCommand(command);
});
reducer.AddReductionPass(
spvtools::MakeUnique<
RemoveOpNameInstructionReductionOpportunityFinder>());
reducer.AddReductionPass(
spvtools::MakeUnique<OperandToUndefReductionOpportunityFinder>());
reducer.AddReductionPass(
spvtools::MakeUnique<OperandToConstReductionOpportunityFinder>());
reducer.AddReductionPass(
spvtools::MakeUnique<OperandToDominatingIdReductionOpportunityFinder>());
reducer.AddReductionPass(
spvtools::MakeUnique<
RemoveUnreferencedInstructionReductionOpportunityFinder>());
reducer.AddReductionPass(
spvtools::MakeUnique<
StructuredLoopToSelectionReductionOpportunityFinder>());
reducer.AddReductionPass(
spvtools::MakeUnique<MergeBlocksReductionOpportunityFinder>());
reducer.AddReductionPass(
spvtools::MakeUnique<RemoveFunctionReductionOpportunityFinder>());
reducer.AddDefaultReductionPasses();
reducer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);