// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

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

#include "include/v8-context.h"
#include "include/v8-function-callback.h"
#include "include/v8-local-handle.h"
#include "include/v8-object.h"
#include "include/v8-persistent-handle.h"
#include "include/v8-promise.h"
#include "include/v8-wasm.h"
#include "src/api/api-inl.h"
#include "src/handles/global-handles.h"
#include "src/objects/object-conversions-inl.h"
#include "src/wasm/wasm-features.h"
#include "src/wasm/wasm-js.h"
#include "src/wasm/wasm-module.h"
#include "test/common/flag-utils.h"
#include "test/common/wasm/wasm-macro-gen.h"
#include "test/unittests/heap/heap-utils.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace v8 {

bool wasm_streaming_callback_got_called = false;
bool wasm_streaming_data_got_collected = false;

// The bytes of a minimal WebAssembly module.
const uint8_t kMinimalWasmModuleBytes[]{0x00, 0x61, 0x73, 0x6d,
                                        0x01, 0x00, 0x00, 0x00};

class ApiWasmTest : public TestWithIsolate {
 public:
  void TestWasmStreaming(WasmStreamingCallback callback,
                         Promise::PromiseState expected_state) {
    isolate()->SetWasmStreamingCallback(callback);
    HandleScope scope(isolate());

    Local<Context> context = Context::New(isolate());
    Context::Scope context_scope(context);
    // Call {WebAssembly.compileStreaming} with {null} as parameter. The
    // parameter is only really processed by the embedder, so for this test the
    // value is irrelevant.
    Local<Promise> promise =
        Local<Promise>::Cast(RunJS("WebAssembly.compileStreaming(null)"));
    EmptyMessageQueues();
    CHECK_EQ(expected_state, promise->State());
  }

  Local<WasmModuleObject> FinishModuleCompilation(
      WasmModuleCompilation& compilation, std::span<const uint8_t> bytes) {
    compilation.OnBytesReceived(bytes.data(), bytes.size());
    // The resolution callback runs within a separate task with its own
    // HandleScope, so persist the result in a Global.
    Global<WasmModuleObject> module_object;
    compilation.Finish(
        isolate(), {},
        [this,
         &module_object](std::variant<Local<WasmModuleObject>, Local<Value>>
                             module_or_error) {
          CHECK(
              std::holds_alternative<Local<WasmModuleObject>>(module_or_error));
          module_object.Reset(
              isolate(), std::get<Local<WasmModuleObject>>(module_or_error));
        });
    EmptyMessageQueues();
    CHECK(!module_object.IsEmpty());
    return module_object.Get(isolate());
  }
};

void WasmStreamingTestFinalizer(const WeakCallbackInfo<void>& data) {
  CHECK(!wasm_streaming_data_got_collected);
  wasm_streaming_data_got_collected = true;
  i::GlobalHandles::Destroy(reinterpret_cast<i::Address*>(data.GetParameter()));
}

void WasmStreamingCallbackTestCallbackIsCalled(
    const FunctionCallbackInfo<Value>& info) {
  CHECK(i::ValidateCallbackInfo(info));
  CHECK(!wasm_streaming_callback_got_called);
  wasm_streaming_callback_got_called = true;

  i::Handle<i::Object> global_handle =
      reinterpret_cast<i::Isolate*>(info.GetIsolate())
          ->global_handles()
          ->Create(*Utils::OpenDirectHandle(*info.Data()));
  i::GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(),
                             WasmStreamingTestFinalizer,
                             WeakCallbackType::kParameter);
}

void WasmStreamingCallbackTestFinishWithSuccess(
    const FunctionCallbackInfo<Value>& info) {
  CHECK(i::ValidateCallbackInfo(info));
  std::shared_ptr<WasmStreaming> streaming =
      WasmStreaming::Unpack(info.GetIsolate(), info.Data());
  streaming->OnBytesReceived(kMinimalWasmModuleBytes,
                             arraysize(kMinimalWasmModuleBytes));
  streaming->Finish(WasmStreaming::ModuleCachingCallback{});
}

void WasmStreamingCallbackTestFinishWithFailure(
    const FunctionCallbackInfo<Value>& info) {
  CHECK(i::ValidateCallbackInfo(info));
  std::shared_ptr<WasmStreaming> streaming =
      WasmStreaming::Unpack(info.GetIsolate(), info.Data());
  streaming->Finish(WasmStreaming::ModuleCachingCallback{});
}

void WasmStreamingCallbackTestAbortWithReject(
    const FunctionCallbackInfo<Value>& info) {
  CHECK(i::ValidateCallbackInfo(info));
  std::shared_ptr<WasmStreaming> streaming =
      WasmStreaming::Unpack(info.GetIsolate(), info.Data());
  streaming->Abort(Object::New(info.GetIsolate()));
}

void WasmStreamingCallbackTestAbortNoReject(
    const FunctionCallbackInfo<Value>& info) {
  CHECK(i::ValidateCallbackInfo(info));
  std::shared_ptr<WasmStreaming> streaming =
      WasmStreaming::Unpack(info.GetIsolate(), info.Data());
  streaming->Abort({});
}

void WasmStreamingCallbackTestOnBytesReceived(
    const FunctionCallbackInfo<Value>& info) {
  CHECK(i::ValidateCallbackInfo(info));
  std::shared_ptr<WasmStreaming> streaming =
      WasmStreaming::Unpack(info.GetIsolate(), info.Data());

  // The first bytes of the WebAssembly magic word.
  const uint8_t bytes[]{0x00, 0x61, 0x73};
  streaming->OnBytesReceived(bytes, arraysize(bytes));
}

void WasmStreamingMoreFunctionsCanBeSerializedCallback(
    const FunctionCallbackInfo<Value>& info) {
  CHECK(i::ValidateCallbackInfo(info));
  std::shared_ptr<WasmStreaming> streaming =
      WasmStreaming::Unpack(info.GetIsolate(), info.Data());
  streaming->SetMoreFunctionsCanBeSerializedCallback([](CompiledWasmModule) {});
}

TEST_F(ApiWasmTest, WasmStreamingCallback) {
  TestWasmStreaming(WasmStreamingCallbackTestCallbackIsCalled,
                    Promise::kPending);
  CHECK(wasm_streaming_callback_got_called);
  {
    // We need to invoke GC without stack, otherwise the WasmStreaming data may
    // not be reclaimed.
    i::DisableConservativeStackScanningScopeForTesting no_css_scope(
        i_isolate()->heap());
    InvokeMemoryReducingMajorGCs(i_isolate());
  }
  CHECK(wasm_streaming_data_got_collected);
}

TEST_F(ApiWasmTest, WasmStreamingOnBytesReceived) {
  TestWasmStreaming(WasmStreamingCallbackTestOnBytesReceived,
                    Promise::kPending);
}

TEST_F(ApiWasmTest, WasmStreamingFinishWithSuccess) {
  TestWasmStreaming(WasmStreamingCallbackTestFinishWithSuccess,
                    Promise::kFulfilled);
}

TEST_F(ApiWasmTest, WasmStreamingFinishWithFailure) {
  TestWasmStreaming(WasmStreamingCallbackTestFinishWithFailure,
                    Promise::kRejected);
}

TEST_F(ApiWasmTest, WasmStreamingAbortWithReject) {
  TestWasmStreaming(WasmStreamingCallbackTestAbortWithReject,
                    Promise::kRejected);
}

TEST_F(ApiWasmTest, WasmStreamingAbortWithoutReject) {
  TestWasmStreaming(WasmStreamingCallbackTestAbortNoReject, Promise::kPending);
}

TEST_F(ApiWasmTest, WasmCompileToWasmModuleObject) {
  Local<Context> context = Context::New(isolate());
  Context::Scope context_scope(context);
  auto maybe_module =
      WasmModuleObject::Compile(isolate(), kMinimalWasmModuleBytes);
  CHECK(!maybe_module.IsEmpty());
}

namespace {
// Number of imports reflected by `WebAssembly.Module.imports`. Compile-time
// imports are bound during compilation and are not reflected here.
int ReflectedImportCount(Isolate* isolate, Local<WasmModuleObject> module) {
  i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
  i::DirectHandle<i::WasmModuleObject> module_object =
      i::Cast<i::WasmModuleObject>(Utils::OpenDirectHandle(*module));
  i::DirectHandle<i::JSArray> imports =
      i::wasm::GetImports(i_isolate, module_object);
  return static_cast<int>(i::Object::NumberValue(imports->length()));
}
}  // namespace

TEST_F(ApiWasmTest, WasmCompileWithJsStringBuiltins) {
  using namespace internal::wasm;  // NOLINT(build/namespaces)
  Local<Context> context = Context::New(isolate());
  Context::Scope context_scope(context);

  // Imports `charCodeAt` from the "wasm:js-string" builtins module with the
  // signature (externref, i32) -> i32, matching the builtin.
  const uint8_t valid_module[] = {
      WASM_MODULE_HEADER,
      SECTION(Type, ENTRY_COUNT(1),
              SIG_ENTRY_x_xx(kI32Code, kExternRefCode, kI32Code)),
      SECTION(Import, ENTRY_COUNT(1),
              // module name: "wasm:js-string"
              14, 'w', 'a', 's', 'm', ':', 'j', 's', '-', 's', 't', 'r', 'i',
              'n', 'g',
              // field name: "charCodeAt"
              10, 'c', 'h', 'a', 'r', 'C', 'o', 'd', 'e', 'A', 't',
              kExternalFunction, 0)};
  // The same import but declaring an i64 result, which no builtin provides.
  const uint8_t invalid_module[] = {
      WASM_MODULE_HEADER,
      SECTION(Type, ENTRY_COUNT(1),
              SIG_ENTRY_x_xx(kI64Code, kExternRefCode, kI32Code)),
      SECTION(Import, ENTRY_COUNT(1), 14, 'w', 'a', 's', 'm', ':', 'j', 's',
              '-', 's', 't', 'r', 'i', 'n', 'g', 10, 'c', 'h', 'a', 'r', 'C',
              'o', 'd', 'e', 'A', 't', kExternalFunction, 0)};

  WasmModuleObject::CompileOptions options{
      .builtins = WasmModuleObject::CompileOptions::Builtins::kJsString};

  // Without compile-time imports the "wasm:js-string" import is an ordinary
  // import resolved at instantiation, so it is reflected and both modules
  // compile.
  Local<WasmModuleObject> plain =
      WasmModuleObject::Compile(isolate(), valid_module).ToLocalChecked();
  CHECK_EQ(1, ReflectedImportCount(isolate(), plain));
  CHECK(!WasmModuleObject::Compile(isolate(), invalid_module).IsEmpty());

  // With the builtins enabled the valid import is bound at compile time, so it
  // is no longer reflected.
  Local<WasmModuleObject> bound =
      WasmModuleObject::Compile(isolate(), valid_module, options)
          .ToLocalChecked();
  CHECK_EQ(0, ReflectedImportCount(isolate(), bound));

  // The invalid signature is rejected at compile time.
  {
    TryCatch try_catch(isolate());
    CHECK(WasmModuleObject::Compile(isolate(), invalid_module, options)
              .IsEmpty());
    CHECK(try_catch.HasCaught());
  }
}

TEST_F(ApiWasmTest, WasmCompileWithImportedStringConstants) {
  using namespace internal::wasm;  // NOLINT(build/namespaces)
  Local<Context> context = Context::New(isolate());
  Context::Scope context_scope(context);

  // Imports an externref global "foo" from the "strings" module, which is
  // turned into a string constant when the constants module is provided.
  const uint8_t module[] = {
      WASM_MODULE_HEADER,
      SECTION(Import, ENTRY_COUNT(1),
              // module name: "strings"
              7, 's', 't', 'r', 'i', 'n', 'g', 's',
              // field name: "foo"
              3, 'f', 'o', 'o', kExternalGlobal, kExternRefCode, 0)};

  // Without a constants module the global is an ordinary reflected import.
  Local<WasmModuleObject> plain =
      WasmModuleObject::Compile(isolate(), module).ToLocalChecked();
  CHECK_EQ(1, ReflectedImportCount(isolate(), plain));

  // Naming "strings" as the constants module binds the import at compile time,
  // so it is no longer reflected.
  Local<WasmModuleObject> bound =
      WasmModuleObject::Compile(isolate(), module,
                                {.imported_string_constants_module = "strings"})
          .ToLocalChecked();
  CHECK_EQ(0, ReflectedImportCount(isolate(), bound));
}

TEST_F(ApiWasmTest, WasmCompileWithSourceUrl) {
  Local<Context> context = Context::New(isolate());
  Context::Scope context_scope(context);

  constexpr std::string_view kUrl = "file:///test/module.wasm";
  Local<WasmModuleObject> with_url =
      WasmModuleObject::Compile(isolate(), kMinimalWasmModuleBytes,
                                {.source_url = kUrl})
          .ToLocalChecked();
  CHECK_EQ(kUrl, with_url->GetCompiledModule().source_url());

  // A distinct module, since scripts (and their URLs) are cached per module
  // per isolate. Without a source URL a wasm:// URL is synthesized.
  const uint8_t distinct_module[] = {0x00, 0x61, 0x73, 0x6d, 0x01, 0x00,
                                     0x00, 0x00, 0x00, 0x02, 0x01, 'x'};
  Local<WasmModuleObject> plain =
      WasmModuleObject::Compile(isolate(), distinct_module).ToLocalChecked();
  CHECK_EQ(0u, plain->GetCompiledModule().source_url().find("wasm://wasm/"));
}

TEST_F(ApiWasmTest, WasmStreamingSetCallback) {
  TestWasmStreaming(WasmStreamingMoreFunctionsCanBeSerializedCallback,
                    Promise::kPending);
}

TEST_F(ApiWasmTest, WasmErrorIsSharedCrossOrigin) {
  Isolate::Scope iscope(isolate());
  HandleScope scope(isolate());
  Local<Context> context = Context::New(isolate());
  Context::Scope cscope(context);

  TryCatch try_catch(isolate());
  // A fairly minimal Wasm module that produces an error at runtime:
  // it returns {null} from an imported function that's typed to return
  // a non-null reference.
  const char* expected_message =
      "Uncaught TypeError: type incompatibility when transforming from/to JS";
  const char* src =
      "let raw = new Uint8Array(["
      "  0x00, 0x61, 0x73, 0x6d,  // wasm magic                            \n"
      "  0x01, 0x00, 0x00, 0x00,  // wasm version                          \n"

      "  0x01, 0x06,              // Type section, length 6                \n"
      "  0x01, 0x60,              // 1 type, kind: func                    \n"
      "  0x00, 0x01, 0x64, 0x6f,  // 0 params, 1 result: (ref extern)      \n"

      "  0x02, 0x07, 0x01,        // Import section, length 7, 1 import    \n"
      "  0x01, 0x6d, 0x01, 0x6e,  // 'm' 'n'                               \n"
      "  0x00, 0x00,              // kind: function $type0                 \n"

      "  0x03, 0x02,              // Function section, length 2            \n"
      "  0x01, 0x00,              // 1 function, $type0                    \n"

      "  0x07, 0x05, 0x01,        // Export section, length 5, 1 export    \n"
      "  0x01, 0x66, 0x00, 0x01,  // 'f': function #1                      \n"

      "  0x0a, 0x06, 0x01,        // Code section, length 6, 1 function    \n"
      "  0x04, 0x00,              // body size 4, 0 locals                 \n"
      "  0x10, 0x00, 0x0b,        // call $m.n; end                        \n"
      "]);                                                                 \n"

      "let mod = new WebAssembly.Module(raw.buffer);                       \n"
      "let instance = new WebAssembly.Instance(mod, {m: {n: () => null}}); \n"
      "instance.exports.f();";

  TryRunJS(src);
  EXPECT_TRUE(try_catch.HasCaught());
  Local<Message> message = try_catch.Message();
  CHECK_EQ(0, strcmp(*String::Utf8Value(isolate(), message->Get()),
                     expected_message));
  EXPECT_TRUE(message->IsSharedCrossOrigin());
}

TEST_F(ApiWasmTest, WasmEnableDisableCustomDescriptors) {
  Local<Context> context_local = Context::New(isolate());
  Context::Scope context_scope(context_local);
  i::DirectHandle<i::NativeContext> context =
      v8::Utils::OpenDirectHandle(*context_local);
  // Test enabling/disabling via flag.
  {
    i::FlagScope<bool> flag_descriptors(&i::v8_flags.wasm_custom_descriptors,
                                        true);
    EXPECT_TRUE(i_isolate()->IsWasmCustomDescriptorsEnabled(context));

    // When flag is on, callback return value has no effect.
    isolate()->SetWasmCustomDescriptorsEnabledCallback(
        [](auto) { return true; });
    EXPECT_TRUE(i_isolate()->IsWasmCustomDescriptorsEnabled(context));
    EXPECT_TRUE(i::wasm::WasmEnabledFeatures::FromIsolate(i_isolate())
                    .has_custom_descriptors());
    isolate()->SetWasmCustomDescriptorsEnabledCallback(
        [](auto) { return false; });
    EXPECT_TRUE(i_isolate()->IsWasmCustomDescriptorsEnabled(context));
    EXPECT_TRUE(i::wasm::WasmEnabledFeatures::FromIsolate(i_isolate())
                    .has_custom_descriptors());
  }
  {
    i::FlagScope<bool> flag_descriptors(&i::v8_flags.wasm_custom_descriptors,
                                        false);
    EXPECT_FALSE(i_isolate()->IsWasmCustomDescriptorsEnabled(context));

    // Test enabling/disabling via callback.
    isolate()->SetWasmCustomDescriptorsEnabledCallback(
        [](auto) { return true; });
    EXPECT_TRUE(i_isolate()->IsWasmCustomDescriptorsEnabled(context));
    EXPECT_TRUE(i::wasm::WasmEnabledFeatures::FromIsolate(i_isolate())
                    .has_custom_descriptors());
    {
      // Test that the kill switch overrides any Origin Trial.
      i::FlagScope<bool> flag_kill_switch(
          &i::v8_flags.wasm_custom_descriptors_permitted, false);
      EXPECT_FALSE(i_isolate()->IsWasmCustomDescriptorsEnabled(context));
      EXPECT_FALSE(i::wasm::WasmEnabledFeatures::FromIsolate(i_isolate())
                       .has_custom_descriptors());
    }
    isolate()->SetWasmCustomDescriptorsEnabledCallback(
        [](auto) { return false; });
    EXPECT_FALSE(i_isolate()->IsWasmCustomDescriptorsEnabled(context));
    EXPECT_FALSE(i::wasm::WasmEnabledFeatures::FromIsolate(i_isolate())
                     .has_custom_descriptors());
  }
}

TEST_F(ApiWasmTest, WasmModuleCompilation_Basic) {
  Isolate::Scope iscope(isolate());
  HandleScope scope(isolate());
  Local<Context> context = Context::New(isolate());
  Context::Scope cscope(context);

  TryCatch try_catch(isolate());

  // Start compilation.
  WasmModuleCompilation compilation;

  // Pass minimal bytes.
  compilation.OnBytesReceived(kMinimalWasmModuleBytes,
                              sizeof(kMinimalWasmModuleBytes));

  // Finish compilation.
  WasmModuleCompilation::ModuleCachingCallback no_caching_callback;
  MaybeLocal<WasmModuleObject> module_object;
  compilation.Finish(
      isolate(), no_caching_callback,
      [&module_object](
          std::variant<Local<WasmModuleObject>, Local<Value>> module_or_error) {
        CHECK(std::holds_alternative<Local<WasmModuleObject>>(module_or_error));
        CHECK(module_object.IsEmpty());
        module_object = std::get<Local<WasmModuleObject>>(module_or_error);
      });

  // Execute pending tasks.
  EmptyMessageQueues();

  // The callback must have been called without any exception.
  CHECK(!module_object.IsEmpty());
  CHECK(!try_catch.HasCaught());
  CHECK(!isolate()->HasPendingException());
}

TEST_F(ApiWasmTest, WasmModuleCompilation_CompileOptions) {
  using namespace internal::wasm;  // NOLINT(build/namespaces)
  Isolate::Scope iscope(isolate());
  HandleScope scope(isolate());
  Local<Context> context = Context::New(isolate());
  Context::Scope cscope(context);
  TryCatch try_catch(isolate());

  // Imports `charCodeAt` from the "wasm:js-string" builtins module, matching
  // the builtin signature, so it binds at compile time when enabled.
  const uint8_t module_bytes[] = {
      WASM_MODULE_HEADER,
      SECTION(Type, ENTRY_COUNT(1),
              SIG_ENTRY_x_xx(kI32Code, kExternRefCode, kI32Code)),
      SECTION(Import, ENTRY_COUNT(1), 14, 'w', 'a', 's', 'm', ':', 'j', 's',
              '-', 's', 't', 'r', 'i', 'n', 'g', 10, 'c', 'h', 'a', 'r', 'C',
              'o', 'd', 'e', 'A', 't', kExternalFunction, 0)};

  constexpr std::string_view kUrl = "file:///test/async-module.wasm";
  WasmModuleCompilation compilation(
      {.builtins = WasmModuleObject::CompileOptions::Builtins::kJsString,
       .source_url = kUrl});
  Local<WasmModuleObject> module =
      FinishModuleCompilation(compilation, module_bytes);
  CHECK(!try_catch.HasCaught());
  CHECK_EQ(0, ReflectedImportCount(isolate(), module));
  CHECK_EQ(kUrl, module->GetCompiledModule().source_url());
}

TEST_F(ApiWasmTest, WasmModuleCompilation_SourceUrl) {
  Isolate::Scope iscope(isolate());
  HandleScope scope(isolate());
  Local<Context> context = Context::New(isolate());
  Context::Scope cscope(context);
  TryCatch try_catch(isolate());

  // SetUrl cleanly replaces a URL provided via compile options.
  {
    constexpr std::string_view kSetUrl = "file:///test/replaced.wasm";
    WasmModuleCompilation compilation(
        {.source_url = "file:///test/original.wasm"});
    compilation.SetUrl(kSetUrl.data(), kSetUrl.size());
    Local<WasmModuleObject> module =
        FinishModuleCompilation(compilation, kMinimalWasmModuleBytes);
    CHECK_EQ(kSetUrl, module->GetCompiledModule().source_url());
  }

  // Without a URL, a wasm:// URL is synthesized. A distinct module, since
  // scripts (and their URLs) are cached per module per isolate.
  {
    const uint8_t distinct_module[] = {0x00, 0x61, 0x73, 0x6d, 0x01, 0x00,
                                       0x00, 0x00, 0x00, 0x02, 0x01, 'y'};
    WasmModuleCompilation compilation;
    Local<WasmModuleObject> module =
        FinishModuleCompilation(compilation, distinct_module);
    CHECK_EQ(0u, module->GetCompiledModule().source_url().find("wasm://wasm/"));
  }
  CHECK(!try_catch.HasCaught());
}

TEST_F(ApiWasmTest, GetWasmMemoryReservationSizeInBytes) {
  constexpr size_t kCapacity = 64 * 1024;  // 64 KiB
  size_t reservation = V8::GetWasmMemoryReservationSizeInBytes(
      V8::WasmMemoryType::kMemory32, kCapacity);
  size_t reservation64 = V8::GetWasmMemoryReservationSizeInBytes(
      V8::WasmMemoryType::kMemory64, kCapacity);

#if V8_TRAP_HANDLER_SUPPORTED
  EXPECT_GE(reservation, kCapacity);
  EXPECT_GE(reservation64, kCapacity);
#else
  EXPECT_EQ(reservation, kCapacity);
  EXPECT_EQ(reservation64, kCapacity);
#endif  // V8_TRAP_HANDLER_SUPPORTED
}

TEST_F(ApiWasmTest, WasmModuleCompilation_MultiThreaded) {
  using namespace internal::wasm;  // NOLINT(build/namespaces)
  // The module we are about to compile. It contains two functions, each
  // returning a constant.
  static const uint8_t module_bytes[] = {
      WASM_MODULE_HEADER, SECTION(Type, ENTRY_COUNT(1), SIG_ENTRY_x(kI32Code)),
      SECTION(Function, ENTRY_COUNT(2), SIG_INDEX(0), SIG_INDEX(0)),
      SECTION(Code, ENTRY_COUNT(2),
              ADD_COUNT(WASM_NO_LOCALS, WASM_I32V_1(1), WASM_END),
              ADD_COUNT(WASM_NO_LOCALS, WASM_I32V_1(2), WASM_END))};

  base::Vector<const uint8_t> remaining_bytes = base::VectorOf(module_bytes);
  auto next_split =
      [&remaining_bytes,
       rng = base::RandomNumberGenerator(
           i::v8_flags.random_seed)]() mutable -> base::Vector<const uint8_t> {
    if (remaining_bytes.empty()) return {};
    size_t split = static_cast<size_t>(
        rng.NextInt(static_cast<int>(remaining_bytes.size())));
    auto split_bytes = remaining_bytes.SubVector(0, split);
    remaining_bytes += split;
    return split_bytes;
  };
  base::Vector<const uint8_t> bytes_0 = next_split();
  base::Vector<const uint8_t> bytes_1 = next_split();

  // We spawn multiple threads to start compilation and deliver the bytes in
  // pieces from multiple threads. Eventually the foreground task will finish
  // compilation.
  std::atomic<int> next_step{0};
  std::unique_ptr<WasmModuleCompilation> compilation;
  std::thread threads[]{
      std::thread{[&] {
        // Start compilation.
        compilation = std::make_unique<WasmModuleCompilation>();
        next_step.store(1, std::memory_order_release);
      }},
      std::thread{[&] {
        while (next_step.load(std::memory_order_acquire) != 1) continue;
        // Deliver first split of the module bytes.
        compilation->OnBytesReceived(bytes_0.data(), bytes_0.size());
        next_step.store(2, std::memory_order_release);
      }},
      std::thread{[&] {
        while (next_step.load(std::memory_order_acquire) != 2) continue;
        // Deliver second split of the module bytes.
        compilation->OnBytesReceived(bytes_1.data(), bytes_1.size());
        next_step.store(3, std::memory_order_release);
      }},
      std::thread{[&] {
        while (next_step.load(std::memory_order_acquire) != 3) continue;
        // Deliver remaining module bytes.
        compilation->OnBytesReceived(remaining_bytes.data(),
                                     remaining_bytes.size());
        next_step.store(4, std::memory_order_release);
      }},
  };

  // Wait for background work to finish.
  while (next_step.load(std::memory_order_acquire) != 4) continue;

  Isolate::Scope iscope(isolate());
  HandleScope scope(isolate());
  Local<Context> context = Context::New(isolate());
  Context::Scope cscope(context);

  TryCatch try_catch(isolate());

  // Finish compilation from foreground.
  WasmModuleCompilation::ModuleCachingCallback no_caching_callback;
  MaybeLocal<WasmModuleObject> module_object;
  compilation->Finish(
      isolate(), no_caching_callback,
      [&module_object](
          std::variant<Local<WasmModuleObject>, Local<Value>> module_or_error) {
        CHECK(std::holds_alternative<Local<WasmModuleObject>>(module_or_error));
        CHECK(module_object.IsEmpty());
        module_object = std::get<Local<WasmModuleObject>>(module_or_error);
      });

  // Execute pending tasks.
  EmptyMessageQueues();

  // The callback must have been called without any exception.
  CHECK(!module_object.IsEmpty());
  CHECK(!try_catch.HasCaught());
  CHECK(!isolate()->HasPendingException());

  // Join all background threads before finishing.
  for (auto& t : threads) t.join();
}

}  // namespace v8
