• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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