// Copyright 2017 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/wasm-run-utils.h"

#include <optional>

#include "src/codegen/assembler-inl.h"
#include "src/compiler/pipeline.h"
#include "src/diagnostics/code-tracer.h"
#include "src/heap/heap-inl.h"
#include "src/wasm/baseline/liftoff-compiler.h"
#include "src/wasm/code-space-access.h"
#include "src/wasm/compilation-environment-inl.h"
#include "src/wasm/leb-helper.h"
#include "src/wasm/module-compiler.h"
#include "src/wasm/module-instantiate.h"
#include "src/wasm/wasm-code-pointer-table-inl.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-import-wrapper-cache.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-opcodes.h"
#include "src/wasm/wasm-subtyping.h"

namespace v8::internal::wasm {

// Helper Functions.
bool IsSameNan(uint16_t expected, uint16_t actual) {
  // Sign is non-deterministic.
  uint16_t expected_bits = expected & ~0x8000;
  uint16_t actual_bits = actual & ~0x8000;
  return (expected_bits == actual_bits);
}

bool IsSameNan(float expected, float actual) {
  // Sign is non-deterministic.
  uint32_t expected_bits = base::bit_cast<uint32_t>(expected) & ~0x80000000;
  uint32_t actual_bits = base::bit_cast<uint32_t>(actual) & ~0x80000000;
  // Some implementations convert signaling NaNs to quiet NaNs.
  return (expected_bits == actual_bits) ||
         ((expected_bits | 0x00400000) == actual_bits);
}

bool IsSameNan(double expected, double actual) {
  // Sign is non-deterministic.
  uint64_t expected_bits =
      base::bit_cast<uint64_t>(expected) & ~0x8000000000000000;
  uint64_t actual_bits = base::bit_cast<uint64_t>(actual) & ~0x8000000000000000;
  // Some implementations convert signaling NaNs to quiet NaNs.
  return (expected_bits == actual_bits) ||
         ((expected_bits | 0x0008000000000000) == actual_bits);
}

Tagged<WasmDispatchTable> GetDispatchTable(
    DirectHandle<WasmTrustedInstanceData> instance, uint32_t table_index) {
  Tagged<Object> table = instance->dispatch_tables()->get(table_index);
  return TrustedCast<WasmDispatchTable>(table);
}

class FunctionTargetAndImplicitArg {
 public:
  FunctionTargetAndImplicitArg(
      Isolate* isolate,
      DirectHandle<WasmTrustedInstanceData> target_instance_data,
      int target_func_index) {
    implicit_arg_ = target_instance_data;
    if (target_func_index <
        static_cast<int>(
            target_instance_data->module()->num_imported_functions)) {
      // The function in the target instance was imported. Load the ref from the
      // dispatch table for imports.
      implicit_arg_ = direct_handle(
          TrustedCast<TrustedObject>(
              target_instance_data->dispatch_table_for_imports()->implicit_arg(
                  target_func_index)),
          isolate);
#if V8_ENABLE_DRUMBRAKE
      target_func_index_ =
          target_instance_data->imported_function_indices()->get(
              target_func_index);
#endif  // V8_ENABLE_DRUMBRAKE
    } else {
      // The function in the target instance was not imported.
#if V8_ENABLE_DRUMBRAKE
      target_func_index_ = target_func_index;
#endif  // V8_ENABLE_DRUMBRAKE
    }
    call_target_ = target_instance_data->GetCallTarget(target_func_index);
  }

  // The "implicit_arg" will be a WasmTrustedInstanceData or a WasmImportData.
  DirectHandle<TrustedObject> implicit_arg() { return implicit_arg_; }
  WasmCodePointer call_target() { return call_target_; }

#if V8_ENABLE_DRUMBRAKE
  int target_func_index() { return target_func_index_; }
#endif  // V8_ENABLE_DRUMBRAKE

 private:
  DirectHandle<TrustedObject> implicit_arg_;
  WasmCodePointer call_target_;

#if V8_ENABLE_DRUMBRAKE
  int target_func_index_;
#endif  // V8_ENABLE_DRUMBRAKE
};

TestingModuleBuilder::TestingModuleBuilder(
    Zone* zone, ManuallyImportedJSFunction* maybe_import,
    TestExecutionTier tier, Isolate* isolate)
    : module_(std::make_shared<WasmModule>()),
      isolate_(isolate),
      enabled_features_(WasmEnabledFeatures::FromIsolate(isolate_)),
      execution_tier_(tier) {
  // In this test setup, the NativeModule gets allocated before functions get
  // added. The tiering budget array, which gets allocated in the NativeModule
  // constructor, therefore does not have slots for functions that get added
  // later. By disabling dynamic tiering, the tiering budget does not get
  // accessed by generated code.
  v8_flags.wasm_dynamic_tiering = false;

  WasmJs::Install(isolate_);
  module_->untagged_globals_buffer_size = kMaxGlobalsSize;

  uint32_t maybe_import_index = 0;
  if (maybe_import) {
    // Manually add an imported function before any other functions.
    // This must happen before the instance object is created, since the
    // instance object allocates import entries.
    maybe_import_index = AddFunction(maybe_import->sig, nullptr, kImport);
    DCHECK_EQ(0, maybe_import_index);
  }

  instance_object_ = InitInstanceObject();
  trusted_instance_data_ =
      direct_handle(instance_object_->trusted_data(isolate_), isolate_);
  trusted_instance_data_->set_tables(
      ReadOnlyRoots{isolate_}.empty_fixed_array());

  if (maybe_import) {
    WasmCodeRefScope code_ref_scope;
    // Manually compile an import wrapper and insert it into the instance.
    CanonicalTypeIndex sig_index =
        GetTypeCanonicalizer()->AddRecursiveGroup(maybe_import->sig);
    const wasm::CanonicalSig* sig =
        GetTypeCanonicalizer()->LookupFunctionSignature(sig_index);
    const wasm::CanonicalValueType type = wasm::CanonicalValueType::Ref(
        sig_index, SharedFlag{false}, wasm::RefTypeKind::kFunction);
    ResolvedWasmImport resolved({}, -1, maybe_import->js_function, type, sig,
                                WellKnownImport::kUninstantiated);
    ImportCallKind kind = resolved.kind();
    DirectHandle<JSReceiver> callable = resolved.callable();
    std::shared_ptr<wasm::WasmWrapperHandle> wrapper_handle =
        GetWasmImportWrapperCache()->GetCompiled(
            isolate,
            {kind, sig, static_cast<int>(sig->parameter_count()), kNoSuspend});

    ImportedFunctionEntry(trusted_instance_data_, maybe_import_index)
        .SetWasmToWrapper(isolate_, callable, std::move(wrapper_handle),
                          resolved.suspend(), sig);
  }
}

TestingModuleBuilder::~TestingModuleBuilder() {
  // When the native module dies and is erased from the cache, it is expected to
  // have either valid bytes or no bytes at all.
  native_module_->SetWireBytes({});
}

uint8_t* TestingModuleBuilder::AddMemory(uint32_t size, SharedFlag shared,
                                         AddressType address_type,
                                         std::optional<size_t> max_size) {
  // The TestingModuleBuilder only supports one memory currently.
  CHECK_EQ(0, module_->memories.size());
  CHECK_NULL(mem0_start_);
  CHECK_EQ(0, mem0_size_);
  CHECK_EQ(0u, trusted_instance_data_->memory_objects()->length().value());

  uint32_t initial_pages = RoundUp(size, kWasmPageSize) / kWasmPageSize;
  uint32_t maximum_pages =
      max_size.has_value()
          ? static_cast<uint32_t>(RoundUp(max_size.value(), kWasmPageSize) /
                                  kWasmPageSize)
          : initial_pages;
  module_->memories.resize(1);
  WasmMemory* memory = &module_->memories[0];
  memory->initial_pages = initial_pages;
  memory->maximum_pages = maximum_pages;
  memory->address_type = address_type;
  UpdateComputedInformation(memory);

  // Create the WasmMemoryObject.
  DirectHandle<WasmMemoryObject> memory_object =
      WasmMemoryObject::New(isolate_, initial_pages, maximum_pages, shared,
                            address_type)
          .ToHandleChecked();
  DirectHandle<FixedArray> memory_objects =
      isolate_->factory()->NewFixedArray(1);
  memory_objects->set(0, *memory_object);
  trusted_instance_data_->set_memory_objects(*memory_objects);

  // Create the memory_bases_and_sizes array.
  DirectHandle<TrustedFixedAddressArray> memory_bases_and_sizes =
      TrustedFixedAddressArray::New(isolate_, 2);
  uint8_t* mem_start = reinterpret_cast<uint8_t*>(
      memory_object->backing_store()->buffer_start());
  memory_bases_and_sizes->set(0, reinterpret_cast<Address>(mem_start));
  memory_bases_and_sizes->set(1, size);
  trusted_instance_data_->set_memory_bases_and_sizes(*memory_bases_and_sizes);

  mem0_start_ = mem_start;
  mem0_size_ = size;
  CHECK(size == 0 || mem0_start_);

  WasmMemoryObject::UseInInstance(isolate_, memory_object,
                                  trusted_instance_data_, 0);
  // TODO(wasm): Delete the following line when test-run-wasm will use a
  // multiple of kPageSize as memory size. At the moment, the effect of these
  // two lines is used to shrink the memory for testing purposes.
  trusted_instance_data_->SetRawMemory(0, mem0_start_, mem0_size_);
  return mem0_start_;
}

uint32_t TestingModuleBuilder::AddFunction(const FunctionSig* sig,
                                           const char* name,
                                           FunctionType type) {
  if (module_->functions.size() == 0) {
    // TODO(titzer): Reserving space here to avoid the underlying WasmFunction
    // structs from moving.
    module_->functions.reserve(kMaxFunctions);
    module_->type_feedback.well_known_imports.Initialize(kMaxFunctions);
  }
  uint32_t index = static_cast<uint32_t>(module_->functions.size());
  module_->functions.push_back({sig,                 // sig
                                index,               // func_index
                                ModuleTypeIndex{0},  // sig_index
                                {0, 0},              // code
                                false,               // imported
                                false,               // exported
                                false});             // declared
  if (type == kImport) {
    DCHECK_EQ(0, module_->num_declared_functions);
    ++module_->num_imported_functions;
    module_->functions.back().imported = true;
  } else {
    ++module_->num_declared_functions;
  }
  DCHECK_EQ(module_->functions.size(),
            module_->num_imported_functions + module_->num_declared_functions);
  if (name) {
    base::Vector<const uint8_t> name_vec =
        base::Vector<const uint8_t>::cast(base::CStrVector(name));
    module_->lazily_generated_names.AddForTesting(
        index, {AddBytes(name_vec), static_cast<uint32_t>(name_vec.size())});
  }
  DCHECK_LT(index, kMaxFunctions);  // limited for testing.
  if (!trusted_instance_data_.is_null()) {
    DirectHandle<FixedArray> func_refs =
        isolate_->factory()->NewFixedArrayWithZeroes(
            static_cast<int>(module_->functions.size()));
    trusted_instance_data_->set_func_refs(*func_refs);
  }
  return index;
}

void TestingModuleBuilder::InitializeWrapperCache() {
  TypeCanonicalizer::PrepareForCanonicalTypeId(
      isolate_, module_->MaxCanonicalTypeIndex(),
      SharedFlag{module_->has_shared_part});
  DirectHandle<FixedArray> maps = isolate_->factory()->NewFixedArray(
      static_cast<int>(module_->types.size()));
  for (uint32_t index = 0; index < module_->types.size(); index++) {
    CreateMapForType(isolate_, module_.get(), ModuleTypeIndex{index}, maps);
  }
  trusted_instance_data_->set_managed_object_maps(*maps);
}

DirectHandle<JSFunction> TestingModuleBuilder::WrapCode(uint32_t index) {
  InitializeWrapperCache();
  DirectHandle<WasmFuncRef> func_ref =
      WasmTrustedInstanceData::GetOrCreateFuncRef(
          isolate_, trusted_instance_data_, index, kPrecreateExternal);
  DirectHandle<WasmInternalFunction> internal{func_ref->internal(isolate_),
                                              isolate_};
  return WasmInternalFunction::GetOrCreateExternal(internal);
}

void TestingModuleBuilder::AddIndirectFunctionTable(
    const uint16_t* function_indexes, uint32_t table_size,
    ValueType table_type) {
  uint32_t table_index = static_cast<uint32_t>(module_->tables.size());
  module_->tables.emplace_back();
  WasmTable& table = module_->tables.back();
  table.initial_size = table_size;
  table.maximum_size = table_size;
  table.has_maximum_size = true;
  table.type = table_type;

  DirectHandle<HeapObject> value =
      table.type.use_wasm_null()
          ? Cast<HeapObject>(isolate_->factory()->wasm_null())
          : Cast<HeapObject>(isolate_->factory()->null_value());
  CanonicalValueType canonical_type = module_->canonical_type(table.type);
  DirectHandle<WasmDispatchTable> dispatch_table;
  DirectHandle<WasmTableObject> table_obj = WasmTableObject::New(
      isolate_,
      direct_handle(instance_object_->trusted_data(isolate_), isolate_),
      table.type, canonical_type, table.initial_size, table.has_maximum_size,
      table.maximum_size, value,
      // TODO(clemensb): Make this configurable.
      wasm::AddressType::kI32, &dispatch_table);
  WasmDispatchTable::AddUse(isolate_, dispatch_table, trusted_instance_data_,
                            table_index);
  {
    // Store the shortcut to the dispatch table.
    DirectHandle<ProtectedFixedArray> old_dispatch_tables{
        trusted_instance_data_->dispatch_tables(), isolate_};
    const uint32_t old_dispatch_tables_len =
        old_dispatch_tables->length().value();
    DCHECK_EQ(table_index, old_dispatch_tables_len);
    DirectHandle<ProtectedFixedArray> new_dispatch_tables =
        isolate_->factory()->NewProtectedFixedArray(table_index + 1);
    for (uint32_t i = 0; i < old_dispatch_tables_len; ++i) {
      new_dispatch_tables->set(i, old_dispatch_tables->get(i));
    }
    new_dispatch_tables->set(table_index, *dispatch_table);
    if (table_index == 0) {
      trusted_instance_data_->set_dispatch_table0(*dispatch_table);
    }
    trusted_instance_data_->set_dispatch_tables(*new_dispatch_tables);
  }

  if (function_indexes) {
    WasmCodeRefScope code_ref_scope;
    for (uint32_t i = 0; i < table_size; ++i) {
      uint32_t function_index = function_indexes[i];
      WasmFunction& function = module_->functions[function_index];
      CanonicalTypeIndex sig_id = module_->canonical_sig_id(function.sig_index);
      FunctionTargetAndImplicitArg entry(isolate_, trusted_instance_data_,
                                         function.func_index);
      auto maybe_wrapper =
          function_index < module_->num_imported_functions
              ? trusted_instance_data_->dispatch_table_for_imports()
                    ->MaybeGetWrapperHandle(function_index)
              : std::nullopt;

      if (maybe_wrapper) {
        GetDispatchTable(trusted_instance_data_, table_index)
            ->SetForWrapper(i,
                            TrustedCast<WasmImportData>(*entry.implicit_arg()),
                            std::move(*maybe_wrapper), sig_id,
#if V8_ENABLE_DRUMBRAKE
                            function.func_index,
#endif  // !V8_ENABLE_DRUMBRAKE
                            WasmDispatchTable::kNewEntry);
      } else {
        GetDispatchTable(trusted_instance_data_, table_index)
            ->SetForNonWrapper(
                i, TrustedCast<WasmTrustedInstanceData>(*entry.implicit_arg()),
                entry.call_target(), sig_id,
#if V8_ENABLE_DRUMBRAKE
                function.func_index,
#endif  // !V8_ENABLE_DRUMBRAKE
                WasmDispatchTable::kNewEntry);
      }

      WasmTableObject::SetFunctionTablePlaceholder(
          isolate_, table_obj, i, trusted_instance_data_, function_indexes[i]);
    }
  }

  DirectHandle<FixedArray> old_tables(trusted_instance_data_->tables(),
                                      isolate_);
  DirectHandle<FixedArray> new_tables =
      isolate_->factory()->CopyFixedArrayAndGrow(old_tables, 1);
  new_tables->set(old_tables->length().value(), *table_obj);
  trusted_instance_data_->set_tables(*new_tables);
}

uint32_t TestingModuleBuilder::AddBytes(base::Vector<const uint8_t> bytes) {
  base::Vector<const uint8_t> old_bytes = native_module_->wire_bytes();
  uint32_t old_size = static_cast<uint32_t>(old_bytes.size());
  // Avoid placing strings at offset 0, this might be interpreted as "not
  // set", e.g. for function names.
  uint32_t bytes_offset = old_size ? old_size : 1;
  size_t new_size = bytes_offset + bytes.size();
  base::OwnedVector<uint8_t> new_bytes =
      base::OwnedVector<uint8_t>::New(new_size);
  if (old_size > 0) {
    memcpy(new_bytes.begin(), old_bytes.begin(), old_size);
  } else {
    // Set the unused byte. It is never decoded, but the bytes are used as the
    // key in the native module cache.
    new_bytes[0] = 0;
  }
  memcpy(new_bytes.begin() + bytes_offset, bytes.begin(), bytes.size());
  native_module_->SetWireBytes(std::move(new_bytes));
  return bytes_offset;
}

uint32_t TestingModuleBuilder::AddException(const FunctionSig* sig) {
  DCHECK_EQ(0, sig->return_count());
  uint32_t index = static_cast<uint32_t>(module_->tags.size());
  module_->tags.emplace_back(sig, AddSignature(sig));
  DirectHandle<WasmExceptionTag> tag = WasmExceptionTag::New(isolate_, index);
  DirectHandle<TrustedFixedArray> table(trusted_instance_data_->tags_table(),
                                        isolate_);
  table = isolate_->factory()->CopyTrustedFixedArrayAndGrow(table, 1);
  trusted_instance_data_->set_tags_table(*table);
  table->set(index, *tag);
  return index;
}

uint32_t TestingModuleBuilder::AddPassiveDataSegment(
    base::Vector<const uint8_t> bytes) {
  uint32_t index = static_cast<uint32_t>(module_->data_segments.size());
  DCHECK_EQ(index, module_->data_segments.size());
  DCHECK_EQ(index, trusted_instance_data_->data_segments()->length().value());

  // Add a passive data segment. This isn't used by function compilation, but
  // but it keeps the index in sync. The data segment's source will not be
  // correct, since we don't store data in the module wire bytes.
  module_->data_segments.push_back(WasmDataSegment::PassiveForTesting());

  // The num_declared_data_segments (from the DataCount section) is used
  // to validate the segment index, during function compilation.
  module_->num_declared_data_segments = index + 1;

  DirectHandle<TrustedPodArray<WireBytesRef>> new_data_segments =
      TrustedPodArray<WireBytesRef>::New(isolate_, index + 1);
  for (uint32_t i = 0; i < index; ++i) {
    new_data_segments->set(i, trusted_instance_data_->data_segments()->get(i));
  }

  uint32_t new_segment_offset = AddBytes(bytes);
  DCHECK_LE(bytes.size(), kMaxUInt32);
  new_data_segments->set(
      index,
      WireBytesRef{new_segment_offset, static_cast<uint32_t>(bytes.size())});

  trusted_instance_data_->set_data_segments(*new_data_segments);
  return index;
}

const WasmGlobal* TestingModuleBuilder::AddGlobal(ValueType type) {
  uint8_t size = type.value_kind_size();
  global_offset_ = RoundUp(global_offset_, size);  // align
  // Make sure that the returned pointer stays valid by pre-reserving enough
  // space.
  module_->globals.reserve(kMaxGlobalsSize);
  module_->globals.push_back(
      {type, true, {}, {global_offset_}, SharedFlag{false}, false, false});
  global_offset_ += size;
  // limit number of globals.
  CHECK_LT(global_offset_, kMaxGlobalsSize);
  return &module_->globals.back();
}

DirectHandle<WasmInstanceObject> TestingModuleBuilder::InitInstanceObject() {
  // Compute the estimate based on {kMaxFunctions} because we might still add
  // functions later. Assume 1k of code per function.
  int estimated_code_section_length = kMaxFunctions * 1024;
  // Pretend to have `kMaxFunctions` already when allocating the `NativeModule`.
  DCHECK_EQ(0, module_->num_declared_functions);
  module_->num_declared_functions = kMaxFunctions;
  size_t code_size_estimate =
      wasm::WasmCodeManager::EstimateNativeModuleCodeSize(
          kMaxFunctions, estimated_code_section_length);
  auto native_module = GetWasmEngine()->NewNativeModule(
      isolate_, enabled_features_, WasmDetectedFeatures{}, CompileTimeImports{},
      module_, code_size_estimate);
  // Reset the declared functions; functions will be added later in the test.
  module_->num_declared_functions = 0;
  native_module->SetWireBytes(base::OwnedVector<const uint8_t>());
  native_module->compilation_state()->set_compilation_id(0);
  constexpr base::Vector<const char> kNoSourceUrl{"", 0};
  DirectHandle<Script> script =
      GetWasmEngine()->GetOrCreateScript(isolate_, native_module, kNoSourceUrl);

  DirectHandle<ByteArray> globals_buffer =
      isolate_->factory()->NewByteArray(kMaxGlobalsSize);
  std::fill(globals_buffer->begin(), globals_buffer->end(), 0);
  DirectHandle<WasmModuleObject> module_object =
      WasmModuleObject::New(isolate_, native_module, script);
  native_module_ = native_module.get();

  DirectHandle<WasmTrustedInstanceData> trusted_data =
      WasmTrustedInstanceData::New(isolate_, module_object,
                                   std::move(native_module));
  // TODO(42204563): Avoid crashing if the instance object is not available.
  CHECK(trusted_data->has_instance_object());
  DirectHandle<WasmInstanceObject> instance_object(
      trusted_data->instance_object(), isolate_);
  trusted_data->set_tags_table(
      *isolate_->factory()->empty_trusted_fixed_array());
  trusted_data->set_untagged_globals_buffer(*globals_buffer);
  DirectHandle<FixedArray> feedback_vector =
      isolate_->factory()->NewFixedArrayWithZeroes(kMaxFunctions);
  trusted_data->set_feedback_vectors(*feedback_vector);
  return instance_object;
}

// This struct is just a type tag for Zone::NewArray<T>(size_t) call.
struct WasmFunctionCompilerBuffer {};

void WasmFunctionCompiler::Build(base::Vector<const uint8_t> bytes) {
  size_t locals_size = local_decls_.Size();
  size_t total_size = bytes.size() + locals_size + 1;
  uint8_t* buffer =
      zone_->AllocateArray<uint8_t, WasmFunctionCompilerBuffer>(total_size);
  // Prepend the local decls to the code.
  local_decls_.Emit(buffer);
  // Emit the code.
  memcpy(buffer + locals_size, bytes.begin(), bytes.size());
  // Append an extra end opcode.
  buffer[total_size - 1] = kExprEnd;

  bytes = base::VectorOf(buffer, total_size);

  function_->code = {builder_->AddBytes(bytes),
                     static_cast<uint32_t>(bytes.size())};

  NativeModule* native_module =
      builder_->trusted_instance_data()->native_module();
  base::Vector<const uint8_t> wire_bytes = native_module->wire_bytes();

  CompilationEnv env = CompilationEnv::ForModule(native_module);
  auto func_wire_bytes =
      base::OwnedVector<uint8_t>::NewForOverwrite(function_->code.length());
  memcpy(func_wire_bytes.begin(), wire_bytes.begin() + function_->code.offset(),
         func_wire_bytes.size());

  FunctionBody func_body{function_->sig, function_->code.offset(),
                         func_wire_bytes.begin(), func_wire_bytes.end()};
  ForDebugging for_debugging =
      native_module->IsInDebugState() ? kForDebugging : kNotForDebugging;

  WasmDetectedFeatures unused_detected_features;
  DecodeResult validation_result =
      ValidateFunctionBody(zone_, env.enabled_features, env.module,
                           &unused_detected_features, func_body);
  if (validation_result.failed()) {
    FATAL("Validation failed: %s", validation_result.error().message().c_str());
  }

  if (v8_flags.wasm_jitless) return;

  std::optional<WasmCompilationResult> result;
  if (builder_->test_execution_tier() ==
      TestExecutionTier::kLiftoffForFuzzing) {
    result.emplace(ExecuteLiftoffCompilation(
        &env, func_body,
        LiftoffOptions{.func_index = static_cast<int>(function_->func_index),
                       .for_debugging = kForDebugging,
                       .counter_updates = native_module->counter_updates(),
                       .max_steps = builder_->max_steps_ptr()}));
  } else {
    WasmCompilationUnit unit(function_->func_index, builder_->execution_tier(),
                             for_debugging, kAlreadyValidated);
    result.emplace(unit.ExecuteCompilation(
        &env, native_module->compilation_state()->GetWireBytesStorage().get(),
        native_module->counter_updates(), &unused_detected_features));
  }
  CHECK(result->succeeded());
  WasmCode* code =
      native_module->PublishCode(native_module->AddCompiledCode(*result));
  DCHECK_NOT_NULL(code);
  DisallowGarbageCollection no_gc;
  Tagged<Script> script =
      builder_->instance_object()->module_object()->script();
  std::unique_ptr<char[]> source_url =
      Cast<String>(script->name())->ToCString();
  if (WasmCode::ShouldBeLogged(isolate())) {
    code->LogCode(isolate(), source_url.get(), script->id());
  }
}

WasmFunctionCompiler::WasmFunctionCompiler(Zone* zone, const FunctionSig* sig,
                                           TestingModuleBuilder* builder,
                                           const char* name)
    : zone_(zone), builder_(builder), local_decls_(zone, sig) {
  // Get a new function from the testing module.
  int index = builder->AddFunction(sig, name, TestingModuleBuilder::kWasm);
  function_ = builder_->GetFunctionAt(index);
}

WasmFunctionCompiler::~WasmFunctionCompiler() = default;

FunctionSig* WasmRunnerBase::CreateSig(MachineType return_type,
                                       base::Vector<MachineType> param_types) {
  size_t return_count = return_type.IsNone() ? 0 : 1;
  size_t param_count = param_types.size();

  FunctionSig::Builder sig_builder{&builder_.module()->signature_storage,
                                   return_count, param_count};

  // Convert machine types to local types, and check that there are no
  // MachineType::None()'s in the parameters.
  if (return_count) sig_builder.AddReturn(ValueType::For(return_type));
  for (MachineType param : param_types) {
    CHECK_NE(MachineType::None(), param);
    sig_builder.AddParam(ValueType::For(param));
  }
  return sig_builder.Get();
}

}  // namespace v8::internal::wasm
