Add tests for fuzzers::RandomGenerator

BUG=tint:1019

Change-Id: Ia462080877a97348c5589bfa71231a832a7ebfd3
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/70081
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
diff --git a/fuzzers/BUILD.gn b/fuzzers/BUILD.gn
index ebcfde3..eb017d3 100644
--- a/fuzzers/BUILD.gn
+++ b/fuzzers/BUILD.gn
@@ -54,7 +54,7 @@
   # fuzzer_test doesn't have configs members, so need to define them in an empty
   # source_set.
 
-  source_set("tint_fuzzer_common") {
+  source_set("tint_fuzzer_common_src") {
     public_configs = [
       "${tint_root_dir}/src:tint_config",
       "${tint_root_dir}/src:tint_common_config",
@@ -67,8 +67,12 @@
 
     sources = [
       "data_builder.h",
+      "mersenne_twister_engine.cc",
+      "mersenne_twister_engine.h",
       "random_generator.cc",
       "random_generator.h",
+      "random_generator_engine.cc",
+      "random_generator_engine.h",
       "tint_common_fuzzer.cc",
       "tint_common_fuzzer.h",
       "tint_reader_writer_fuzzer.h",
@@ -76,8 +80,8 @@
     ]
   }
 
-  source_set("tint_fuzzer_common_with_init") {
-    public_deps = [ ":tint_fuzzer_common" ]
+  source_set("tint_fuzzer_common_with_init_src") {
+    public_deps = [ ":tint_fuzzer_common_src" ]
 
     sources = [
       "cli.cc",
@@ -90,7 +94,7 @@
   if (tint_build_wgsl_reader && tint_build_wgsl_writer) {
     fuzzer_test("tint_ast_clone_fuzzer") {
       sources = [ "tint_ast_clone_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
       dict = "dictionary.txt"
       libfuzzer_options = tint_fuzzer_common_libfuzzer_options
       seed_corpus = fuzzer_corpus_wgsl_dir
@@ -115,7 +119,7 @@
 
     fuzzer_test("tint_wgsl_reader_wgsl_writer_fuzzer") {
       sources = [ "tint_wgsl_reader_wgsl_writer_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
       dict = "dictionary.txt"
       libfuzzer_options = tint_fuzzer_common_libfuzzer_options
       seed_corpus = fuzzer_corpus_wgsl_dir
@@ -126,7 +130,7 @@
   if (tint_build_wgsl_reader && tint_build_spv_writer) {
     fuzzer_test("tint_all_transforms_fuzzer") {
       sources = [ "tint_all_transforms_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
       dict = "dictionary.txt"
       libfuzzer_options = tint_fuzzer_common_libfuzzer_options
       seed_corpus = fuzzer_corpus_wgsl_dir
@@ -143,7 +147,7 @@
 
     fuzzer_test("tint_binding_remapper_fuzzer") {
       sources = [ "tint_binding_remapper_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
       dict = "dictionary.txt"
       libfuzzer_options = tint_fuzzer_common_libfuzzer_options
       seed_corpus = fuzzer_corpus_wgsl_dir
@@ -152,7 +156,7 @@
 
     fuzzer_test("tint_first_index_offset_fuzzer") {
       sources = [ "tint_first_index_offset_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
       dict = "dictionary.txt"
       libfuzzer_options = tint_fuzzer_common_libfuzzer_options
       seed_corpus = fuzzer_corpus_wgsl_dir
@@ -169,7 +173,7 @@
 
     fuzzer_test("tint_renamer_fuzzer") {
       sources = [ "tint_renamer_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
       dict = "dictionary.txt"
       libfuzzer_options = tint_fuzzer_common_libfuzzer_options
       seed_corpus = fuzzer_corpus_wgsl_dir
@@ -178,7 +182,7 @@
 
     fuzzer_test("tint_robustness_fuzzer") {
       sources = [ "tint_robustness_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
       dict = "dictionary.txt"
       libfuzzer_options = tint_fuzzer_common_libfuzzer_options
       seed_corpus = fuzzer_corpus_wgsl_dir
@@ -187,7 +191,7 @@
 
     fuzzer_test("tint_single_entry_point_fuzzer") {
       sources = [ "tint_single_entry_point_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
       dict = "dictionary.txt"
       libfuzzer_options = tint_fuzzer_common_libfuzzer_options
       seed_corpus = fuzzer_corpus_wgsl_dir
@@ -196,7 +200,7 @@
 
     fuzzer_test("tint_vertex_pulling_fuzzer") {
       sources = [ "tint_vertex_pulling_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
       dict = "dictionary.txt"
       libfuzzer_options = tint_fuzzer_common_libfuzzer_options
       seed_corpus = fuzzer_corpus_wgsl_dir
@@ -205,7 +209,7 @@
 
     fuzzer_test("tint_wgsl_reader_spv_writer_fuzzer") {
       sources = [ "tint_wgsl_reader_spv_writer_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
       dict = "dictionary.txt"
       libfuzzer_options = tint_fuzzer_common_libfuzzer_options
       seed_corpus = fuzzer_corpus_wgsl_dir
@@ -232,7 +236,7 @@
 
     fuzzer_test("tint_wgsl_reader_hlsl_writer_fuzzer") {
       sources = [ "tint_wgsl_reader_hlsl_writer_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
       dict = "dictionary.txt"
       libfuzzer_options = tint_fuzzer_common_libfuzzer_options
       seed_corpus = fuzzer_corpus_wgsl_dir
@@ -259,7 +263,7 @@
 
     fuzzer_test("tint_wgsl_reader_msl_writer_fuzzer") {
       sources = [ "tint_wgsl_reader_msl_writer_fuzzer.cc" ]
-      deps = [ ":tint_fuzzer_common_with_init" ]
+      deps = [ ":tint_fuzzer_common_with_init_src" ]
       dict = "dictionary.txt"
       libfuzzer_options = tint_fuzzer_common_libfuzzer_options
       seed_corpus = fuzzer_corpus_wgsl_dir
@@ -272,7 +276,7 @@
       tint_build_wgsl_writer) {
     executable("tint_black_box_fuzz_target") {
       sources = [ "tint_black_box_fuzz_target.cc" ]
-      deps = [ ":tint_fuzzer_common" ]
+      deps = [ ":tint_fuzzer_common_src" ]
     }
   }
 
diff --git a/fuzzers/CMakeLists.txt b/fuzzers/CMakeLists.txt
index 25e53a3..36d9e1f 100644
--- a/fuzzers/CMakeLists.txt
+++ b/fuzzers/CMakeLists.txt
@@ -20,8 +20,12 @@
     data_builder.h
     fuzzer_init.cc
     fuzzer_init.h
+    mersenne_twister_engine.cc
+    mersenne_twister_engine.h
     random_generator.cc
     random_generator.h
+    random_generator_engine.cc
+    random_generator_engine.h
     tint_common_fuzzer.cc
     tint_common_fuzzer.h
     tint_reader_writer_fuzzer.h
@@ -93,9 +97,13 @@
     AND ${TINT_BUILD_SPV_WRITER}
     AND ${TINT_BUILD_WGSL_WRITER})
   add_executable(tint_black_box_fuzz_target
+    mersenne_twister_engine.cc
+    mersenne_twister_engine.h
     random_generator.cc
     random_generator.h
-    tint_black_box_fuzz_target
+    random_generator_engine.cc
+    random_generator_engine.h
+    tint_black_box_fuzz_target.cc
     tint_common_fuzzer.cc
     tint_common_fuzzer.h
     )
diff --git a/fuzzers/data_builder.h b/fuzzers/data_builder.h
index 47a4fea..39625ef 100644
--- a/fuzzers/data_builder.h
+++ b/fuzzers/data_builder.h
@@ -36,7 +36,7 @@
   /// @param data - data fuzzer to calculate seed from
   /// @param size - size of data buffer
   explicit DataBuilder(const uint8_t* data, size_t size)
-      : generator_(data, size) {
+      : generator_(RandomGenerator::CalculateSeed(data, size)) {
     assert(data != nullptr && "|data| must be !nullptr");
   }
 
diff --git a/fuzzers/mersenne_twister_engine.cc b/fuzzers/mersenne_twister_engine.cc
new file mode 100644
index 0000000..951b706
--- /dev/null
+++ b/fuzzers/mersenne_twister_engine.cc
@@ -0,0 +1,59 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "fuzzers/mersenne_twister_engine.h"
+
+#include <algorithm>
+#include <cassert>
+
+#include "src/utils/hash.h"
+
+namespace tint {
+namespace fuzzers {
+
+namespace {
+
+/// Generate integer from uniform distribution
+/// @tparam I - integer type
+/// @param engine - random number engine to use
+/// @param lower - Lower bound of integer generated
+/// @param upper - Upper bound of integer generated
+/// @returns i, where lower <= i < upper
+template <typename I>
+I RandomInteger(std::mt19937_64* engine, I lower, I upper) {
+  assert(lower < upper && "|lower| must be strictly less than |upper|");
+  return std::uniform_int_distribution<I>(lower, upper - 1)(*engine);
+}
+
+}  // namespace
+
+MersenneTwisterEngine::MersenneTwisterEngine(uint64_t seed) : engine_(seed) {}
+
+uint32_t MersenneTwisterEngine::RandomUInt32(uint32_t lower, uint32_t upper) {
+  return RandomInteger(&engine_, lower, upper);
+}
+
+uint64_t MersenneTwisterEngine::RandomUInt64(uint64_t lower, uint64_t upper) {
+  return RandomInteger(&engine_, lower, upper);
+}
+
+void MersenneTwisterEngine::RandomNBytes(uint8_t* dest, size_t n) {
+  assert(dest && "|dest| must not be nullptr");
+  std::generate(
+      dest, dest + n,
+      std::independent_bits_engine<std::mt19937_64, 8, uint8_t>(engine_));
+}
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/fuzzers/mersenne_twister_engine.h b/fuzzers/mersenne_twister_engine.h
new file mode 100644
index 0000000..f384cac
--- /dev/null
+++ b/fuzzers/mersenne_twister_engine.h
@@ -0,0 +1,62 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef FUZZERS_MERSENNE_TWISTER_ENGINE_H_
+#define FUZZERS_MERSENNE_TWISTER_ENGINE_H_
+
+#include <random>
+
+#include "fuzzers/random_generator_engine.h"
+
+namespace tint {
+namespace fuzzers {
+
+/// Standard MT based random number generation
+class MersenneTwisterEngine : public RandomGeneratorEngine {
+ public:
+  /// @brief Initializes using provided seed
+  /// @param seed - seed value to use
+  explicit MersenneTwisterEngine(uint64_t seed);
+  ~MersenneTwisterEngine() override = default;
+
+  /// Generate random uint32_t value from uniform distribution.
+  /// @param lower - lower bound of integer generated
+  /// @param upper - upper bound of integer generated
+  /// @returns i, where lower <= i < upper
+  uint32_t RandomUInt32(uint32_t lower, uint32_t upper) override;
+
+  /// Get random uint64_t value from uniform distribution.
+  /// @param lower - lower bound of integer generated
+  /// @param upper - upper bound of integer generated
+  /// @returns i, where lower <= i < upper
+  uint64_t RandomUInt64(uint64_t lower, uint64_t upper) override;
+
+  /// Get N bytes of pseudo-random data
+  /// @param dest - memory location to store data
+  /// @param n - number of bytes of data to generate
+  void RandomNBytes(uint8_t* dest, size_t n) override;
+
+ private:
+  // Disallow copy & assign
+  MersenneTwisterEngine(const MersenneTwisterEngine&) = delete;
+  MersenneTwisterEngine& operator=(const MersenneTwisterEngine&) = delete;
+
+  std::mt19937_64 engine_;
+
+};  // class MersenneTwisterEngine
+
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // FUZZERS_MERSENNE_TWISTER_ENGINE_H_
diff --git a/fuzzers/random_generator.cc b/fuzzers/random_generator.cc
index f97633a..c535450 100644
--- a/fuzzers/random_generator.cc
+++ b/fuzzers/random_generator.cc
@@ -16,8 +16,10 @@
 
 #include <algorithm>
 #include <cassert>
-#include <vector>
+#include <utility>
 
+#include "fuzzers/mersenne_twister_engine.h"
+#include "fuzzers/random_generator_engine.h"
 #include "src/utils/hash.h"
 
 namespace tint {
@@ -25,19 +27,6 @@
 
 namespace {
 
-/// Generate integer from uniform distribution
-/// @tparam I - integer type
-/// @param engine - random number engine to use
-/// @param lower - Lower bound of integer generated
-/// @param upper - Upper bound of integer generated
-/// @returns i, where lower <= i < upper
-template <typename I>
-I RandomUInt(std::mt19937_64* engine, I lower, I upper) {
-  assert(lower < upper && "|lower| must be stictly less than |upper|");
-
-  return std::uniform_int_distribution<I>(lower, upper - 1)(*engine);
-}
-
 /// Calculate the hash for the contents of a c-style data buffer
 /// This is intentionally not implemented as a generic override of HashCombine
 /// in "src/utils/hash.h", because it conflicts with the vardiac override for
@@ -56,55 +45,58 @@
 
 }  // namespace
 
-RandomGenerator::RandomGenerator(uint64_t seed) : engine_(seed) {}
+RandomGenerator::RandomGenerator(std::unique_ptr<RandomGeneratorEngine> engine)
+    : engine_(std::move(engine)) {}
 
-RandomGenerator::RandomGenerator(const uint8_t* data, size_t size)
-    : engine_(RandomGenerator::CalculateSeed(data, size)) {
-  assert(data != nullptr && "|data| must be !nullptr");
-}
+RandomGenerator::RandomGenerator(uint64_t seed)
+    : RandomGenerator(std::make_unique<MersenneTwisterEngine>(seed)) {}
 
 uint32_t RandomGenerator::GetUInt32(uint32_t lower, uint32_t upper) {
-  return RandomUInt(&engine_, lower, upper);
+  assert(lower < upper && "|lower| must be strictly less than |upper|");
+  return engine_->RandomUInt32(lower, upper);
 }
 
 uint32_t RandomGenerator::GetUInt32(uint32_t bound) {
   assert(bound > 0 && "|bound| must be greater than 0");
-  return RandomUInt(&engine_, 0u, bound);
+  return engine_->RandomUInt32(0u, bound);
 }
 
 uint64_t RandomGenerator::GetUInt64(uint64_t lower, uint64_t upper) {
-  return RandomUInt(&engine_, lower, upper);
+  assert(lower < upper && "|lower| must be strictly less than |upper|");
+  return engine_->RandomUInt64(lower, upper);
 }
 
 uint64_t RandomGenerator::GetUInt64(uint64_t bound) {
   assert(bound > 0 && "|bound| must be greater than 0");
-  return RandomUInt(&engine_, static_cast<uint64_t>(0), bound);
+  return engine_->RandomUInt64(static_cast<uint64_t>(0), bound);
 }
 
 uint8_t RandomGenerator::GetByte() {
-  return std::independent_bits_engine<std::mt19937_64, 8, uint8_t>(engine_)();
+  uint8_t result;
+  engine_->RandomNBytes(&result, 1);
+  return result;
 }
 
 uint32_t RandomGenerator::Get4Bytes() {
-  return std::independent_bits_engine<std::mt19937_64, 32, uint32_t>(engine_)();
+  uint32_t result;
+  engine_->RandomNBytes(reinterpret_cast<uint8_t*>(&result), 4);
+  return result;
 }
 
 void RandomGenerator::GetNBytes(uint8_t* dest, size_t n) {
   assert(dest && "|dest| must not be nullptr");
-  std::generate(
-      dest, dest + n,
-      std::independent_bits_engine<std::mt19937_64, 8, uint8_t>(engine_));
+  engine_->RandomNBytes(dest, n);
 }
 
 bool RandomGenerator::GetBool() {
-  return RandomUInt(&engine_, 0u, 2u);
+  return engine_->RandomUInt32(0u, 2u);
 }
 
 bool RandomGenerator::GetWeightedBool(uint32_t percentage) {
   static const uint32_t kMaxPercentage = 100;
   assert(percentage <= kMaxPercentage &&
          "|percentage| needs to be within [0, 100]");
-  return RandomUInt(&engine_, 0u, kMaxPercentage) < percentage;
+  return engine_->RandomUInt32(0u, kMaxPercentage) < percentage;
 }
 
 uint64_t RandomGenerator::CalculateSeed(const uint8_t* data, size_t size) {
@@ -119,16 +111,14 @@
   static const int64_t kHashDesiredMinBytes = 4;
   // Maximum number of bytes we want to use in the hash.
   static const int64_t kHashDesiredMaxBytes = 32;
-  int64_t size_i64 = static_cast<int64_t>(size);
-  int64_t hash_begin_i64 =
+  auto size_i64 = static_cast<int64_t>(size);
+  auto hash_begin_i64 =
       std::min(kHashDesiredLeadingSkipBytes,
                std::max<int64_t>(size_i64 - kHashDesiredMinBytes, 0));
-  int64_t hash_end_i64 =
-      std::min(hash_begin_i64 + kHashDesiredMaxBytes, size_i64);
-  size_t hash_begin = static_cast<size_t>(hash_begin_i64);
-  size_t hash_size = static_cast<size_t>(hash_end_i64) - hash_begin;
+  auto hash_end_i64 = std::min(hash_begin_i64 + kHashDesiredMaxBytes, size_i64);
+  auto hash_begin = static_cast<size_t>(hash_begin_i64);
+  auto hash_size = static_cast<size_t>(hash_end_i64) - hash_begin;
   return HashBuffer(data + hash_begin, hash_size);
 }
-
 }  // namespace fuzzers
 }  // namespace tint
diff --git a/fuzzers/random_generator.h b/fuzzers/random_generator.h
index 5a61bae..6f37f8e 100644
--- a/fuzzers/random_generator.h
+++ b/fuzzers/random_generator.h
@@ -15,23 +15,25 @@
 #ifndef FUZZERS_RANDOM_GENERATOR_H_
 #define FUZZERS_RANDOM_GENERATOR_H_
 
+#include <memory>
 #include <random>
 #include <vector>
 
+#include "fuzzers/random_generator_engine.h"
+
 namespace tint {
 namespace fuzzers {
 
 /// Pseudo random generator utility class for fuzzing
 class RandomGenerator {
  public:
-  /// @brief Initializes the internal engine
-  /// @param seed - seed value passed to engine
-  explicit RandomGenerator(uint64_t seed);
+  /// @brief Initializes using provided engine
+  /// @param engine - engine implementation to use
+  explicit RandomGenerator(std::unique_ptr<RandomGeneratorEngine> engine);
 
-  /// @brief Wrapper that invokes CalculateSeed for caller
-  /// @param data - data fuzzer to calculate seed from
-  /// @param size - size of data buffer
-  explicit RandomGenerator(const uint8_t* data, size_t size);
+  /// @brief Creates a MersenneTwisterEngine and initializes using that
+  /// @param seed - seed value to use for engine
+  explicit RandomGenerator(uint64_t seed);
 
   ~RandomGenerator() = default;
   RandomGenerator(RandomGenerator&&) = default;
@@ -70,7 +72,7 @@
 
   /// Get N bytes of pseudo-random data
   /// @param dest - memory location to store data
-  /// @param n - number of bytes of data to generate
+  /// @param n - number of bytes of data to get
   void GetNBytes(uint8_t* dest, size_t n);
 
   /// Get random bool with even odds
@@ -83,6 +85,14 @@
   /// of the time.
   bool GetWeightedBool(uint32_t percentage);
 
+  /// Returns a randomly-chosen element from vector v.
+  /// @param v - the vector from which the random element will be selected.
+  /// @return a random element of vector v.
+  template <typename T>
+  inline T GetRandomElement(const std::vector<T>& v) {
+    return v[GetUInt64(0, v.size())];
+  }
+
   /// Calculate a seed value based on a blob of data.
   /// Currently hashes bytes near the front of the buffer, after skipping N
   /// bytes.
@@ -90,21 +100,13 @@
   /// @param size - number of elements in |data|, must be > 0
   static uint64_t CalculateSeed(const uint8_t* data, size_t size);
 
-  /// Returns a randomly-chosen element from vector v.
-  /// @param v - the vector from which the random element will be selected.
-  /// @return a random element of vector v.
-  template <typename T>
-  inline T GetRandomElement(const std::vector<T>& v) {
-    return v[GetUInt64(0, v.size() - 1)];
-  }
-
  private:
-  std::mt19937_64 engine_;
-
   // Disallow copy & assign
   RandomGenerator(const RandomGenerator&) = delete;
   RandomGenerator& operator=(const RandomGenerator&) = delete;
 
+  std::unique_ptr<RandomGeneratorEngine> engine_;
+
 };  // class RandomGenerator
 
 }  // namespace fuzzers
diff --git a/fuzzers/random_generator_engine.cc b/fuzzers/random_generator_engine.cc
new file mode 100644
index 0000000..6c14178
--- /dev/null
+++ b/fuzzers/random_generator_engine.cc
@@ -0,0 +1,26 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "fuzzers/random_generator_engine.h"
+
+namespace tint {
+namespace fuzzers {
+
+// Not in header to avoid weak vtable warnings from clang
+RandomGeneratorEngine::RandomGeneratorEngine() = default;
+RandomGeneratorEngine::~RandomGeneratorEngine() = default;
+RandomGeneratorEngine::RandomGeneratorEngine(RandomGeneratorEngine&&) = default;
+
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/fuzzers/random_generator_engine.h b/fuzzers/random_generator_engine.h
new file mode 100644
index 0000000..5df64fa
--- /dev/null
+++ b/fuzzers/random_generator_engine.h
@@ -0,0 +1,58 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef FUZZERS_RANDOM_GENERATOR_ENGINE_H_
+#define FUZZERS_RANDOM_GENERATOR_ENGINE_H_
+
+#include <memory>
+#include <random>
+#include <vector>
+
+namespace tint {
+namespace fuzzers {
+
+/// Wrapper interface around STL random number engine
+class RandomGeneratorEngine {
+ public:
+  RandomGeneratorEngine();
+  virtual ~RandomGeneratorEngine();
+  RandomGeneratorEngine(RandomGeneratorEngine&&);
+
+  /// Generate random uint32_t value from uniform distribution.
+  /// @param lower - lower bound of integer generated
+  /// @param upper - upper bound of integer generated
+  /// @returns i, where lower <= i < upper
+  virtual uint32_t RandomUInt32(uint32_t lower, uint32_t upper) = 0;
+
+  /// Get random uint64_t value from uniform distribution.
+  /// @param lower - lower bound of integer generated
+  /// @param upper - upper bound of integer generated
+  /// @returns i, where lower <= i < upper
+  virtual uint64_t RandomUInt64(uint64_t lower, uint64_t upper) = 0;
+
+  /// Get N bytes of pseudo-random data
+  /// @param dest - memory location to store data
+  /// @param n - number of bytes of data to generate
+  virtual void RandomNBytes(uint8_t* dest, size_t n) = 0;
+
+ private:
+  // Disallow copy & assign
+  RandomGeneratorEngine(const RandomGeneratorEngine&) = delete;
+  RandomGeneratorEngine& operator=(const RandomGeneratorEngine&) = delete;
+};  // class RandomGeneratorEngine
+
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // FUZZERS_RANDOM_GENERATOR_ENGINE_H_
diff --git a/fuzzers/random_generator_test.cc b/fuzzers/random_generator_test.cc
new file mode 100644
index 0000000..53bc5cb
--- /dev/null
+++ b/fuzzers/random_generator_test.cc
@@ -0,0 +1,202 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "fuzzers/random_generator.h"
+
+#include <memory>
+
+#include "gtest/gtest.h"
+
+#include "fuzzers/mersenne_twister_engine.h"
+
+namespace tint {
+namespace fuzzers {
+namespace {
+
+/// Implementation of RandomGeneratorEngine that just returns a stream of
+/// monotonically increasing numbers.
+class MonotonicEngine : public RandomGeneratorEngine {
+ public:
+  uint32_t RandomUInt32(uint32_t, uint32_t) override { return next_++; }
+
+  uint64_t RandomUInt64(uint64_t, uint64_t) override { return next_++; }
+
+  void RandomNBytes(uint8_t*, size_t) override {
+    assert(false && "MonotonicDelegate does not implement RandomNBytes");
+  }
+
+ private:
+  uint32_t next_ = 0;
+};
+
+class RandomGeneratorTest : public testing::Test {
+ public:
+  void SetUp() override { rng_ = std::make_unique<RandomGenerator>(0); }
+
+  void TearDown() override {}
+
+ protected:
+  std::unique_ptr<RandomGenerator> rng_;
+};
+
+#ifndef NDEBUG
+TEST_F(RandomGeneratorTest, GetUInt32ReversedBoundsCrashes) {
+  EXPECT_DEATH(rng_->GetUInt32(10, 5), ".*");
+}
+
+TEST_F(RandomGeneratorTest, GetUInt32EmptyBoundsCrashes) {
+  EXPECT_DEATH(rng_->GetUInt32(5, 5), ".*");
+}
+
+TEST_F(RandomGeneratorTest, GetUInt32ZeroBoundCrashes) {
+  EXPECT_DEATH(rng_->GetUInt32(0u), ".*");
+}
+#endif  // NDEBUG
+
+TEST_F(RandomGeneratorTest, GetUInt32SingularReturnsOneValue) {
+  {
+    uint32_t result = rng_->GetUInt32(5u, 6u);
+    ASSERT_EQ(5u, result);
+  }
+  {
+    uint32_t result = rng_->GetUInt32(1u);
+    ASSERT_EQ(0u, result);
+  }
+}
+
+TEST_F(RandomGeneratorTest, GetUInt32StaysInBounds) {
+  {
+    uint32_t result = rng_->GetUInt32(5u, 10u);
+    ASSERT_LE(5u, result);
+    ASSERT_GT(10u, result);
+  }
+  {
+    uint32_t result = rng_->GetUInt32(10u);
+    ASSERT_LE(0u, result);
+    ASSERT_GT(10u, result);
+  }
+}
+
+#ifndef NDEBUG
+TEST_F(RandomGeneratorTest, GetUInt64ReversedBoundsCrashes) {
+  EXPECT_DEATH(rng_->GetUInt64(10, 5), ".*");
+}
+
+TEST_F(RandomGeneratorTest, GetUInt64EmptyBoundsCrashes) {
+  EXPECT_DEATH(rng_->GetUInt64(5, 5), ".*");
+}
+
+TEST_F(RandomGeneratorTest, GetUInt64ZeroBoundCrashes) {
+  EXPECT_DEATH(rng_->GetUInt64(0u), ".*");
+}
+#endif  // NDEBUG
+
+TEST_F(RandomGeneratorTest, GetUInt64SingularReturnsOneValue) {
+  {
+    uint64_t result = rng_->GetUInt64(5u, 6u);
+    ASSERT_EQ(5u, result);
+  }
+  {
+    uint64_t result = rng_->GetUInt64(1u);
+    ASSERT_EQ(0u, result);
+  }
+}
+
+TEST_F(RandomGeneratorTest, GetUInt64StaysInBounds) {
+  {
+    uint64_t result = rng_->GetUInt64(5u, 10u);
+    ASSERT_LE(5u, result);
+    ASSERT_GT(10u, result);
+  }
+  {
+    uint64_t result = rng_->GetUInt64(10u);
+    ASSERT_LE(0u, result);
+    ASSERT_GT(10u, result);
+  }
+}
+
+TEST_F(RandomGeneratorTest, GetByte) {
+  rng_->GetByte();
+}
+
+#ifndef NDEBUG
+TEST_F(RandomGeneratorTest, GetNBytesNullDataBufferCrashes) {
+  EXPECT_DEATH(rng_->GetNBytes(nullptr, 5), ".*");
+}
+#endif  // NDEBUG
+
+TEST_F(RandomGeneratorTest, GetNBytes) {
+  std::vector<uint8_t> data;
+  for (uint32_t i = 25; i < 1000u; i = i + 25) {
+    data.resize(i);
+    rng_->GetNBytes(data.data(), data.size());
+  }
+}
+
+TEST_F(RandomGeneratorTest, GetBool) {
+  rng_->GetBool();
+}
+
+TEST_F(RandomGeneratorTest, GetWeightedBoolZeroAlwaysFalse) {
+  ASSERT_FALSE(rng_->GetWeightedBool(0));
+}
+
+TEST_F(RandomGeneratorTest, GetWeightedBoolHundredAlwaysTrue) {
+  ASSERT_TRUE(rng_->GetWeightedBool(100));
+}
+
+#ifndef NDEBUG
+TEST_F(RandomGeneratorTest, GetWeightedBoolAboveHundredCrashes) {
+  EXPECT_DEATH(rng_->GetWeightedBool(101), ".*");
+  EXPECT_DEATH(rng_->GetWeightedBool(500), ".*");
+}
+#endif  // NDEBUG
+
+TEST_F(RandomGeneratorTest, GetWeightedBool) {
+  for (uint32_t i = 0; i <= 100; i++) {
+    rng_ =
+        std::make_unique<RandomGenerator>(std::make_unique<MonotonicEngine>());
+    for (uint32_t j = 0; j <= 100; j++) {
+      if (j < i) {
+        ASSERT_TRUE(rng_->GetWeightedBool(i));
+      } else {
+        ASSERT_FALSE(rng_->GetWeightedBool(i));
+      }
+    }
+  }
+}
+
+#ifndef NDEBUG
+TEST_F(RandomGeneratorTest, GetRandomElementEmptyVectorCrashes) {
+  std::vector<uint8_t> v;
+  EXPECT_DEATH(rng_->GetRandomElement(v), ".*");
+}
+#endif  // NDEBUG
+
+TEST_F(RandomGeneratorTest, GetRandomElement) {
+  std::vector<uint32_t> v;
+  for (uint32_t i = 25; i < 100u; i = i + 25) {
+    rng_ =
+        std::make_unique<RandomGenerator>(std::make_unique<MonotonicEngine>());
+    v.resize(i);
+    std::iota(v.begin(), v.end(), 0);
+    for (uint32_t j = 0; j < i; j++) {
+      EXPECT_EQ(j, rng_->GetRandomElement(v));
+    }
+  }
+}
+
+}  // namespace
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/fuzzers/tint_ast_fuzzer/BUILD.gn b/fuzzers/tint_ast_fuzzer/BUILD.gn
index 1b8a1f5..aedb1ab 100644
--- a/fuzzers/tint_ast_fuzzer/BUILD.gn
+++ b/fuzzers/tint_ast_fuzzer/BUILD.gn
@@ -34,7 +34,7 @@
 
     deps = [
       ":tint_ast_fuzzer_proto",
-      "${tint_root_dir}/fuzzers:tint_fuzzer_common",
+      "${tint_root_dir}/fuzzers:tint_fuzzer_common_src",
       "//third_party/protobuf:protobuf_full",
     ]
 
diff --git a/fuzzers/tint_ast_fuzzer/CMakeLists.txt b/fuzzers/tint_ast_fuzzer/CMakeLists.txt
index 1dcb668..757cd53 100644
--- a/fuzzers/tint_ast_fuzzer/CMakeLists.txt
+++ b/fuzzers/tint_ast_fuzzer/CMakeLists.txt
@@ -35,7 +35,9 @@
         COMMENT "Generate protobuf sources from proto definition file.")
 
 set(LIBTINT_AST_FUZZER_SOURCES
+        ../mersenne_twister_engine.h
         ../random_generator.h
+        ../random_generator_engine.h
         mutation.h
         mutation_finder.h
         mutation_finders/replace_identifiers.h
@@ -48,7 +50,9 @@
         ${CMAKE_CURRENT_BINARY_DIR}/protobufs/tint_ast_fuzzer.pb.h)
 
 set(LIBTINT_AST_FUZZER_SOURCES ${LIBTINT_AST_FUZZER_SOURCES}
+        ../mersenne_twister_engine.cc
         ../random_generator.cc
+        ../random_generator_engine.cc
         mutation.cc
         mutation_finder.cc
         mutation_finders/replace_identifiers.cc
diff --git a/fuzzers/tint_regex_fuzzer/BUILD.gn b/fuzzers/tint_regex_fuzzer/BUILD.gn
index 7b898a9..646c11a 100644
--- a/fuzzers/tint_regex_fuzzer/BUILD.gn
+++ b/fuzzers/tint_regex_fuzzer/BUILD.gn
@@ -22,7 +22,7 @@
       "${tint_root_dir}/src:tint_common_config",
     ]
 
-    deps = [ "${tint_root_dir}/fuzzers:tint_fuzzer_common" ]
+    deps = [ "${tint_root_dir}/fuzzers:tint_fuzzer_common_src" ]
 
     sources = [
       "cli.cc",
diff --git a/fuzzers/tint_regex_fuzzer/CMakeLists.txt b/fuzzers/tint_regex_fuzzer/CMakeLists.txt
index bd3fd8c..bcd0885 100644
--- a/fuzzers/tint_regex_fuzzer/CMakeLists.txt
+++ b/fuzzers/tint_regex_fuzzer/CMakeLists.txt
@@ -21,8 +21,12 @@
 endfunction()
 
 set(LIBTINT_REGEX_FUZZER_SOURCES
+        ../mersenne_twister_engine.cc
+        ../mersenne_twister_engine.h
         ../random_generator.cc
         ../random_generator.h
+        ../random_generator_engine.cc
+        ../random_generator_engine.h
         wgsl_mutator.cc
         wgsl_mutator.h)
 
diff --git a/fuzzers/tint_spirv_tools_fuzzer/CMakeLists.txt b/fuzzers/tint_spirv_tools_fuzzer/CMakeLists.txt
index 23d9741..c83333a 100644
--- a/fuzzers/tint_spirv_tools_fuzzer/CMakeLists.txt
+++ b/fuzzers/tint_spirv_tools_fuzzer/CMakeLists.txt
@@ -13,7 +13,9 @@
 # limitations under the License.
 
 set(FUZZER_SOURCES
+        ../mersenne_twister_engine.cc
         ../random_generator.cc
+        ../random_generator_engine.cc
         cli.cc
         fuzzer.cc
         mutator.cc
@@ -24,7 +26,9 @@
         util.cc)
 
 set(FUZZER_SOURCES ${FUZZER_SOURCES}
+        ../mersenne_twister_engine.h
         ../random_generator.h
+        ../random_generator_engine.h
         cli.h
         mutator.h
         mutator_cache.h
@@ -76,7 +80,9 @@
 add_tint_spirv_tools_fuzzer(tint_spirv_tools_wgsl_writer_fuzzer)
 
 set(DEBUGGER_SOURCES
+        ../mersenne_twister_engine.cc
         ../random_generator.cc
+        ../random_generator_engine.cc
         cli.cc
         mutator.cc
         mutator_debugger.cc
@@ -86,7 +92,9 @@
         util.cc)
 
 set(DEBUGGER_SOURCES ${DEBUGGER_SOURCES}
+        ../mersenne_twister_engine.h
         ../random_generator.h
+        ../random_generator_engine.h
         cli.h
         mutator.h
         spirv_fuzz_mutator.h
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 2fd70f5..6ccac4a 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -409,9 +409,9 @@
     "sem/storage_texture_type.h",
     "sem/switch_statement.h",
     "sem/texture_type.h",
+    "sem/type.h",
     "sem/type_constructor.h",
     "sem/type_conversion.h",
-    "sem/type.h",
     "sem/type_manager.h",
     "sem/type_mappings.h",
     "sem/u32_type.h",
@@ -581,12 +581,12 @@
     "sem/switch_statement.h",
     "sem/texture_type.cc",
     "sem/texture_type.h",
+    "sem/type.cc",
+    "sem/type.h",
     "sem/type_constructor.cc",
     "sem/type_constructor.h",
     "sem/type_conversion.cc",
     "sem/type_conversion.h",
-    "sem/type.cc",
-    "sem/type.h",
     "sem/type_manager.cc",
     "sem/type_manager.h",
     "sem/type_mappings.h",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0e8c98e..fc5d3f2 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1083,6 +1083,18 @@
     )
   endif()
 
+  if (${TINT_BUILD_FUZZERS})
+    list(APPEND TINT_TEST_SRCS
+      ../fuzzers/mersenne_twister_engine.cc
+      ../fuzzers/mersenne_twister_engine.h
+      ../fuzzers/random_generator.cc
+      ../fuzzers/random_generator.h
+      ../fuzzers/random_generator_engine.cc
+      ../fuzzers/random_generator_engine.h
+      ../fuzzers/random_generator_test.cc
+    )
+  endif()
+
   add_executable(tint_unittests ${TINT_TEST_SRCS})
 
   if(NOT MSVC)
diff --git a/test/BUILD.gn b/test/BUILD.gn
index a634ea4..bfaa498 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -169,9 +169,9 @@
     "../src/ast/function_test.cc",
     "../src/ast/group_decoration_test.cc",
     "../src/ast/i32_test.cc",
-    "../src/ast/index_accessor_expression_test.cc",
     "../src/ast/identifier_expression_test.cc",
     "../src/ast/if_statement_test.cc",
+    "../src/ast/index_accessor_expression_test.cc",
     "../src/ast/int_literal_expression_test.cc",
     "../src/ast/interpolate_decoration_test.cc",
     "../src/ast/intrinsic_texture_helper_test.cc",
@@ -213,7 +213,6 @@
   ]
 }
 
-
 tint_unittests_source_set("tint_unittests_diagnostic_src") {
   sources = [
     "../src/diagnostic/formatter_test.cc",
@@ -269,9 +268,7 @@
     "../src/resolver/var_let_test.cc",
     "../src/resolver/var_let_validation_test.cc",
   ]
-  deps = [
-    ":tint_unittests_ast_src",
-  ]
+  deps = [ ":tint_unittests_ast_src" ]
 }
 
 tint_unittests_source_set("tint_unittests_sem_src") {
@@ -397,9 +394,7 @@
     "../src/reader/spirv/usage_test.cc",
   ]
 
-  deps = [
-    "${tint_root_dir}/src:libtint_spv_reader_src",
-  ]
+  deps = [ "${tint_root_dir}/src:libtint_spv_reader_src" ]
 }
 
 tint_unittests_source_set("tint_unittests_spv_writer_src") {
@@ -524,9 +519,7 @@
     "../src/reader/wgsl/token_test.cc",
   ]
 
-  deps = [
-    "${tint_root_dir}/src:libtint_wgsl_reader_src",
-  ]
+  deps = [ "${tint_root_dir}/src:libtint_wgsl_reader_src" ]
 }
 
 tint_unittests_source_set("tint_unittests_wgsl_writer_src") {
@@ -705,9 +698,18 @@
     "../src/traits_test.cc",
   ]
 
-  deps = [
-    ":tint_unittests_ast_src",
-  ]
+  deps = [ ":tint_unittests_ast_src" ]
+}
+
+if (build_with_chromium) {
+  tint_unittests_source_set("tint_unittests_fuzzer_src") {
+    sources = [ "../fuzzers/random_generator_test.cc" ]
+
+    deps = [
+      ":tint_unittests_core_src",
+      "${tint_root_dir}/fuzzers:tint_fuzzer_common_src",
+    ]
+  }
 }
 
 source_set("tint_unittests_src") {
@@ -755,6 +757,10 @@
     deps += [ ":tint_unittests_glsl_writer_src" ]
   }
 
+  if (build_with_chromium) {
+    deps += [ ":tint_unittests_fuzzer_src" ]
+  }
+
   configs += [ ":tint_unittests_config" ]
 
   if (build_with_chromium) {