[tint][fuzzers] Add --concurrent flag to tint_wgsl_fuzzer

Checks that all the WGSL fuzzers can be run concurrently

Change-Id: Iad7a95918954848ac7ffd6cee17c5538cf5c2cd3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/155382
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: James Price <jrprice@google.com>
Auto-Submit: Ben Clayton <bclayton@google.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/cmd/fuzz/wgsl/BUILD.cmake b/src/tint/cmd/fuzz/wgsl/BUILD.cmake
index b485bef..9ba6530 100644
--- a/src/tint/cmd/fuzz/wgsl/BUILD.cmake
+++ b/src/tint/cmd/fuzz/wgsl/BUILD.cmake
@@ -53,6 +53,7 @@
   tint_lang_wgsl_program
   tint_lang_wgsl_sem
   tint_lang_wgsl_fuzz
+  tint_utils_cli
   tint_utils_containers
   tint_utils_diagnostic
   tint_utils_ice
@@ -62,6 +63,7 @@
   tint_utils_memory
   tint_utils_result
   tint_utils_rtti
+  tint_utils_strconv
   tint_utils_symbol
   tint_utils_text
   tint_utils_traits
@@ -113,6 +115,10 @@
   tint_utils_traits
 )
 
+tint_target_add_external_dependencies(tint_cmd_fuzz_wgsl_fuzz fuzz
+  "thread"
+)
+
 if(TINT_BUILD_WGSL_READER)
   tint_target_add_dependencies(tint_cmd_fuzz_wgsl_fuzz fuzz
     tint_lang_wgsl_reader
diff --git a/src/tint/cmd/fuzz/wgsl/BUILD.gn b/src/tint/cmd/fuzz/wgsl/BUILD.gn
index 908cd3b..5c4ccc6 100644
--- a/src/tint/cmd/fuzz/wgsl/BUILD.gn
+++ b/src/tint/cmd/fuzz/wgsl/BUILD.gn
@@ -44,6 +44,7 @@
       "wgsl_fuzz.h",
     ]
     deps = [
+      "${tint_src_dir}:thread",
       "${tint_src_dir}/api/common",
       "${tint_src_dir}/lang/core",
       "${tint_src_dir}/lang/core/constant",
@@ -86,6 +87,7 @@
       "${tint_src_dir}/lang/wgsl/ast",
       "${tint_src_dir}/lang/wgsl/program",
       "${tint_src_dir}/lang/wgsl/sem",
+      "${tint_src_dir}/utils/cli",
       "${tint_src_dir}/utils/containers",
       "${tint_src_dir}/utils/diagnostic",
       "${tint_src_dir}/utils/ice",
@@ -95,6 +97,7 @@
       "${tint_src_dir}/utils/memory",
       "${tint_src_dir}/utils/result",
       "${tint_src_dir}/utils/rtti",
+      "${tint_src_dir}/utils/strconv",
       "${tint_src_dir}/utils/symbol",
       "${tint_src_dir}/utils/text",
       "${tint_src_dir}/utils/traits",
diff --git a/src/tint/cmd/fuzz/wgsl/main_fuzz.cc b/src/tint/cmd/fuzz/wgsl/main_fuzz.cc
index 586493d..fa27edc 100644
--- a/src/tint/cmd/fuzz/wgsl/main_fuzz.cc
+++ b/src/tint/cmd/fuzz/wgsl/main_fuzz.cc
@@ -25,12 +25,64 @@
 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+#include <iostream>
+
 #include "src/tint/cmd/fuzz/wgsl/wgsl_fuzz.h"
+#include "src/tint/utils/cli/cli.h"
+
+namespace {
+
+tint::fuzz::wgsl::Options options;
+
+}
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
     if (size > 0) {
         std::string_view wgsl(reinterpret_cast<const char*>(data), size);
-        tint::fuzz::wgsl::Run(wgsl);
+        tint::fuzz::wgsl::Run(wgsl, options);
     }
     return 0;
 }
+
+extern "C" int LLVMFuzzerInitialize(int* argc, const char*** argv) {
+    tint::cli::OptionSet opts;
+
+    tint::Vector<std::string_view, 8> arguments;
+    for (int i = 1; i < *argc; i++) {
+        std::string_view arg((*argv)[i]);
+        if (!arg.empty()) {
+            arguments.Push(arg);
+        }
+    }
+
+    auto show_help = [&] {
+        std::cerr << "Custom fuzzer options:" << std::endl;
+        opts.ShowHelp(std::cerr);
+        std::cerr << std::endl;
+        // Change args to show libfuzzer help
+        std::cerr << "Standard libfuzzer ";  // libfuzzer will print 'Usage:'
+        static const char* help[] = {(*argv)[0], "-help=1"};
+        *argc = 2;
+        *argv = help;
+    };
+
+    auto& opt_help = opts.Add<tint::cli::BoolOption>("help", "shows the usage");
+    auto& opt_concurrent =
+        opts.Add<tint::cli::BoolOption>("concurrent", "runs the fuzzers concurrently");
+
+    tint::cli::ParseOptions parse_opts;
+    parse_opts.ignore_unknown = true;
+    if (auto res = opts.Parse(arguments, parse_opts); !res) {
+        show_help();
+        std::cerr << res.Failure();
+        return 0;
+    }
+
+    if (opt_help.value.value_or(false)) {
+        show_help();
+        return 0;
+    }
+
+    options.run_concurrently = opt_concurrent.value.value_or(false);
+    return 0;
+}
diff --git a/src/tint/cmd/fuzz/wgsl/wgsl_fuzz.cc b/src/tint/cmd/fuzz/wgsl/wgsl_fuzz.cc
index a139e91..69d3587 100644
--- a/src/tint/cmd/fuzz/wgsl/wgsl_fuzz.cc
+++ b/src/tint/cmd/fuzz/wgsl/wgsl_fuzz.cc
@@ -28,6 +28,7 @@
 #include "src/tint/cmd/fuzz/wgsl/wgsl_fuzz.h"
 
 #include <iostream>
+#include <thread>
 
 #include "src/tint/lang/wgsl/reader/reader.h"
 #include "src/tint/utils/containers/vector.h"
@@ -38,7 +39,7 @@
 namespace {
 
 Vector<ProgramFuzzer, 32> fuzzers;
-std::string_view currently_running;
+thread_local std::string_view currently_running;
 
 [[noreturn]] void TintInternalCompilerErrorReporter(const tint::InternalCompilerError& err) {
     std::cerr << "ICE while running fuzzer: '" << currently_running << "'" << std::endl;
@@ -52,7 +53,7 @@
     fuzzers.Push(fuzzer);
 }
 
-void Run(std::string_view wgsl) {
+void Run(std::string_view wgsl, const Options& options) {
     tint::SetInternalCompilerErrorReporter(&TintInternalCompilerErrorReporter);
 
     // Ensure that fuzzers are sorted. Without this, the fuzzers may be registered in any order,
@@ -69,10 +70,26 @@
     }
 
     // Run each of the program fuzzer functions
-    TINT_DEFER(currently_running = "");
-    for (auto& fuzzer : fuzzers) {
-        currently_running = fuzzer.name;
-        fuzzer.fn(program);
+    if (options.run_concurrently) {
+        size_t n = fuzzers.Length();
+        tint::Vector<std::thread, 32> threads;
+        threads.Resize(n);
+        for (size_t i = 0; i < n; i++) {
+            threads[i] = std::thread([i, &program] {
+                auto& fuzzer = fuzzers[i];
+                currently_running = fuzzer.name;
+                fuzzer.fn(program);
+            });
+        }
+        for (auto& thread : threads) {
+            thread.join();
+        }
+    } else {
+        TINT_DEFER(currently_running = "");
+        for (auto& fuzzer : fuzzers) {
+            currently_running = fuzzer.name;
+            fuzzer.fn(program);
+        }
     }
 }
 
diff --git a/src/tint/cmd/fuzz/wgsl/wgsl_fuzz.h b/src/tint/cmd/fuzz/wgsl/wgsl_fuzz.h
index 2231743..909c394 100644
--- a/src/tint/cmd/fuzz/wgsl/wgsl_fuzz.h
+++ b/src/tint/cmd/fuzz/wgsl/wgsl_fuzz.h
@@ -47,9 +47,16 @@
     Fn* fn = nullptr;
 };
 
+/// Options for Run()
+struct Options {
+    /// If true, the fuzzers will be run concurrently on separate threads.
+    bool run_concurrently = false;
+};
+
 /// Runs all the registered WGSL fuzzers with the supplied WGSL
 /// @param wgsl the input WGSL
-void Run(std::string_view wgsl);
+/// @param options the options for running the fuzzers
+void Run(std::string_view wgsl, const Options& options);
 
 /// Registers the fuzzer function with the WGSL fuzzer executable.
 /// @param fuzzer the fuzzer
diff --git a/src/tint/utils/cli/cli.cc b/src/tint/utils/cli/cli.cc
index 5095b73..557d8bc 100644
--- a/src/tint/utils/cli/cli.cc
+++ b/src/tint/utils/cli/cli.cc
@@ -37,6 +37,7 @@
 
 namespace tint::cli {
 
+Option::Option() = default;
 Option::~Option() = default;
 
 void OptionSet::ShowHelp(std::ostream& s_out) {
@@ -131,7 +132,8 @@
     }
 }
 
-Result<OptionSet::Unconsumed> OptionSet::Parse(VectorRef<std::string_view> arguments_raw) {
+Result<OptionSet::Unconsumed> OptionSet::Parse(VectorRef<std::string_view> arguments_raw,
+                                               const ParseOptions& parse_options /* = {} */) {
     // Build a map of name to option, and set defaults
     Hashmap<std::string, Option*, 32> options_by_name;
     for (auto* opt : options.Objects()) {
@@ -171,7 +173,7 @@
             if (auto err = (*opt)->Parse(arguments); !err.empty()) {
                 return Failure{err};
             }
-        } else {
+        } else if (!parse_options.ignore_unknown) {
             StringStream err;
             err << "unknown flag: " << arg << std::endl;
             auto names = options_by_name.Keys();
diff --git a/src/tint/utils/cli/cli.h b/src/tint/utils/cli/cli.h
index 5bfa35a..b315884 100644
--- a/src/tint/utils/cli/cli.h
+++ b/src/tint/utils/cli/cli.h
@@ -101,6 +101,9 @@
     /// An alias to std::string, used to hold error messages.
     using Error = std::string;
 
+    /// Constructor
+    Option();
+
     /// Destructor
     virtual ~Option();
 
@@ -162,6 +165,18 @@
         }
         return err;
     }
+
+  private:
+    Option(const Option&) = delete;
+    Option& operator=(const Option&) = delete;
+    Option(Option&&) = delete;
+    Option& operator=(Option&&) = delete;
+};
+
+/// Options for OptionSet::Parse
+struct ParseOptions {
+    /// If true, then unknown flags will be ignored instead of treated as an error
+    bool ignore_unknown = false;
 };
 
 /// OptionSet is a set of Options, which can parse the command line arguments.
@@ -186,8 +201,10 @@
 
     /// Parses all the options in @p options.
     /// @param arguments the command line arguments, excluding the initial executable name
+    /// @param parse_options the optional parser options
     /// @return a Result holding a list of arguments that were not consumed as options
-    Result<Unconsumed> Parse(VectorRef<std::string_view> arguments);
+    Result<Unconsumed> Parse(VectorRef<std::string_view> arguments,
+                             const ParseOptions& parse_options = {});
 
   private:
     /// The list of options to parse
diff --git a/src/tint/utils/cli/cli_test.cc b/src/tint/utils/cli/cli_test.cc
index 6673a01..4db57f8 100644
--- a/src/tint/utils/cli/cli_test.cc
+++ b/src/tint/utils/cli/cli_test.cc
@@ -159,6 +159,28 @@
 )");
 }
 
+TEST_F(CLITest, UnknownFlag) {
+    OptionSet opts;
+    opts.Add<BoolOption>("my_option", "a boolean value");
+
+    auto res = opts.Parse(Split("--myoption false", " "));
+    ASSERT_FALSE(res) << res;
+    EXPECT_EQ(res.Failure().reason.str(), R"(error: unknown flag: --myoption
+Did you mean '--my_option'?)");
+}
+
+TEST_F(CLITest, UnknownFlag_Ignored) {
+    OptionSet opts;
+    auto& opt = opts.Add<BoolOption>("my_option", "a boolean value");
+
+    ParseOptions parse_opts;
+    parse_opts.ignore_unknown = true;
+
+    auto res = opts.Parse(Split("--myoption false", " "), parse_opts);
+    ASSERT_TRUE(res) << res;
+    EXPECT_EQ(opt.value, std::nullopt);
+}
+
 TEST_F(CLITest, ParseBool_Flag) {
     OptionSet opts;
     auto& opt = opts.Add<BoolOption>("my_option", "a boolean value");