[maglev] Implement Maglev-to-Turbofan OSR

This implementation sticks closely to what Ignition-to-Turbofan (and now
Sparkplug-to-TF) does. OSR is detected in the TieringManager by having
optimized code available, without having entered it. The osr_urgency is
increased to enable OSR for increasing loop depths. When a candidate
JumpLoop backedge is reached, we call into runtime to trigger OSR
compilation.

JumpLoop also detects the availability of cached OSR'd code. When a
matching OSR code object is available, Maglev 1) deoptimizes s.t. the
unoptimized frame layout is reconstructed, and 2) delegates the actual
OSR tierup to the unoptimized tier. For purposes of 1), we add a new
DeoptimizeReason that causes a one-time eager deopt without invalidating
any code.

Drive-by: Annotate OSR for more --trace-opt output.

Todo: Refactor non-Sparkplug-specific bits of the BaselineAssembler
into a generic spot that both SP and ML can use.

Bug: v8:7700
Change-Id: I6ebab2df8b87f9f70ffb78162a3c1226ec545468
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3859850
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Commit-Queue: Jakob Linke <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/main@{#82816}
This commit is contained in:
Jakob Linke 2022-08-30 12:00:28 +02:00 committed by V8 LUCI CQ
parent 8e069d6294
commit ed90ea5cf7
36 changed files with 472 additions and 141 deletions

View File

@ -3623,6 +3623,7 @@ v8_header_set("v8_internal_headers") {
"src/maglev/maglev-graph-verifier.h",
"src/maglev/maglev-graph.h",
"src/maglev/maglev-interpreter-frame-state.h",
"src/maglev/maglev-ir-inl.h",
"src/maglev/maglev-ir.h",
"src/maglev/maglev-regalloc-data.h",
"src/maglev/maglev-regalloc.h",

View File

@ -1900,7 +1900,7 @@ void BaselineCompiler::VisitCreateRestParameter() {
void BaselineCompiler::VisitJumpLoop() {
Label osr_armed, osr_not_armed;
using D = BaselineOnStackReplacementDescriptor;
using D = OnStackReplacementDescriptor;
Register feedback_vector = Register::no_reg();
Register osr_state = Register::no_reg();
const int loop_depth = iterator().GetImmediateOperand(1);

View File

@ -1780,14 +1780,14 @@ void OnStackReplacement(MacroAssembler* masm, OsrSourceTier source,
} // namespace
void Builtins::Generate_InterpreterOnStackReplacement(MacroAssembler* masm) {
using D = InterpreterOnStackReplacementDescriptor;
using D = OnStackReplacementDescriptor;
static_assert(D::kParameterCount == 1);
OnStackReplacement(masm, OsrSourceTier::kInterpreter,
D::MaybeTargetCodeRegister());
}
void Builtins::Generate_BaselineOnStackReplacement(MacroAssembler* masm) {
using D = BaselineOnStackReplacementDescriptor;
using D = OnStackReplacementDescriptor;
static_assert(D::kParameterCount == 1);
__ ldr(kContextRegister,

View File

@ -2018,14 +2018,14 @@ void OnStackReplacement(MacroAssembler* masm, OsrSourceTier source,
} // namespace
void Builtins::Generate_InterpreterOnStackReplacement(MacroAssembler* masm) {
using D = InterpreterOnStackReplacementDescriptor;
using D = OnStackReplacementDescriptor;
static_assert(D::kParameterCount == 1);
OnStackReplacement(masm, OsrSourceTier::kInterpreter,
D::MaybeTargetCodeRegister());
}
void Builtins::Generate_BaselineOnStackReplacement(MacroAssembler* masm) {
using D = BaselineOnStackReplacementDescriptor;
using D = OnStackReplacementDescriptor;
static_assert(D::kParameterCount == 1);
__ ldr(kContextRegister,

View File

@ -185,17 +185,20 @@ namespace internal {
InterpreterPushArgsThenConstruct) \
ASM(InterpreterEnterAtBytecode, Void) \
ASM(InterpreterEnterAtNextBytecode, Void) \
ASM(InterpreterOnStackReplacement, InterpreterOnStackReplacement) \
ASM(InterpreterOnStackReplacement, OnStackReplacement) \
\
/* Baseline Compiler */ \
ASM(BaselineOutOfLinePrologue, BaselineOutOfLinePrologue) \
ASM(BaselineOutOfLinePrologueDeopt, Void) \
ASM(BaselineOnStackReplacement, BaselineOnStackReplacement) \
ASM(BaselineOnStackReplacement, OnStackReplacement) \
ASM(BaselineLeaveFrame, BaselineLeaveFrame) \
ASM(BaselineOrInterpreterEnterAtBytecode, Void) \
ASM(BaselineOrInterpreterEnterAtNextBytecode, Void) \
ASM(InterpreterOnStackReplacement_ToBaseline, Void) \
\
/* Maglev Compiler */ \
ASM(MaglevOnStackReplacement, OnStackReplacement) \
\
/* Code life-cycle */ \
TFC(CompileLazy, JSTrampoline) \
TFC(CompileLazyDeoptimizedCode, JSTrampoline) \

View File

@ -1300,6 +1300,16 @@ void Builtins::Generate_BaselineOnStackReplacement(MacroAssembler* masm) {
}
#endif
// TODO(v8:11421): Remove #if once the Maglev compiler is ported to other
// architectures.
#ifndef V8_TARGET_ARCH_X64
void Builtins::Generate_MaglevOnStackReplacement(MacroAssembler* masm) {
using D = OnStackReplacementDescriptor;
static_assert(D::kParameterCount == 1);
masm->Trap();
}
#endif // V8_TARGET_ARCH_X64
// ES6 [[Get]] operation.
TF_BUILTIN(GetProperty, CodeStubAssembler) {
auto object = Parameter<Object>(Descriptor::kObject);

View File

@ -2750,14 +2750,14 @@ void OnStackReplacement(MacroAssembler* masm, OsrSourceTier source,
} // namespace
void Builtins::Generate_InterpreterOnStackReplacement(MacroAssembler* masm) {
using D = InterpreterOnStackReplacementDescriptor;
using D = OnStackReplacementDescriptor;
static_assert(D::kParameterCount == 1);
OnStackReplacement(masm, OsrSourceTier::kInterpreter,
D::MaybeTargetCodeRegister());
}
void Builtins::Generate_BaselineOnStackReplacement(MacroAssembler* masm) {
using D = BaselineOnStackReplacementDescriptor;
using D = OnStackReplacementDescriptor;
static_assert(D::kParameterCount == 1);
__ mov(kContextRegister,

View File

@ -2599,6 +2599,7 @@ void Generate_OSREntry(MacroAssembler* masm, Register entry_address) {
enum class OsrSourceTier {
kInterpreter,
kBaseline,
kMaglev,
};
void OnStackReplacement(MacroAssembler* masm, OsrSourceTier source,
@ -2625,6 +2626,14 @@ void OnStackReplacement(MacroAssembler* masm, OsrSourceTier source,
__ bind(&jump_to_optimized_code);
DCHECK_EQ(maybe_target_code, rax); // Already in the right spot.
if (source == OsrSourceTier::kMaglev) {
// Maglev doesn't enter OSR'd code itself, since OSR depends on the
// unoptimized (~= Ignition) stack frame layout. Instead, return to Maglev
// code and let it deoptimize.
__ ret(0);
return;
}
// OSR entry tracing.
{
Label next;
@ -2646,7 +2655,7 @@ void OnStackReplacement(MacroAssembler* masm, OsrSourceTier source,
if (source == OsrSourceTier::kInterpreter) {
// Drop the handler frame that is be sitting on top of the actual
// JavaScript frame. This is the case then OSR is triggered from bytecode.
// JavaScript frame.
__ leave();
}
@ -2675,14 +2684,14 @@ void OnStackReplacement(MacroAssembler* masm, OsrSourceTier source,
} // namespace
void Builtins::Generate_InterpreterOnStackReplacement(MacroAssembler* masm) {
using D = InterpreterOnStackReplacementDescriptor;
using D = OnStackReplacementDescriptor;
static_assert(D::kParameterCount == 1);
OnStackReplacement(masm, OsrSourceTier::kInterpreter,
D::MaybeTargetCodeRegister());
}
void Builtins::Generate_BaselineOnStackReplacement(MacroAssembler* masm) {
using D = BaselineOnStackReplacementDescriptor;
using D = OnStackReplacementDescriptor;
static_assert(D::kParameterCount == 1);
__ movq(kContextRegister,
MemOperand(rbp, BaselineFrameConstants::kContextOffset));
@ -2690,6 +2699,13 @@ void Builtins::Generate_BaselineOnStackReplacement(MacroAssembler* masm) {
D::MaybeTargetCodeRegister());
}
void Builtins::Generate_MaglevOnStackReplacement(MacroAssembler* masm) {
using D = OnStackReplacementDescriptor;
static_assert(D::kParameterCount == 1);
OnStackReplacement(masm, OsrSourceTier::kMaglev,
D::MaybeTargetCodeRegister());
}
#if V8_ENABLE_WEBASSEMBLY
void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
// The function index was pushed to the stack by the caller as int32.

View File

@ -183,6 +183,7 @@ class CompilerTracer : public AllStatic {
if (!FLAG_trace_opt || !info->IsOptimizing()) return;
CodeTracer::Scope scope(isolate->GetCodeTracer());
PrintTracePrefix(scope, "optimizing", info);
if (info->is_osr()) PrintF(scope.file(), " OSR");
PrintF(scope.file(), " - took %0.3f, %0.3f, %0.3f ms", ms_creategraph,
ms_optimize, ms_codegen);
PrintTraceSuffix(scope);
@ -215,6 +216,7 @@ class CompilerTracer : public AllStatic {
if (!FLAG_trace_opt) return;
CodeTracer::Scope scope(isolate->GetCodeTracer());
PrintTracePrefix(scope, "completed optimizing", info);
if (info->is_osr()) PrintF(scope.file(), " OSR");
PrintTraceSuffix(scope);
}
@ -223,6 +225,7 @@ class CompilerTracer : public AllStatic {
if (!FLAG_trace_opt) return;
CodeTracer::Scope scope(isolate->GetCodeTracer());
PrintTracePrefix(scope, "aborted optimizing", info);
if (info->is_osr()) PrintF(scope.file(), " OSR");
PrintF(scope.file(), " because: %s",
GetBailoutReason(info->bailout_reason()));
PrintTraceSuffix(scope);

View File

@ -365,31 +365,17 @@ constexpr auto BaselineLeaveFrameDescriptor::registers() {
}
// static
constexpr auto BaselineOnStackReplacementDescriptor::registers() {
constexpr auto OnStackReplacementDescriptor::registers() {
return DefaultRegisterArray();
}
// static
constexpr Register
BaselineOnStackReplacementDescriptor::MaybeTargetCodeRegister() {
constexpr Register OnStackReplacementDescriptor::MaybeTargetCodeRegister() {
// Picking the first register on purpose because it's convenient that this
// register is the same as the platform's return-value register.
return registers()[0];
}
// static
constexpr auto InterpreterOnStackReplacementDescriptor::registers() {
using BaselineD = BaselineOnStackReplacementDescriptor;
return BaselineD::registers();
}
// static
constexpr Register
InterpreterOnStackReplacementDescriptor::MaybeTargetCodeRegister() {
using BaselineD = BaselineOnStackReplacementDescriptor;
return BaselineD::MaybeTargetCodeRegister();
}
// static
constexpr auto VoidDescriptor::registers() { return RegisterArray(); }

View File

@ -32,7 +32,6 @@ namespace internal {
V(ArraySingleArgumentConstructor) \
V(AsyncFunctionStackParameter) \
V(BaselineLeaveFrame) \
V(BaselineOnStackReplacement) \
V(BaselineOutOfLinePrologue) \
V(BigIntToI32Pair) \
V(BigIntToI64) \
@ -82,7 +81,6 @@ namespace internal {
V(InterpreterCEntry1) \
V(InterpreterCEntry2) \
V(InterpreterDispatch) \
V(InterpreterOnStackReplacement) \
V(InterpreterPushArgsThenCall) \
V(InterpreterPushArgsThenConstruct) \
V(JSTrampoline) \
@ -104,6 +102,7 @@ namespace internal {
V(LookupBaseline) \
V(NewHeapNumber) \
V(NoContext) \
V(OnStackReplacement) \
V(RestartFrameTrampoline) \
V(ResumeGenerator) \
V(ResumeGeneratorBaseline) \
@ -1722,26 +1721,12 @@ class BaselineLeaveFrameDescriptor
static constexpr inline auto registers();
};
class InterpreterOnStackReplacementDescriptor
: public StaticCallInterfaceDescriptor<
InterpreterOnStackReplacementDescriptor> {
class OnStackReplacementDescriptor
: public StaticCallInterfaceDescriptor<OnStackReplacementDescriptor> {
public:
DEFINE_PARAMETERS(kMaybeTargetCode)
DEFINE_PARAMETER_TYPES(MachineType::AnyTagged()) // kMaybeTargetCode
DECLARE_DESCRIPTOR(InterpreterOnStackReplacementDescriptor)
static constexpr inline Register MaybeTargetCodeRegister();
static constexpr inline auto registers();
};
class BaselineOnStackReplacementDescriptor
: public StaticCallInterfaceDescriptor<
BaselineOnStackReplacementDescriptor> {
public:
DEFINE_PARAMETERS_NO_CONTEXT(kMaybeTargetCode)
DEFINE_PARAMETER_TYPES(MachineType::AnyTagged()) // kMaybeTargetCode
DECLARE_DESCRIPTOR(BaselineOnStackReplacementDescriptor)
DECLARE_DESCRIPTOR(OnStackReplacementDescriptor)
static constexpr inline Register MaybeTargetCodeRegister();

View File

@ -2348,6 +2348,16 @@ void TurboAssembler::JumpCodeTObject(Register code, JumpMode jump_mode) {
}
}
void TurboAssembler::CodeDataContainerFromCodeT(Register destination,
Register codet) {
if (V8_EXTERNAL_CODE_SPACE_BOOL) {
Move(destination, codet);
} else {
LoadTaggedPointerField(destination,
FieldOperand(codet, Code::kCodeDataContainerOffset));
}
}
void TurboAssembler::PextrdPreSse41(Register dst, XMMRegister src,
uint8_t imm8) {
if (imm8 == 0) {

View File

@ -415,6 +415,7 @@ class V8_EXPORT_PRIVATE TurboAssembler
void LoadCodeTEntry(Register destination, Register code);
void CallCodeTObject(Register code);
void JumpCodeTObject(Register code, JumpMode jump_mode = JumpMode::kJump);
void CodeDataContainerFromCodeT(Register destination, Register codet);
void Jump(Address destination, RelocInfo::Mode rmode);
void Jump(const ExternalReference& reference);

View File

@ -15,6 +15,7 @@ namespace internal {
V(BigIntTooBig, "BigInt too big") \
V(CowArrayElementsChanged, "copy-on-write array's elements changed") \
V(CouldNotGrowElements, "failed to grow elements store") \
V(PrepareForOnStackReplacement, "prepare for on stack replacement (OSR)") \
V(DeoptimizeNow, "%_DeoptimizeNow") \
V(DivisionByZero, "division by zero") \
V(Hole, "hole") \
@ -91,6 +92,15 @@ size_t hash_value(DeoptimizeReason reason);
V8_EXPORT_PRIVATE char const* DeoptimizeReasonToString(DeoptimizeReason reason);
constexpr bool IsDeoptimizationWithoutCodeInvalidation(
DeoptimizeReason reason) {
// Maglev OSRs into Turbofan by first deoptimizing in order to restore the
// unoptimized frame layout. Since no actual assumptions in the Maglev code
// object are violated, it (and any associated cached optimized code) should
// not be invalidated s.t. we may reenter it in the future.
return reason == DeoptimizeReason::kPrepareForOnStackReplacement;
}
} // namespace internal
} // namespace v8

View File

@ -673,8 +673,7 @@ void Deoptimizer::TraceDeoptBegin(int optimization_id,
BytecodeOffset bytecode_offset) {
DCHECK(tracing_enabled());
FILE* file = trace_scope()->file();
Deoptimizer::DeoptInfo info =
Deoptimizer::GetDeoptInfo(compiled_code_, from_);
Deoptimizer::DeoptInfo info = Deoptimizer::GetDeoptInfo();
PrintF(file, "[bailout (kind: %s, reason: %s): begin. deoptimizing ",
MessageFor(deopt_kind_), DeoptimizeReasonToString(info.deopt_reason));
if (function_.IsJSFunction()) {
@ -1971,8 +1970,7 @@ void Deoptimizer::MaterializeHeapObjects() {
bool feedback_updated = translated_state_.DoUpdateFeedback();
if (verbose_tracing_enabled() && feedback_updated) {
FILE* file = trace_scope()->file();
Deoptimizer::DeoptInfo info =
Deoptimizer::GetDeoptInfo(compiled_code_, from_);
Deoptimizer::DeoptInfo info = Deoptimizer::GetDeoptInfo();
PrintF(file, "Feedback updated from deoptimization at ");
OFStream outstr(file);
info.position.Print(outstr, compiled_code_);

View File

@ -44,6 +44,9 @@ class Deoptimizer : public Malloced {
};
static DeoptInfo GetDeoptInfo(Code code, Address from);
DeoptInfo GetDeoptInfo() const {
return Deoptimizer::GetDeoptInfo(compiled_code_, from_);
}
static int ComputeSourcePositionFromBytecodeArray(
Isolate* isolate, SharedFunctionInfo shared,

View File

@ -1525,6 +1525,17 @@ void MaglevFrame::Iterate(RootVisitor* v) const {
IteratePc(v, pc_address(), constant_pool_address(), entry->code);
}
BytecodeOffset MaglevFrame::GetBytecodeOffsetForOSR() const {
int deopt_index = SafepointEntry::kNoDeoptIndex;
const DeoptimizationData data = GetDeoptimizationData(&deopt_index);
if (deopt_index == SafepointEntry::kNoDeoptIndex) {
CHECK(data.is_null());
FATAL("Missing deoptimization information for OptimizedFrame::Summarize.");
}
return data.GetBytecodeOffset(deopt_index);
}
bool CommonFrame::HasTaggedOutgoingParams(CodeLookupResult& code_lookup) const {
#if V8_ENABLE_WEBASSEMBLY
// With inlined JS-to-Wasm calls, we can be in an OptimizedFrame and

View File

@ -968,6 +968,8 @@ class MaglevFrame : public OptimizedFrame {
int FindReturnPCForTrampoline(CodeT code, int trampoline_pc) const override;
BytecodeOffset GetBytecodeOffsetForOSR() const;
protected:
inline explicit MaglevFrame(StackFrameIteratorBase* iterator);

View File

@ -263,12 +263,15 @@ void TieringManager::MaybeOptimizeFrame(JSFunction function,
const bool is_marked_for_any_optimization =
(static_cast<uint32_t>(tiering_state) & kNoneOrInProgressMask) != 0;
// Baseline OSR uses a separate mechanism and must not be considered here,
// therefore we limit to kOptimizedJSFunctionCodeKindsMask.
// TODO(v8:7700): Change the condition below for Maglev OSR once it is
// implemented.
if (is_marked_for_any_optimization ||
function.HasAvailableCodeKind(CodeKind::TURBOFAN)) {
function.HasAvailableHigherTierCodeThanWithFilter(
code_kind, kOptimizedJSFunctionCodeKindsMask)) {
// OSR kicks in only once we've previously decided to tier up, but we are
// still in the unoptimized frame (this implies a long-running loop).
// still in the lower-tier frame (this implies a long-running loop).
if (SmallEnoughForOSR(isolate_, function)) {
TryIncrementOsrUrgency(isolate_, function);
}
@ -279,7 +282,8 @@ void TieringManager::MaybeOptimizeFrame(JSFunction function,
}
DCHECK(!is_marked_for_any_optimization &&
!function.HasAvailableCodeKind(CodeKind::TURBOFAN));
!function.HasAvailableHigherTierCodeThanWithFilter(
code_kind, kOptimizedJSFunctionCodeKindsMask));
OptimizationDecision d = ShouldOptimize(function, code_kind);
if (d.should_optimize()) Optimize(function, d);
}

View File

@ -8,4 +8,10 @@ specific_include_rules = {
"maglev-graph-builder\.h": [
"+src/interpreter/interpreter-intrinsics.h",
],
}
"maglev-ir\.cc": [
# Allow Maglev to reuse the baseline assembler.
# TODO(v8:7700): Clean up these dependencies by extracting common code to a
# separate directory.
"+src/baseline/baseline-assembler-inl.h",
],
}

View File

@ -2361,13 +2361,14 @@ void MaglevGraphBuilder::VisitJumpLoop() {
if (relative_jump_bytecode_offset > 0) {
AddNewNode<ReduceInterruptBudget>({}, relative_jump_bytecode_offset);
}
AddNewNode<JumpLoopPrologue>({}, loop_offset, feedback_slot,
BytecodeOffset(iterator_.current_offset()),
compilation_unit_);
BasicBlock* block =
target == iterator_.current_offset()
? FinishBlock<JumpLoop>(next_offset(), {}, &jump_targets_[target],
loop_offset, feedback_slot)
? FinishBlock<JumpLoop>(next_offset(), {}, &jump_targets_[target])
: FinishBlock<JumpLoop>(next_offset(), {},
jump_targets_[target].block_ptr(),
loop_offset, feedback_slot);
jump_targets_[target].block_ptr());
merge_states_[target]->MergeLoop(*compilation_unit_,
current_interpreter_frame_, block, target);

View File

@ -94,6 +94,7 @@ class MaglevGraphVerifier {
case Opcode::kJump:
case Opcode::kJumpFromInlined:
case Opcode::kJumpLoop:
case Opcode::kJumpLoopPrologue:
case Opcode::kJumpToInlined:
case Opcode::kRegisterInput:
case Opcode::kRootConstant:

View File

@ -0,0 +1,45 @@
// Copyright 2021 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.
#ifndef V8_MAGLEV_MAGLEV_IR_INL_H_
#define V8_MAGLEV_MAGLEV_IR_INL_H_
#include "src/maglev/maglev-interpreter-frame-state.h"
#include "src/maglev/maglev-ir.h"
namespace v8 {
namespace internal {
namespace maglev {
namespace detail {
template <typename Function>
void DeepForEachInputImpl(const MaglevCompilationUnit& unit,
const CheckpointedInterpreterState* state,
InputLocation* input_locations, int& index,
Function&& f) {
if (state->parent) {
DeepForEachInputImpl(*unit.caller(), state->parent, input_locations, index,
f);
}
state->register_frame->ForEachValue(
unit, [&](ValueNode* node, interpreter::Register reg) {
f(node, reg, &input_locations[index++]);
});
}
template <typename Function>
void DeepForEachInput(const EagerDeoptInfo* node, Function&& f) {
int index = 0;
DeepForEachInputImpl(node->unit, &node->state, node->input_locations, index,
f);
}
} // namespace detail
} // namespace maglev
} // namespace internal
} // namespace v8
#endif // V8_MAGLEV_MAGLEV_IR_INL_H_

View File

@ -6,6 +6,7 @@
#include "src/base/bits.h"
#include "src/base/logging.h"
#include "src/baseline/baseline-assembler-inl.h"
#include "src/builtins/builtins-constructor.h"
#include "src/codegen/interface-descriptors-inl.h"
#include "src/codegen/interface-descriptors.h"
@ -25,6 +26,7 @@
#include "src/maglev/maglev-graph-printer.h"
#include "src/maglev/maglev-graph-processor.h"
#include "src/maglev/maglev-interpreter-frame-state.h"
#include "src/maglev/maglev-ir-inl.h"
#include "src/maglev/maglev-vreg-allocator.h"
namespace v8 {
@ -271,6 +273,10 @@ struct CopyForDeferredHelper<ZoneLabelRef>
template <>
struct CopyForDeferredHelper<RegisterSnapshot>
: public CopyForDeferredByValue<RegisterSnapshot> {};
// Feedback slots are copied by value.
template <>
struct CopyForDeferredHelper<FeedbackSlot>
: public CopyForDeferredByValue<FeedbackSlot> {};
template <typename T>
T CopyForDeferred(MaglevCompilationInfo* compilation_info, T&& value) {
@ -288,13 +294,19 @@ T CopyForDeferred(MaglevCompilationInfo* compilation_info, const T& value) {
return CopyForDeferredHelper<T>::Copy(compilation_info, value);
}
template <typename Function, typename FunctionPointer = Function>
template <typename Function>
struct FunctionArgumentsTupleHelper
: FunctionArgumentsTupleHelper<Function,
decltype(&FunctionPointer::operator())> {};
: public FunctionArgumentsTupleHelper<decltype(&Function::operator())> {};
template <typename T, typename C, typename R, typename... A>
struct FunctionArgumentsTupleHelper<T, R (C::*)(A...) const> {
template <typename C, typename R, typename... A>
struct FunctionArgumentsTupleHelper<R (C::*)(A...) const> {
using FunctionPointer = R (*)(A...);
using Tuple = std::tuple<A...>;
static constexpr size_t kSize = sizeof...(A);
};
template <typename R, typename... A>
struct FunctionArgumentsTupleHelper<R (&)(A...)> {
using FunctionPointer = R (*)(A...);
using Tuple = std::tuple<A...>;
static constexpr size_t kSize = sizeof...(A);
@ -358,6 +370,9 @@ DeferredCodeInfo* PushDeferredCode(MaglevCodeGenState* code_gen_state,
return deferred_code;
}
// Note this doesn't take capturing lambdas by design, since state may
// change until `deferred_code_gen` is actually executed. Use either a
// non-capturing lambda, or a plain function pointer.
template <typename Function, typename... Args>
void JumpToDeferredIf(Condition cond, MaglevCodeGenState* code_gen_state,
Function&& deferred_code_gen, Args&&... args) {
@ -3164,16 +3179,6 @@ void ThrowIfNotSuperConstructor::GenerateCode(
this);
}
namespace {
void AttemptOnStackReplacement(MaglevCodeGenState* code_gen_state,
int32_t loop_depth, FeedbackSlot feedback_slot) {
// TODO(v8:7700): Implement me. See also
// InterpreterAssembler::OnStackReplacement.
}
} // namespace
// ---
// Control nodes
// ---
@ -3279,11 +3284,112 @@ void JumpFromInlined::GenerateCode(MaglevCodeGenState* code_gen_state,
}
}
namespace {
void AttemptOnStackReplacement(MaglevCodeGenState* code_gen_state,
Label* return_label, JumpLoopPrologue* node,
Register scratch0, Register scratch1,
int32_t loop_depth, FeedbackSlot feedback_slot,
BytecodeOffset osr_offset) {
// Two cases may cause us to attempt OSR, in the following order:
//
// 1) Presence of cached OSR Turbofan code.
// 2) The OSR urgency exceeds the current loop depth - in that case, trigger
// a Turbofan OSR compilation.
//
// See also: InterpreterAssembler::OnStackReplacement.
baseline::BaselineAssembler basm(code_gen_state->masm());
// Case 1).
Label deopt;
Register maybe_target_code = scratch1;
{
basm.TryLoadOptimizedOsrCode(maybe_target_code, scratch0, feedback_slot,
&deopt, Label::kFar);
}
// Case 2).
{
__ AssertFeedbackVector(scratch0);
__ movb(scratch0, FieldOperand(scratch0, FeedbackVector::kOsrStateOffset));
__ DecodeField<FeedbackVector::OsrUrgencyBits>(scratch0);
basm.JumpIfByte(baseline::Condition::kUnsignedLessThanEqual, scratch0,
loop_depth, return_label, Label::kNear);
// The osr_urgency exceeds the current loop_depth, signaling an OSR
// request. Call into runtime to compile.
{
// At this point we need a custom register snapshot since additional
// registers may be live at the eager deopt below (the normal
// register_snapshot only contains live registers *after this
// node*).
// TODO(v8:7700): Consider making the snapshot location
// configurable.
RegisterSnapshot snapshot = node->register_snapshot();
detail::DeepForEachInput(
node->eager_deopt_info(),
[&](ValueNode* node, interpreter::Register reg,
InputLocation* input) {
if (!input->IsAnyRegister()) return;
if (input->IsDoubleRegister()) {
snapshot.live_double_registers.set(
input->AssignedDoubleRegister());
} else {
snapshot.live_registers.set(input->AssignedGeneralRegister());
if (node->is_tagged()) {
snapshot.live_tagged_registers.set(
input->AssignedGeneralRegister());
}
}
});
SaveRegisterStateForCall save_register_state(code_gen_state, snapshot);
__ Move(kContextRegister, code_gen_state->native_context().object());
__ Push(Smi::FromInt(osr_offset.ToInt()));
__ CallRuntime(Runtime::kCompileOptimizedOSRFromMaglev, 1);
__ Move(scratch0, rax);
}
// A `0` return value means there is no OSR code available yet. Fall
// through for now, OSR code will be picked up once it exists and is
// cached on the feedback vector.
__ testq(scratch0, scratch0);
__ j(equal, return_label, Label::kNear);
}
__ bind(&deopt);
EmitEagerDeopt(code_gen_state, node,
DeoptimizeReason::kPrepareForOnStackReplacement);
}
} // namespace
void JumpLoopPrologue::AllocateVreg(MaglevVregAllocationState* vreg_state) {
set_temporaries_needed(2);
}
void JumpLoopPrologue::GenerateCode(MaglevCodeGenState* code_gen_state,
const ProcessingState& state) {
Register scratch0 = temporaries().PopFirst();
Register scratch1 = temporaries().PopFirst();
const Register osr_state = scratch1;
__ Move(scratch0, unit_->feedback().object());
__ AssertFeedbackVector(scratch0);
__ movb(osr_state, FieldOperand(scratch0, FeedbackVector::kOsrStateOffset));
// The quick initial OSR check. If it passes, we proceed on to more
// expensive OSR logic.
static_assert(FeedbackVector::MaybeHasOptimizedOsrCodeBit::encode(true) >
FeedbackVector::kMaxOsrUrgency);
__ cmpl(osr_state, Immediate(loop_depth_));
JumpToDeferredIf(above, code_gen_state, AttemptOnStackReplacement, this,
scratch0, scratch1, loop_depth_, feedback_slot_,
osr_offset_);
}
void JumpLoop::AllocateVreg(MaglevVregAllocationState* vreg_state) {}
void JumpLoop::GenerateCode(MaglevCodeGenState* code_gen_state,
const ProcessingState& state) {
AttemptOnStackReplacement(code_gen_state, loop_depth_, feedback_slot_);
__ jmp(target()->label());
}

View File

@ -191,6 +191,7 @@ class CompactInterpreterFrameState;
V(CheckString) \
V(CheckMapsWithMigration) \
V(GeneratorStore) \
V(JumpLoopPrologue) \
V(StoreTaggedFieldNoWriteBarrier) \
V(StoreTaggedFieldWithWriteBarrier) \
V(IncreaseInterruptBudget) \
@ -573,6 +574,7 @@ class ValueLocation {
return compiler::AllocatedOperand::cast(operand_).GetDoubleRegister();
}
bool IsAnyRegister() const { return operand_.IsAnyRegister(); }
bool IsDoubleRegister() const { return operand_.IsDoubleRegister(); }
const compiler::InstructionOperand& operand() const { return operand_; }
@ -832,7 +834,7 @@ class NodeBase : public ZoneObject {
return NumTemporariesNeededField::decode(bitfield_);
}
RegList temporaries() const { return temporaries_; }
RegList& temporaries() { return temporaries_; }
void assign_temporaries(RegList list) { temporaries_ = list; }
@ -1109,6 +1111,11 @@ class ValueNode : public Node {
ValueRepresentation::kFloat64);
}
constexpr bool is_tagged() const {
return (properties().value_representation() ==
ValueRepresentation::kTagged);
}
constexpr MachineRepresentation GetMachineRepresentation() const {
switch (properties().value_representation()) {
case ValueRepresentation::kTagged:
@ -1959,6 +1966,35 @@ class GeneratorStore : public NodeT<GeneratorStore> {
const int bytecode_offset_;
};
class JumpLoopPrologue : public FixedInputNodeT<0, JumpLoopPrologue> {
using Base = FixedInputNodeT<0, JumpLoopPrologue>;
public:
explicit JumpLoopPrologue(uint64_t bitfield, int32_t loop_depth,
FeedbackSlot feedback_slot,
BytecodeOffset osr_offset,
MaglevCompilationUnit* unit)
: Base(bitfield),
loop_depth_(loop_depth),
feedback_slot_(feedback_slot),
osr_offset_(osr_offset),
unit_(unit) {}
static constexpr OpProperties kProperties =
OpProperties::NeedsRegisterSnapshot() | OpProperties::EagerDeopt();
void AllocateVreg(MaglevVregAllocationState*);
void GenerateCode(MaglevCodeGenState*, const ProcessingState&);
void PrintParams(std::ostream&, MaglevGraphLabeller*) const {}
private:
// For OSR.
const int32_t loop_depth_;
const FeedbackSlot feedback_slot_;
const BytecodeOffset osr_offset_;
MaglevCompilationUnit* const unit_;
};
class ForInPrepare : public FixedInputValueNodeT<2, ForInPrepare> {
using Base = FixedInputValueNodeT<2, ForInPrepare>;
@ -3657,7 +3693,7 @@ class UnconditionalControlNodeT : public UnconditionalControlNode {
class ConditionalControlNode : public ControlNode {
public:
ConditionalControlNode(uint64_t bitfield) : ControlNode(bitfield) {}
explicit ConditionalControlNode(uint64_t bitfield) : ControlNode(bitfield) {}
};
class BranchControlNode : public ConditionalControlNode {
@ -3715,17 +3751,11 @@ class JumpLoop : public UnconditionalControlNodeT<JumpLoop> {
using Base = UnconditionalControlNodeT<JumpLoop>;
public:
explicit JumpLoop(uint64_t bitfield, BasicBlock* target, int32_t loop_depth,
FeedbackSlot feedback_slot)
: Base(bitfield, target),
loop_depth_(loop_depth),
feedback_slot_(feedback_slot) {}
explicit JumpLoop(uint64_t bitfield, BasicBlock* target)
: Base(bitfield, target) {}
explicit JumpLoop(uint64_t bitfield, BasicBlockRef* ref, int32_t loop_depth,
FeedbackSlot feedback_slot)
: Base(bitfield, ref),
loop_depth_(loop_depth),
feedback_slot_(feedback_slot) {}
explicit JumpLoop(uint64_t bitfield, BasicBlockRef* ref)
: Base(bitfield, ref) {}
void AllocateVreg(MaglevVregAllocationState*);
void GenerateCode(MaglevCodeGenState*, const ProcessingState&);
@ -3737,9 +3767,6 @@ class JumpLoop : public UnconditionalControlNodeT<JumpLoop> {
}
private:
// For OSR.
const int32_t loop_depth_;
const FeedbackSlot feedback_slot_;
base::Vector<Input> used_node_locations_;
};
@ -3758,7 +3785,7 @@ class JumpToInlined : public UnconditionalControlNodeT<JumpToInlined> {
const MaglevCompilationUnit* unit() const { return unit_; }
private:
MaglevCompilationUnit* unit_;
MaglevCompilationUnit* const unit_;
};
class JumpFromInlined : public UnconditionalControlNodeT<JumpFromInlined> {

View File

@ -19,7 +19,7 @@
#include "src/maglev/maglev-graph-processor.h"
#include "src/maglev/maglev-graph.h"
#include "src/maglev/maglev-interpreter-frame-state.h"
#include "src/maglev/maglev-ir.h"
#include "src/maglev/maglev-ir-inl.h"
#include "src/maglev/maglev-regalloc-data.h"
namespace v8 {
@ -472,25 +472,13 @@ void StraightForwardRegisterAllocator::UpdateUse(
void StraightForwardRegisterAllocator::UpdateUse(
const EagerDeoptInfo& deopt_info) {
int index = 0;
UpdateUse(deopt_info.unit, &deopt_info.state, deopt_info.input_locations,
index);
}
void StraightForwardRegisterAllocator::UpdateUse(
const LazyDeoptInfo& deopt_info) {
const CompactInterpreterFrameState* checkpoint_state =
deopt_info.state.register_frame;
int index = 0;
checkpoint_state->ForEachValue(
deopt_info.unit, [&](ValueNode* node, interpreter::Register reg) {
// Skip over the result location.
if (reg == deopt_info.result_location) return;
detail::DeepForEachInput(
&deopt_info,
[&](ValueNode* node, interpreter::Register reg, InputLocation* input) {
if (FLAG_trace_maglev_regalloc) {
printing_visitor_->os()
<< "- using " << PrintNodeLabel(graph_labeller(), node) << "\n";
}
InputLocation* input = &deopt_info.input_locations[index++];
// We might have dropped this node without spilling it. Spill it now.
if (!node->has_register() && !node->is_loadable()) {
Spill(node);
@ -501,20 +489,21 @@ void StraightForwardRegisterAllocator::UpdateUse(
}
void StraightForwardRegisterAllocator::UpdateUse(
const MaglevCompilationUnit& unit,
const CheckpointedInterpreterState* state, InputLocation* input_locations,
int& index) {
if (state->parent) {
UpdateUse(*unit.caller(), state->parent, input_locations, index);
}
const CompactInterpreterFrameState* checkpoint_state = state->register_frame;
const LazyDeoptInfo& deopt_info) {
const CompactInterpreterFrameState* checkpoint_state =
deopt_info.state.register_frame;
int index = 0;
// TODO(leszeks): This is missing parent recursion, fix it.
// See also: UpdateUse(EagerDeoptInfo&).
checkpoint_state->ForEachValue(
unit, [&](ValueNode* node, interpreter::Register reg) {
deopt_info.unit, [&](ValueNode* node, interpreter::Register reg) {
// Skip over the result location.
if (reg == deopt_info.result_location) return;
if (FLAG_trace_maglev_regalloc) {
printing_visitor_->os()
<< "- using " << PrintNodeLabel(graph_labeller(), node) << "\n";
}
InputLocation* input = &input_locations[index++];
InputLocation* input = &deopt_info.input_locations[index++];
// We might have dropped this node without spilling it. Spill it now.
if (!node->has_register() && !node->is_loadable()) {
Spill(node);

View File

@ -134,9 +134,6 @@ class StraightForwardRegisterAllocator {
void UpdateUse(ValueNode* node, InputLocation* input_location);
void UpdateUse(const EagerDeoptInfo& deopt_info);
void UpdateUse(const LazyDeoptInfo& deopt_info);
void UpdateUse(const MaglevCompilationUnit& unit,
const CheckpointedInterpreterState* state,
InputLocation* input_locations, int& index);
void AllocateControlNode(ControlNode* node, BasicBlock* block);
void AllocateNode(Node* node);

View File

@ -1809,7 +1809,7 @@ DEFINE_DEOPT_ENTRY_ACCESSORS(Pc, Smi)
DEFINE_DEOPT_ENTRY_ACCESSORS(NodeId, Smi)
#endif // DEBUG
BytecodeOffset DeoptimizationData::GetBytecodeOffset(int i) {
BytecodeOffset DeoptimizationData::GetBytecodeOffset(int i) const {
return BytecodeOffset(BytecodeOffsetRaw(i).value());
}

View File

@ -1385,7 +1385,7 @@ class DeoptimizationData : public FixedArray {
#undef DECL_ENTRY_ACCESSORS
inline BytecodeOffset GetBytecodeOffset(int i);
inline BytecodeOffset GetBytecodeOffset(int i) const;
inline void SetBytecodeOffset(int i, BytecodeOffset value);

View File

@ -66,11 +66,19 @@ bool JSFunction::HasAttachedOptimizedCode() const {
}
bool JSFunction::HasAvailableHigherTierCodeThan(CodeKind kind) const {
return HasAvailableHigherTierCodeThanWithFilter(kind,
kJSFunctionCodeKindsMask);
}
bool JSFunction::HasAvailableHigherTierCodeThanWithFilter(
CodeKind kind, CodeKinds filter_mask) const {
const int kind_as_int_flag = static_cast<int>(CodeKindToCodeKindFlag(kind));
DCHECK(base::bits::IsPowerOfTwo(kind_as_int_flag));
// Smear right - any higher present bit means we have a higher tier available.
const int mask = kind_as_int_flag | (kind_as_int_flag - 1);
return (GetAvailableCodeKinds() & static_cast<CodeKinds>(~mask)) != 0;
const CodeKinds masked_available_kinds =
GetAvailableCodeKinds() & filter_mask;
return (masked_available_kinds & static_cast<CodeKinds>(~mask)) != 0;
}
bool JSFunction::HasAvailableOptimizedCode() const {

View File

@ -153,6 +153,9 @@ class JSFunction : public TorqueGeneratedJSFunction<
// will happen on its next activation.
bool HasAvailableHigherTierCodeThan(CodeKind kind) const;
// As above but only considers available code kinds passing the filter mask.
bool HasAvailableHigherTierCodeThanWithFilter(CodeKind kind,
CodeKinds filter_mask) const;
// True, iff any generated code kind is attached/available to this function.
V8_EXPORT_PRIVATE bool HasAttachedOptimizedCode() const;

View File

@ -324,6 +324,8 @@ RUNTIME_FUNCTION(Runtime_NotifyDeoptimized) {
// code object from deoptimizer.
Handle<Code> optimized_code = deoptimizer->compiled_code();
const DeoptimizeKind deopt_kind = deoptimizer->deopt_kind();
const DeoptimizeReason deopt_reason =
deoptimizer->GetDeoptInfo().deopt_reason;
// TODO(turbofan): We currently need the native context to materialize
// the arguments object, but only to get to its map.
@ -347,6 +349,12 @@ RUNTIME_FUNCTION(Runtime_NotifyDeoptimized) {
return ReadOnlyRoots(isolate).undefined_value();
}
// Some eager deopts also don't invalidate Code (e.g. when preparing for OSR
// from Maglev to Turbofan).
if (IsDeoptimizationWithoutCodeInvalidation(deopt_reason)) {
return ReadOnlyRoots(isolate).undefined_value();
}
// Non-OSR'd code is deoptimized unconditionally. If the deoptimization occurs
// inside the outermost loop containning a loop that can trigger OSR
// compilation, we remove the OSR code, it will avoid hit the out of date OSR
@ -387,25 +395,30 @@ RUNTIME_FUNCTION(Runtime_VerifyType) {
return *obj;
}
RUNTIME_FUNCTION(Runtime_CompileOptimizedOSR) {
HandleScope handle_scope(isolate);
DCHECK_EQ(0, args.length());
DCHECK(FLAG_use_osr);
namespace {
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.
JavaScriptFrameIterator it(isolate);
UnoptimizedFrame* frame = UnoptimizedFrame::cast(it.frame());
DCHECK_IMPLIES(frame->is_interpreted(),
frame->LookupCodeT().is_interpreter_trampoline_builtin());
DCHECK_IMPLIES(frame->is_baseline(),
frame->LookupCodeT().kind() == CodeKind::BASELINE);
DCHECK(frame->function().shared().HasBytecodeArray());
// Determine the entry point for which this OSR request has been fired.
BytecodeOffset osr_offset = BytecodeOffset(frame->GetBytecodeOffset());
DCHECK(!osr_offset.IsNone());
*osr_offset = BytecodeOffset(frame->GetBytecodeOffset());
*function = handle(frame->function(), isolate);
DCHECK(!osr_offset->IsNone());
DCHECK((*function)->shared().HasBytecodeArray());
}
Object CompileOptimizedOSR(Isolate* isolate, Handle<JSFunction> function,
BytecodeOffset osr_offset) {
const ConcurrencyMode mode =
V8_LIKELY(isolate->concurrent_recompilation_enabled() &&
FLAG_concurrent_osr)
@ -413,7 +426,6 @@ RUNTIME_FUNCTION(Runtime_CompileOptimizedOSR) {
: ConcurrencyMode::kSynchronous;
Handle<CodeT> result;
Handle<JSFunction> function(frame->function(), isolate);
if (!Compiler::CompileOptimizedOSR(isolate, function, osr_offset, mode)
.ToHandle(&result)) {
// An empty result can mean one of two things:
@ -456,20 +468,44 @@ RUNTIME_FUNCTION(Runtime_CompileOptimizedOSR) {
return *result;
}
} // namespace
RUNTIME_FUNCTION(Runtime_CompileOptimizedOSR) {
HandleScope handle_scope(isolate);
DCHECK_EQ(0, args.length());
DCHECK(FLAG_use_osr);
BytecodeOffset osr_offset = BytecodeOffset::None();
Handle<JSFunction> function;
GetOsrOffsetAndFunctionForOSR(isolate, &osr_offset, &function);
return CompileOptimizedOSR(isolate, function, osr_offset);
}
RUNTIME_FUNCTION(Runtime_CompileOptimizedOSRFromMaglev) {
HandleScope handle_scope(isolate);
DCHECK_EQ(1, args.length());
DCHECK(FLAG_use_osr);
const BytecodeOffset osr_offset(args.positive_smi_value_at(0));
JavaScriptFrameIterator it(isolate);
MaglevFrame* frame = MaglevFrame::cast(it.frame());
DCHECK_EQ(frame->LookupCodeT().kind(), CodeKind::MAGLEV);
Handle<JSFunction> function = handle(frame->function(), isolate);
return CompileOptimizedOSR(isolate, function, osr_offset);
}
RUNTIME_FUNCTION(Runtime_TraceOptimizedOSREntry) {
HandleScope handle_scope(isolate);
DCHECK_EQ(0, args.length());
CHECK(FLAG_trace_osr);
// Determine the frame that triggered the OSR request.
JavaScriptFrameIterator it(isolate);
UnoptimizedFrame* frame = UnoptimizedFrame::cast(it.frame());
BytecodeOffset osr_offset = BytecodeOffset::None();
Handle<JSFunction> function;
GetOsrOffsetAndFunctionForOSR(isolate, &osr_offset, &function);
// Determine the entry point for which this OSR request has been fired.
BytecodeOffset osr_offset = BytecodeOffset(frame->GetBytecodeOffset());
DCHECK(!osr_offset.IsNone());
Handle<JSFunction> function(frame->function(), isolate);
PrintF(CodeTracer::Scope{isolate->GetCodeTracer()}.file(),
"[OSR - entry. function: %s, osr offset: %d]\n",
function->DebugNameCStr().get(), osr_offset.ToInt());

View File

@ -492,6 +492,13 @@ RUNTIME_FUNCTION(Runtime_IsTurbofanEnabled) {
return isolate->heap()->ToBoolean(FLAG_turbofan);
}
RUNTIME_FUNCTION(Runtime_CurrentFrameIsTurbofan) {
HandleScope scope(isolate);
DCHECK_EQ(args.length(), 0);
JavaScriptFrameIterator it(isolate);
return isolate->heap()->ToBoolean(it.frame()->is_turbofan());
}
#ifdef V8_ENABLE_MAGLEV
RUNTIME_FUNCTION(Runtime_OptimizeMaglevOnNextCall) {
HandleScope scope(isolate);

View File

@ -106,6 +106,7 @@ namespace internal {
#define FOR_EACH_INTRINSIC_COMPILER(F, I) \
F(CompileOptimizedOSR, 0, 1) \
F(CompileOptimizedOSRFromMaglev, 1, 1) \
F(TraceOptimizedOSREntry, 0, 1) \
F(CompileLazy, 1, 1) \
F(CompileBaseline, 1, 1) \
@ -491,6 +492,7 @@ namespace internal {
F(ConstructInternalizedString, 1, 1) \
F(ConstructSlicedString, 2, 1) \
F(ConstructThinString, 1, 1) \
F(CurrentFrameIsTurbofan, 0, 1) \
F(DebugPrint, 1, 1) \
F(DebugPrintPtr, 1, 1) \
F(DebugTrace, 0, 1) \

View File

@ -0,0 +1,35 @@
// Copyright 2022 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.
//
// Flags: --allow-natives-syntax --maglev --no-stress-opt
// Flags: --no-baseline-batch-compilation --use-osr --turbofan
let keep_going = 10000000; // A counter to avoid test hangs on failure.
let i; // The loop counter for the test function.
function f() {
let sum = i;
while (--i > 0 && !%CurrentFrameIsTurbofan() && --keep_going) {
// This loop should trigger OSR.
sum += i;
}
return sum;
}
function g() {
assertTrue(%IsMaglevEnabled());
while (!%ActiveTierIsMaglev(f) && --keep_going) {
i = 5.2;
f();
}
console.log('osr to tf', keep_going);
assertTrue(%IsTurbofanEnabled());
i = 66666666666;
f();
assertTrue(keep_going > 0);
}
%NeverOptimizeFunction(g);
g();

View File

@ -0,0 +1,25 @@
// Copyright 2022 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.
//
// Flags: --allow-natives-syntax --maglev --no-stress-opt
// Flags: --no-baseline-batch-compilation --use-osr --turbofan
let keep_going = 100000; // A counter to avoid test hangs on failure.
function f() {
let reached_tf = false;
while (!reached_tf && --keep_going) {
// This loop should trigger OSR.
reached_tf = %CurrentFrameIsTurbofan();
}
}
function g() {
assertTrue(%IsTurbofanEnabled());
f();
assertTrue(keep_going > 0);
}
%NeverOptimizeFunction(g);
g();