// Copyright 2016 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 "test/common/wasm/fuzzer-common.h"

#include <cstdint>
#include <iomanip>
#include <iostream>
#include <memory>
#include <sstream>
#include <unordered_map>
#include <utility>
#include <variant>
#include <vector>

#if V8_OS_LINUX || V8_OS_DARWIN
#include <sys/mman.h>  // For `mincore`.
#endif                 // V8_OS_LINUX || V8_OS_DARWIN

#include "include/v8-context.h"
#include "include/v8-exception.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "include/v8-metrics.h"
#include "src/common/assert-scope.h"
#include "src/execution/isolate.h"
#include "src/objects/managed.h"
#include "src/utils/ostreams.h"
#include "src/wasm/baseline/liftoff-compiler.h"
#include "src/wasm/compilation-environment-inl.h"
#include "src/wasm/function-body-decoder-impl.h"
#include "src/wasm/module-compiler.h"
#include "src/wasm/module-decoder-impl.h"
#include "src/wasm/module-instantiate.h"
#include "src/wasm/string-builder-multiline.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-features.h"
#include "src/wasm/wasm-module-builder.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-opcodes-inl.h"
#include "src/wasm/wasm-tracing.h"
#include "src/zone/accounting-allocator.h"
#include "src/zone/zone.h"
#include "test/common/flag-utils.h"
#include "test/common/wasm/wasm-macro-gen.h"
#include "test/common/wasm/wasm-module-runner.h"
#include "test/fuzzer/fuzzer-support.h"
#include "tools/wasm/mjsunit-module-disassembler-impl.h"

#if V8_ENABLE_DRUMBRAKE
#include "src/wasm/interpreter/wasm-interpreter.h"
#endif  // V8_ENABLE_DRUMBRAKE

namespace v8::internal::wasm::fuzzing {

namespace {

V8_WARN_UNUSED_RESULT
bool CompileAllFunctionsForReferenceExecution(NativeModule* native_module,
                                              int32_t* max_steps) {
  const WasmModule* module = native_module->module();
  WasmCodeRefScope code_ref_scope;
  CompilationEnv env = CompilationEnv::ForModule(native_module);
  ModuleWireBytes wire_bytes_accessor{native_module->wire_bytes()};
  for (size_t i = module->num_imported_functions; i < module->functions.size();
       ++i) {
    auto& func = module->functions[i];
    base::Vector<const uint8_t> func_code =
        wire_bytes_accessor.GetFunctionBytes(&func);
    FunctionBody func_body(func.sig, func.code.offset(), func_code.begin(),
                           func_code.end());
    auto result = ExecuteLiftoffCompilation(
        &env, func_body,
        LiftoffOptions{.func_index = static_cast<int>(func.func_index),
                       .for_debugging = kForDebugging,
                       .counter_updates = native_module->counter_updates(),
                       .max_steps = max_steps});
    if (!result.succeeded()) {
#if V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_IA32
      // Liftoff compilation can bailout on x64 / ia32 if SSE4.1 is unavailable.
      if (!CpuFeatures::IsSupported(SSE4_1)) return false;
#endif
      FATAL(
          "Liftoff compilation failed on a valid module. Run with "
          "--trace-wasm-decoder (in a debug build) to see why.");
    }
    native_module->PublishCode(native_module->AddCompiledCode(result));
  }
  return true;
}

}  // namespace

bool ValuesEquivalent(const WasmValue& init_lhs, const WasmValue& init_rhs,
                      Isolate* isolate) {
  DisallowGarbageCollection no_gc;
  // Stack of elements to be checked.
  std::vector<std::pair<WasmValue, WasmValue>> cmp = {{init_lhs, init_rhs}};
  using TaggedT = decltype(Tagged<Object>().ptr());
  // Map of lhs objects we have already seen to their rhs object on the first
  // visit. This is needed to ensure a reasonable runtime for the check.
  // Example:
  //   (array.new $myArray 10 (array.new_default $myArray 10))
  // This creates a nested array where each outer array element is the same
  // inner array. Without memorizing the inner array, we'd end up performing
  // 100+ comparisons.
  std::unordered_map<TaggedT, TaggedT> lhs_map;

  auto CheckArray = [&cmp, &lhs_map](Tagged<WasmArray> lhs,
                                     Tagged<WasmArray> rhs) -> bool {
    auto [iter, inserted] = lhs_map.insert({lhs.ptr(), rhs.ptr()});
    if (!inserted) {
      return iter->second == rhs.ptr();
    }
    if (lhs->map()->wasm_type_info()->type() !=
        rhs->map()->wasm_type_info()->type()) {
      return false;
    }
    if (lhs->length() != rhs->length()) {
      return false;
    }
    cmp.reserve(cmp.size() + lhs->length());
    for (uint32_t i = 0; i < lhs->length(); ++i) {
      cmp.emplace_back(lhs->GetElement(i), rhs->GetElement(i));
    }
    return true;
  };

  auto CheckStruct = [&cmp, &lhs_map](Tagged<WasmStruct> lhs,
                                      Tagged<WasmStruct> rhs) -> bool {
    auto [iter, inserted] = lhs_map.insert({lhs.ptr(), rhs.ptr()});
    if (!inserted) {
      return iter->second == rhs.ptr();
    }
    if (lhs->map()->wasm_type_info()->type() !=
        rhs->map()->wasm_type_info()->type()) {
      return false;
    }
    const CanonicalStructType* type = GetTypeCanonicalizer()->LookupStruct(
        lhs->map()->wasm_type_info()->type_index());
    uint32_t field_count = type->field_count();
    for (uint32_t i = 0; i < field_count; ++i) {
      cmp.emplace_back(lhs->GetFieldValue(i), rhs->GetFieldValue(i));
    }
    return true;
  };

  while (!cmp.empty()) {
    const auto [lhs, rhs] = cmp.back();
    cmp.pop_back();

    if (lhs.type() != rhs.type()) return false;
    CHECK(!lhs.type().is_sentinel());  // top, bottom, void can't get here.

    if (lhs.type().is_numeric()) {
      if (lhs != rhs) return false;
      continue;
    }

    CHECK(lhs.type().is_ref());
    Tagged<Object> lhs_ref = *lhs.to_ref();
    Tagged<Object> rhs_ref = *rhs.to_ref();

    // Nulls and Smis can be compared by pointer equality.
    if (IsNull(lhs_ref) || IsWasmNull(lhs_ref) || IsSmi(lhs_ref)) {
      if (rhs_ref != lhs_ref) return false;
    } else if (IsWasmFuncRef(lhs_ref)) {
      if (!IsWasmFuncRef(rhs_ref)) return false;
      if (Cast<WasmFuncRef>(lhs_ref)->internal(isolate)->function_index() !=
          Cast<WasmFuncRef>(rhs_ref)->internal(isolate)->function_index()) {
        return false;
      }
    } else if (IsWasmStruct(lhs_ref)) {
      if (!IsWasmStruct(rhs_ref)) return false;
      if (!CheckStruct(Cast<WasmStruct>(lhs_ref), Cast<WasmStruct>(rhs_ref))) {
        return false;
      }
    } else if (IsWasmArray(lhs_ref)) {
      if (!IsWasmArray(rhs_ref)) return false;
      if (!CheckArray(Cast<WasmArray>(lhs_ref), Cast<WasmArray>(rhs_ref))) {
        return false;
      }
    } else if (IsString(lhs_ref)) {
      if (!IsString(rhs_ref)) return false;
      if (!Cast<String>(lhs_ref)->Equals(Cast<String>(rhs_ref))) return false;
    } else {
      i::StdoutStream{} << "Equivalence checking not implemented for: "
                        << Brief(lhs_ref) << "\n";
      FATAL(
          "Equivalence checking for instance type %d is not implemented "
          "(find printed object in output above)\n",
          Cast<HeapObject>(lhs_ref)->map()->instance_type());
    }
  }

  return true;
}

void PrintValue(std::ostream& os, const WasmValue& value) {
  enum class PrintSymbol {
    kStructClose,
    kArrayClose,
    kComma,
  };

  using PrintTask = std::variant<WasmValue, PrintSymbol>;
  using ObjectPtr = decltype(Tagged<Object>().ptr());

  std::vector<PrintTask> print_stack = {value};
  std::unordered_set<ObjectPtr> seen_objects;

  while (!print_stack.empty()) {
    PrintTask task = print_stack.back();
    print_stack.pop_back();

    if (PrintSymbol* symbol = std::get_if<PrintSymbol>(&task)) {
      switch (*symbol) {
        case PrintSymbol::kStructClose:
          os << "}";
          break;
        case PrintSymbol::kArrayClose:
          os << "]";
          break;
        case PrintSymbol::kComma:
          os << ", ";
          break;
      }
    } else {
      WasmValue current_val = std::get<WasmValue>(task);
      if (current_val.type().is_numeric()) {
        os << current_val.to_string();
      } else if (current_val.type().is_ref()) {
        Tagged<Object> ref = *current_val.to_ref();
        if (IsNull(ref) || IsWasmNull(ref)) {
          os << "null";
          continue;
        }

        if (IsSmi(ref) || IsWasmFuncRef(ref)) {
          os << "<" << current_val.to_string() << ">";
          continue;
        }

        if (seen_objects.count(ref.ptr())) {
          os << "<repeat>";
          continue;
        }
        seen_objects.insert(ref.ptr());

        if (IsWasmStruct(ref)) {
          Tagged<WasmStruct> struct_ref = Cast<WasmStruct>(ref);
          const auto* type = GetTypeCanonicalizer()->LookupStruct(
              struct_ref->map()->wasm_type_info()->type_index());
          uint32_t count = type->field_count();

          print_stack.push_back(PrintSymbol::kStructClose);
          for (uint32_t i = count; i-- > 0;) {
            print_stack.push_back(struct_ref->GetFieldValue(i));
            if (i > 0) {
              print_stack.push_back(PrintSymbol::kComma);
            }
          }
          os << '{';
        } else if (IsWasmArray(ref)) {
          Tagged<WasmArray> array_ref = Cast<WasmArray>(ref);
          uint32_t len = array_ref->length();

          print_stack.push_back(PrintSymbol::kArrayClose);
          for (uint32_t i = len; i-- > 0;) {
            print_stack.push_back(array_ref->GetElement(i));
            if (i > 0) {
              print_stack.push_back(PrintSymbol::kComma);
            }
          }
          os << '[';
        } else {
          os << "<" << current_val.to_string() << ">";
        }
      } else {
        os << "?";
      }
    }
  }
}

template <typename TraceEntry, typename PrintEntryFunc>
void CompareAndPrintTraces(const std::vector<TraceEntry>& trace,
                           const std::vector<TraceEntry>& ref_trace,
                           NativeModule* native_module, std::ostream& outs,
                           PrintEntryFunc print_entry) {
  CHECK_EQ(trace.size(), ref_trace.size());

  static constexpr size_t kMaxTracesToPrint = 50;
  static constexpr size_t kFirstMismatchContextSize = 10;

  // If the trace is small enough, just print everything.
  if (trace.size() <= kMaxTracesToPrint) {
    uint32_t mismatches = 0;
    for (size_t i = 0; i < trace.size(); ++i) {
      if (trace[i] != ref_trace[i]) {
        outs << "Mismatch at line " << (i + 1) << "\n";
        outs << "  Reference: ";
        print_entry(ref_trace[i], native_module, outs);
        outs << "\n  Actual:    ";
        print_entry(trace[i], native_module, outs);
        mismatches++;
      } else {
        print_entry(trace[i], native_module, outs);
      }
      outs << "\n";
    }
    if (mismatches == 0) {
      outs << "Traces are identical (" << trace.size() << " entries).\n";
    } else {
      outs << "Found " << mismatches << " mismatches in " << trace.size()
           << " entries.\n";
    }
    return;
  }

  // Get the indices of each mismatch.
  std::vector<size_t> mismatch_indices;
  for (size_t i = 0; i < trace.size(); ++i) {
    if (trace[i] != ref_trace[i]) {
      mismatch_indices.push_back(i);
    }
  }

  // If there are no mismatches, print the start and end of the trace.
  if (mismatch_indices.empty()) {
    size_t half = kMaxTracesToPrint / 2;
    for (size_t i = 0; i < half; ++i) {
      print_entry(trace[i], native_module, outs);
      outs << "\n";
    }
    outs << "[...]\n";
    for (size_t i = trace.size() - half; i < trace.size(); ++i) {
      print_entry(trace[i], native_module, outs);
      outs << "\n";
    }
    outs << "Traces are identical (" << trace.size() << " entries).\n";
    return;
  }

  // If there are mismatches, print the first mismatch with context and then the
  // rest of the mismatches.
  outs << "Found " << mismatch_indices.size() << " mismatches in "
       << trace.size() << " entries.\n";

  size_t first_mismatch_idx = mismatch_indices[0];
  outs << "\nContext around the first mismatch at line "
       << (first_mismatch_idx + 1) << ":\n";
  size_t start_context = (first_mismatch_idx > kFirstMismatchContextSize)
                             ? (first_mismatch_idx - kFirstMismatchContextSize)
                             : 0;
  size_t end_context = std::min(
      trace.size(), first_mismatch_idx + kFirstMismatchContextSize + 1);

  for (size_t i = start_context; i < end_context; ++i) {
    if (trace[i] != ref_trace[i]) {
      outs << "Mismatch at line " << (i + 1) << "\n";
      outs << "  Reference: ";
      print_entry(ref_trace[i], native_module, outs);
      outs << "\n  Actual:    ";
      print_entry(trace[i], native_module, outs);
      outs << "\n";
    } else {
      outs << "Match    at line " << (i + 1) << ": ";
      print_entry(trace[i], native_module, outs);
      outs << "\n";
    }
  }

  // Print the rest of mismatches.
  size_t last_printed_idx_in_context = end_context - 1;

  size_t mismatches_to_print =
      std::min(mismatch_indices.size(), kMaxTracesToPrint);
  for (size_t i = 1; i < mismatches_to_print; ++i) {
    size_t current_mismatch_idx = mismatch_indices[i];

    // Skip if this mismatch was already shown in the initial context.
    if (current_mismatch_idx <= last_printed_idx_in_context) {
      continue;
    }

    // If there's a gap between this mismatch and the previous one, print
    // "[...]".
    if (current_mismatch_idx > mismatch_indices[i - 1] + 1) {
      outs << "[...]\n";
    }

    outs << "Mismatch at line " << (current_mismatch_idx + 1) << "\n";
    outs << "  Reference: ";
    print_entry(ref_trace[current_mismatch_idx], native_module, outs);
    outs << "\n  Actual:    ";
    print_entry(trace[current_mismatch_idx], native_module, outs);
    outs << "\n";
  }
  if (mismatch_indices.size() > kMaxTracesToPrint) {
    outs << "[... " << (mismatch_indices.size() - kMaxTracesToPrint)
         << " more mismatches not shown]\n";
  }
}

void CompareAndPrintMemoryTraces(wasm::MemoryTrace& memory_trace,
                                 wasm::MemoryTrace& ref_memory_trace,
                                 NativeModule* native_module,
                                 std::ostream& outs) {
  CompareAndPrintTraces(memory_trace, ref_memory_trace, native_module, outs,
                        PrintMemoryTraceString);
}

void CompareAndPrintGlobalTraces(wasm::GlobalTrace& global_trace,
                                 wasm::GlobalTrace& ref_global_trace,
                                 NativeModule* native_module,
                                 std::ostream& outs) {
  CompareAndPrintTraces(global_trace, ref_global_trace, native_module, outs,
                        PrintGlobalTraceString);
}

CompileTimeImports CompileTimeImportsForFuzzing() {
  CompileTimeImports result;
  result.Add(CompileTimeImport::kJsString);
  result.Add(CompileTimeImport::kTextDecoder);
  result.Add(CompileTimeImport::kTextEncoder);
  return result;
}

// Compile a baseline module. We pass a pointer to a max step counter and a
// nondeterminsm flag that are updated during execution by Liftoff.
MaybeDirectHandle<WasmModuleObject> CompileReferenceModule(
    Isolate* isolate, base::Vector<const uint8_t> wire_bytes,
    int32_t* max_steps) {
  // Create the native module.
  std::shared_ptr<NativeModule> native_module;
  constexpr bool kNoVerifyFunctions = false;
  auto enabled_features = WasmEnabledFeatures::FromIsolate(isolate);
  WasmDetectedFeatures detected_features;
  ModuleResult module_res = DecodeWasmModule(
      enabled_features, wire_bytes, kNoVerifyFunctions, &detected_features);
  CHECK(module_res.ok());
  std::shared_ptr<WasmModule> module = std::move(module_res).value();
  CHECK_NOT_NULL(module);
  CompileTimeImports compile_imports = CompileTimeImportsForFuzzing();
  WasmError imports_error = ValidateAndSetBuiltinImports(
      module.get(), wire_bytes, compile_imports, &detected_features);
  CHECK(!imports_error.has_error());  // The module was compiled before.
  const size_t code_size_estimate =
      WasmCodeManager::EstimateNativeModuleCodeSize(module.get());
  native_module = GetWasmEngine()->NewNativeModule(
      isolate, enabled_features, detected_features,
      CompileTimeImportsForFuzzing(), module, code_size_estimate);
  native_module->SetWireBytes(base::OwnedCopyOf(wire_bytes));

  // The value is -3 so that it is different than the compilation ID of actual
  // compilations, different than the sentinel value of the CompilationState
  // (-1) and the value used by native module deserialization (-2).
  const int dummy_fuzzing_compilation_id = -3;
  native_module->compilation_state()->set_compilation_id(
      dummy_fuzzing_compilation_id);
  InitializeCompilationForTesting(native_module.get());

  // Compile all functions with Liftoff.
  // This can fail on missing CPU features.
  if (!CompileAllFunctionsForReferenceExecution(native_module.get(),
                                                max_steps)) {
    return {};
  }

  // Create the module object.
  constexpr base::Vector<const char> kNoSourceUrl;
  DirectHandle<Script> script =
      GetWasmEngine()->GetOrCreateScript(isolate, native_module, kNoSourceUrl);
  TypeCanonicalizer::PrepareForCanonicalTypeId(
      isolate, module->MaxCanonicalTypeIndex(),
      SharedFlag{module->has_shared_part});
  return WasmModuleObject::New(isolate, std::move(native_module), script);
}

#if V8_ENABLE_DRUMBRAKE
void ClearJsToWasmWrappersForTesting(Isolate* isolate) {
  isolate->heap()->SetJSToWasmWrappers(
      ReadOnlyRoots(isolate).empty_weak_fixed_array());
}
#endif  // V8_ENABLE_DRUMBRAKE

struct ExecutionResult {
  DirectHandle<WasmInstanceObject> instance;
  std::unique_ptr<const char[]> exception;
  int32_t result = -1;
  bool should_execute_non_reference = false;
};

ExecutionResult ExecuteReferenceRun(Isolate* isolate,
                                    base::Vector<const uint8_t> wire_bytes,
                                    int exported_main_function_index,
                                    int32_t max_executed_instructions) {
  // The reference module uses a special compilation mode of Liftoff for
  // termination detection, and that would be undone by flushing that code.
  FlagScope<bool> no_liftoff_code_flushing(&v8_flags.flush_liftoff_code, false);

  int32_t max_steps = max_executed_instructions;

  Zone reference_module_zone(isolate->allocator(), "wasm reference module");
  DirectHandle<WasmModuleObject> module_ref;
  if (!CompileReferenceModule(isolate, wire_bytes, &max_steps)
           .ToHandle(&module_ref)) {
    return {};
  }
  DirectHandle<WasmInstanceObject> instance_ref;

  // Before execution, there should be no dangling nondeterminism registered on
  // the engine, no pending exception, and no termination request.
  DCHECK(!WasmEngine::had_nondeterminism());
  DCHECK(!isolate->has_exception());
  DCHECK(!isolate->stack_guard()->CheckTerminateExecution());

  // Try to instantiate the reference instance, return if it fails.
  {
    ErrorThrower thrower(isolate, "ExecuteAgainstReference");
    if (!GetWasmEngine()
             ->SyncInstantiate(isolate, &thrower, module_ref,
                               /* no imports */ {})
             .ToHandle(&instance_ref)) {
      isolate->clear_exception();
      thrower.Reset();  // Ignore errors.
      return {};
    }
  }

  // Get the "main" exported function. We checked before that this exists.
  DirectHandle<WasmExportedFunction> main_function;
  CHECK(testing::GetExportedFunction(isolate, instance_ref, "main")
            .ToHandle(&main_function));

  struct OomCallbackData {
    Isolate* const isolate;
    int32_t* const max_steps;
    bool heap_limit_reached{false};
    size_t initial_limit{0};
  };
  OomCallbackData oom_callback_data{isolate, &max_steps};
  auto heap_limit_callback = [](void* raw_data, size_t current_limit,
                                size_t initial_limit) -> size_t {
    OomCallbackData* data = reinterpret_cast<OomCallbackData*>(raw_data);
    if (data->heap_limit_reached) return initial_limit;
    data->heap_limit_reached = true;
    data->initial_limit = initial_limit;
    // We can not throw an exception directly at this point, so we try to stop
    // execution as soon as possible by
    // 1) requesting termination (on the next stack check, or checked in runtime
    //    functions), and
    // 2) resetting max_steps to 0, which will make the reference run stop on
    //    the next instruction.
    data->isolate->stack_guard()->RequestTerminateExecution();
    *data->max_steps = 0;
    // Return a generously raised limit to maximize the chance to finish
    // executing until one of the two conditions created above is detected.
    return initial_limit * 4;
  };
  isolate->heap()->AddNearHeapLimitCallback(heap_limit_callback,
                                            &oom_callback_data);

  Tagged<WasmExportedFunctionData> func_data =
      main_function->shared()->wasm_exported_function_data();
  DCHECK_EQ(exported_main_function_index, func_data->function_index());
  const FunctionSig* sig = func_data->instance_data()
                               ->module()
                               ->functions[func_data->function_index()]
                               .sig;
  DirectHandleVector<Object> compiled_args =
      testing::MakeDefaultArguments(isolate, sig);
  std::unique_ptr<const char[]> exception;
  int32_t result_ref = testing::CallWasmFunctionForTesting(
      isolate, instance_ref, "main", base::VectorOf(compiled_args), &exception);
  bool execute = true;
  // Reached max steps, do not try to execute the test module as it might
  // never terminate.
  if (max_steps < 0) execute = false;
  // If there is nondeterminism, we cannot guarantee the behavior of the test
  // module, and in particular it may not terminate.
  if (WasmEngine::clear_nondeterminism()) execute = false;
  // Similar to max steps reached, also discard modules that need too much
  // memory.
  isolate->heap()->RemoveNearHeapLimitCallback(heap_limit_callback,
                                               oom_callback_data.initial_limit);
  if (oom_callback_data.heap_limit_reached) {
    execute = false;
    isolate->stack_guard()->ClearTerminateExecution();
  }

  if (exception) {
    if (strcmp(exception.get(),
               "RangeError: Maximum call stack size exceeded") == 0) {
      // There was a stack overflow, which may happen nondeterministically. We
      // cannot guarantee the behavior of the test module, and in particular it
      // may not terminate.
      execute = false;
    }
  }

  if (!execute) {
    // Before discarding the module, see if Turbofan runs into any DCHECKs.
    TierUpAllForTesting(isolate, instance_ref->trusted_data(isolate));
    return {};
  }

  return {instance_ref, std::move(exception), result_ref, true};
}

// This function instantiates and runs the module for the actual, non-reference
// run.
static std::optional<ExecutionResult> ExecuteNonReferenceRun(
    Isolate* isolate, const WasmModule* module,
    DirectHandle<WasmModuleObject> module_object, int exported_main) {
  DirectHandle<WasmInstanceObject> instance;
  {
    ErrorThrower thrower(isolate, "ExecuteNonReferenceRun");
    if (!GetWasmEngine()
             ->SyncInstantiate(isolate, &thrower, module_object,
                               /* no imports */ {})
             .ToHandle(&instance)) {
      CHECK(thrower.error());
      // The only reason to fail this instantiation should be OOM, because
      // there is a previous instantiation in the reference run.
      if (strstr(thrower.error_msg(), "Out of memory")) {
        // The initial memory size might be too large for instantiation
        // (especially on 32 bit systems), therefore do not treat it as a fuzzer
        // failure.
        return {};
      }
      FATAL("Instantiation failed unexpectedly: %s", thrower.error_msg());
    }
    CHECK(!thrower.error());
  }

  std::unique_ptr<const char[]> exception;
  const FunctionSig* sig = module->functions[exported_main].sig;
  DirectHandleVector<Object> compiled_args =
      testing::MakeDefaultArguments(isolate, sig);
  int32_t result = testing::CallWasmFunctionForTesting(
      isolate, instance, "main", base::VectorOf(compiled_args), &exception);

  if (exception) {
    if (strcmp(exception.get(),
               "RangeError: Maximum call stack size exceeded") == 0) {
      // There was a stack overflow. The reference run didn't have one,
      // otherwise we wouldn't have gotten here; but we have seen cases where
      // TF stack frames are slightly larger and optimized code hence
      // runs into a stack overflow where unoptimized code didn't.
      // Just ignore this, it's not a bug.
      return {};
    }
  }

  return {{instance, std::move(exception), result, false}};
}

int FindExportedMainFunction(const WasmModule* module,
                             base::Vector<const uint8_t> wire_bytes) {
  constexpr base::Vector<const char> kMainName = base::StaticCharVector("main");
  for (const WasmExport& exp : module->export_table) {
    if (exp.kind != ImportExportKindCode::kExternalFunction) continue;
    base::Vector<const uint8_t> name =
        wire_bytes.SubVector(exp.name.offset(), exp.name.end_offset());
    if (base::Vector<const char>::cast(name) != kMainName) continue;
    return exp.index;
  }
  return -1;
}

bool GlobalsMatch(Isolate* isolate, const WasmModule* module,
                  Tagged<WasmTrustedInstanceData> instance_data,
                  Tagged<WasmTrustedInstanceData> ref_instance_data,
                  bool print_difference) {
  size_t globals_count = module->globals.size();

  int global_mismatches = 0;
  for (size_t i = 0; i < globals_count; ++i) {
    const WasmGlobal& global = module->globals[i];
    WasmValue value = instance_data->GetGlobalValue(isolate, global);
    WasmValue ref_value = ref_instance_data->GetGlobalValue(isolate, global);

    if (!ValuesEquivalent(value, ref_value, isolate)) {
      if (print_difference) {
        std::ostringstream ss;
        ss << "Error: Global variables at index " << i
           << " have different values!\n";
        ss << "  - Reference: ";
        PrintValue(ss, ref_value);
        ss << "\n  - Actual:    ";
        PrintValue(ss, value);
        base::OS::PrintError("%s\n", ss.str().c_str());
      }
      global_mismatches++;
    }
  }

  return global_mismatches == 0;
}

#if V8_OS_LINUX || V8_OS_DARWIN
// Compares two memory regions efficiently by skipping non-resident pages.
// Processes memory in batches to minimize metadata overhead.
bool sparse_memory_equal(uint8_t* addr1, uint8_t* addr2, size_t total_length) {
  const size_t page_size = i::CommitPageSize();
  // TODO(clemensb): Add batching if necessary. 16GB is 4M pages of 4kB, hence
  // the two vectors below are <= 4MB, which is OK.
  static_assert(kMaxMemory64Size <= uint64_t{16} * GB);
  DCHECK_GE(kMaxMemory64Size, total_length);
  const size_t num_pages = total_length / page_size;
  DCHECK_EQ(total_length, num_pages * page_size);

#ifdef V8_OS_DARWIN
  using mincore_dst_type = char;
#else
  using mincore_dst_type = unsigned char;
#endif  // V8_OS_DARWIN
  // Allocate storage for the two residency vectors.
  auto storage =
      base::OwnedVector<mincore_dst_type>::NewForOverwrite(2 * num_pages);
  mincore_dst_type* vec1 = storage.data();
  mincore_dst_type* vec2 = vec1 + num_pages;

  // Fetch residency status for both address ranges.
  if (mincore(addr1, total_length, vec1) != 0 ||
      mincore(addr2, total_length, vec2) != 0) {
    FATAL("mincore failed: %s", strerror(errno));
  }

  for (size_t i = 0; i < num_pages; ++i) {
    bool p1_res = vec1[i] & 1;
    bool p2_res = vec2[i] & 1;

    // Compare pages if at least one is resident.
    if (!p1_res && !p2_res) continue;
    if (std::memcmp(addr1 + (i * page_size), addr2 + (i * page_size),
                    page_size) != 0) {
      return false;
    }
  }

  return true;
}
#endif  // V8_OS_LINUX || V8_OS_DARWIN

bool MemoriesMatch(Isolate* isolate, const WasmModule* module,
                   Tagged<WasmTrustedInstanceData> instance_data,
                   Tagged<WasmTrustedInstanceData> ref_instance_data,
                   bool print_difference) {
  int memory_mismatches = 0;
  size_t memories_count = module->memories.size();
  for (size_t i = 0; i < memories_count; ++i) {
    int memory_index = module->memories[i].index;
    Tagged<WasmMemoryObject> memory =
        instance_data->memory_object(memory_index);
    Tagged<WasmMemoryObject> ref_memory =
        ref_instance_data->memory_object(memory_index);

    CppGCManaged<BackingStore>::Ptr store = memory->backing_store();
    CppGCManaged<BackingStore>::Ptr ref_store = ref_memory->backing_store();

    size_t memory_size = store->byte_length();
    size_t ref_memory_size = ref_store->byte_length();

    if (ref_memory_size != memory_size) {
      memory_mismatches++;

      if (print_difference) {
        std::ostringstream ss;
        ss << "Error: Memories at index " << i << " have different sizes!\n";
        ss << "  - Reference: " << ref_memory_size << " bytes\n;";
        ss << "  - Actual:    " << memory_size << " bytes" << std::endl;
        base::OS::PrintError("%s", ss.str().c_str());
      }
      continue;
    }

    uint8_t* data = static_cast<uint8_t*>(store->buffer_start());
    uint8_t* ref_data = static_cast<uint8_t*>(ref_store->buffer_start());

#if V8_OS_LINUX || V8_OS_DARWIN
    const bool memory_equal = sparse_memory_equal(ref_data, data, memory_size);
#else
    const bool memory_equal = std::memcmp(ref_data, data, memory_size) == 0;
#endif  // V8_OS_LINUX || V8_OS_DARWIN

    if (memory_equal) continue;

    memory_mismatches++;

    if (!print_difference) {
      continue;
    }

    constexpr int block_size = 16;

    auto print_mem_block = [](std::ostream& os, const uint8_t* buffer) {
      for (size_t k = 0; k < block_size; ++k) {
        os << std::setw(2) << std::setfill('0')
           << static_cast<uint32_t>(buffer[k]) << " ";
        if ((k + 1) % 4 == 0) {
          os << " ";
        }
      }
    };

    if (memory_size != 0) {
      CHECK_NOT_NULL(data);
      CHECK_NOT_NULL(ref_data);

      for (size_t j = 0; j < memory_size; j += block_size) {
        if (memcmp(ref_data + j, data + j, block_size) != 0) {
          std::ostringstream ss;
          ss << "Error: Memory difference found in range 0x" << std::hex << j
             << "-0x" << (j + block_size) << " (" << std::dec << j << "-"
             << (j + block_size) << ")!\n"
             << std::hex;

          ss << "  - Reference: ";
          print_mem_block(ss, ref_data + j);

          ss << "\n  - Actual:    ";
          print_mem_block(ss, data + j);

          ss << "\n               ";
          for (size_t k = 0; k < block_size; ++k) {
            if (ref_data[j + k] != data[j + k]) {
              ss << "^^ ";
            } else {
              ss << "   ";
            }
            if ((k + 1) % 4 == 0) {
              ss << " ";
            }
          }
          base::OS::PrintError("%s\n", ss.str().c_str());
        }
      }
    }
  }

  return memory_mismatches == 0;
}

bool TablesMatch(Isolate* isolate, const WasmModule* module,
                 Tagged<WasmTrustedInstanceData> instance_data,
                 Tagged<WasmTrustedInstanceData> ref_instance_data,
                 bool print_difference) {
  size_t tables_count = module->tables.size();

  Tagged<FixedArray> tables = instance_data->tables();
  Tagged<FixedArray> ref_tables = ref_instance_data->tables();

  int table_mismatches = 0;
  for (size_t i = 0; i < tables_count; ++i) {
    int table_index = static_cast<int>(i);
    Tagged<WasmTableObject> table =
        Cast<WasmTableObject>(tables->get(table_index));
    Tagged<WasmTableObject> ref_table =
        Cast<WasmTableObject>(ref_tables->get(table_index));

    int length = table->current_length();
    int ref_length = ref_table->current_length();

    if (ref_length != length) {
      table_mismatches++;
      if (print_difference) {
        std::ostringstream ss;
        ss << "Error: Tables at index " << i << " have different lengths!\n";
        ss << "  - Reference: " << ref_length << "\n";
        ss << "  - Actual:    " << length << std::endl;
        base::OS::PrintError("%s", ss.str().c_str());
      }
      continue;
    }

    Tagged<FixedArray> table_entries = table->entries();
    Tagged<FixedArray> ref_table_entries = ref_table->entries();

    for (int j = 0; j < length; ++j) {
      // TODO(clemensb): Avoid handle allocation.
      DirectHandle<Object> entry(table_entries->get(j), isolate);
      DirectHandle<Object> ref_entry(ref_table_entries->get(j), isolate);

      // TODO(clemensb): Avoid putting Tuple2 in WasmValue.
      WasmValue value = WasmValue(entry, table->canonical_type(module));
      WasmValue ref_value =
          WasmValue(ref_entry, ref_table->canonical_type(module));

      // Tuple2 is used as placeholder in tables (see
      // `WasmTableObject::SetFunctionTablePlaceholder`). They reference the
      // instance and the function index; just check the stored function index.
      if (!IsWasmNull(*entry) && IsTuple2(*entry)) {
        if (!IsWasmNull(*ref_entry) && IsTuple2(*ref_entry) &&
            Cast<Tuple2>(*entry)->value2() ==
                Cast<Tuple2>(*ref_entry)->value2()) {
          continue;
        }
      } else {
        if (ValuesEquivalent(value, ref_value, isolate)) continue;
      }

      table_mismatches++;

      if (print_difference) {
        std::ostringstream ss;
        ss << "Error: Table entries at index " << j << " of table " << i
           << " have different values!\n";
        ss << "  - Reference: ";
        PrintValue(ss, ref_value);
        ss << "\n  - Actual:    ";
        PrintValue(ss, value);
        base::OS::PrintError("%s\n", ss.str().c_str());
      }
    }
  }

  return table_mismatches == 0;
}

int ExecuteAgainstReference(Isolate* isolate,
                            DirectHandle<WasmModuleObject> module_object,
                            int32_t max_executed_instructions
#if V8_ENABLE_DRUMBRAKE
                            ,
                            bool is_wasm_jitless
#endif  // V8_ENABLE_DRUMBRAKE
) {
  HandleScope handle_scope(isolate);

  CppGCManaged<wasm::NativeModule>::Ptr native_module =
      module_object->native_module();
  const WasmModule* module = native_module->module();
  const base::Vector<const uint8_t> wire_bytes = native_module->wire_bytes();
  int exported_main = FindExportedMainFunction(module, wire_bytes);
  if (exported_main < 0) return -1;

  // We do not instantiate the module if there is a start function, because a
  // start function can contain an infinite loop which we cannot handle.
  if (module->start_function_index >= 0) return -1;

  ExecutionResult ref_result = ExecuteReferenceRun(
      isolate, wire_bytes, exported_main, max_executed_instructions);
  if (!ref_result.should_execute_non_reference) return -1;

#if V8_ENABLE_DRUMBRAKE
  if (is_wasm_jitless) {
    v8::internal::v8_flags.jitless = true;
    v8::internal::v8_flags.wasm_jitless = true;

    FlagList::EnforceFlagImplications();
    v8::internal::wasm::WasmInterpreterThread::Initialize();
    ClearJsToWasmWrappersForTesting(isolate);

    // Compiled WasmCode objects should be cleared before running drumbrake.
    isolate->heap()->CollectAllGarbage(GCFlag::kNoFlags,
                                       i::GarbageCollectionReason::kTesting);

    // The module should be validated when compiled for jitless mode.
    // But, we already compiled the module without jitless for the reference
    // instance. So, we run the validation here before running drumbrake.
    auto enabled_features = WasmEnabledFeatures::FromIsolate(isolate);
    WasmDetectedFeatures unused_detected_features;
    ModuleDecoderImpl decoder(enabled_features,
                              module_object->native_module()->wire_bytes(),
                              &unused_detected_features);
    if (decoder.DecodeModule(/*validate_functions=*/true).failed()) return -1;
  }
#endif  // V8_ENABLE_DRUMBRAKE

  std::optional<ExecutionResult> result_opt =
      ExecuteNonReferenceRun(isolate, module, module_object, exported_main);
  if (!result_opt) {
    // The execution of non-reference run can fail if it runs OOM during
    // instantiation or overflows the stack.
    return -1;
  }
  const ExecutionResult& result = *result_opt;

  // Also the second run can hit nondeterminism which was not hit before (when
  // growing memory). In that case, do not compare results.
  // TODO(384781857): Due to nondeterminism, the second run could even not
  // terminate. If this happens often enough we should do something about this.
  if (WasmEngine::clear_nondeterminism()) return -1;

  if ((ref_result.exception != nullptr) != (result.exception != nullptr) ||
      (ref_result.exception &&
       strcmp(ref_result.exception.get(), result.exception.get()) != 0)) {
    FATAL("Exception mismatch! Expected: <%s>; got: <%s>",
          ref_result.exception ? ref_result.exception.get() : "<no exception>",
          result.exception ? result.exception.get() : "<no exception>");
  }

  if (!result.exception) {
    CHECK_EQ(ref_result.result, result.result);
  }

  bool globals_match;
  bool memories_match;
  bool tables_match;
  {
    DisallowGarbageCollection no_gc;

    Tagged<WasmTrustedInstanceData> instance_data =
        result.instance->trusted_data(isolate);
    Tagged<WasmTrustedInstanceData> ref_instance_data =
        ref_result.instance->trusted_data(isolate);

    globals_match =
        GlobalsMatch(isolate, module, instance_data, ref_instance_data, true);
    memories_match =
        MemoriesMatch(isolate, module, instance_data, ref_instance_data, true);
    tables_match =
        TablesMatch(isolate, module, instance_data, ref_instance_data, true);
    if (globals_match && memories_match && tables_match) {
      return 0;
    }

    if (!tables_match && (globals_match && memories_match)) {
      FATAL(
          "\nMismatch detected in tables! Tracing for tables is not "
          "implemented "
          "yet.");
    }

    base::OS::PrintError(
        "\nMismatch detected in global variables, memories or tables - "
        "rerunning "
        "with tracing enabled.\n");
  }

  bool should_trace_globals = !globals_match;
  bool should_trace_memory = !memories_match;

  // Disable module cache so that the module is actually recompiled and the
  // runtime calls for tracing are generated.
  FlagScope<bool> no_native_module_cache(&v8_flags.wasm_native_module_cache,
                                         false);
  FlagScope<bool> trace_globals_scope(&v8_flags.trace_wasm_globals,
                                      should_trace_globals);
  FlagScope<bool> trace_memory_scope(&v8_flags.trace_wasm_memory,
                                     should_trace_memory);

  wasm::WasmTracesForTesting& traces = GetWasmTracesForTesting();
  // This flag makes sure the runtime functions store the traces instead of
  // printing them to stdout.
  traces.should_store_trace = true;

  if (isolate->has_exception()) isolate->clear_exception();
  if (WasmEngine::clear_nondeterminism()) return -1;

  ExecutionResult ref_result_traced = ExecuteReferenceRun(
      isolate, wire_bytes, exported_main, max_executed_instructions);
  CHECK(ref_result_traced.should_execute_non_reference);

  // Copy the traces from the reference run to preserve them.
  wasm::MemoryTrace ref_memory_trace = std::move(traces.memory_trace);
  wasm::GlobalTrace ref_global_trace = std::move(traces.global_trace);
  // Reset the vectors to store the traces for the actual run.
  traces.memory_trace.clear();
  traces.global_trace.clear();

  DirectHandle<WasmModuleObject> module_object_traced;

  {
    ErrorThrower thrower(isolate, "ExecuteAgainstReference (traced compile)");
    auto enabled_features = WasmEnabledFeatures::FromIsolate(isolate);
    MaybeDirectHandle<WasmModuleObject> maybe_module =
        GetWasmEngine()->SyncCompile(isolate, enabled_features,
                                     CompileTimeImportsForFuzzing(), &thrower,
                                     base::OwnedCopyOf(wire_bytes));
    module_object_traced = maybe_module.ToHandleChecked();
    CHECK(!thrower.error());
  }

  std::optional<ExecutionResult> result_traced_opt = ExecuteNonReferenceRun(
      isolate, module, module_object_traced, exported_main);
  CHECK(result_traced_opt);
  const ExecutionResult& result_traced = *result_traced_opt;

  Tagged<WasmTrustedInstanceData> instance_data_traced =
      result_traced.instance->trusted_data(isolate);
  Tagged<WasmTrustedInstanceData> ref_instance_data_traced =
      ref_result_traced.instance->trusted_data(isolate);

  bool globals_match_traced = GlobalsMatch(
      isolate, module, instance_data_traced, ref_instance_data_traced, false);
  bool memories_match_traced = MemoriesMatch(
      isolate, module, instance_data_traced, ref_instance_data_traced, false);

  if (globals_match_traced && memories_match_traced) {
    FATAL("Mismatch disappeared when re-running with tracing enabled.");
  }

  wasm::MemoryTrace memory_trace = std::move(traces.memory_trace);
  wasm::GlobalTrace global_trace = std::move(traces.global_trace);

  if (should_trace_memory) {
    std::ostringstream ss;
    ss << "\nMemory trace.\n";
    CompareAndPrintMemoryTraces(memory_trace, ref_memory_trace,
                                native_module.raw(), ss);
    base::OS::PrintError("%s", ss.str().c_str());
  }

  if (should_trace_globals) {
    std::ostringstream ss;
    ss << "\nGlobal trace.\n";
    CompareAndPrintGlobalTraces(global_trace, ref_global_trace,
                                native_module.raw(), ss);
    base::OS::PrintError("%s", ss.str().c_str());
  }

  if (!tables_match) {
    base::OS::PrintError("\nTracing for tables is not implemented yet.");
  }

  FATAL("Execution mismatch. Tracing has been printed.");
}

void GenerateTestCase(Isolate* isolate, ModuleWireBytes wire_bytes,
                      bool compiles) {
  StdoutStream os;
  GenerateTestCase(os, isolate, wire_bytes, compiles, false, "");
  os.flush();
}

void GenerateTestCase(StdoutStream& os, Isolate* isolate,
                      ModuleWireBytes wire_bytes, bool compiles,
                      bool emit_call_main, std::string_view extra_flags) {
  // Libfuzzer sometimes runs a test twice (for detecting memory leaks), and in
  // this case we do not want multiple outputs by this function.
  // Similarly if we explicitly execute the same test multiple times (via
  // `-runs=N`).
  static std::atomic<bool> did_output_before{false};
  if (did_output_before.exchange(true)) return;

  constexpr bool kVerifyFunctions = false;
  auto enabled_features = WasmEnabledFeatures::FromIsolate(isolate);
  WasmDetectedFeatures unused_detected_features;
  ModuleResult module_res =
      DecodeWasmModule(enabled_features, wire_bytes.module_bytes(),
                       kVerifyFunctions, &unused_detected_features);
  CHECK_WITH_MSG(module_res.ok(), module_res.error().message().c_str());
  WasmModule* module = module_res.value().get();
  CHECK_NOT_NULL(module);

  AccountingAllocator allocator;
  Zone zone(&allocator, "constant expression zone");

  MultiLineStringBuilder out;
  NamesProvider names(module, wire_bytes.module_bytes());
  MjsunitModuleDis disassembler(out, module, &names, wire_bytes, &allocator,
                                !compiles);
  disassembler.PrintModule(extra_flags, emit_call_main);
  const bool offsets = false;  // Not supported by MjsunitModuleDis.
  out.WriteTo(os, offsets);
}

namespace {
static uint8_t kDummyModuleWireBytes[]{
    WASM_MODULE_HEADER,
    SECTION(Type, ENTRY_COUNT(2),
            // recgroup of 2 types
            WASM_REC_GROUP(ENTRY_COUNT(2),
                           // (array (field (mut f32)))
                           WASM_ARRAY_DEF(kF32Code, true),
                           // (struct (field i64) (field externref))
                           WASM_NONFINAL(WASM_STRUCT_DEF(
                               FIELD_COUNT(2), STRUCT_FIELD(kI64Code, false),
                               STRUCT_FIELD(kExternRefCode, false)))),
            // function type (void -> i32)
            SIG_ENTRY_x(kI32Code))};

}  // namespace

void AddDummyTypesToTypeCanonicalizer(Isolate* isolate) {
#ifdef DEBUG
  // We do not want to dump this dummy module; it would be confusing to dump two
  // modules.
  FlagScope<bool> no_dump_module(&v8_flags.dump_wasm_module, false);
#else
  static_assert(v8_flags.dump_wasm_module.value() == false);
#endif

  const size_t type_count = GetTypeCanonicalizer()->GetCurrentNumberOfTypes();
  const bool is_valid = GetWasmEngine()->SyncValidate(
      isolate, WasmEnabledFeatures(), CompileTimeImportsForFuzzing(),
      base::VectorOf(kDummyModuleWireBytes));
  CHECK(is_valid);
  // As the types are reset on each run by the fuzzer, the validation should
  // have added new types to the TypeCanonicalizer.
  CHECK_GT(GetTypeCanonicalizer()->GetCurrentNumberOfTypes(), type_count);
}

void EnableExperimentalWasmFeatures(v8::Isolate* isolate) {
  struct EnableExperimentalWasmFeatures {
    explicit EnableExperimentalWasmFeatures(v8::Isolate* isolate)
        : isolate(isolate) {
      // Enable all staged features.
#define ENABLE_PRE_STAGED_AND_STAGED_FEATURES(feat, ...) \
  v8_flags.wasm_##feat = true;
      FOREACH_PRE_STAGED_FEATURE_FLAG(IGNORE_NON_WASM_FEATURE,
                                      ENABLE_PRE_STAGED_AND_STAGED_FEATURES,
                                      IGNORE_NON_WASM_FEATURE)
      FOREACH_STAGED_FEATURE_FLAG(IGNORE_NON_WASM_FEATURE,
                                  ENABLE_PRE_STAGED_AND_STAGED_FEATURES,
                                  IGNORE_NON_WASM_FEATURE)
#undef ENABLE_PRE_STAGED_AND_STAGED_FEATURES

      // Enable non-staged experimental features or other experimental flags
      // that we also want to fuzz, e.g., new optimizations.
      // Note: If you add a Wasm feature here, you will also have to add the
      // respective flag(s) to the mjsunit/wasm/generate-random-module.js test,
      // otherwise that fails on an unsupported feature.
      // You may also want to add the flag(s) to the JS file header in
      // `PrintModule()` of `mjsunit-module-disassembler-impl.h`, to make bugs
      // easier to reproduce with generated mjsunit test cases.

      // The "pure Wasm" part of this proposal is considered ready for
      // fuzzing, the JS-related part (prototypes etc) not yet.
      v8_flags.wasm_custom_descriptors = true;

#ifdef V8_ENABLE_WASM_SIMD256_REVEC
      // Fuzz revectorization, which is otherwise still considered experimental.
      v8_flags.wasm_revectorize = true;
#endif  // V8_ENABLE_WASM_SIMD256_REVEC

#if V8_TARGET_ARCH_ARM64
      // Fuzz the Wasm SIMD optimizations in Turboshaft for aarch64.
      v8_flags.future_wasm_simd_opt = true;
      v8_flags.wasm_deinterleave_loads = true;
#endif  // V8_TARGET_ARCH_ARM64

      // Enforce implications from enabling features.
      FlagList::EnforceFlagImplications();

      // Last, install any conditional features. Implications are handled
      // implicitly.
      isolate->InstallConditionalFeatures(isolate->GetCurrentContext());
    }

    const v8::Isolate* const isolate;
  };

  // Call the constructor exactly once (per process!). The compiler will
  // properly synchronize this.
  static EnableExperimentalWasmFeatures one_time_enable_experimental_features(
      isolate);
  // Ensure that within the same process we always pass the same isolate. You
  // would get surprising results otherwise.
  CHECK_EQ(one_time_enable_experimental_features.isolate, isolate);
}

void ResetTypeCanonicalizer(v8::Isolate* isolate) {
  v8::internal::Isolate* i_isolate =
      reinterpret_cast<v8::internal::Isolate*>(isolate);

  // Make sure that there are no NativeModules left referencing the canonical
  // types. Collecting NativeModules can require two rounds of GC.
  for (int i = 0; i < 2 && GetWasmEngine()->NativeModuleCount() != 0; i++) {
    // We need to invoke GC without stack, otherwise the native module may
    // survive.
    DisableConservativeStackScanningScopeForTesting no_stack_scanning(
        i_isolate->heap());
    isolate->RequestGarbageCollectionForTesting(
        v8::Isolate::kFullGarbageCollection);
  }
  GetTypeCanonicalizer()->EmptyStorageForTesting();
  TypeCanonicalizer::ClearWasmCanonicalTypesForTesting(i_isolate);
  AddDummyTypesToTypeCanonicalizer(i_isolate);
}

int WasmExecutionFuzzer::FuzzWasmModule(base::Vector<const uint8_t> data,
                                        bool require_valid) {
  v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get();
  v8::Isolate* isolate = support->GetIsolate();

  // Strictly enforce the input size limit. Note that setting "max_len" on the
  // fuzzer target is not enough, since different fuzzers are used and not all
  // respect that limit.
  if (data.size() > max_input_size()) return -1;

  Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);

  v8::Isolate::Scope isolate_scope(isolate);
  v8::HandleScope handle_scope(isolate);
  v8::Context::Scope context_scope(support->GetContext());

  // We explicitly enable staged WebAssembly features here to increase fuzzer
  // coverage. For libfuzzer fuzzers it is not possible that the fuzzer enables
  // the flag by itself.
  EnableExperimentalWasmFeatures(isolate);

  // Allow mixed old and new EH instructions in the same module for fuzzing, to
  // help us test the interaction between the two EH proposals without requiring
  // multiple modules.
  v8_flags.wasm_allow_mixed_eh_for_testing = true;

  AccountingAllocator allocator;
  Zone zone(&allocator, ZONE_NAME);

  // The first byte specifies which flags are set.
  uint8_t flags_byte = data.empty() ? 0 : data[0];
  if (!data.empty()) data += 1;

#if defined(DEBUG) && defined(V8_USE_ADDRESS_SANITIZER)
  // Disable register allocator verification on slow builds (Debug + ASan) to
  // avoid timeouts in TurboFan/Turboshaft compilation on pathological inputs
  // (see crbug.com/527760872).
  FlagScope<bool> no_verify_allocator(&v8_flags.turbo_verify_allocation, false);
  // Disable type assertions on slow builds (Debug + ASan) to avoid timeouts in
  // TurboFan compilation (see crbug.com/520317061).
  const bool assert_types = false;
#else
  // Enable Wasm type assertions half the time.
  const bool assert_types = flags_byte & 1;
#endif
  flags_byte >>= 1;
  FlagScope<bool> assert_types_scope(&v8_flags.wasm_assert_types, assert_types);

  // Enable rescheduling of operations in the Turboshaft graph half the time.
  const bool turbofan_random_rescheduling = flags_byte & 1;
  flags_byte >>= 1;
  FlagScope<bool> rescheduling_scope(&v8_flags.wasm_random_rescheduling,
                                     turbofan_random_rescheduling);

  // The next byte specifies some internal configuration, like which function
  // is compiled with which compiler.
  uint8_t configuration_byte = data.empty() ? 0 : data[0];
  if (!data.empty()) data += 1;

  // Derive the compiler configuration for the first four functions from the
  // configuration byte, to choose for each function between:
  // 0: TurboFan
  // 1: Liftoff
  // 2: Liftoff for debugging
  uint8_t tier_mask = 0;
  uint8_t debug_mask = 0;
  for (int i = 0; i < 4; ++i, configuration_byte /= 3) {
    int compiler_config = configuration_byte % 3;
    tier_mask |= (compiler_config == 0) << i;
    debug_mask |= (compiler_config == 2) << i;
  }
  configuration_byte = 0;
  // The purpose of setting the tier mask (which affects the initial
  // compilation of each function) is to deterministically test a combination
  // of Liftoff and Turbofan.
  FlagScope<int> tier_mask_scope(&v8_flags.wasm_tier_mask_for_testing,
                                 tier_mask);
  FlagScope<int> debug_mask_scope(&v8_flags.wasm_debug_mask_for_testing,
                                  debug_mask);

  ZoneBuffer buffer(&zone);
  if (!GenerateModule(i_isolate, &zone, data, &buffer)) {
    return -1;
  }

  return SyncCompileAndExecuteAgainstReference(isolate, base::VectorOf(buffer),
                                               require_valid);
}

int SyncCompileAndExecuteAgainstReference(
    v8::Isolate* isolate, base::Vector<const uint8_t> wire_bytes,
    bool require_valid) {
  i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);

  // Clear recursive groups: The fuzzer creates random types in every run. These
  // are saved as recursive groups as part of the type canonicalizer, but types
  // from previous runs just waste memory.
  ResetTypeCanonicalizer(isolate);

  // Clear any exceptions from a prior run.
  if (i_isolate->has_exception()) i_isolate->clear_exception();

  v8::TryCatch try_catch(isolate);
  HandleScope scope(i_isolate);

  auto enabled_features = WasmEnabledFeatures::FromIsolate(i_isolate);

  bool valid = GetWasmEngine()->SyncValidate(
      i_isolate, enabled_features, CompileTimeImportsForFuzzing(), wire_bytes);

  if (v8_flags.wasm_fuzzer_gen_test) {
    GenerateTestCase(i_isolate, ModuleWireBytes{wire_bytes}, valid);
  }

  FlagScope<bool> eager_compile(&v8_flags.wasm_lazy_compilation, false);
  // We want to keep dynamic tiering enabled because that changes the code
  // Liftoff generates as well as optimizing compilers' behavior (especially
  // around inlining). We switch it to synchronous mode to avoid the
  // nondeterminism of background jobs finishing at random times.
  FlagScope<bool> sync_tier_up(&v8_flags.wasm_sync_tier_up, true);

  ErrorThrower thrower(i_isolate, "WasmFuzzerSyncCompile");
  MaybeDirectHandle<WasmModuleObject> compiled_module =
      GetWasmEngine()->SyncCompile(i_isolate, enabled_features,
                                   CompileTimeImportsForFuzzing(), &thrower,
                                   base::OwnedCopyOf(wire_bytes));
  CHECK_EQ(valid, !compiled_module.is_null());
  CHECK_EQ(!valid, thrower.error());
  if (require_valid && !valid) {
    FATAL("Generated module should validate, but got: %s", thrower.error_msg());
  }
  thrower.Reset();

  // Do not execute invalid modules, and return `-1` to avoid adding them to the
  // corpus. Even though invalid modules are also somewhat interesting to fuzz,
  // we will get them often enough via mutations, so we do not add them to the
  // corpus.
  if (!valid) return -1;

  return ExecuteAgainstReference(i_isolate, compiled_module.ToHandleChecked(),
                                 kDefaultMaxFuzzerExecutedInstructions);
}

}  // namespace v8::internal::wasm::fuzzing
