blob: e998c4d59f4fc9785ddb4ae62bb80e15bf2e4bd7 [file]
// Copyright 2014 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 <optional>
#include "src/codegen/assembler-inl.h"
#include "src/codegen/compiler.h"
#include "src/common/assert-scope.h"
#include "src/common/globals.h"
#include "src/common/message-template.h"
#include "src/deoptimizer/deoptimizer.h"
#include "src/execution/arguments-inl.h"
#include "src/execution/frames-inl.h"
#include "src/execution/isolate-inl.h"
#include "src/objects/abstract-code-inl.h"
#include "src/objects/js-array-buffer-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/shared-function-info.h"
#include "src/runtime/runtime-utils.h"
#ifdef V8_ENABLE_SPARKPLUG_PLUS
#include "src/common/code-memory-access.h"
#endif // V8_ENABLE_SPARKPLUG_PLUS
namespace v8::internal {
namespace {
void LogExecution(Isolate* isolate, DirectHandle<JSFunction> function) {
DCHECK(v8_flags.log_function_events);
if (!function->has_feedback_vector()) return;
DCHECK(function->IsLoggingRequested(isolate));
isolate->js_dispatch_table().ResetTieringRequest(function->dispatch_handle());
DirectHandle<SharedFunctionInfo> sfi(function->shared(), isolate);
DirectHandle<String> name = SharedFunctionInfo::DebugName(isolate, sfi);
DisallowGarbageCollection no_gc;
Tagged<SharedFunctionInfo> raw_sfi = *sfi;
std::string event_name = "first-execution";
CodeKind kind = function->abstract_code(isolate)->kind();
// Not adding "-interpreter" for tooling backwards compatibility.
if (kind != CodeKind::INTERPRETED_FUNCTION) {
event_name += "-";
event_name += CodeKindToString(kind);
}
LOG(isolate, FunctionEvent(
event_name.c_str(), Cast<Script>(raw_sfi->script())->id(), 0,
raw_sfi->StartPosition(), raw_sfi->EndPosition(), *name));
}
#ifdef V8_ENABLE_SPARKPLUG_PLUS
Builtin GetTypedBuiltinForCompare(int hint, Builtin current_builtin) noexcept {
Builtin target_builtin = Builtin::kNoBuiltinId;
#define TYPED_COMPARE_DISPATCH_CASE(hint_value, op_type) \
case static_cast<int>(CompareOperationFeedback::TypeIndex::k##hint_value): \
target_builtin = Builtin::k##op_type##_##hint_value##_Baseline; \
break;
#define TYPED_COMPARE_FAMILY_SWITCH(stub_list, op_type) \
if (IsTyped##op_type##Builtin(current_builtin)) { \
switch (hint) { \
stub_list(TYPED_COMPARE_DISPATCH_CASE, op_type) default \
: target_builtin = Builtin::k##op_type##_Generic_Baseline; \
break; \
} \
} else /* NOLINT */
#define TYPED_COMPARE_DISPATCH_LIST(V) \
V(TYPED_STRICTEQUAL_STUB_LIST, StrictEqual) \
V(TYPED_EQUAL_STUB_LIST, Equal) \
V(TYPED_RELATIONAL_COMPARE_STUB_LIST, LessThan) \
V(TYPED_RELATIONAL_COMPARE_STUB_LIST, GreaterThan) \
V(TYPED_RELATIONAL_COMPARE_STUB_LIST, LessThanOrEqual) \
V(TYPED_RELATIONAL_COMPARE_STUB_LIST, GreaterThanOrEqual)
TYPED_COMPARE_DISPATCH_LIST(TYPED_COMPARE_FAMILY_SWITCH)
/* else */ {}
return target_builtin;
#undef TYPED_COMPARE_DISPATCH_LIST
#undef TYPED_COMPARE_FAMILY_SWITCH
#undef TYPED_COMPARE_DISPATCH_CASE
}
Builtin GetTypedBuiltinForBinop(int hint, Builtin current_builtin) noexcept {
Builtin target_builtin = Builtin::kNoBuiltinId;
#define TYPED_BINOP_DISPATCH_CASE(hint_value, op_type) \
case static_cast<int>(BinaryOperationFeedback::TypeIndex::k##hint_value): \
target_builtin = Builtin::k##op_type##_##hint_value##_Baseline; \
break;
#define TYPED_BINOP_FAMILY_SWITCH(stub_list, op_type) \
if (IsTyped##op_type##Builtin(current_builtin)) { \
switch (hint) { \
stub_list(TYPED_BINOP_DISPATCH_CASE, op_type) default \
: target_builtin = Builtin::k##op_type##_Generic_Baseline; \
break; \
} \
} else /* NOLINT */
#define TYPED_BINOP_DISPATCH_LIST(V) \
V(TYPED_ADD_STUB_LIST, Add) \
V(TYPED_BINOP_STUB_LIST, Subtract) \
V(TYPED_BINOP_STUB_LIST, Multiply) \
V(TYPED_BINOP_STUB_LIST, Divide) \
V(TYPED_BINOP_STUB_LIST, Modulus) \
V(TYPED_EXP_STUB_LIST, Exponentiate) \
V(TYPED_BITWISE_BINOP_STUB_LIST, BitwiseOr) \
V(TYPED_BITWISE_BINOP_STUB_LIST, BitwiseXor) \
V(TYPED_BITWISE_BINOP_STUB_LIST, BitwiseAnd) \
V(TYPED_BITWISE_BINOP_STUB_LIST, ShiftLeft) \
V(TYPED_BITWISE_BINOP_STUB_LIST, ShiftRight) \
V(TYPED_BITWISE_BINOP_STUB_LIST, ShiftRightLogical)
TYPED_BINOP_DISPATCH_LIST(TYPED_BINOP_FAMILY_SWITCH)
/* else */ {}
return target_builtin;
#undef TYPED_BINOP_DISPATCH_LIST
#undef TYPED_BINOP_FAMILY_SWITCH
#undef TYPED_BINOP_DISPATCH_CASE
}
Builtin GetTypedBuiltinForUnary(int hint, Builtin current_builtin) noexcept {
Builtin target_builtin = Builtin::kNoBuiltinId;
#define TYPED_UNOP_DISPATCH_CASE(hint_value, op_type) \
case static_cast<int>(BinaryOperationFeedback::TypeIndex::k##hint_value): \
target_builtin = Builtin::k##op_type##_##hint_value##_Baseline; \
break;
#define TYPED_UNOP_FAMILY_SWITCH(stub_list, op_type) \
if (IsTyped##op_type##Builtin(current_builtin)) { \
switch (hint) { \
stub_list(TYPED_UNOP_DISPATCH_CASE, op_type) default \
: target_builtin = Builtin::k##op_type##_Generic_Baseline; \
break; \
} \
} else /* NOLINT */
#define TYPED_UNOP_DISPATCH_LIST(V) \
V(TYPED_UNARY_STUB_LIST, Increment) \
V(TYPED_UNARY_STUB_LIST, Decrement) \
V(TYPED_NEGATE_STUB_LIST, Negate) \
V(TYPED_UNARY_STUB_LIST, BitwiseNot)
TYPED_UNOP_DISPATCH_LIST(TYPED_UNOP_FAMILY_SWITCH)
/* else */ {}
return target_builtin;
#undef TYPED_UNOP_DISPATCH_LIST
#undef TYPED_UNOP_FAMILY_SWITCH
#undef TYPED_UNOP_DISPATCH_CASE
}
V8_INLINE void UpdateEmbeddedFeedback(Tagged<BytecodeArray> bytecode_array,
int feedback_offset,
int current_feedback) {
feedback_offset -= BytecodeArray::kHeaderSize - kHeapObjectTag;
bytecode_array->set(feedback_offset, static_cast<uint8_t>(current_feedback));
}
using GetTypedBuiltinFn = Builtin (*)(int, Builtin);
V8_INLINE void TryPatchBaselineCodeImpl(Isolate* isolate, int current_feedback,
GetTypedBuiltinFn get_typed_builtin) {
DisallowGarbageCollection no_gc;
const Address entry = Isolate::c_entry_fp(isolate->thread_local_top());
Address* pc_address =
reinterpret_cast<Address*>(entry + ExitFrameConstants::kCallerPCOffset);
Address pc =
StackFrame::ReadPC(pc_address) - Assembler::kCallTargetAddressOffset;
Address current = Assembler::target_address_at(pc, kNullAddress);
// TODO(chromium:429351411): Consider using a cache.
Builtin current_builtin =
OffHeapInstructionStream::TryLookupCode(isolate, current);
Builtin target_builtin = get_typed_builtin(current_feedback, current_builtin);
if (target_builtin != Builtin::kNoBuiltinId) {
Address target = Builtins::EntryOf(target_builtin, isolate);
WritableJitAllocation jit_allocation =
WritableJitAllocation::ForPatchableBaselineJIT(
pc, Assembler::kCallTargetAddressOffset);
Assembler::set_target_address_at(pc, kNullAddress, target, &jit_allocation,
FLUSH_ICACHE_IF_NEEDED);
}
}
V8_INLINE void TryPatchCompareOpBaselineCode(Isolate* isolate,
int current_feedback) {
TryPatchBaselineCodeImpl(isolate, current_feedback,
GetTypedBuiltinForCompare);
}
V8_INLINE void TryPatchBinaryOpBaselineCode(Isolate* isolate,
int current_feedback) {
TryPatchBaselineCodeImpl(isolate, current_feedback, GetTypedBuiltinForBinop);
}
V8_INLINE void TryPatchUnaryOpBaselineCode(Isolate* isolate,
int current_feedback) {
TryPatchBaselineCodeImpl(isolate, current_feedback, GetTypedBuiltinForUnary);
}
V8_INLINE int UpdateEmbeddedFeedbackAndGetCurrent(RuntimeArguments args) {
int current_feedback = args.smi_value_at(0);
DCHECK_LE(current_feedback, std::numeric_limits<uint8_t>::max());
DCHECK_GE(current_feedback, 0);
UpdateEmbeddedFeedback(TrustedCast<BytecodeArray>(args[2]),
static_cast<int>(args.number_value_at(3)),
current_feedback);
return current_feedback;
}
#endif // V8_ENABLE_SPARKPLUG_PLUS
} // namespace
RUNTIME_FUNCTION(Runtime_CompileLazy) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
DirectHandle<JSFunction> function = args.at<JSFunction>(0);
StackLimitCheck check(isolate);
if (V8_UNLIKELY(
check.JsHasOverflowed(kStackSpaceRequiredForCompilation * KB))) {
return isolate->StackOverflow();
}
DirectHandle<SharedFunctionInfo> sfi(function->shared(), isolate);
DCHECK(!function->is_compiled(isolate));
#ifdef DEBUG
if (v8_flags.trace_lazy && sfi->is_compiled()) {
PrintF("[unoptimized: %s]\n", function->DebugNameCStr().get());
}
#endif
IsCompiledScope is_compiled_scope;
if (!Compiler::Compile(isolate, function, Compiler::KEEP_EXCEPTION,
&is_compiled_scope)) {
return ReadOnlyRoots(isolate).exception();
}
DCHECK(function->is_compiled(isolate));
return function->code(isolate);
}
RUNTIME_FUNCTION(Runtime_InstallBaselineCode) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
DirectHandle<JSFunction> function = args.at<JSFunction>(0);
DirectHandle<SharedFunctionInfo> sfi(function->shared(), isolate);
DCHECK(sfi->HasBaselineCode());
{
if (!function->has_feedback_vector()) {
IsCompiledScope is_compiled_scope(*sfi, isolate);
IsBaselineCompiledScope is_baseline_compiled_scope(*sfi, isolate);
DCHECK(is_baseline_compiled_scope.is_compiled());
DCHECK(!function->HasAvailableOptimizedCode(isolate));
DCHECK(!function->has_feedback_vector());
JSFunction::CreateAndAttachFeedbackVector(isolate, function,
&is_compiled_scope);
}
DisallowGarbageCollection no_gc;
Tagged<Code> baseline_code = sfi->baseline_code(kAcquireLoad);
function->UpdateCodeKeepTieringRequests(isolate, baseline_code);
return baseline_code;
}
}
RUNTIME_FUNCTION(Runtime_InstallSFICode) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
DirectHandle<JSFunction> function = args.at<JSFunction>(0);
{
DisallowGarbageCollection no_gc;
Tagged<SharedFunctionInfo> sfi = function->shared();
DCHECK(sfi->is_compiled());
Tagged<Code> sfi_code = sfi->GetCode(isolate);
if (V8_LIKELY(sfi_code->kind() != CodeKind::BASELINE ||
function->has_feedback_vector())) {
function->UpdateCode(isolate, sfi_code);
return sfi_code;
}
}
// This could be the first time we are installing baseline code so we need to
// ensure that a feedback vectors is allocated.
IsCompiledScope is_compiled_scope(function->shared(), isolate);
DCHECK(!function->HasAvailableOptimizedCode(isolate));
DCHECK(!function->has_feedback_vector());
JSFunction::CreateAndAttachFeedbackVector(isolate, function,
&is_compiled_scope);
Tagged<Code> sfi_code = function->shared()->GetCode(isolate);
function->UpdateCode(isolate, sfi_code);
return sfi_code;
}
namespace {
void CompileOptimized(DirectHandle<JSFunction> function, ConcurrencyMode mode,
CodeKind target_kind, Isolate* isolate) {
// Ensure that the tiering request is reset even if compilation fails.
function->ResetTieringRequests(isolate);
// As a pre- and post-condition of CompileOptimized, the function *must* be
// compiled, i.e. the installed InstructionStream object must not be
// CompileLazy.
IsCompiledScope is_compiled_scope(function->shared(), isolate);
if (V8_UNLIKELY(!is_compiled_scope.is_compiled())) {
// This happens if the code is flushed while we still have an optimization
// request pending (or if manually an optimization is requested on an
// uncompiled function).
// Instead of calling into Compiler::Compile and having to do exception
// handling here, we reset and return and thus tail-call into CompileLazy.
function->ResetIfCodeFlushed(isolate);
return;
}
if (mode == ConcurrencyMode::kConcurrent) {
// No need to start another compile job.
if (function->tiering_in_progress() ||
function->GetActiveTier(isolate) >= target_kind) {
static_assert(kTieringStateInProgressBlocksTierup);
function->SetInterruptBudget(isolate, BudgetModification::kRaise);
return;
}
}
// Concurrent optimization runs on another thread, thus no additional gap.
const int gap =
IsConcurrent(mode) ? 0 : kStackSpaceRequiredForCompilation * KB;
StackLimitCheck check(isolate);
if (check.JsHasOverflowed(gap)) return;
Compiler::CompileOptimized(isolate, function, mode, target_kind);
DCHECK(function->is_compiled(isolate));
}
} // namespace
RUNTIME_FUNCTION(Runtime_StartMaglevOptimizeJob) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
DirectHandle<JSFunction> function = args.at<JSFunction>(0);
DCHECK(function->IsOptimizationRequested(isolate));
CompileOptimized(function, ConcurrencyMode::kConcurrent, CodeKind::MAGLEV,
isolate);
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_StartTurbofanOptimizeJob) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
DirectHandle<JSFunction> function = args.at<JSFunction>(0);
DCHECK(function->IsOptimizationRequested(isolate));
CompileOptimized(function, ConcurrencyMode::kConcurrent,
CodeKind::TURBOFAN_JS, isolate);
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_OptimizeMaglevEager) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
DirectHandle<JSFunction> function = args.at<JSFunction>(0);
DCHECK(function->IsOptimizationRequested(isolate));
CompileOptimized(function, ConcurrencyMode::kSynchronous, CodeKind::MAGLEV,
isolate);
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_OptimizeTurbofanEager) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
DirectHandle<JSFunction> function = args.at<JSFunction>(0);
DCHECK(function->IsOptimizationRequested(isolate));
CompileOptimized(function, ConcurrencyMode::kSynchronous,
CodeKind::TURBOFAN_JS, isolate);
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_MarkLazyDeoptimizedOrFlushed) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
DirectHandle<JSFunction> function = args.at<JSFunction>(0);
bool reoptimize = (*args.at<Smi>(1)).value();
IsCompiledScope is_compiled_scope(function->shared(), isolate);
if (!is_compiled_scope.is_compiled()) {
StackLimitCheck check(isolate);
if (V8_UNLIKELY(
check.JsHasOverflowed(kStackSpaceRequiredForCompilation * KB))) {
return isolate->StackOverflow();
}
if (!Compiler::Compile(isolate, function, Compiler::KEEP_EXCEPTION,
&is_compiled_scope)) {
return ReadOnlyRoots(isolate).exception();
}
// In case this code was flushed we should not re-optimize it too quickly.
reoptimize = false;
}
if (!function->code(isolate)->marked_for_deoptimization()) {
function->ResetTieringRequests(isolate);
if (reoptimize) {
// Set the budget such that we have one invocation which allows us to
// detect if any ICs need updating before re-optimization.
function->raw_feedback_cell()->set_interrupt_budget(1);
} else {
function->SetInterruptBudget(isolate, BudgetModification::kReset,
CodeKind::INTERPRETED_FUNCTION);
}
}
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_FunctionLogNextExecution) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
DirectHandle<JSFunction> js_function = args.at<JSFunction>(0);
DCHECK(v8_flags.log_function_events);
LogExecution(isolate, js_function);
return js_function->code(isolate);
}
namespace {
bool TryGetOptimizedOsrCode(Isolate* isolate, Tagged<FeedbackVector> vector,
const interpreter::BytecodeArrayIterator& it,
Tagged<Code>* code_out) {
std::optional<Tagged<Code>> maybe_code =
vector->GetOptimizedOsrCode(isolate, {}, it.GetSlotOperand(2));
if (maybe_code.has_value()) {
*code_out = maybe_code.value();
return true;
}
return false;
}
// Deoptimize all osr'd loops which contain a particular deopt exit.
// We only deoptimize OSR code if the exit is inside the actual loop. The OSR
// code might still be affected (if the exit is after the loop, or in the case
// of turbofan if the exit is before the loop, but reachable through an outer
// loop). Regardless in both of these two cases, the OSR code is still usable to
// quickly get out of the loop itself.
bool DeoptAllOsrLoopsContainingDeoptExit(Isolate* isolate,
Tagged<JSFunction> function,
BytecodeOffset deopt_exit_offset) {
DisallowGarbageCollection no_gc;
DCHECK(!deopt_exit_offset.IsNone());
if (!v8_flags.use_ic ||
!function->feedback_vector()->maybe_has_optimized_osr_code()) {
return false;
}
Handle<BytecodeArray> bytecode_array(
function->shared()->GetBytecodeArray(isolate), isolate);
DCHECK(interpreter::BytecodeArrayIterator::IsValidOffset(
bytecode_array, deopt_exit_offset.ToInt()));
Tagged<FeedbackVector> vector = function->feedback_vector();
Tagged<Code> code;
bool any_marked = false;
bool has_maglev_code = false;
bool has_turbofan_code = false;
interpreter::BytecodeArrayIterator it(bytecode_array);
for (; !it.done(); it.Advance()) {
// We're only interested in loop ranges.
if (it.current_bytecode() != interpreter::Bytecode::kJumpLoop) continue;
bool has_osr_code = TryGetOptimizedOsrCode(isolate, vector, it, &code);
if (has_osr_code) {
if (base::IsInRange(deopt_exit_offset.ToInt(), it.GetJumpTargetOffset(),
it.current_offset())) {
code->SetMarkedForDeoptimization(isolate,
LazyDeoptimizeReason::kEagerDeopt);
any_marked = true;
} else {
has_turbofan_code |= code->is_turbofanned();
has_maglev_code |= code->is_maglevved();
}
}
}
Tagged<FeedbackVector> fbv = function->feedback_vector();
if (!has_maglev_code && fbv->maybe_has_maglev_osr_code()) {
fbv->set_maybe_has_optimized_osr_code(false, CodeKind::MAGLEV);
}
if (!has_turbofan_code && fbv->maybe_has_turbofan_osr_code()) {
fbv->set_maybe_has_optimized_osr_code(false, CodeKind::TURBOFAN_JS);
}
return any_marked;
}
void GetOsrOffsetAndFunctionForOSR(Isolate* isolate, BytecodeOffset* osr_offset,
Handle<JSFunction>* function) {
DCHECK(osr_offset->IsNone());
DCHECK(function->is_null());
// Determine the frame that triggered the OSR request.
JavaScriptStackFrameIterator it(isolate);
UnoptimizedJSFrame* frame = UnoptimizedJSFrame::cast(it.frame());
DCHECK_IMPLIES(frame->is_interpreted(),
frame->LookupCode()->is_interpreter_trampoline_builtin());
DCHECK_IMPLIES(frame->is_baseline(),
frame->LookupCode()->kind() == CodeKind::BASELINE);
*osr_offset = BytecodeOffset(frame->GetBytecodeOffset());
*function = handle(frame->function(), isolate);
DCHECK(!osr_offset->IsNone());
DCHECK((*function)->shared()->HasBytecodeArray());
}
Tagged<Object> CompileOptimizedOSR(Isolate* isolate,
DirectHandle<JSFunction> function,
CodeKind min_opt_level,
BytecodeOffset osr_offset) {
ConcurrencyMode mode =
V8_LIKELY(isolate->concurrent_recompilation_enabled() &&
v8_flags.concurrent_osr)
? ConcurrencyMode::kConcurrent
: ConcurrencyMode::kSynchronous;
if (V8_UNLIKELY(isolate->EfficiencyModeEnabled() &&
min_opt_level == CodeKind::MAGLEV)) {
mode = ConcurrencyMode::kSynchronous;
}
DirectHandle<Code> result;
if (!Compiler::CompileOptimizedOSR(
isolate, function, osr_offset, mode,
(maglev::IsMaglevOsrEnabled() && min_opt_level == CodeKind::MAGLEV)
? CodeKind::MAGLEV
: CodeKind::TURBOFAN_JS)
.ToHandle(&result) ||
result->marked_for_deoptimization()) {
// An empty result can mean one of two things:
// 1) we've started a concurrent compilation job - everything is fine.
// 2) synchronous compilation failed for some reason.
return Smi::zero();
}
DCHECK(!result.is_null());
DCHECK(result->is_turbofanned() || result->is_maglevved());
DCHECK(CodeKindIsOptimizedJSFunction(result->kind()));
#ifdef DEBUG
Tagged<DeoptimizationData> data = result->deoptimization_data();
DCHECK_EQ(BytecodeOffset(data->OsrBytecodeOffset().value()), osr_offset);
DCHECK_GE(data->OsrPcOffset().value(), 0);
#endif // DEBUG
// First execution logging happens in LogOrTraceOptimizedOSREntry
return *result;
}
} // namespace
#ifdef V8_DUMPLING
RUNTIME_FUNCTION(Runtime_PrintDumpedFrame) {
HandleScope scope(isolate);
DCHECK_EQ(args.length(), 1);
CHECK(isolate->context().is_null());
DCHECK(!AllowGarbageCollection::IsAllowed());
Deoptimizer* dumper = isolate->GetAndClearCurrentDeoptimizer();
Tagged<Context> saved_context = isolate->context();
isolate->set_context(dumper->function()->native_context());
dumper->VirtualMaterializeAndPrint();
delete dumper;
isolate->set_context(saved_context);
return ReadOnlyRoots(isolate).undefined_value();
}
#endif // V8_DUMPLING
RUNTIME_FUNCTION(Runtime_NotifyDeoptimized) {
HandleScope scope(isolate);
DCHECK_EQ(0, args.length());
Deoptimizer* deoptimizer = Deoptimizer::Grab(isolate);
DCHECK(CodeKindCanDeoptimize(deoptimizer->compiled_code()->kind()));
DCHECK(AllowGarbageCollection::IsAllowed());
DCHECK(isolate->context().is_null());
TimerEventScope<TimerEventDeoptimizeCode> timer(isolate);
TRACE_EVENT("v8", "V8.DeoptimizeCode");
DirectHandle<JSFunction> function = deoptimizer->function();
// For OSR the optimized code isn't installed on the function, so get the
// code object from deoptimizer.
DirectHandle<Code> optimized_code = deoptimizer->compiled_code();
const DeoptimizeKind deopt_kind = deoptimizer->deopt_kind();
const DeoptimizeReason deopt_reason =
deoptimizer->GetDeoptInfo().deopt_reason;
const Deoptimizer::CodeValidity code_validity = deoptimizer->code_validity();
// TODO(turbofan): We currently need the native context to materialize
// the arguments object, but only to get to its map.
isolate->set_context(deoptimizer->function()->native_context());
// Make sure to materialize objects before causing any allocation.
deoptimizer->MaterializeHeapObjects();
deoptimizer->ProcessDeoptReason(deopt_reason);
const BytecodeOffset deopt_exit_offset =
deoptimizer->bytecode_offset_in_outermost_frame();
delete deoptimizer;
// Ensure the context register is updated for materialized objects.
JavaScriptStackFrameIterator top_it(isolate);
JavaScriptFrame* top_frame = top_it.frame();
isolate->set_context(Cast<Context>(top_frame->context()));
if (deopt_kind == DeoptimizeKind::kLazy) {
DCHECK_EQ(code_validity, Deoptimizer::CodeValidity::kUnaffected);
return ReadOnlyRoots(isolate).undefined_value();
}
const BytecodeOffset osr_offset = optimized_code->osr_offset();
// Some deopts don't invalidate InstructionStream (e.g. lazy deopts, or when
// preparing for OSR from Maglev to Turbofan).
if (code_validity == Deoptimizer::CodeValidity::kUnaffected) {
// If there's a turbolevved inner OSR loop and a maglevved outer OSR loop,
// force the outer loop to be turbolevved.
//
// Otherwise we're stuck in a state where the outer loop is maglevved and
// the inner loop is turbolevved, and we deopt the inner loop at every outer
// loop backedge.
BytecodeOffset outer_loop_osr_offset = BytecodeOffset::None();
if (v8_flags.turbolev && deopt_reason == DeoptimizeReason::kOSREarlyExit) {
CHECK_GT(deopt_exit_offset.ToInt(), osr_offset.ToInt());
if (optimized_code->kind() == CodeKind::TURBOFAN_JS &&
Deoptimizer::GetOutermostOuterLoopWithCodeKind(
isolate, *function, osr_offset, CodeKind::MAGLEV,
&outer_loop_osr_offset)) {
auto result =
CompileOptimizedOSR(isolate, handle(*function, isolate),
CodeKind::TURBOFAN_JS, outer_loop_osr_offset);
USE(result);
}
}
// Expedite tiering of the main function if we tier in OSR.
if (deopt_reason == DeoptimizeReason::kPrepareForOnStackReplacement &&
function->ActiveTierIsMaglev(isolate)) {
isolate->tiering_manager()->MarkForTurboFanOptimization(*function);
}
return ReadOnlyRoots(isolate).undefined_value();
}
USE(deopt_kind);
DCHECK_EQ(deopt_kind, DeoptimizeKind::kEager);
DCHECK(!IsDeoptimizationWithoutCodeInvalidation(deopt_reason));
// Non-OSR'd code is deoptimized unconditionally. If the deoptimization occurs
// inside the outermost loop containing a loop that can trigger OSR
// compilation, we remove the OSR code, it will avoid hit the out of date OSR
// code and soon later deoptimization.
//
// For OSR'd code, we keep the optimized code around if deoptimization occurs
// outside the outermost loop containing the loop that triggered OSR
// compilation. The reasoning is that OSR is intended to speed up the
// long-running loop; so if the deoptimization occurs outside this loop it is
// still worth jumping to the OSR'd code on the next run. The reduced cost of
// the loop should pay for the deoptimization costs.
bool any_marked = false;
if (!optimized_code->marked_for_deoptimization()) {
optimized_code->SetMarkedForDeoptimization(
isolate, LazyDeoptimizeReason::kEagerDeopt);
any_marked = true;
}
if (osr_offset.IsNone()) {
if (DeoptAllOsrLoopsContainingDeoptExit(isolate, *function,
deopt_exit_offset)) {
any_marked = true;
}
}
if (any_marked) {
Deoptimizer::DeoptimizeMarkedCode(isolate);
}
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_ObserveNode) {
// The %ObserveNode intrinsic only tracks the changes to an observed node in
// code compiled by TurboFan.
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
DirectHandle<Object> obj = args.at(0);
return *obj;
}
RUNTIME_FUNCTION(Runtime_VerifyType) {
// %VerifyType has no effect in the interpreter.
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
DirectHandle<Object> obj = args.at(0);
return *obj;
}
RUNTIME_FUNCTION(Runtime_CheckTurboshaftTypeOf) {
// %CheckTurboshaftTypeOf has no effect in the interpreter.
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
DirectHandle<Object> obj = args.at(0);
return *obj;
}
RUNTIME_FUNCTION(Runtime_CompileOptimizedOSR) {
HandleScope handle_scope(isolate);
DCHECK_EQ(0, args.length());
DCHECK(v8_flags.use_osr);
BytecodeOffset osr_offset = BytecodeOffset::None();
Handle<JSFunction> function;
GetOsrOffsetAndFunctionForOSR(isolate, &osr_offset, &function);
return CompileOptimizedOSR(isolate, function, CodeKind::MAGLEV, osr_offset);
}
namespace {
Tagged<Object> CompileOptimizedOSRFromMaglev(Isolate* isolate,
DirectHandle<JSFunction> function,
BytecodeOffset osr_offset) {
// This path is only relevant for tests (all production configurations enable
// concurrent OSR). It's quite subtle, if interested read on:
if (V8_UNLIKELY(!isolate->concurrent_recompilation_enabled() ||
!v8_flags.concurrent_osr)) {
// - Synchronous Turbofan compilation may trigger lazy deoptimization (e.g.
// through compilation dependency finalization actions).
// - Maglev (currently) disallows marking an opcode as both can_lazy_deopt
// and can_eager_deopt.
// - Maglev's JumpLoop opcode (the logical caller of this runtime function)
// is marked as can_eager_deopt since OSR'ing to Turbofan involves
// deoptimizing to Ignition under the hood.
// - Thus this runtime function *must not* trigger a lazy deopt, and
// therefore cannot trigger synchronous Turbofan compilation (see above).
//
// We solve this synchronous OSR case by bailing out early to Ignition, and
// letting it handle OSR. How do we trigger the early bailout? Returning
// any non-null InstructionStream from this function triggers the deopt in
// JumpLoop.
if (v8_flags.trace_osr) {
CodeTracer::Scope scope(isolate->GetCodeTracer());
PrintF(scope.file(),
"[OSR - Tiering from Maglev to Turbofan failed because "
"concurrent_osr is disabled. function: %s, osr offset: %d]\n",
function->DebugNameCStr().get(), osr_offset.ToInt());
}
return Smi::zero();
}
if (V8_UNLIKELY(isolate->EfficiencyModeEnabled() ||
isolate->BatterySaverModeEnabled())) {
function->feedback_vector()->reset_osr_urgency();
function->SetInterruptBudget(isolate, BudgetModification::kRaise);
return Smi::zero();
}
return CompileOptimizedOSR(isolate, function, CodeKind::TURBOFAN_JS,
osr_offset);
}
} // namespace
RUNTIME_FUNCTION(Runtime_CompileOptimizedOSRFromMaglev) {
HandleScope handle_scope(isolate);
DCHECK_EQ(1, args.length());
DCHECK(v8_flags.use_osr);
const BytecodeOffset osr_offset(args.positive_smi_value_at(0));
JavaScriptStackFrameIterator it(isolate);
MaglevFrame* frame = MaglevFrame::cast(it.frame());
DCHECK_EQ(frame->LookupCode()->kind(), CodeKind::MAGLEV);
DirectHandle<JSFunction> function = direct_handle(frame->function(), isolate);
return CompileOptimizedOSRFromMaglev(isolate, function, osr_offset);
}
RUNTIME_FUNCTION(Runtime_CompileOptimizedOSRFromMaglevInlined) {
HandleScope handle_scope(isolate);
DCHECK_EQ(2, args.length());
DCHECK(v8_flags.use_osr);
const BytecodeOffset osr_offset(args.positive_smi_value_at(0));
DirectHandle<JSFunction> function = args.at<JSFunction>(1);
JavaScriptStackFrameIterator it(isolate);
MaglevFrame* frame = MaglevFrame::cast(it.frame());
DCHECK_EQ(frame->LookupCode()->kind(), CodeKind::MAGLEV);
if (*function != frame->function()) {
// We are OSRing an inlined function. Mark the top frame one for
// optimization.
if (!frame->function()->ActiveTierIsTurbofan(isolate)) {
isolate->tiering_manager()->MarkForTurboFanOptimization(
frame->function());
}
}
return CompileOptimizedOSRFromMaglev(isolate, function, osr_offset);
}
RUNTIME_FUNCTION(Runtime_LogOrTraceOptimizedOSREntry) {
HandleScope handle_scope(isolate);
DCHECK_EQ(0, args.length());
CHECK(v8_flags.log_or_trace_osr);
BytecodeOffset osr_offset = BytecodeOffset::None();
Handle<JSFunction> function;
GetOsrOffsetAndFunctionForOSR(isolate, &osr_offset, &function);
if (v8_flags.trace_osr) {
PrintF(CodeTracer::Scope{isolate->GetCodeTracer()}.file(),
"[OSR - entry. function: %s, osr offset: %d]\n",
function->DebugNameCStr().get(), osr_offset.ToInt());
}
return ReadOnlyRoots(isolate).undefined_value();
}
static Tagged<Object> CompileGlobalEval(
Isolate* isolate, Handle<i::Object> source_object,
DirectHandle<SharedFunctionInfo> outer_info, LanguageMode language_mode,
int eval_scope_info_index, int eval_position) {
DirectHandle<NativeContext> native_context = isolate->native_context();
// Check if native context allows code generation from
// strings. Throw an exception if it doesn't.
MaybeDirectHandle<String> source;
bool unknown_object;
std::tie(source, unknown_object) = Compiler::ValidateDynamicCompilationSource(
isolate, native_context, source_object);
// If the argument is an unhandled string time, bounce to GlobalEval.
if (unknown_object) {
return native_context->global_eval_fun();
}
if (source.is_null()) {
Handle<Object> error_message =
native_context->ErrorMessageForCodeGenerationFromStrings();
DirectHandle<Object> error;
MaybeDirectHandle<Object> maybe_error = isolate->factory()->NewEvalError(
MessageTemplate::kCodeGenFromStrings, error_message);
if (maybe_error.ToHandle(&error)) isolate->Throw(*error);
return ReadOnlyRoots(isolate).exception();
}
// Deal with a normal eval call with a string argument. Compile it
// and return the compiled function bound in the local context.
static const ParseRestriction restriction = NO_PARSE_RESTRICTION;
DirectHandle<JSFunction> compiled;
DirectHandle<Context> context(isolate->context(), isolate);
if (!Is<NativeContext>(*context)) {
Tagged<WeakFixedArray> array = Cast<Script>(outer_info->script())->infos();
Tagged<ScopeInfo> stored_info;
CHECK(array->get(eval_scope_info_index)
.GetHeapObjectIfWeak(isolate, &stored_info));
CHECK_EQ(stored_info, context->scope_info());
}
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, compiled,
Compiler::GetFunctionFromEval(
isolate, source.ToHandleChecked(), outer_info, context, language_mode,
restriction, kNoSourcePosition, eval_position),
ReadOnlyRoots(isolate).exception());
return *compiled;
}
RUNTIME_FUNCTION(Runtime_ResolvePossiblyDirectEval) {
HandleScope scope(isolate);
DCHECK_EQ(6, args.length());
DirectHandle<Object> callee = args.at(0);
// If "eval" didn't refer to the original GlobalEval, it's not a
// direct call to eval.
if (*callee != isolate->native_context()->global_eval_fun()) {
return *callee;
}
DCHECK(is_valid_language_mode(args.smi_value_at(3)));
LanguageMode language_mode = static_cast<LanguageMode>(args.smi_value_at(3));
DirectHandle<SharedFunctionInfo> outer_info(args.at<JSFunction>(2)->shared(),
isolate);
return CompileGlobalEval(isolate, args.at<Object>(1), outer_info,
language_mode, args.smi_value_at(4),
args.smi_value_at(5));
}
#ifdef V8_ENABLE_SPARKPLUG_PLUS
RUNTIME_FUNCTION(Runtime_PatchCompareOpBaselineCode) {
HandleScope scope(isolate);
CHECK(v8_flags.sparkplug_plus);
DCHECK_EQ(4, args.length());
DirectHandle<Boolean> compare_result = args.at<Boolean>(1);
TryPatchCompareOpBaselineCode(isolate,
UpdateEmbeddedFeedbackAndGetCurrent(args));
return *compare_result;
}
RUNTIME_FUNCTION(Runtime_PatchCompareOpBaselineCodeAndThrow) {
HandleScope scope(isolate);
CHECK(v8_flags.sparkplug_plus);
DCHECK_EQ(4, args.length());
DirectHandle<Object> exception = args.at<Object>(1);
TryPatchCompareOpBaselineCode(isolate,
UpdateEmbeddedFeedbackAndGetCurrent(args));
return isolate->ReThrow(*exception);
}
RUNTIME_FUNCTION(Runtime_PatchBinopBaselineCode) {
HandleScope scope(isolate);
CHECK(v8_flags.sparkplug_plus);
DCHECK_EQ(4, args.length());
DirectHandle<Object> result = args.at<Object>(1);
TryPatchBinaryOpBaselineCode(isolate,
UpdateEmbeddedFeedbackAndGetCurrent(args));
return *result;
}
RUNTIME_FUNCTION(Runtime_PatchBinopBaselineCodeAndThrow) {
HandleScope scope(isolate);
CHECK(v8_flags.sparkplug_plus);
DCHECK_EQ(4, args.length());
DirectHandle<Object> exception = args.at<Object>(1);
TryPatchBinaryOpBaselineCode(isolate,
UpdateEmbeddedFeedbackAndGetCurrent(args));
return isolate->ReThrow(*exception);
}
RUNTIME_FUNCTION(Runtime_PatchUnaryOpBaselineCode) {
HandleScope scope(isolate);
CHECK(v8_flags.sparkplug_plus);
DCHECK_EQ(4, args.length());
DirectHandle<Object> result = args.at<Object>(1);
TryPatchUnaryOpBaselineCode(isolate,
UpdateEmbeddedFeedbackAndGetCurrent(args));
return *result;
}
RUNTIME_FUNCTION(Runtime_PatchUnaryOpBaselineCodeAndThrow) {
HandleScope scope(isolate);
CHECK(v8_flags.sparkplug_plus);
DCHECK_EQ(4, args.length());
DirectHandle<Object> exception = args.at<Object>(1);
TryPatchUnaryOpBaselineCode(isolate,
UpdateEmbeddedFeedbackAndGetCurrent(args));
return isolate->ReThrow(*exception);
}
#endif // V8_ENABLE_SPARKPLUG_PLUS
} // namespace v8::internal