// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "vm/ffi_callback_metadata.h"

#include <memory>
#include <thread>  // NOLINT(build/c++11)
#include <unordered_set>
#include <vector>

#include "include/dart_api.h"
#include "platform/assert.h"
#include "vm/class_finalizer.h"
#include "vm/compiler/ffi/callback.h"
#include "vm/compiler/jit/compiler.h"
#include "vm/message_handler.h"
#include "vm/object.h"
#include "vm/port.h"
#include "vm/symbols.h"
#include "vm/unit_test.h"

namespace dart {

FunctionPtr CreateTestFunction(FfiCallbackKind kind) {
  const auto& ffi_lib = Library::Handle(Library::FfiLibrary());
  const auto& ffi_void = Class::Handle(ffi_lib.LookupClass(Symbols::FfiVoid()));
  const auto& ffi_void_type =
      Type::Handle(Type::NewNonParameterizedType(ffi_void));

  auto* thread = Thread::Current();
  const char* kScriptChars =
      R"(
      void testFunction() {
      }
      )";
  Dart_Handle library;
  {
    TransitionVMToNative transition(thread);
    library = TestCase::LoadTestScript(kScriptChars, nullptr);
    EXPECT_VALID(library);
  }

  const auto& lib =
      Library::Handle(Library::RawCast(Api::UnwrapHandle(library)));
  EXPECT(ClassFinalizer::ProcessPendingClasses());
  const auto& cls = Class::Handle(lib.toplevel_class());
  EXPECT(!cls.IsNull());
  const auto& error = cls.EnsureIsFinalized(thread);
  EXPECT(error == Error::null());

  auto& function_name = String::Handle(String::New("testFunction"));
  const auto& func = Function::Handle(cls.LookupStaticFunction(function_name));
  EXPECT(!func.IsNull());

  FunctionType& signature = FunctionType::Handle(FunctionType::New());
  signature.set_result_type(ffi_void_type);
  signature.SetIsFinalized();
  signature ^= signature.Canonicalize(thread);

  const auto& callback = Function::Handle(compiler::ffi::NativeCallbackFunction(
      signature, func, Instance::Handle(Instance::null()), kind));

  const auto& result = Object::Handle(
      thread->zone(), Compiler::CompileFunction(thread, callback));
  EXPECT(!result.IsError());

  return callback.ptr();
}

class FakeMessageHandler : public MessageHandler {
 public:
  MessageStatus HandleMessage(std::unique_ptr<Message> message) override {
    return MessageHandler::kOK;
  }
};

VM_UNIT_TEST_CASE(FfiCallbackMetadata_CreateSyncFfiCallback) {
  FfiCallbackMetadata::Trampoline tramp1 = 0;
  FfiCallbackMetadata::Trampoline tramp2 = 0;

  {
    TestIsolateScope isolate_scope;
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    ASSERT(isolate == isolate_scope.isolate());
    auto* fcm = isolate->group()->callback_metadata();
    TransitionNativeToVM transition(thread);
    StackZone stack_zone(thread);

    auto* zone = thread->zone();

    const auto& func = Function::Handle(
        CreateTestFunction(FfiCallbackKind::kIsolateLocalStaticCallback));
    const auto& code = Code::Handle(func.EnsureHasCode());
    EXPECT(!code.IsNull());

    tramp1 = isolate->CreateIsolateLocalFfiCallback(
        zone, func, Closure::Handle(Closure::null()), false);
    EXPECT_NE(tramp1, 0u);

    {
      FfiCallbackMetadata::Metadata m1 =
          fcm->LookupMetadataForTrampolineUnlocked(tramp1);
      EXPECT(m1.IsLive());
      EXPECT_EQ(m1.target_isolate(), isolate);
      EXPECT_EQ(m1.target_entry_point(), code.EntryPoint());
      EXPECT_EQ(m1.closure_handle(), nullptr);
      EXPECT_EQ(static_cast<int>(m1.trampoline_type()),
                static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync));

      // head -> tramp1
      auto* e1 = fcm->MetadataEntryOfTrampoline(tramp1);
      EXPECT_EQ(isolate->ffi_callback_list_head(), e1);
      EXPECT_EQ(e1->list_prev(), nullptr);
      EXPECT_EQ(e1->list_next(), nullptr);
    }

    tramp2 = isolate->CreateIsolateLocalFfiCallback(
        zone, func, Closure::Handle(Closure::null()), false);
    EXPECT_NE(tramp2, 0u);
    EXPECT_NE(tramp2, tramp1);

    {
      FfiCallbackMetadata::Metadata m2 =
          fcm->LookupMetadataForTrampolineUnlocked(tramp2);
      EXPECT(m2.IsLive());
      EXPECT_EQ(m2.target_isolate(), isolate);
      EXPECT_EQ(m2.target_entry_point(), code.EntryPoint());
      EXPECT_EQ(m2.closure_handle(), nullptr);
      EXPECT_EQ(static_cast<int>(m2.trampoline_type()),
                static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync));
    }

    {
      // head -> tramp2 -> tramp1
      auto* e1 = fcm->MetadataEntryOfTrampoline(tramp1);
      auto* e2 = fcm->MetadataEntryOfTrampoline(tramp2);
      EXPECT_EQ(isolate->ffi_callback_list_head(), e2);
      EXPECT_EQ(e2->list_prev(), nullptr);
      EXPECT_EQ(e2->list_next(), e1);
      EXPECT_EQ(e1->list_prev(), e2);
      EXPECT_EQ(e1->list_next(), nullptr);
    }

    {
      isolate->DeleteFfiCallback(tramp1);
      FfiCallbackMetadata::Metadata m1 =
          fcm->LookupMetadataForTrampolineUnlocked(tramp1);
      EXPECT(!m1.IsLive());

      // head -> tramp2
      auto* e2 = fcm->MetadataEntryOfTrampoline(tramp2);
      EXPECT_EQ(isolate->ffi_callback_list_head(), e2);
      EXPECT_EQ(e2->list_prev(), nullptr);
      EXPECT_EQ(e2->list_next(), nullptr);
    }
  }
}

VM_UNIT_TEST_CASE(FfiCallbackMetadata_CreateAsyncFfiCallback) {
  FfiCallbackMetadata::Trampoline tramp1 = 0;
  FfiCallbackMetadata::Trampoline tramp2 = 0;

  {
    TestIsolateScope isolate_scope;
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    ASSERT(thread->isolate() == isolate_scope.isolate());
    auto* fcm = isolate->group()->callback_metadata();
    TransitionNativeToVM transition(thread);
    StackZone stack_zone(thread);

    auto* zone = thread->zone();

    const Function& func =
        Function::Handle(CreateTestFunction(FfiCallbackKind::kAsyncCallback));
    const Code& code = Code::Handle(func.EnsureHasCode());
    EXPECT(!code.IsNull());

    EXPECT_EQ(isolate->ffi_callback_list_head(), nullptr);

    auto port1 = PortMap::CreatePort(new FakeMessageHandler());
    tramp1 = isolate->CreateAsyncFfiCallback(zone, func, port1);
    EXPECT_NE(tramp1, 0u);

    {
      FfiCallbackMetadata::Metadata m1 =
          fcm->LookupMetadataForTrampolineUnlocked(tramp1);
      EXPECT(m1.IsLive());
      EXPECT_EQ(m1.target_isolate(), isolate);
      EXPECT_EQ(m1.target_entry_point(), code.EntryPoint());
      EXPECT_EQ(m1.send_port(), port1);
      EXPECT_EQ(static_cast<int>(m1.trampoline_type()),
                static_cast<int>(FfiCallbackMetadata::TrampolineType::kAsync));

      // head -> tramp1
      auto* e1 = fcm->MetadataEntryOfTrampoline(tramp1);
      EXPECT_EQ(isolate->ffi_callback_list_head(), e1);
      EXPECT_EQ(e1->list_prev(), nullptr);
      EXPECT_EQ(e1->list_next(), nullptr);
    }

    auto port2 = PortMap::CreatePort(new FakeMessageHandler());
    tramp2 = isolate->CreateAsyncFfiCallback(zone, func, port2);
    EXPECT_NE(tramp2, 0u);
    EXPECT_NE(tramp2, tramp1);

    {
      FfiCallbackMetadata::Metadata m2 =
          fcm->LookupMetadataForTrampolineUnlocked(tramp2);
      EXPECT(m2.IsLive());
      EXPECT_EQ(m2.target_isolate(), isolate);
      EXPECT_EQ(m2.target_entry_point(), code.EntryPoint());
      EXPECT_EQ(m2.send_port(), port2);
      EXPECT_EQ(static_cast<int>(m2.trampoline_type()),
                static_cast<int>(FfiCallbackMetadata::TrampolineType::kAsync));
    }

    {
      // head -> tramp2 -> tramp1
      auto* e1 = fcm->MetadataEntryOfTrampoline(tramp1);
      auto* e2 = fcm->MetadataEntryOfTrampoline(tramp2);
      EXPECT_EQ(isolate->ffi_callback_list_head(), e2);
      EXPECT_EQ(e2->list_prev(), nullptr);
      EXPECT_EQ(e2->list_next(), e1);
      EXPECT_EQ(e1->list_prev(), e2);
      EXPECT_EQ(e1->list_next(), nullptr);
    }

    {
      isolate->DeleteFfiCallback(tramp2);
      FfiCallbackMetadata::Metadata m2 =
          fcm->LookupMetadataForTrampolineUnlocked(tramp2);
      EXPECT(!m2.IsLive());

      // head -> tramp1
      auto* e1 = fcm->MetadataEntryOfTrampoline(tramp1);
      EXPECT_EQ(isolate->ffi_callback_list_head(), e1);
      EXPECT_EQ(e1->list_prev(), nullptr);
      EXPECT_EQ(e1->list_next(), nullptr);
    }
  }
}

VM_UNIT_TEST_CASE(FfiCallbackMetadata_CreateIsolateLocalFfiCallback) {
  FfiCallbackMetadata::Trampoline tramp1 = 0;
  FfiCallbackMetadata::Trampoline tramp2 = 0;

  {
    TestIsolateScope isolate_scope;
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    ASSERT(thread->isolate() == isolate_scope.isolate());
    auto* fcm = isolate->group()->callback_metadata();
    TransitionNativeToVM transition(thread);
    StackZone stack_zone(thread);

    auto* zone = thread->zone();

    const Function& func = Function::Handle(
        CreateTestFunction(FfiCallbackKind::kIsolateLocalClosureCallback));
    const Code& code = Code::Handle(func.EnsureHasCode());
    EXPECT(!code.IsNull());

    // Using a tear-off of target of the callback function as a dummy closure.
    const Function& closure_func = Function::Handle(
        Function::Handle(func.FfiCallbackTarget()).ImplicitClosureFunction());
    const Context& context = Context::Handle(Context::null());
    const Closure& closure1 = Closure::Handle(
        Closure::New(Object::null_type_arguments(),
                     Object::null_type_arguments(), closure_func, context));

    EXPECT_EQ(isolate->ffi_callback_list_head(), nullptr);

    tramp1 = isolate->CreateIsolateLocalFfiCallback(zone, func, closure1, true);
    EXPECT_NE(tramp1, 0u);

    {
      FfiCallbackMetadata::Metadata m1 =
          fcm->LookupMetadataForTrampolineUnlocked(tramp1);
      EXPECT(m1.IsLive());
      EXPECT_EQ(m1.target_isolate(), isolate);
      EXPECT_EQ(m1.target_entry_point(), code.EntryPoint());
      EXPECT_EQ(m1.closure_handle()->ptr(), closure1.ptr());
      EXPECT_EQ(static_cast<int>(m1.trampoline_type()),
                static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync));

      // head -> tramp1
      auto* e1 = fcm->MetadataEntryOfTrampoline(tramp1);
      EXPECT_EQ(isolate->ffi_callback_list_head(), e1);
      EXPECT_EQ(e1->list_prev(), nullptr);
      EXPECT_EQ(e1->list_next(), nullptr);
    }

    const Closure& closure2 = Closure::Handle(
        Closure::New(Object::null_type_arguments(),
                     Object::null_type_arguments(), closure_func, context));
    tramp2 = isolate->CreateIsolateLocalFfiCallback(zone, func, closure2, true);
    EXPECT_NE(tramp2, 0u);
    EXPECT_NE(tramp2, tramp1);

    {
      FfiCallbackMetadata::Metadata m2 =
          fcm->LookupMetadataForTrampolineUnlocked(tramp2);
      EXPECT(m2.IsLive());
      EXPECT_EQ(m2.target_isolate(), isolate);
      EXPECT_EQ(m2.target_entry_point(), code.EntryPoint());
      EXPECT_EQ(m2.closure_handle()->ptr(), closure2.ptr());
      EXPECT_EQ(static_cast<int>(m2.trampoline_type()),
                static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync));
    }

    {
      // head -> tramp2 -> tramp1
      auto* e1 = fcm->MetadataEntryOfTrampoline(tramp1);
      auto* e2 = fcm->MetadataEntryOfTrampoline(tramp2);
      EXPECT_EQ(isolate->ffi_callback_list_head(), e2);
      EXPECT_EQ(e2->list_prev(), nullptr);
      EXPECT_EQ(e2->list_next(), e1);
      EXPECT_EQ(e1->list_prev(), e2);
      EXPECT_EQ(e1->list_next(), nullptr);
    }

    {
      isolate->DeleteFfiCallback(tramp2);
      FfiCallbackMetadata::Metadata m2 =
          fcm->LookupMetadataForTrampolineUnlocked(tramp2);
      EXPECT(!m2.IsLive());

      // head -> tramp1
      auto* e1 = fcm->MetadataEntryOfTrampoline(tramp1);
      EXPECT_EQ(isolate->ffi_callback_list_head(), e1);
      EXPECT_EQ(e1->list_prev(), nullptr);
      EXPECT_EQ(e1->list_next(), nullptr);
    }
  }
}

ISOLATE_UNIT_TEST_CASE(FfiCallbackMetadata_TrampolineRecycling) {
  Isolate* isolate = thread->isolate();
  auto* zone = thread->zone();
  auto* fcm = IsolateGroup::Current()->callback_metadata();

  const Function& func =
      Function::Handle(CreateTestFunction(FfiCallbackKind::kAsyncCallback));
  const Code& code = Code::Handle(func.EnsureHasCode());
  EXPECT(!code.IsNull());

  auto port = PortMap::CreatePort(new FakeMessageHandler());
  FfiCallbackMetadata::MetadataEntry* list_head = nullptr;

  // Allocate and free one callback at a time, and verify that we don't reuse
  // them. Allocate enough that the whole page fills up with dead trampolines.
  std::vector<FfiCallbackMetadata::Trampoline> allocation_order;
  std::unordered_set<FfiCallbackMetadata::Trampoline> allocated;
  const intptr_t trampolines_per_page =
      FfiCallbackMetadata::NumCallbackTrampolinesPerPage();
  for (intptr_t i = 0; i < trampolines_per_page; ++i) {
    auto tramp =
        fcm->CreateAsyncFfiCallback(isolate, zone, func, port, &list_head);
    EXPECT_EQ(allocated.count(tramp), 0u);
    allocation_order.push_back(tramp);
    allocated.insert(tramp);
    fcm->DeleteCallback(tramp, &list_head);
  }

  // Now  as we continue allocating and freeing, we start reusing them, in the
  // same allocation order as before.
  for (intptr_t i = 0; i < trampolines_per_page; ++i) {
    auto tramp =
        fcm->CreateAsyncFfiCallback(isolate, zone, func, port, &list_head);
    EXPECT_EQ(allocated.count(tramp), 1u);
    EXPECT_EQ(allocation_order[i], tramp);
    fcm->DeleteCallback(tramp, &list_head);
  }

  // Now allocate enough to fill the page without freeing them. Again they
  // should come out in the same order.
  for (intptr_t i = 0; i < trampolines_per_page; ++i) {
    auto tramp =
        fcm->CreateAsyncFfiCallback(isolate, zone, func, port, &list_head);
    EXPECT_EQ(allocated.count(tramp), 1u);
    EXPECT_EQ(allocation_order[i], tramp);
  }

  // Now that the page is full, we should allocate a new page and see new
  // trampolines we haven't seen before.
  for (intptr_t i = 0; i < 3 * trampolines_per_page; ++i) {
    auto tramp =
        fcm->CreateAsyncFfiCallback(isolate, zone, func, port, &list_head);
    EXPECT_EQ(allocated.count(tramp), 0u);
  }
}

VM_UNIT_TEST_CASE(FfiCallbackMetadata_DeleteTrampolines) {
  static constexpr int kCreations = 1000;
  static constexpr int kDeletions = 100;

  TestIsolateScope isolate_scope;
  Thread* thread = Thread::Current();
  Isolate* isolate = thread->isolate();
  ASSERT(isolate == isolate_scope.isolate());
  TransitionNativeToVM transition(thread);
  StackZone stack_zone(thread);

  auto* fcm = IsolateGroup::Current()->callback_metadata();
  std::unordered_set<FfiCallbackMetadata::Trampoline> tramps;
  FfiCallbackMetadata::MetadataEntry* list_head = nullptr;

  const auto& sync_func = Function::Handle(
      CreateTestFunction(FfiCallbackKind::kIsolateLocalStaticCallback));
  const auto& sync_code = Code::Handle(sync_func.EnsureHasCode());
  EXPECT(!sync_code.IsNull());

  // Create some callbacks.
  for (int itr = 0; itr < kCreations; ++itr) {
    tramps.insert(fcm->CreateLocalFfiCallback(
        isolate, /*isolate_group=*/nullptr, thread->zone(), sync_func,
        Closure::Handle(Closure::null()), &list_head));
  }

  // Delete some of the callbacks.
  for (int itr = 0; itr < kDeletions; ++itr) {
    auto tramp = *tramps.begin();
    fcm->DeleteCallback(tramp, &list_head);
    tramps.erase(tramp);
  }

  // Verify all the callbacks.
  for (FfiCallbackMetadata::Trampoline tramp : tramps) {
    auto metadata = fcm->LookupMetadataForTrampolineUnlocked(tramp);
    EXPECT(metadata.IsLive());
    EXPECT_EQ(metadata.target_isolate(), isolate);
    EXPECT_EQ(static_cast<int>(metadata.trampoline_type()),
              static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync));
    EXPECT_EQ(metadata.target_entry_point(), sync_code.EntryPoint());
  }

  // Verify the list of callbacks.
  uword list_length = 0;
  for (FfiCallbackMetadata::MetadataEntry* me = list_head; me != nullptr;) {
    ++list_length;
    auto tramp = fcm->TrampolineOfMetadataEntry(me);
    EXPECT(me->metadata()->IsLive());
    EXPECT_EQ(me->metadata()->target_isolate(), isolate);
    EXPECT_EQ(tramps.count(tramp), 1u);
    auto* next = me->list_next();
    auto* prev = me->list_prev();
    if (prev != nullptr) {
      EXPECT_EQ(prev->list_next(), me);
    } else {
      EXPECT_EQ(list_head, me);
    }
    if (next != nullptr) {
      EXPECT_EQ(next->list_prev(), me);
    }
    me = me->list_next();
  }
  EXPECT_EQ(list_length, tramps.size());

  // Delete all callbacks and verify they're destroyed.
  fcm->DeleteAllCallbacks(&list_head);
  EXPECT_EQ(list_head, nullptr);
  for (FfiCallbackMetadata::Trampoline tramp : tramps) {
    EXPECT(!fcm->LookupMetadataForTrampolineUnlocked(tramp).IsLive());
  }
}

static void RunBigRandomMultithreadedTest(uint64_t seed) {
  static constexpr int kIterations = 1000;

  TestIsolateScope isolate_scope;
  Thread* thread = Thread::Current();
  Isolate* isolate = thread->isolate();
  ASSERT(isolate == isolate_scope.isolate());
  TransitionNativeToVM transition(thread);
  StackZone stack_zone(thread);

  struct TrampolineWithPort {
    FfiCallbackMetadata::Trampoline tramp;
    Dart_Port port;
  };

  auto* fcm = IsolateGroup::Current()->callback_metadata();
  Random random(seed);
  std::vector<TrampolineWithPort> tramps;
  std::unordered_set<FfiCallbackMetadata::Trampoline> tramp_set;
  FfiCallbackMetadata::MetadataEntry* list_head = nullptr;

  const Function& async_func =
      Function::Handle(CreateTestFunction(FfiCallbackKind::kAsyncCallback));
  const Code& async_code = Code::Handle(async_func.EnsureHasCode());
  EXPECT(!async_code.IsNull());
  const Function& sync_func = Function::Handle(
      CreateTestFunction(FfiCallbackKind::kIsolateLocalStaticCallback));
  const auto& sync_code = Code::Handle(sync_func.EnsureHasCode());
  EXPECT(!sync_code.IsNull());

  for (int itr = 0; itr < kIterations; ++itr) {
    // Do a random action:
    //  - Allocate a sync callback
    //  - Allocate an async callback
    //  - Delete a callback
    //  - Delete all the sync callbacks for an isolate

    if ((random.NextUInt32() % 100) == 0) {
      // 1% chance of deleting all the callbacks on the thread.
      fcm->DeleteAllCallbacks(&list_head);

      // It would be nice to verify that all the trampolines have been deleted,
      // but this is flaky because other threads can recycle these trampolines
      // before we finish checking all of them.
      tramps.clear();
      tramp_set.clear();
      EXPECT_EQ(list_head, nullptr);
    } else if (tramps.size() > 0 && (random.NextUInt32() % 4) == 0) {
      // 25% chance of deleting a callback.
      uint32_t r = random.NextUInt32() % tramps.size();
      auto tramp = tramps[r].tramp;
      fcm->DeleteCallback(tramp, &list_head);
      tramps[r] = tramps[tramps.size() - 1];
      tramps.pop_back();
      tramp_set.erase(tramp);
    } else {
      TrampolineWithPort tramp;
      if ((random.NextUInt32() % 2) == 0) {
        // 50% chance of creating a sync callback.
        tramp.port = ILLEGAL_PORT;
        tramp.tramp = fcm->CreateLocalFfiCallback(
            isolate, /*isolate_group=*/nullptr, thread->zone(), sync_func,
            Closure::Handle(Closure::null()), &list_head);
      } else {
        // 50% chance of creating an async callback.
        tramp.port = PortMap::CreatePort(new FakeMessageHandler());
        tramp.tramp = fcm->CreateAsyncFfiCallback(
            isolate, thread->zone(), async_func, tramp.port, &list_head);
      }
      tramps.push_back(tramp);
      tramp_set.insert(tramp.tramp);
    }

    // Verify all the callbacks.
    for (const auto& tramp : tramps) {
      auto metadata = fcm->LookupMetadataForTrampolineUnlocked(tramp.tramp);
      EXPECT(metadata.IsLive());
      EXPECT_EQ(metadata.target_isolate(), isolate);
      if (metadata.trampoline_type() ==
          FfiCallbackMetadata::TrampolineType::kSync) {
        EXPECT_EQ(metadata.closure_handle(), nullptr);
        EXPECT_EQ(metadata.target_entry_point(), sync_code.EntryPoint());
      } else {
        EXPECT_EQ(metadata.send_port(), tramp.port);
        EXPECT_EQ(metadata.target_entry_point(), async_code.EntryPoint());
      }
    }

    // Verify the isolate's list of callbacks.
    uword list_length = 0;
    for (FfiCallbackMetadata::MetadataEntry* me = list_head; me != nullptr;) {
      ++list_length;
      auto tramp = fcm->TrampolineOfMetadataEntry(me);
      EXPECT(me->metadata()->IsLive());
      EXPECT_EQ(me->metadata()->target_isolate(), isolate);
      EXPECT_EQ(tramp_set.count(tramp), 1u);
      me = me->list_next();
    }
    EXPECT_EQ(list_length, tramps.size());
    EXPECT_EQ(list_length, tramp_set.size());
  }

  // Delete all remaining callbacks.
  fcm->DeleteAllCallbacks(&list_head);
  EXPECT_EQ(list_head, nullptr);
}

ISOLATE_UNIT_TEST_CASE(FfiCallbackMetadata_BigRandomMultithreadedTest) {
  static constexpr int kThreads = 5;

  std::vector<std::thread> threads;

  Random random;
  for (int i = 0; i < kThreads; ++i) {
    threads.push_back(
        std::thread(RunBigRandomMultithreadedTest, random.NextUInt64()));
  }

  for (auto& thread : threads) {
    thread.join();
  }
}

}  // namespace dart
