spirv-fuzz: Add validator options (#3254)

Allows several validator options to be passed to the fuzzer, to be
used when validation is invoked during fuzzing.
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 119bd3c..3a4fa0e 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -86,26 +86,31 @@
 }  // namespace
 
 struct Fuzzer::Impl {
-  explicit Impl(spv_target_env env, uint32_t random_seed,
-                bool validate_after_each_pass)
+  Impl(spv_target_env env, uint32_t random_seed, bool validate_after_each_pass,
+       spv_validator_options options)
       : target_env(env),
         seed(random_seed),
-        validate_after_each_fuzzer_pass(validate_after_each_pass) {}
+        validate_after_each_fuzzer_pass(validate_after_each_pass),
+        validator_options(options) {}
 
   bool ApplyPassAndCheckValidity(FuzzerPass* pass,
                                  const opt::IRContext& ir_context,
                                  const spvtools::SpirvTools& tools) const;
 
   const spv_target_env target_env;       // Target environment.
+  MessageConsumer consumer;              // Message consumer.
   const uint32_t seed;                   // Seed for random number generator.
   bool validate_after_each_fuzzer_pass;  // Determines whether the validator
-  // should be invoked after every fuzzer pass.
-  MessageConsumer consumer;  // Message consumer.
+                                         // should be invoked after every fuzzer
+                                         // pass.
+  spv_validator_options validator_options;  // Options to control validation.
 };
 
 Fuzzer::Fuzzer(spv_target_env env, uint32_t seed,
-               bool validate_after_each_fuzzer_pass)
-    : impl_(MakeUnique<Impl>(env, seed, validate_after_each_fuzzer_pass)) {}
+               bool validate_after_each_fuzzer_pass,
+               spv_validator_options validator_options)
+    : impl_(MakeUnique<Impl>(env, seed, validate_after_each_fuzzer_pass,
+                             validator_options)) {}
 
 Fuzzer::~Fuzzer() = default;
 
@@ -120,7 +125,8 @@
   if (validate_after_each_fuzzer_pass) {
     std::vector<uint32_t> binary_to_validate;
     ir_context.module()->ToBinary(&binary_to_validate, false);
-    if (!tools.Validate(&binary_to_validate[0], binary_to_validate.size())) {
+    if (!tools.Validate(&binary_to_validate[0], binary_to_validate.size(),
+                        validator_options)) {
       consumer(SPV_MSG_INFO, nullptr, {},
                "Binary became invalid during fuzzing (set a breakpoint to "
                "inspect); stopping.");
@@ -149,7 +155,8 @@
   }
 
   // Initial binary should be valid.
-  if (!tools.Validate(&binary_in[0], binary_in.size())) {
+  if (!tools.Validate(&binary_in[0], binary_in.size(),
+                      impl_->validator_options)) {
     impl_->consumer(SPV_MSG_ERROR, nullptr, {},
                     "Initial binary is invalid; stopping.");
     return Fuzzer::FuzzerResultStatus::kInitialBinaryInvalid;
diff --git a/source/fuzz/fuzzer.h b/source/fuzz/fuzzer.h
index 3ac73a1..6c3ef71 100644
--- a/source/fuzz/fuzzer.h
+++ b/source/fuzz/fuzzer.h
@@ -41,8 +41,9 @@
   // seed for pseudo-random number generation.
   // |validate_after_each_fuzzer_pass| controls whether the validator will be
   // invoked after every fuzzer pass is applied.
-  explicit Fuzzer(spv_target_env env, uint32_t seed,
-                  bool validate_after_each_fuzzer_pass);
+  Fuzzer(spv_target_env env, uint32_t seed,
+         bool validate_after_each_fuzzer_pass,
+         spv_validator_options validator_options);
 
   // Disables copy/move constructor/assignment operations.
   Fuzzer(const Fuzzer&) = delete;
diff --git a/source/fuzz/replayer.cpp b/source/fuzz/replayer.cpp
index 398ce59..07dfe72 100644
--- a/source/fuzz/replayer.cpp
+++ b/source/fuzz/replayer.cpp
@@ -37,18 +37,22 @@
 namespace fuzz {
 
 struct Replayer::Impl {
-  explicit Impl(spv_target_env env, bool validate)
-      : target_env(env), validate_during_replay(validate) {}
+  Impl(spv_target_env env, bool validate, spv_validator_options options)
+      : target_env(env),
+        validate_during_replay(validate),
+        validator_options(options) {}
 
-  const spv_target_env target_env;  // Target environment.
-  MessageConsumer consumer;         // Message consumer.
-
+  const spv_target_env target_env;    // Target environment.
+  MessageConsumer consumer;           // Message consumer.
   const bool validate_during_replay;  // Controls whether the validator should
                                       // be run after every replay step.
+  spv_validator_options validator_options;  // Options to control
+                                            // validation
 };
 
-Replayer::Replayer(spv_target_env env, bool validate_during_replay)
-    : impl_(MakeUnique<Impl>(env, validate_during_replay)) {}
+Replayer::Replayer(spv_target_env env, bool validate_during_replay,
+                   spv_validator_options validator_options)
+    : impl_(MakeUnique<Impl>(env, validate_during_replay, validator_options)) {}
 
 Replayer::~Replayer() = default;
 
@@ -74,7 +78,8 @@
   }
 
   // Initial binary should be valid.
-  if (!tools.Validate(&binary_in[0], binary_in.size())) {
+  if (!tools.Validate(&binary_in[0], binary_in.size(),
+                      impl_->validator_options)) {
     impl_->consumer(SPV_MSG_INFO, nullptr, {},
                     "Initial binary is invalid; stopping.");
     return Replayer::ReplayerResultStatus::kInitialBinaryInvalid;
@@ -111,8 +116,8 @@
         ir_context->module()->ToBinary(&binary_to_validate, false);
 
         // Check whether the latest transformation led to a valid binary.
-        if (!tools.Validate(&binary_to_validate[0],
-                            binary_to_validate.size())) {
+        if (!tools.Validate(&binary_to_validate[0], binary_to_validate.size(),
+                            impl_->validator_options)) {
           impl_->consumer(SPV_MSG_INFO, nullptr, {},
                           "Binary became invalid during replay (set a "
                           "breakpoint to inspect); stopping.");
diff --git a/source/fuzz/replayer.h b/source/fuzz/replayer.h
index 1d58bae..e77d840 100644
--- a/source/fuzz/replayer.h
+++ b/source/fuzz/replayer.h
@@ -37,7 +37,8 @@
   };
 
   // Constructs a replayer from the given target environment.
-  explicit Replayer(spv_target_env env, bool validate_during_replay);
+  Replayer(spv_target_env env, bool validate_during_replay,
+           spv_validator_options validator_options);
 
   // Disables copy/move constructor/assignment operations.
   Replayer(const Replayer&) = delete;
diff --git a/source/fuzz/shrinker.cpp b/source/fuzz/shrinker.cpp
index 1bb92f1..b8e4145 100644
--- a/source/fuzz/shrinker.cpp
+++ b/source/fuzz/shrinker.cpp
@@ -60,20 +60,27 @@
 }  // namespace
 
 struct Shrinker::Impl {
-  explicit Impl(spv_target_env env, uint32_t limit, bool validate)
-      : target_env(env), step_limit(limit), validate_during_replay(validate) {}
+  Impl(spv_target_env env, uint32_t limit, bool validate,
+       spv_validator_options options)
+      : target_env(env),
+        step_limit(limit),
+        validate_during_replay(validate),
+        validator_options(options) {}
 
-  const spv_target_env target_env;    // Target environment.
-  MessageConsumer consumer;           // Message consumer.
-  const uint32_t step_limit;          // Step limit for reductions.
-  const bool validate_during_replay;  // Determines whether to check for
-                                      // validity during the replaying of
-                                      // transformations.
+  const spv_target_env target_env;          // Target environment.
+  MessageConsumer consumer;                 // Message consumer.
+  const uint32_t step_limit;                // Step limit for reductions.
+  const bool validate_during_replay;        // Determines whether to check for
+                                            // validity during the replaying of
+                                            // transformations.
+  spv_validator_options validator_options;  // Options to control validation.
 };
 
 Shrinker::Shrinker(spv_target_env env, uint32_t step_limit,
-                   bool validate_during_replay)
-    : impl_(MakeUnique<Impl>(env, step_limit, validate_during_replay)) {}
+                   bool validate_during_replay,
+                   spv_validator_options validator_options)
+    : impl_(MakeUnique<Impl>(env, step_limit, validate_during_replay,
+                             validator_options)) {}
 
 Shrinker::~Shrinker() = default;
 
@@ -113,7 +120,8 @@
   // succeeds, (b) get the binary that results from running these
   // transformations, and (c) get the subsequence of the initial transformations
   // that actually apply (in principle this could be a strict subsequence).
-  if (Replayer(impl_->target_env, impl_->validate_during_replay)
+  if (Replayer(impl_->target_env, impl_->validate_during_replay,
+               impl_->validator_options)
           .Run(binary_in, initial_facts, transformation_sequence_in,
                &current_best_binary, &current_best_transformations) !=
       Replayer::ReplayerResultStatus::kComplete) {
@@ -184,7 +192,8 @@
       // transformations inapplicable.
       std::vector<uint32_t> next_binary;
       protobufs::TransformationSequence next_transformation_sequence;
-      if (Replayer(impl_->target_env, false)
+      if (Replayer(impl_->target_env, impl_->validate_during_replay,
+                   impl_->validator_options)
               .Run(binary_in, initial_facts, transformations_with_chunk_removed,
                    &next_binary, &next_transformation_sequence) !=
           Replayer::ReplayerResultStatus::kComplete) {
diff --git a/source/fuzz/shrinker.h b/source/fuzz/shrinker.h
index 0163a53..17b15bf 100644
--- a/source/fuzz/shrinker.h
+++ b/source/fuzz/shrinker.h
@@ -50,8 +50,8 @@
       const std::vector<uint32_t>& binary, uint32_t counter)>;
 
   // Constructs a shrinker from the given target environment.
-  Shrinker(spv_target_env env, uint32_t step_limit,
-           bool validate_during_replay);
+  Shrinker(spv_target_env env, uint32_t step_limit, bool validate_during_replay,
+           spv_validator_options validator_options);
 
   // Disables copy/move constructor/assignment operations.
   Shrinker(const Shrinker&) = delete;
diff --git a/test/fuzz/fuzzer_replayer_test.cpp b/test/fuzz/fuzzer_replayer_test.cpp
index b91393e..b9024b9 100644
--- a/test/fuzz/fuzzer_replayer_test.cpp
+++ b/test/fuzz/fuzzer_replayer_test.cpp
@@ -1602,8 +1602,9 @@
     std::vector<uint32_t> fuzzer_binary_out;
     protobufs::TransformationSequence fuzzer_transformation_sequence_out;
 
-    Fuzzer fuzzer(env, seed, true);
-    fuzzer.SetMessageConsumer(kSilentConsumer);
+    spvtools::ValidatorOptions validator_options;
+    Fuzzer fuzzer(env, seed, true, validator_options);
+    fuzzer.SetMessageConsumer(kConsoleMessageConsumer);
     auto fuzzer_result_status =
         fuzzer.Run(binary_in, initial_facts, donor_suppliers,
                    &fuzzer_binary_out, &fuzzer_transformation_sequence_out);
@@ -1613,8 +1614,8 @@
     std::vector<uint32_t> replayer_binary_out;
     protobufs::TransformationSequence replayer_transformation_sequence_out;
 
-    Replayer replayer(env, false);
-    replayer.SetMessageConsumer(kSilentConsumer);
+    Replayer replayer(env, false, validator_options);
+    replayer.SetMessageConsumer(kConsoleMessageConsumer);
     auto replayer_result_status = replayer.Run(
         binary_in, initial_facts, fuzzer_transformation_sequence_out,
         &replayer_binary_out, &replayer_transformation_sequence_out);
diff --git a/test/fuzz/fuzzer_shrinker_test.cpp b/test/fuzz/fuzzer_shrinker_test.cpp
index c906a1e..24b4460 100644
--- a/test/fuzz/fuzzer_shrinker_test.cpp
+++ b/test/fuzz/fuzzer_shrinker_test.cpp
@@ -979,15 +979,19 @@
 // The |step_limit| parameter restricts the number of steps that the shrinker
 // will try; it can be set to something small for a faster (but less thorough)
 // test.
+//
+// The |validator_options| parameter provides validator options that should be
+// used during shrinking.
 void RunAndCheckShrinker(
     const spv_target_env& target_env, const std::vector<uint32_t>& binary_in,
     const protobufs::FactSequence& initial_facts,
     const protobufs::TransformationSequence& transformation_sequence_in,
     const Shrinker::InterestingnessFunction& interestingness_function,
     const std::vector<uint32_t>& expected_binary_out,
-    uint32_t expected_transformations_out_size, uint32_t step_limit) {
+    uint32_t expected_transformations_out_size, uint32_t step_limit,
+    spv_validator_options validator_options) {
   // Run the shrinker.
-  Shrinker shrinker(target_env, step_limit, false);
+  Shrinker shrinker(target_env, step_limit, false, validator_options);
   shrinker.SetMessageConsumer(kSilentConsumer);
 
   std::vector<uint32_t> binary_out;
@@ -1035,7 +1039,8 @@
   // Run the fuzzer and check that it successfully yields a valid binary.
   std::vector<uint32_t> fuzzer_binary_out;
   protobufs::TransformationSequence fuzzer_transformation_sequence_out;
-  Fuzzer fuzzer(env, seed, true);
+  spvtools::ValidatorOptions validator_options;
+  Fuzzer fuzzer(env, seed, true, validator_options);
   fuzzer.SetMessageConsumer(kSilentConsumer);
   auto fuzzer_result_status =
       fuzzer.Run(binary_in, initial_facts, donor_suppliers, &fuzzer_binary_out,
@@ -1048,9 +1053,10 @@
 
   // With the AlwaysInteresting test, we should quickly shrink to the original
   // binary with no transformations remaining.
-  RunAndCheckShrinker(
-      env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
-      AlwaysInteresting().AsFunction(), binary_in, 0, kReasonableStepLimit);
+  RunAndCheckShrinker(env, binary_in, initial_facts,
+                      fuzzer_transformation_sequence_out,
+                      AlwaysInteresting().AsFunction(), binary_in, 0,
+                      kReasonableStepLimit, validator_options);
 
   // With the OnlyInterestingFirstTime test, no shrinking should be achieved.
   RunAndCheckShrinker(
@@ -1058,14 +1064,14 @@
       OnlyInterestingFirstTime().AsFunction(), fuzzer_binary_out,
       static_cast<uint32_t>(
           fuzzer_transformation_sequence_out.transformation_size()),
-      kReasonableStepLimit);
+      kReasonableStepLimit, validator_options);
 
   // The PingPong test is unpredictable; passing an empty expected binary
   // means that we don't check anything beyond that shrinking completes
   // successfully.
-  RunAndCheckShrinker(env, binary_in, initial_facts,
-                      fuzzer_transformation_sequence_out,
-                      PingPong().AsFunction(), {}, 0, kSmallStepLimit);
+  RunAndCheckShrinker(
+      env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
+      PingPong().AsFunction(), {}, 0, kSmallStepLimit, validator_options);
 
   // The InterestingThenRandom test is unpredictable; passing an empty
   // expected binary means that we do not check anything about shrinking
@@ -1073,7 +1079,7 @@
   RunAndCheckShrinker(
       env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
       InterestingThenRandom(PseudoRandomGenerator(seed)).AsFunction(), {}, 0,
-      kSmallStepLimit);
+      kSmallStepLimit, validator_options);
 }
 
 TEST(FuzzerShrinkerTest, Miscellaneous1) {
diff --git a/tools/fuzz/fuzz.cpp b/tools/fuzz/fuzz.cpp
index 718d038..469c81b 100644
--- a/tools/fuzz/fuzz.cpp
+++ b/tools/fuzz/fuzz.cpp
@@ -145,6 +145,13 @@
   --version
                Display fuzzer version information.
 
+Supported validator options are as follows. See `spirv-val --help` for details.
+  --before-hlsl-legalization
+  --relax-block-layout
+  --relax-logical-pointer
+  --relax-struct-store
+  --scalar-block-layout
+  --skip-block-layout
 )",
       program, program, program, program);
 }
@@ -166,7 +173,8 @@
                       std::vector<std::string>* interestingness_test,
                       std::string* shrink_transformations_file,
                       std::string* shrink_temp_file_prefix,
-                      spvtools::FuzzerOptions* fuzzer_options) {
+                      spvtools::FuzzerOptions* fuzzer_options,
+                      spvtools::ValidatorOptions* validator_options) {
   uint32_t positional_arg_index = 0;
   bool only_positional_arguments_remain = false;
   bool force_render_red = false;
@@ -227,6 +235,18 @@
                               sizeof("--shrinker-temp-file-prefix=") - 1)) {
         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
         *shrink_temp_file_prefix = std::string(split_flag.second);
+      } else if (0 == strcmp(cur_arg, "--before-hlsl-legalization")) {
+        validator_options->SetBeforeHlslLegalization(true);
+      } else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) {
+        validator_options->SetRelaxLogicalPointer(true);
+      } else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
+        validator_options->SetRelaxBlockLayout(true);
+      } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
+        validator_options->SetScalarBlockLayout(true);
+      } else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
+        validator_options->SetSkipBlockLayout(true);
+      } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
+        validator_options->SetRelaxStructStore(true);
       } else if (0 == strcmp(cur_arg, "--")) {
         only_positional_arguments_remain = true;
       } else {
@@ -357,6 +377,7 @@
 
 bool Replay(const spv_target_env& target_env,
             spv_const_fuzzer_options fuzzer_options,
+            spv_validator_options validator_options,
             const std::vector<uint32_t>& binary_in,
             const spvtools::fuzz::protobufs::FactSequence& initial_facts,
             const std::string& replay_transformations_file,
@@ -368,8 +389,8 @@
                             &transformation_sequence)) {
     return false;
   }
-  spvtools::fuzz::Replayer replayer(target_env,
-                                    fuzzer_options->replay_validation_enabled);
+  spvtools::fuzz::Replayer replayer(
+      target_env, fuzzer_options->replay_validation_enabled, validator_options);
   replayer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
   auto replay_result_status =
       replayer.Run(binary_in, initial_facts, transformation_sequence,
@@ -380,6 +401,7 @@
 
 bool Shrink(const spv_target_env& target_env,
             spv_const_fuzzer_options fuzzer_options,
+            spv_validator_options validator_options,
             const std::vector<uint32_t>& binary_in,
             const spvtools::fuzz::protobufs::FactSequence& initial_facts,
             const std::string& shrink_transformations_file,
@@ -393,9 +415,9 @@
                             &transformation_sequence)) {
     return false;
   }
-  spvtools::fuzz::Shrinker shrinker(target_env,
-                                    fuzzer_options->shrinker_step_limit,
-                                    fuzzer_options->replay_validation_enabled);
+  spvtools::fuzz::Shrinker shrinker(
+      target_env, fuzzer_options->shrinker_step_limit,
+      fuzzer_options->replay_validation_enabled, validator_options);
   shrinker.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
 
   assert(!interestingness_command.empty() &&
@@ -434,6 +456,7 @@
 
 bool Fuzz(const spv_target_env& target_env,
           spv_const_fuzzer_options fuzzer_options,
+          spv_validator_options validator_options,
           const std::vector<uint32_t>& binary_in,
           const spvtools::fuzz::protobufs::FactSequence& initial_facts,
           const std::string& donors, std::vector<uint32_t>* binary_out,
@@ -469,7 +492,7 @@
       fuzzer_options->has_random_seed
           ? fuzzer_options->random_seed
           : static_cast<uint32_t>(std::random_device()()),
-      fuzzer_options->fuzzer_pass_validation_enabled);
+      fuzzer_options->fuzzer_pass_validation_enabled, validator_options);
   fuzzer.SetMessageConsumer(message_consumer);
   auto fuzz_result_status =
       fuzzer.Run(binary_in, initial_facts, donor_suppliers, binary_out,
@@ -513,11 +536,13 @@
   std::string shrink_temp_file_prefix = "temp_";
 
   spvtools::FuzzerOptions fuzzer_options;
+  spvtools::ValidatorOptions validator_options;
 
-  FuzzStatus status = ParseFlags(
-      argc, argv, &in_binary_file, &out_binary_file, &donors_file,
-      &replay_transformations_file, &interestingness_test,
-      &shrink_transformations_file, &shrink_temp_file_prefix, &fuzzer_options);
+  FuzzStatus status =
+      ParseFlags(argc, argv, &in_binary_file, &out_binary_file, &donors_file,
+                 &replay_transformations_file, &interestingness_test,
+                 &shrink_transformations_file, &shrink_temp_file_prefix,
+                 &fuzzer_options, &validator_options);
 
   if (status.action == FuzzActions::STOP) {
     return status.code;
@@ -561,14 +586,15 @@
       }
       break;
     case FuzzActions::FUZZ:
-      if (!Fuzz(target_env, fuzzer_options, binary_in, initial_facts,
-                donors_file, &binary_out, &transformations_applied)) {
+      if (!Fuzz(target_env, fuzzer_options, validator_options, binary_in,
+                initial_facts, donors_file, &binary_out,
+                &transformations_applied)) {
         return 1;
       }
       break;
     case FuzzActions::REPLAY:
-      if (!Replay(target_env, fuzzer_options, binary_in, initial_facts,
-                  replay_transformations_file, &binary_out,
+      if (!Replay(target_env, fuzzer_options, validator_options, binary_in,
+                  initial_facts, replay_transformations_file, &binary_out,
                   &transformations_applied)) {
         return 1;
       }
@@ -579,9 +605,9 @@
                   << std::endl;
         return 1;
       }
-      if (!Shrink(target_env, fuzzer_options, binary_in, initial_facts,
-                  shrink_transformations_file, shrink_temp_file_prefix,
-                  interestingness_test, &binary_out,
+      if (!Shrink(target_env, fuzzer_options, validator_options, binary_in,
+                  initial_facts, shrink_transformations_file,
+                  shrink_temp_file_prefix, interestingness_test, &binary_out,
                   &transformations_applied)) {
         return 1;
       }