1 // Copyright 2012 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "src/execution/tiering-manager.h"
6
7 #include "src/base/platform/platform.h"
8 #include "src/baseline/baseline-batch-compiler.h"
9 #include "src/baseline/baseline.h"
10 #include "src/codegen/assembler.h"
11 #include "src/codegen/compilation-cache.h"
12 #include "src/codegen/compiler.h"
13 #include "src/codegen/pending-optimization-table.h"
14 #include "src/diagnostics/code-tracer.h"
15 #include "src/execution/execution.h"
16 #include "src/execution/frames-inl.h"
17 #include "src/handles/global-handles.h"
18 #include "src/init/bootstrapper.h"
19 #include "src/interpreter/interpreter.h"
20 #include "src/objects/code.h"
21 #include "src/tracing/trace-event.h"
22
23 namespace v8 {
24 namespace internal {
25
26 // Maximum size in bytes of generate code for a function to allow OSR.
27 static const int kOSRBytecodeSizeAllowanceBase = 119;
28 static const int kOSRBytecodeSizeAllowancePerTick = 44;
29
30 #define OPTIMIZATION_REASON_LIST(V) \
31 V(DoNotOptimize, "do not optimize") \
32 V(HotAndStable, "hot and stable") \
33 V(SmallFunction, "small function")
34
35 enum class OptimizationReason : uint8_t {
36 #define OPTIMIZATION_REASON_CONSTANTS(Constant, message) k##Constant,
37 OPTIMIZATION_REASON_LIST(OPTIMIZATION_REASON_CONSTANTS)
38 #undef OPTIMIZATION_REASON_CONSTANTS
39 };
40
OptimizationReasonToString(OptimizationReason reason)41 char const* OptimizationReasonToString(OptimizationReason reason) {
42 static char const* reasons[] = {
43 #define OPTIMIZATION_REASON_TEXTS(Constant, message) message,
44 OPTIMIZATION_REASON_LIST(OPTIMIZATION_REASON_TEXTS)
45 #undef OPTIMIZATION_REASON_TEXTS
46 };
47 size_t const index = static_cast<size_t>(reason);
48 DCHECK_LT(index, arraysize(reasons));
49 return reasons[index];
50 }
51
52 #undef OPTIMIZATION_REASON_LIST
53
operator <<(std::ostream & os,OptimizationReason reason)54 std::ostream& operator<<(std::ostream& os, OptimizationReason reason) {
55 return os << OptimizationReasonToString(reason);
56 }
57
58 class OptimizationDecision {
59 public:
Maglev()60 static constexpr OptimizationDecision Maglev() {
61 // TODO(v8:7700): Consider using another reason here.
62 return {OptimizationReason::kHotAndStable, CodeKind::MAGLEV,
63 ConcurrencyMode::kConcurrent};
64 }
TurbofanHotAndStable()65 static constexpr OptimizationDecision TurbofanHotAndStable() {
66 return {OptimizationReason::kHotAndStable, CodeKind::TURBOFAN,
67 ConcurrencyMode::kConcurrent};
68 }
TurbofanSmallFunction()69 static constexpr OptimizationDecision TurbofanSmallFunction() {
70 return {OptimizationReason::kSmallFunction, CodeKind::TURBOFAN,
71 ConcurrencyMode::kConcurrent};
72 }
DoNotOptimize()73 static constexpr OptimizationDecision DoNotOptimize() {
74 return {OptimizationReason::kDoNotOptimize,
75 // These values don't matter but we have to pass something.
76 CodeKind::TURBOFAN, ConcurrencyMode::kConcurrent};
77 }
78
should_optimize() const79 constexpr bool should_optimize() const {
80 return optimization_reason != OptimizationReason::kDoNotOptimize;
81 }
82
83 OptimizationReason optimization_reason;
84 CodeKind code_kind;
85 ConcurrencyMode concurrency_mode;
86
87 private:
88 OptimizationDecision() = default;
OptimizationDecision(OptimizationReason optimization_reason,CodeKind code_kind,ConcurrencyMode concurrency_mode)89 constexpr OptimizationDecision(OptimizationReason optimization_reason,
90 CodeKind code_kind,
91 ConcurrencyMode concurrency_mode)
92 : optimization_reason(optimization_reason),
93 code_kind(code_kind),
94 concurrency_mode(concurrency_mode) {}
95 };
96 // Since we pass by value:
97 STATIC_ASSERT(sizeof(OptimizationDecision) <= kInt32Size);
98
99 namespace {
100
TraceInOptimizationQueue(JSFunction function)101 void TraceInOptimizationQueue(JSFunction function) {
102 if (FLAG_trace_opt_verbose) {
103 PrintF("[not marking function %s for optimization: already queued]\n",
104 function.DebugNameCStr().get());
105 }
106 }
107
TraceHeuristicOptimizationDisallowed(JSFunction function)108 void TraceHeuristicOptimizationDisallowed(JSFunction function) {
109 if (FLAG_trace_opt_verbose) {
110 PrintF(
111 "[not marking function %s for optimization: marked with "
112 "%%PrepareFunctionForOptimization for manual optimization]\n",
113 function.DebugNameCStr().get());
114 }
115 }
116
TraceRecompile(Isolate * isolate,JSFunction function,OptimizationDecision d)117 void TraceRecompile(Isolate* isolate, JSFunction function,
118 OptimizationDecision d) {
119 if (FLAG_trace_opt) {
120 CodeTracer::Scope scope(isolate->GetCodeTracer());
121 PrintF(scope.file(), "[marking ");
122 function.ShortPrint(scope.file());
123 PrintF(scope.file(), " for optimization to %s, %s, reason: %s",
124 CodeKindToString(d.code_kind), ToString(d.concurrency_mode),
125 OptimizationReasonToString(d.optimization_reason));
126 PrintF(scope.file(), "]\n");
127 }
128 }
129
130 } // namespace
131
TraceManualRecompile(JSFunction function,CodeKind code_kind,ConcurrencyMode concurrency_mode)132 void TraceManualRecompile(JSFunction function, CodeKind code_kind,
133 ConcurrencyMode concurrency_mode) {
134 if (FLAG_trace_opt) {
135 PrintF("[manually marking ");
136 function.ShortPrint();
137 PrintF(" for optimization to %s, %s]\n", CodeKindToString(code_kind),
138 ToString(concurrency_mode));
139 }
140 }
141
Optimize(JSFunction function,CodeKind code_kind,OptimizationDecision d)142 void TieringManager::Optimize(JSFunction function, CodeKind code_kind,
143 OptimizationDecision d) {
144 DCHECK(d.should_optimize());
145 TraceRecompile(isolate_, function, d);
146 function.MarkForOptimization(isolate_, d.code_kind, d.concurrency_mode);
147 }
148
149 namespace {
150
HaveCachedOSRCodeForCurrentBytecodeOffset(UnoptimizedFrame * frame,int * osr_urgency_out)151 bool HaveCachedOSRCodeForCurrentBytecodeOffset(UnoptimizedFrame* frame,
152 int* osr_urgency_out) {
153 JSFunction function = frame->function();
154 const int current_offset = frame->GetBytecodeOffset();
155 OSROptimizedCodeCache cache = function.native_context().osr_code_cache();
156 interpreter::BytecodeArrayIterator iterator(
157 handle(frame->GetBytecodeArray(), frame->isolate()));
158 for (BytecodeOffset osr_offset : cache.OsrOffsetsFor(function.shared())) {
159 DCHECK(!osr_offset.IsNone());
160 iterator.SetOffset(osr_offset.ToInt());
161 if (base::IsInRange(current_offset, iterator.GetJumpTargetOffset(),
162 osr_offset.ToInt())) {
163 int loop_depth = iterator.GetImmediateOperand(1);
164 // `+ 1` because osr_urgency is an exclusive upper limit on the depth.
165 *osr_urgency_out = loop_depth + 1;
166 return true;
167 }
168 }
169 return false;
170 }
171
172 } // namespace
173
174 namespace {
175
TiersUpToMaglev(CodeKind code_kind)176 bool TiersUpToMaglev(CodeKind code_kind) {
177 // TODO(v8:7700): Flip the UNLIKELY when appropriate.
178 return V8_UNLIKELY(FLAG_maglev) && CodeKindIsUnoptimizedJSFunction(code_kind);
179 }
180
TiersUpToMaglev(base::Optional<CodeKind> code_kind)181 bool TiersUpToMaglev(base::Optional<CodeKind> code_kind) {
182 return code_kind.has_value() && TiersUpToMaglev(code_kind.value());
183 }
184
185 } // namespace
186
187 // static
InterruptBudgetFor(Isolate * isolate,JSFunction function)188 int TieringManager::InterruptBudgetFor(Isolate* isolate, JSFunction function) {
189 if (function.has_feedback_vector()) {
190 return TiersUpToMaglev(function.GetActiveTier())
191 ? FLAG_interrupt_budget_for_maglev
192 : FLAG_interrupt_budget;
193 }
194
195 DCHECK(!function.has_feedback_vector());
196 DCHECK(function.shared().is_compiled());
197 return function.shared().GetBytecodeArray(isolate).length() *
198 FLAG_interrupt_budget_factor_for_feedback_allocation;
199 }
200
201 // static
InitialInterruptBudget()202 int TieringManager::InitialInterruptBudget() {
203 return V8_LIKELY(FLAG_lazy_feedback_allocation)
204 ? FLAG_interrupt_budget_for_feedback_allocation
205 : FLAG_interrupt_budget;
206 }
207
208 namespace {
209
SmallEnoughForOSR(Isolate * isolate,JSFunction function)210 bool SmallEnoughForOSR(Isolate* isolate, JSFunction function) {
211 return function.shared().GetBytecodeArray(isolate).length() <=
212 kOSRBytecodeSizeAllowanceBase +
213 function.feedback_vector().profiler_ticks() *
214 kOSRBytecodeSizeAllowancePerTick;
215 }
216
TrySetOsrUrgency(Isolate * isolate,JSFunction function,int osr_urgency)217 void TrySetOsrUrgency(Isolate* isolate, JSFunction function, int osr_urgency) {
218 SharedFunctionInfo shared = function.shared();
219
220 if (V8_UNLIKELY(!FLAG_use_osr)) return;
221 if (V8_UNLIKELY(!shared.IsUserJavaScript())) return;
222 if (V8_UNLIKELY(shared.optimization_disabled())) return;
223
224 // We've passed all checks - bump the OSR urgency.
225
226 BytecodeArray bytecode = shared.GetBytecodeArray(isolate);
227 if (V8_UNLIKELY(FLAG_trace_osr)) {
228 CodeTracer::Scope scope(isolate->GetCodeTracer());
229 PrintF(scope.file(),
230 "[OSR - setting osr urgency. function: %s, old urgency: %d, new "
231 "urgency: %d]\n",
232 function.DebugNameCStr().get(), bytecode.osr_urgency(), osr_urgency);
233 }
234
235 DCHECK_GE(osr_urgency, bytecode.osr_urgency()); // Never lower urgency here.
236 bytecode.set_osr_urgency(osr_urgency);
237 }
238
TryIncrementOsrUrgency(Isolate * isolate,JSFunction function)239 void TryIncrementOsrUrgency(Isolate* isolate, JSFunction function) {
240 int old_urgency = function.shared().GetBytecodeArray(isolate).osr_urgency();
241 int new_urgency = std::min(old_urgency + 1, BytecodeArray::kMaxOsrUrgency);
242 TrySetOsrUrgency(isolate, function, new_urgency);
243 }
244
TryRequestOsrAtNextOpportunity(Isolate * isolate,JSFunction function)245 void TryRequestOsrAtNextOpportunity(Isolate* isolate, JSFunction function) {
246 TrySetOsrUrgency(isolate, function, BytecodeArray::kMaxOsrUrgency);
247 }
248
TryRequestOsrForCachedOsrCode(Isolate * isolate,JSFunction function,int osr_urgency_for_cached_osr_code)249 void TryRequestOsrForCachedOsrCode(Isolate* isolate, JSFunction function,
250 int osr_urgency_for_cached_osr_code) {
251 DCHECK_LE(osr_urgency_for_cached_osr_code, BytecodeArray::kMaxOsrUrgency);
252 int old_urgency = function.shared().GetBytecodeArray(isolate).osr_urgency();
253 // Make sure not to decrease the existing urgency.
254 int new_urgency = std::max(old_urgency, osr_urgency_for_cached_osr_code);
255 TrySetOsrUrgency(isolate, function, new_urgency);
256 }
257
ShouldOptimizeAsSmallFunction(int bytecode_size,bool any_ic_changed)258 bool ShouldOptimizeAsSmallFunction(int bytecode_size, bool any_ic_changed) {
259 return !any_ic_changed &&
260 bytecode_size < FLAG_max_bytecode_size_for_early_opt;
261 }
262
263 } // namespace
264
RequestOsrAtNextOpportunity(JSFunction function)265 void TieringManager::RequestOsrAtNextOpportunity(JSFunction function) {
266 DisallowGarbageCollection no_gc;
267 TryRequestOsrAtNextOpportunity(isolate_, function);
268 }
269
MaybeOptimizeFrame(JSFunction function,UnoptimizedFrame * frame,CodeKind code_kind)270 void TieringManager::MaybeOptimizeFrame(JSFunction function,
271 UnoptimizedFrame* frame,
272 CodeKind code_kind) {
273 const TieringState tiering_state = function.feedback_vector().tiering_state();
274 const TieringState osr_tiering_state =
275 function.feedback_vector().osr_tiering_state();
276 if (V8_UNLIKELY(IsInProgress(tiering_state)) ||
277 V8_UNLIKELY(IsInProgress(osr_tiering_state))) {
278 // Note: This effectively disables OSR for the function while it is being
279 // compiled.
280 TraceInOptimizationQueue(function);
281 return;
282 }
283
284 if (V8_UNLIKELY(FLAG_testing_d8_test_runner) &&
285 !PendingOptimizationTable::IsHeuristicOptimizationAllowed(isolate_,
286 function)) {
287 TraceHeuristicOptimizationDisallowed(function);
288 return;
289 }
290
291 // TODO(v8:7700): Consider splitting this up for Maglev/Turbofan.
292 if (V8_UNLIKELY(function.shared().optimization_disabled())) return;
293
294 if (V8_UNLIKELY(FLAG_always_osr)) {
295 TryRequestOsrAtNextOpportunity(isolate_, function);
296 // Continue below and do a normal optimized compile as well.
297 }
298
299 // If we have matching cached OSR'd code, request OSR at the next opportunity.
300 int osr_urgency_for_cached_osr_code;
301 if (HaveCachedOSRCodeForCurrentBytecodeOffset(
302 frame, &osr_urgency_for_cached_osr_code)) {
303 TryRequestOsrForCachedOsrCode(isolate_, function,
304 osr_urgency_for_cached_osr_code);
305 }
306
307 const bool is_marked_for_any_optimization =
308 (static_cast<uint32_t>(tiering_state) & kNoneOrInProgressMask) != 0;
309 if (is_marked_for_any_optimization || function.HasAvailableOptimizedCode()) {
310 // OSR kicks in only once we've previously decided to tier up, but we are
311 // still in the unoptimized frame (this implies a long-running loop).
312 if (SmallEnoughForOSR(isolate_, function)) {
313 TryIncrementOsrUrgency(isolate_, function);
314 }
315
316 // Return unconditionally and don't run through the optimization decision
317 // again; we've already decided to tier up previously.
318 return;
319 }
320
321 DCHECK(!is_marked_for_any_optimization &&
322 !function.HasAvailableOptimizedCode());
323 OptimizationDecision d = ShouldOptimize(function, code_kind, frame);
324 if (d.should_optimize()) Optimize(function, code_kind, d);
325 }
326
ShouldOptimize(JSFunction function,CodeKind code_kind,JavaScriptFrame * frame)327 OptimizationDecision TieringManager::ShouldOptimize(JSFunction function,
328 CodeKind code_kind,
329 JavaScriptFrame* frame) {
330 DCHECK_EQ(code_kind, function.GetActiveTier().value());
331
332 if (TiersUpToMaglev(code_kind) &&
333 !function.shared(isolate_).maglev_compilation_failed()) {
334 return OptimizationDecision::Maglev();
335 } else if (code_kind == CodeKind::TURBOFAN) {
336 // Already in the top tier.
337 return OptimizationDecision::DoNotOptimize();
338 }
339
340 BytecodeArray bytecode = function.shared().GetBytecodeArray(isolate_);
341 const int ticks = function.feedback_vector().profiler_ticks();
342 const int ticks_for_optimization =
343 FLAG_ticks_before_optimization +
344 (bytecode.length() / FLAG_bytecode_size_allowance_per_tick);
345 if (ticks >= ticks_for_optimization) {
346 return OptimizationDecision::TurbofanHotAndStable();
347 } else if (ShouldOptimizeAsSmallFunction(bytecode.length(),
348 any_ic_changed_)) {
349 // If no IC was patched since the last tick and this function is very
350 // small, optimistically optimize it now.
351 return OptimizationDecision::TurbofanSmallFunction();
352 } else if (FLAG_trace_opt_verbose) {
353 PrintF("[not yet optimizing %s, not enough ticks: %d/%d and ",
354 function.DebugNameCStr().get(), ticks, ticks_for_optimization);
355 if (any_ic_changed_) {
356 PrintF("ICs changed]\n");
357 } else {
358 PrintF(" too large for small function optimization: %d/%d]\n",
359 bytecode.length(), FLAG_max_bytecode_size_for_early_opt);
360 }
361 }
362
363 return OptimizationDecision::DoNotOptimize();
364 }
365
OnInterruptTickScope(TieringManager * profiler)366 TieringManager::OnInterruptTickScope::OnInterruptTickScope(
367 TieringManager* profiler)
368 : profiler_(profiler) {
369 TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
370 "V8.MarkCandidatesForOptimization");
371 }
372
~OnInterruptTickScope()373 TieringManager::OnInterruptTickScope::~OnInterruptTickScope() {
374 profiler_->any_ic_changed_ = false;
375 }
376
OnInterruptTick(Handle<JSFunction> function)377 void TieringManager::OnInterruptTick(Handle<JSFunction> function) {
378 IsCompiledScope is_compiled_scope(
379 function->shared().is_compiled_scope(isolate_));
380
381 // Remember whether the function had a vector at this point. This is relevant
382 // later since the configuration 'Ignition without a vector' can be
383 // considered a tier on its own. We begin tiering up to tiers higher than
384 // Sparkplug only when reaching this point *with* a feedback vector.
385 const bool had_feedback_vector = function->has_feedback_vector();
386
387 // Ensure that the feedback vector has been allocated, and reset the
388 // interrupt budget in preparation for the next tick.
389 if (had_feedback_vector) {
390 function->SetInterruptBudget(isolate_);
391 } else {
392 JSFunction::CreateAndAttachFeedbackVector(isolate_, function,
393 &is_compiled_scope);
394 DCHECK(is_compiled_scope.is_compiled());
395 // Also initialize the invocation count here. This is only really needed for
396 // OSR. When we OSR functions with lazy feedback allocation we want to have
397 // a non zero invocation count so we can inline functions.
398 function->feedback_vector().set_invocation_count(1, kRelaxedStore);
399 }
400
401 DCHECK(function->has_feedback_vector());
402 DCHECK(function->shared().is_compiled());
403 DCHECK(function->shared().HasBytecodeArray());
404
405 // TODO(jgruber): Consider integrating this into a linear tiering system
406 // controlled by TieringState in which the order is always
407 // Ignition-Sparkplug-Turbofan, and only a single tierup is requested at
408 // once.
409 // It's unclear whether this is possible and/or makes sense - for example,
410 // batching compilation can introduce arbitrary latency between the SP
411 // compile request and fulfillment, which doesn't work with strictly linear
412 // tiering.
413 if (CanCompileWithBaseline(isolate_, function->shared()) &&
414 !function->ActiveTierIsBaseline()) {
415 if (FLAG_baseline_batch_compilation) {
416 isolate_->baseline_batch_compiler()->EnqueueFunction(function);
417 } else {
418 IsCompiledScope is_compiled_scope(
419 function->shared().is_compiled_scope(isolate_));
420 Compiler::CompileBaseline(isolate_, function, Compiler::CLEAR_EXCEPTION,
421 &is_compiled_scope);
422 }
423 }
424
425 // We only tier up beyond sparkplug if we already had a feedback vector.
426 if (!had_feedback_vector) return;
427
428 // Don't tier up if Turbofan is disabled.
429 // TODO(jgruber): Update this for a multi-tier world.
430 if (V8_UNLIKELY(!isolate_->use_optimizer())) return;
431
432 // --- We've decided to proceed for now. ---
433
434 DisallowGarbageCollection no_gc;
435 OnInterruptTickScope scope(this);
436 JSFunction function_obj = *function;
437
438 function_obj.feedback_vector().SaturatingIncrementProfilerTicks();
439
440 JavaScriptFrameIterator it(isolate_);
441 UnoptimizedFrame* frame = UnoptimizedFrame::cast(it.frame());
442 const CodeKind code_kind = function_obj.GetActiveTier().value();
443 MaybeOptimizeFrame(function_obj, frame, code_kind);
444 }
445
446 } // namespace internal
447 } // namespace v8
448