[tint] Use embedded benchmark inputs

Rework the benchmark framework to use the auto-generate benchmark
input header instead of loading shaders at runtime.

During initialization, build a map from benchmark name to WGSL shader,
converting SPIR-V inputs as necessary. This map is used when a
benchmark requests the parsed WGSL program for a registered benchmark.

Bug: 42251293
Change-Id: I002d7ad3690eb973c999866e4aa57e8d4aecb7a2
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/200517
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: James Price <jrprice@google.com>
diff --git a/src/tint/cmd/bench/bench.cc b/src/tint/cmd/bench/bench.cc
index 5114ada..9fbe9ef 100644
--- a/src/tint/cmd/bench/bench.cc
+++ b/src/tint/cmd/bench/bench.cc
@@ -25,145 +25,65 @@
 // 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 <filesystem>
-#include <iostream>
-#include <utility>
-#include <vector>
-
 #include "src/tint/cmd/bench/bench.h"
 
-#if TINT_BUILD_SPV_READER
+#include <iostream>
+#include <utility>
+
 #include "src/tint/lang/spirv/reader/reader.h"
-#endif
-
-#if TINT_BUILD_WGSL_WRITER
-#include "src/tint/lang/wgsl/writer/writer.h"
-#endif
-
-#if TINT_BUILD_WGSL_READER
 #include "src/tint/lang/wgsl/reader/reader.h"
-#endif
-
-#include "src/tint/utils/text/string.h"
-#include "src/tint/utils/text/string_stream.h"
+#include "src/tint/lang/wgsl/writer/writer.h"
+#include "src/tint/utils/containers/hashmap.h"
 
 namespace tint::bench {
 namespace {
 
-std::filesystem::path kInputFileDir;
-
-/// Copies the content from the file named `input_file` to `buffer`,
-/// assuming each element in the file is of type `T`.  If any error occurs,
-/// writes error messages to the standard error stream and returns false.
-/// Assumes the size of a `T` object is divisible by its required alignment.
-/// @returns true if we successfully read the file.
-template <typename T>
-Result<std::vector<T>> ReadFile(const std::string& input_file) {
-    FILE* file = nullptr;
-#if defined(_MSC_VER)
-    fopen_s(&file, input_file.c_str(), "rb");
-#else
-    file = fopen(input_file.c_str(), "rb");
-#endif
-    if (!file) {
-        return Failure{"Failed to open " + input_file};
-    }
-
-    fseek(file, 0, SEEK_END);
-    const auto file_size = static_cast<size_t>(ftell(file));
-    if (0 != (file_size % sizeof(T))) {
-        StringStream err;
-        err << "File " << input_file
-            << " does not contain an integral number of objects: " << file_size
-            << " bytes in the file, require " << sizeof(T) << " bytes per object";
-        fclose(file);
-        return Failure{err.str()};
-    }
-    fseek(file, 0, SEEK_SET);
-
-    std::vector<T> buffer;
-    buffer.resize(file_size / sizeof(T));
-
-    size_t bytes_read = fread(buffer.data(), 1, file_size, file);
-    fclose(file);
-    if (bytes_read != file_size) {
-        return Failure{"Failed to read " + input_file};
-    }
-
-    return buffer;
-}
-
-bool FindBenchmarkInputDir() {
-    // Attempt to find the benchmark input files by searching up from the current
-    // working directory.
-    auto path = std::filesystem::current_path();
-    while (std::filesystem::is_directory(path)) {
-        auto test = path / "test" / "tint" / "benchmark";
-        if (std::filesystem::is_directory(test)) {
-            kInputFileDir = test;
-            return true;
-        }
-        auto parent = path.parent_path();
-        if (path == parent) {
-            break;
-        }
-        path = parent;
-    }
-    return false;
-}
+// A map from benchmark input name to the corresponding WGSL shader.
+Hashmap<std::string, std::string, 16> kBenchmarkWgslShaders;
 
 }  // namespace
 
 bool Initialize() {
-    if (!FindBenchmarkInputDir()) {
-        std::cerr << "failed to locate benchmark input files\n";
-        return false;
-    }
-    return true;
-}
-
-Result<Source::File> LoadInputFile(std::string name) {
-    auto path = std::filesystem::path(name).is_absolute() ? name : (kInputFileDir / name).string();
-    if (tint::HasSuffix(path, ".wgsl")) {
-#if TINT_BUILD_WGSL_READER
-        auto data = ReadFile<uint8_t>(path);
-        if (data != Success) {
-            return data.Failure();
-        }
-        return tint::Source::File(path, std::string(data->begin(), data->end()));
-#else
-        return Failure{"cannot load " + path + " file as TINT_BUILD_WGSL_READER is not enabled"};
-#endif
-    }
-    if (tint::HasSuffix(path, ".spv")) {
-#if !TINT_BUILD_SPV_READER
-        return Failure{"cannot load " + path + " as TINT_BUILD_SPV_READER is not enabled"};
-#elif !TINT_BUILD_WGSL_WRITER
-        return Failure{"cannot load " + path + " as TINT_BUILD_WGSL_WRITER is not enabled"};
-#else
-
-        auto spirv = ReadFile<uint32_t>(path);
-        if (spirv == Success) {
+    // Populate the map from benchmark input name to WGSL shader.
+    for (auto& benchmark : kBenchmarkInputs) {
+        if (!benchmark.wgsl.empty()) {
+            // If the input is WGSL, we just add it as is.
+            kBenchmarkWgslShaders.Add(benchmark.name, benchmark.wgsl);
+        } else if (!benchmark.spirv.empty()) {
+            // If the input is SPIR-V, we convert it to WGSL and add that.
             tint::spirv::reader::Options spirv_opts;
             spirv_opts.allow_non_uniform_derivatives = true;
-            auto program = tint::spirv::reader::Read(spirv.Get(), spirv_opts);
+            auto program = tint::spirv::reader::Read(benchmark.spirv, spirv_opts);
             if (!program.IsValid()) {
-                return Failure{program.Diagnostics()};
+                std::cerr << "Failed to convert '" << benchmark.name
+                          << "': " << program.Diagnostics() << "\n";
+                return false;
             }
             auto result = tint::wgsl::writer::Generate(program, {});
             if (result != Success) {
-                return result.Failure();
+                std::cerr << "Failed to generate WGSL for '" << benchmark.name
+                          << "': " << result.Failure() << "\n";
+                return false;
             }
-            return tint::Source::File(path, result->wgsl);
+            kBenchmarkWgslShaders.Add(benchmark.name, result->wgsl);
+        } else {
+            TINT_UNREACHABLE();
         }
-        return spirv.Failure();
-#endif
     }
-    return Failure{"unsupported file extension: '" + name + "'"};
+
+    return true;
 }
 
-Result<ProgramAndFile> LoadProgram(std::string name) {
-    auto res = bench::LoadInputFile(name);
+Result<Source::File> GetWgslFile(std::string name) {
+    auto wgsl = kBenchmarkWgslShaders.GetOr(name, "");
+    if (wgsl.empty()) {
+        return Failure{"failed to find WGSL shader for '" + name + "'"};
+    }
+    return tint::Source::File("<input>", wgsl);
+}
+
+Result<ProgramAndFile> GetWgslProgram(std::string name) {
+    auto res = GetWgslFile(name);
     if (res != Success) {
         return res.Failure();
     }
diff --git a/src/tint/cmd/bench/bench.h b/src/tint/cmd/bench/bench.h
index e6cf6e7..7a8fb70 100644
--- a/src/tint/cmd/bench/bench.h
+++ b/src/tint/cmd/bench/bench.h
@@ -38,8 +38,10 @@
 #include "src/tint/utils/macros/concat.h"
 #include "src/tint/utils/result/result.h"
 
-namespace tint::bench {
+// Autogenerated header that defines the benchmark shaders and macros for registering benchmarks.
+#include "src/tint/cmd/bench/benchmark_inputs.h"  // GEN_BUILD:IGNORE_INCLUDE
 
+namespace tint::bench {
 /// ProgramAndFile holds a Program and a Source::File.
 struct ProgramAndFile {
     /// The tint program parsed from file.
@@ -53,52 +55,16 @@
 /// @returns true on success, false of failure
 bool Initialize();
 
-/// LoadInputFile attempts to load a benchmark input file with the given file
-/// name. Accepts files with the .wgsl and .spv extension.
-/// SPIR-V files are automatically converted to WGSL.
-/// @param name the file name
-/// @returns the loaded Source::File
-Result<Source::File> LoadInputFile(std::string name);
+/// GetWgslFile retrieves the WGSL for the benchmark input shader called @p name.
+/// Benchmarks that are SPIR-V shaders will have been converted to WGSL at startup.
+/// @param name the benchmark input name
+/// @returns the Source::File
+Result<Source::File> GetWgslFile(std::string name);
 
-/// LoadInputFile attempts to load a benchmark input program with the given file
-/// name.
-/// @param name the file name
-/// @returns the loaded Program
-Result<ProgramAndFile> LoadProgram(std::string name);
-
-// If TINT_BENCHMARK_EXTERNAL_SHADERS_HEADER is defined, include that to
-// declare the TINT_BENCHMARK_EXTERNAL_WGSL_PROGRAMS() and TINT_BENCHMARK_EXTERNAL_SPV_PROGRAMS()
-// macros, which appends external programs to the TINT_BENCHMARK_WGSL_PROGRAMS() and
-// TINT_BENCHMARK_SPV_PROGRAMS() list.
-#ifdef TINT_BENCHMARK_EXTERNAL_SHADERS_HEADER
-#include TINT_BENCHMARK_EXTERNAL_SHADERS_HEADER
-#else
-#define TINT_BENCHMARK_EXTERNAL_WGSL_PROGRAMS(x)
-#define TINT_BENCHMARK_EXTERNAL_SPV_PROGRAMS(x)
-#endif
-
-/// Declares a benchmark with the given function and WGSL file name
-#define TINT_BENCHMARK_WGSL_PROGRAM(FUNC, WGSL_NAME) BENCHMARK_CAPTURE(FUNC, WGSL_NAME, WGSL_NAME)
-
-/// Declares a set of benchmarks for the given function using a list of WGSL files.
-#define TINT_BENCHMARK_WGSL_PROGRAMS(FUNC)                                   \
-    TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "atan2-const-eval.wgsl");              \
-    TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "cluster-lights.wgsl");                \
-    TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "metaball-isosurface.wgsl");           \
-    TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "particles.wgsl");                     \
-    TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "shadow-fragment.wgsl");               \
-    TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "skinned-shadowed-pbr-fragment.wgsl"); \
-    TINT_BENCHMARK_WGSL_PROGRAM(FUNC, "skinned-shadowed-pbr-vertex.wgsl");   \
-    TINT_BENCHMARK_EXTERNAL_WGSL_PROGRAMS(FUNC)
-
-/// Declares a set of benchmarks for the given function using a list of SPIR-V files.
-#define TINT_BENCHMARK_SPV_PROGRAMS(FUNC) TINT_BENCHMARK_EXTERNAL_SPV_PROGRAMS(FUNC)
-
-/// Declares a set of benchmarks for the given function using a list of WGSL and SPIR-V files.
-#define TINT_BENCHMARK_PROGRAMS(FUNC)  \
-    TINT_BENCHMARK_WGSL_PROGRAMS(FUNC) \
-    TINT_BENCHMARK_SPV_PROGRAMS(FUNC)  \
-    TINT_REQUIRE_SEMICOLON
+/// GetWgslProgram parses the WGSL for a benchmark input program with the given name.
+/// @param name the benchmark input name
+/// @returns the parsed WGSL program
+Result<ProgramAndFile> GetWgslProgram(std::string name);
 
 }  // namespace tint::bench
 
diff --git a/src/tint/lang/glsl/writer/writer_bench.cc b/src/tint/lang/glsl/writer/writer_bench.cc
index 8505dac..659adb9 100644
--- a/src/tint/lang/glsl/writer/writer_bench.cc
+++ b/src/tint/lang/glsl/writer/writer_bench.cc
@@ -36,7 +36,7 @@
 namespace {
 
 void GenerateGLSL(benchmark::State& state, std::string input_name) {
-    auto res = bench::LoadProgram(input_name);
+    auto res = bench::GetWgslProgram(input_name);
     if (res != Success) {
         state.SkipWithError(res.Failure().reason.Str());
         return;
diff --git a/src/tint/lang/hlsl/writer/writer_bench.cc b/src/tint/lang/hlsl/writer/writer_bench.cc
index 55939cf..f84d151 100644
--- a/src/tint/lang/hlsl/writer/writer_bench.cc
+++ b/src/tint/lang/hlsl/writer/writer_bench.cc
@@ -34,7 +34,7 @@
 namespace {
 
 void GenerateHLSL(benchmark::State& state, std::string input_name) {
-    auto res = bench::LoadProgram(input_name);
+    auto res = bench::GetWgslProgram(input_name);
     if (res != Success) {
         state.SkipWithError(res.Failure().reason.Str());
         return;
diff --git a/src/tint/lang/msl/writer/writer_bench.cc b/src/tint/lang/msl/writer/writer_bench.cc
index 47d07a1..e9f30ae 100644
--- a/src/tint/lang/msl/writer/writer_bench.cc
+++ b/src/tint/lang/msl/writer/writer_bench.cc
@@ -37,7 +37,7 @@
 namespace {
 
 void GenerateMSL(benchmark::State& state, std::string input_name) {
-    auto res = bench::LoadProgram(input_name);
+    auto res = bench::GetWgslProgram(input_name);
     if (res != Success) {
         state.SkipWithError(res.Failure().reason.Str());
         return;
diff --git a/src/tint/lang/spirv/writer/writer_bench.cc b/src/tint/lang/spirv/writer/writer_bench.cc
index 11a1c7a..a89e10d 100644
--- a/src/tint/lang/spirv/writer/writer_bench.cc
+++ b/src/tint/lang/spirv/writer/writer_bench.cc
@@ -46,7 +46,7 @@
 namespace {
 
 void GenerateSPIRV(benchmark::State& state, std::string input_name) {
-    auto res = bench::LoadProgram(input_name);
+    auto res = bench::GetWgslProgram(input_name);
     if (res != Success) {
         state.SkipWithError(res.Failure().reason.Str());
         return;
@@ -61,7 +61,7 @@
 
 void GenerateSPIRV_UseIR(benchmark::State& state, std::string input_name) {
 #if TINT_BUILD_WGSL_READER
-    auto res = bench::LoadProgram(input_name);
+    auto res = bench::GetWgslProgram(input_name);
     if (res != Success) {
         state.SkipWithError(res.Failure().reason.Str());
         return;
diff --git a/src/tint/lang/wgsl/reader/reader_bench.cc b/src/tint/lang/wgsl/reader/reader_bench.cc
index ba260af..39e1aa6 100644
--- a/src/tint/lang/wgsl/reader/reader_bench.cc
+++ b/src/tint/lang/wgsl/reader/reader_bench.cc
@@ -34,7 +34,7 @@
 namespace {
 
 void ParseWGSL(benchmark::State& state, std::string input_name) {
-    auto res = bench::LoadInputFile(input_name);
+    auto res = bench::GetWgslFile(input_name);
     if (res != Success) {
         state.SkipWithError(res.Failure().reason.Str());
         return;
diff --git a/src/tint/lang/wgsl/writer/writer_bench.cc b/src/tint/lang/wgsl/writer/writer_bench.cc
index 285710e..1f9cbfc 100644
--- a/src/tint/lang/wgsl/writer/writer_bench.cc
+++ b/src/tint/lang/wgsl/writer/writer_bench.cc
@@ -34,7 +34,7 @@
 namespace {
 
 void GenerateWGSL(benchmark::State& state, std::string input_name) {
-    auto res = bench::LoadProgram(input_name);
+    auto res = bench::GetWgslProgram(input_name);
     if (res != Success) {
         state.SkipWithError(res.Failure().reason.Str());
         return;