// 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);
wasm::FunctionBody func_body{func->sig, func->code.offset(), code.begin(),
code.end()};
base::ElapsedTimer compile_timer;
if (base::TimeTicks::IsHighResolution()) compile_timer.Start();
if (v8_flags.trace_wasm_compiler) {
PrintF("Compiling wasm function %d with %s\n", func_index_,
ExecutionTierToString(tier_));
}
// When we compile functions while streaming a module, we let the compile
// task also take care of validation. See {DecodeWasmModule} for an overview.
// In the rare case when we create both a Liftoff and a Turbofan compilation
// task up front, we currently validate twice; this is not expected to occur
// in production.
if (validation_ == kMustValidate) {
Zone validation_zone{GetWasmEngine()->allocator(), ZONE_NAME};
if (ValidateFunctionBody(&validation_zone, env->enabled_features,
env->module, detected, func_body)
.failed()) {
return {};
}
}
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.
// Tiering is not supported when we are generating compilation hints.
bool try_liftoff = true;
if (V8_UNLIKELY(v8_flags.wasm_tier_mask_for_testing != 0 &&
!v8_flags.wasm_generate_compilation_hints &&
!v8_flags.trace_wasm_generate_compilation_hints)) {
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;
data.validate_callees = validation_;
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 =
&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 =
&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());
FunctionBody function_body{function->sig, function->code.offset(),
wire_bytes.start() + function->code.offset(),
wire_bytes.start() + function->code.end_offset()};
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,
kAlreadyValidated);
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)
: isolate_(isolate),
sig_(sig),
job_(v8_flags.wasm_jitless
? nullptr
: compiler::NewJSToWasmCompilationJob(isolate, sig)) {
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_EVENT(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::kJSToWasmInterpreterWrapper);
}
#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(), 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) {
// Run the compilation unit synchronously.
JSToWasmWrapperCompilationUnit unit(isolate, sig);
unit.Execute();
return unit.Finalize();
}
} // namespace v8::internal::wasm