// Copyright 2018 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 "src/wasm/function-compiler.h"

#include <optional>

#include "src/base/fpu.h"
#include "src/codegen/compiler.h"
#include "src/codegen/optimized-compilation-info.h"
#include "src/compiler/turboshaft/wasm-turboshaft-compiler.h"
#include "src/compiler/wasm-compiler.h"
#include "src/handles/handles-inl.h"
#include "src/logging/counters-scopes.h"
#include "src/logging/log.h"
#include "src/objects/code-inl.h"
#include "src/wasm/baseline/liftoff-compiler.h"
#include "src/wasm/compilation-environment-inl.h"
#include "src/wasm/turboshaft-graph-interface.h"
#include "src/wasm/wasm-code-manager.h"
#include "src/wasm/wasm-debug.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-export-wrapper-cache.h"

namespace v8::internal::wasm {

WasmCompilationResult WasmCompilationUnit::ExecuteCompilation(
    CompilationEnv* env, const WireBytesStorage* wire_bytes_storage,
    DelayedCounterUpdates* counter_updates, WasmDetectedFeatures* detected) {
  DCHECK_GE(func_index_, static_cast<int>(env->module->num_imported_functions));
  const WasmFunction* func = &env->module->functions[func_index_];
  base::Vector<const uint8_t> code = wire_bytes_storage->GetCode(func->code);
  bool is_shared = env->module->type(func->sig_index).is_shared;
  wasm::FunctionBody func_body{func->sig, func->code.offset(), code.begin(),
                               code.end(), is_shared};

  base::ElapsedTimer compile_timer;
  if (base::TimeTicks::IsHighResolution()) compile_timer.Start();

  // Before executing compilation, make sure that the function was validated.
  // Both Liftoff and TurboFan compilation do not perform validation, so can
  // only run on valid functions.
  if (V8_UNLIKELY(!env->module->function_was_validated(func_index_))) {
    // This code path can only be reached in
    // - eager compilation mode,
    // - with lazy validation,
    // - with PGO (which compiles some functions eagerly), or
    // - with compilation hints (which compiles some functions eagerly).
    DCHECK(!v8_flags.wasm_lazy_compilation || v8_flags.wasm_lazy_validation ||
           v8_flags.experimental_wasm_pgo_from_file ||
           v8_flags.experimental_wasm_compilation_hints);
    Zone validation_zone{GetWasmEngine()->allocator(), ZONE_NAME};
    if (ValidateFunctionBody(&validation_zone, env->enabled_features,
                             env->module, detected, func_body)
            .failed()) {
      return {};
    }
    env->module->set_function_validated(func_index_);
  }

  if (v8_flags.trace_wasm_compiler) {
    PrintF("Compiling wasm function %d with %s\n", func_index_,
           ExecutionTierToString(tier_));
  }

  WasmCompilationResult result;
  int declared_index = declared_function_index(env->module, func_index_);

  switch (tier_) {
    case ExecutionTier::kNone:
#if V8_ENABLE_DRUMBRAKE
    case ExecutionTier::kInterpreter:
#endif  // V8_ENABLE_DRUMBRAKE
      UNREACHABLE();

    case ExecutionTier::kLiftoff: {
      // The --wasm-tier-mask-for-testing flag can force functions to be
      // compiled with TurboFan, and the --wasm-debug-mask-for-testing can force
      // them to be compiled for debugging, see documentation.
      bool try_liftoff = true;
      if (V8_UNLIKELY(v8_flags.wasm_tier_mask_for_testing != 0)) {
        bool must_use_liftoff =
            v8_flags.liftoff_only ||
            for_debugging_ != ForDebugging::kNotForDebugging;
        bool tiering_requested =
            declared_index < 32 &&
            (v8_flags.wasm_tier_mask_for_testing & (1 << declared_index));
        if (!must_use_liftoff && tiering_requested) try_liftoff = false;
      }

      if (V8_LIKELY(try_liftoff)) {
        LiftoffOptions options{.func_index = func_index_,
                               .for_debugging = for_debugging_,
                               .counter_updates = counter_updates,
                               .detected_features = detected};
        // We do not use the debug side table, we only (optionally) pass it to
        // cover different code paths in Liftoff for testing.
        std::unique_ptr<DebugSideTable> unused_debug_sidetable;
        if (V8_UNLIKELY(declared_index < 32 &&
                        (v8_flags.wasm_debug_mask_for_testing &
                         (1 << declared_index)) != 0)) {
          options.debug_sidetable = &unused_debug_sidetable;
          if (!for_debugging_) options.for_debugging = kForDebugging;
        }
        if (v8_flags.wasm_code_coverage &&
            options.for_debugging == kNotForDebugging) {
          options.for_debugging = kForDebugging;
        }
        result = ExecuteLiftoffCompilation(env, func_body, options);
        if (result.succeeded()) break;
      }

      // If --liftoff-only, do not fall back to turbofan, even if compilation
      // failed.
      if (v8_flags.liftoff_only) break;

      // If Liftoff failed, fall back to TurboFan.
      // TODO(wasm): We could actually stop or remove the tiering unit for this
      // function to avoid compiling it twice with TurboFan.
      [[fallthrough]];
    }
    case ExecutionTier::kTurbofan: {
#ifdef V8_ENABLE_TURBOFAN
      compiler::WasmCompilationData data(func_body);
      data.func_index = func_index_;
      data.wire_bytes_storage = wire_bytes_storage;
      result = compiler::turboshaft::ExecuteTurboshaftWasmCompilation(
          env, data, detected, counter_updates);
      // In exceptional cases it can happen that compilation requests for
      // debugging end up being executed by Turbofan, e.g. if Liftoff bails out
      // because of unsupported features or the --wasm-tier-mask-for-testing is
      // set. In that case we set the for_debugging field for the TurboFan
      // result to match the requested for_debugging_.
      result.for_debugging = for_debugging_;
#endif
      break;
    }
  }

  DCHECK(result.succeeded());
  counter_updates->AddIncrement(&Counters::wasm_generated_code_size,
                                result.code_desc.instr_size);
  counter_updates->AddIncrement(&Counters::wasm_reloc_size,
                                result.code_desc.reloc_size);
  counter_updates->AddIncrement(&Counters::wasm_deopt_data_size,
                                static_cast<int>(result.deopt_data.size()));

  result.func_index = func_index_;

  if (compile_timer.IsStarted()) {
    base::TimeDelta compile_time = compile_timer.Elapsed();
    if (func_body.end - func_body.start >= 100 * KB) {
      DelayedCounterUpdates::GetHistogramFn huge_size_histogram =
          is_asmjs_module(env->module)
              ? &Counters::wasm_asm_huge_function_size_bytes
              : &Counters::wasm_wasm_huge_function_size_bytes;
      counter_updates->AddSample(
          huge_size_histogram,
          static_cast<int>(func_body.end - func_body.start));
      counter_updates->AddTimedSample(
          &Counters::wasm_compile_huge_function_time, compile_time);
    }
    DelayedCounterUpdates::GetTimedHistogramFn compile_time_histogram =
        is_asmjs_module(env->module)
            ? &Counters::wasm_compile_asm_function_time
            : &Counters::wasm_compile_wasm_function_time;
    counter_updates->AddTimedSample(compile_time_histogram, compile_time);
  }
  return result;
}

// static
void WasmCompilationUnit::CompileWasmFunction(NativeModule* native_module,
                                              WasmDetectedFeatures* detected,
                                              const WasmFunction* function,
                                              ExecutionTier tier) {
  ModuleWireBytes wire_bytes(native_module->wire_bytes());
  bool is_shared = native_module->module()->type(function->sig_index).is_shared;
  FunctionBody function_body{function->sig, function->code.offset(),
                             wire_bytes.start() + function->code.offset(),
                             wire_bytes.start() + function->code.end_offset(),
                             is_shared};

  DCHECK_LE(native_module->num_imported_functions(), function->func_index);
  DCHECK_LT(function->func_index, native_module->num_functions());
  WasmCompilationUnit unit(function->func_index, tier, kNotForDebugging);
  CompilationEnv env = CompilationEnv::ForModule(native_module);
  base::FlushDenormalsScope disable_denormals(
      tier == ExecutionTier::kTurbofan &&
      native_module->compile_imports().contains(
          CompileTimeImport::kDisableDenormalFloats));
  WasmCompilationResult result = unit.ExecuteCompilation(
      &env, native_module->compilation_state()->GetWireBytesStorage().get(),
      native_module->counter_updates(), detected);
  if (result.succeeded()) {
    WasmCodeRefScope code_ref_scope;
    native_module->PublishCode(native_module->AddCompiledCode(result));
  } else {
    native_module->compilation_state()->SetError();
  }
}

JSToWasmWrapperCompilationUnit::JSToWasmWrapperCompilationUnit(
    Isolate* isolate, const CanonicalSig* sig, bool receiver_is_first_param)
    : isolate_(isolate),
      sig_(sig),
      receiver_is_first_param_(receiver_is_first_param),
      job_(v8_flags.wasm_jitless ? nullptr
                                 : compiler::NewJSToWasmCompilationJob(
                                       isolate, sig, receiver_is_first_param)) {
  if (!v8_flags.wasm_jitless) {
    OptimizedCompilationInfo* info =
        static_cast<compiler::turboshaft::TurboshaftCompilationJob*>(job_.get())
            ->compilation_info();
    if (info->trace_turbo_graph()) {
      // Make sure that code tracer is initialized on the main thread if tracing
      // is enabled.
      isolate->GetCodeTracer();
    }
  }
}

JSToWasmWrapperCompilationUnit::~JSToWasmWrapperCompilationUnit() = default;

void JSToWasmWrapperCompilationUnit::Execute() {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
               "wasm.CompileJSToWasmWrapper");
  if (!v8_flags.wasm_jitless) {
    CompilationJob::Status status = job_->ExecuteJob(nullptr);
    CHECK_EQ(status, CompilationJob::SUCCEEDED);
  }
}

DirectHandle<Code> JSToWasmWrapperCompilationUnit::Finalize() {
#if V8_ENABLE_DRUMBRAKE
  if (v8_flags.wasm_jitless) {
    return isolate_->builtins()->code_handle(
        Builtin::kGenericJSToWasmInterpreterWrapper);
  }
#endif  // V8_ENABLE_DRUMBRAKE

  CompilationJob::Status status = job_->FinalizeJob(isolate_);
  CHECK_EQ(status, CompilationJob::SUCCEEDED);
  OptimizedCompilationInfo* info =
      static_cast<compiler::turboshaft::TurboshaftCompilationJob*>(job_.get())
          ->compilation_info();
  DirectHandle<Code> code = info->code();
  if (isolate_->IsLoggingCodeCreation()) {
    DirectHandle<String> name = isolate_->factory()->NewStringFromAsciiChecked(
        info->GetDebugName().get());
    PROFILE(isolate_, CodeCreateEvent(LogEventListener::CodeTag::kStub,
                                      Cast<AbstractCode>(code), name));
  }
  // Install the compiled wrapper in the cache now.
  WasmExportWrapperCache::Put(isolate_, sig_->index(), receiver_is_first_param_,
                              code);
  Counters* counters = isolate_->counters();
  counters->wasm_generated_code_size()->Increment(code->body_size());
  counters->wasm_reloc_size()->Increment(code->relocation_size());
  counters->wasm_compiled_export_wrapper()->Increment(1);
  return code;
}

// static
DirectHandle<Code> JSToWasmWrapperCompilationUnit::CompileJSToWasmWrapper(
    Isolate* isolate, const CanonicalSig* sig, bool receiver_is_first_param) {
  // Run the compilation unit synchronously.
  JSToWasmWrapperCompilationUnit unit(isolate, sig, receiver_is_first_param);
  unit.Execute();
  return unit.Finalize();
}

}  // namespace v8::internal::wasm