• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2016 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/compiler/bytecode-analysis.h"
6 
7 #include "src/interpreter/bytecode-array-iterator.h"
8 #include "src/interpreter/bytecode-array-random-iterator.h"
9 #include "src/objects-inl.h"
10 
11 namespace v8 {
12 namespace internal {
13 namespace compiler {
14 
15 using namespace interpreter;
16 
BytecodeLoopAssignments(int parameter_count,int register_count,Zone * zone)17 BytecodeLoopAssignments::BytecodeLoopAssignments(int parameter_count,
18                                                  int register_count, Zone* zone)
19     : parameter_count_(parameter_count),
20       bit_vector_(new (zone)
21                       BitVector(parameter_count + register_count, zone)) {}
22 
Add(interpreter::Register r)23 void BytecodeLoopAssignments::Add(interpreter::Register r) {
24   if (r.is_parameter()) {
25     bit_vector_->Add(r.ToParameterIndex(parameter_count_));
26   } else {
27     bit_vector_->Add(parameter_count_ + r.index());
28   }
29 }
30 
AddPair(interpreter::Register r)31 void BytecodeLoopAssignments::AddPair(interpreter::Register r) {
32   if (r.is_parameter()) {
33     DCHECK(interpreter::Register(r.index() + 1).is_parameter());
34     bit_vector_->Add(r.ToParameterIndex(parameter_count_));
35     bit_vector_->Add(r.ToParameterIndex(parameter_count_) + 1);
36   } else {
37     DCHECK(!interpreter::Register(r.index() + 1).is_parameter());
38     bit_vector_->Add(parameter_count_ + r.index());
39     bit_vector_->Add(parameter_count_ + r.index() + 1);
40   }
41 }
42 
AddTriple(interpreter::Register r)43 void BytecodeLoopAssignments::AddTriple(interpreter::Register r) {
44   if (r.is_parameter()) {
45     DCHECK(interpreter::Register(r.index() + 1).is_parameter());
46     DCHECK(interpreter::Register(r.index() + 2).is_parameter());
47     bit_vector_->Add(r.ToParameterIndex(parameter_count_));
48     bit_vector_->Add(r.ToParameterIndex(parameter_count_) + 1);
49     bit_vector_->Add(r.ToParameterIndex(parameter_count_) + 2);
50   } else {
51     DCHECK(!interpreter::Register(r.index() + 1).is_parameter());
52     DCHECK(!interpreter::Register(r.index() + 2).is_parameter());
53     bit_vector_->Add(parameter_count_ + r.index());
54     bit_vector_->Add(parameter_count_ + r.index() + 1);
55     bit_vector_->Add(parameter_count_ + r.index() + 2);
56   }
57 }
58 
AddAll()59 void BytecodeLoopAssignments::AddAll() { bit_vector_->AddAll(); }
60 
Union(const BytecodeLoopAssignments & other)61 void BytecodeLoopAssignments::Union(const BytecodeLoopAssignments& other) {
62   bit_vector_->Union(*other.bit_vector_);
63 }
64 
ContainsParameter(int index) const65 bool BytecodeLoopAssignments::ContainsParameter(int index) const {
66   DCHECK_GE(index, 0);
67   DCHECK_LT(index, parameter_count());
68   return bit_vector_->Contains(index);
69 }
70 
ContainsLocal(int index) const71 bool BytecodeLoopAssignments::ContainsLocal(int index) const {
72   DCHECK_GE(index, 0);
73   DCHECK_LT(index, local_count());
74   return bit_vector_->Contains(parameter_count_ + index);
75 }
76 
ContainsAccumulator() const77 bool BytecodeLoopAssignments::ContainsAccumulator() const {
78   // TODO(leszeks): This assumes the accumulator is always assigned. This is
79   // probably correct, but that assignment is also probably dead, so we should
80   // check liveness.
81   return true;
82 }
83 
BytecodeAnalysis(Handle<BytecodeArray> bytecode_array,Zone * zone,bool do_liveness_analysis)84 BytecodeAnalysis::BytecodeAnalysis(Handle<BytecodeArray> bytecode_array,
85                                    Zone* zone, bool do_liveness_analysis)
86     : bytecode_array_(bytecode_array),
87       do_liveness_analysis_(do_liveness_analysis),
88       zone_(zone),
89       loop_stack_(zone),
90       loop_end_index_queue_(zone),
91       end_to_header_(zone),
92       header_to_info_(zone),
93       liveness_map_(bytecode_array->length(), zone) {}
94 
95 namespace {
96 
UpdateInLiveness(Bytecode bytecode,BytecodeLivenessState & in_liveness,const BytecodeArrayAccessor & accessor)97 void UpdateInLiveness(Bytecode bytecode, BytecodeLivenessState& in_liveness,
98                       const BytecodeArrayAccessor& accessor) {
99   int num_operands = Bytecodes::NumberOfOperands(bytecode);
100   const OperandType* operand_types = Bytecodes::GetOperandTypes(bytecode);
101 
102   if (Bytecodes::WritesAccumulator(bytecode)) {
103     in_liveness.MarkAccumulatorDead();
104   }
105   for (int i = 0; i < num_operands; ++i) {
106     switch (operand_types[i]) {
107       case OperandType::kRegOut: {
108         interpreter::Register r = accessor.GetRegisterOperand(i);
109         if (!r.is_parameter()) {
110           in_liveness.MarkRegisterDead(r.index());
111         }
112         break;
113       }
114       case OperandType::kRegOutPair: {
115         interpreter::Register r = accessor.GetRegisterOperand(i);
116         if (!r.is_parameter()) {
117           DCHECK(!interpreter::Register(r.index() + 1).is_parameter());
118           in_liveness.MarkRegisterDead(r.index());
119           in_liveness.MarkRegisterDead(r.index() + 1);
120         }
121         break;
122       }
123       case OperandType::kRegOutTriple: {
124         interpreter::Register r = accessor.GetRegisterOperand(i);
125         if (!r.is_parameter()) {
126           DCHECK(!interpreter::Register(r.index() + 1).is_parameter());
127           DCHECK(!interpreter::Register(r.index() + 2).is_parameter());
128           in_liveness.MarkRegisterDead(r.index());
129           in_liveness.MarkRegisterDead(r.index() + 1);
130           in_liveness.MarkRegisterDead(r.index() + 2);
131         }
132         break;
133       }
134       default:
135         DCHECK(!Bytecodes::IsRegisterOutputOperandType(operand_types[i]));
136         break;
137     }
138   }
139 
140   if (Bytecodes::ReadsAccumulator(bytecode)) {
141     in_liveness.MarkAccumulatorLive();
142   }
143   for (int i = 0; i < num_operands; ++i) {
144     switch (operand_types[i]) {
145       case OperandType::kReg: {
146         interpreter::Register r = accessor.GetRegisterOperand(i);
147         if (!r.is_parameter()) {
148           in_liveness.MarkRegisterLive(r.index());
149         }
150         break;
151       }
152       case OperandType::kRegPair: {
153         interpreter::Register r = accessor.GetRegisterOperand(i);
154         if (!r.is_parameter()) {
155           DCHECK(!interpreter::Register(r.index() + 1).is_parameter());
156           in_liveness.MarkRegisterLive(r.index());
157           in_liveness.MarkRegisterLive(r.index() + 1);
158         }
159         break;
160       }
161       case OperandType::kRegList: {
162         interpreter::Register r = accessor.GetRegisterOperand(i++);
163         uint32_t reg_count = accessor.GetRegisterCountOperand(i);
164         if (!r.is_parameter()) {
165           for (uint32_t j = 0; j < reg_count; ++j) {
166             DCHECK(!interpreter::Register(r.index() + j).is_parameter());
167             in_liveness.MarkRegisterLive(r.index() + j);
168           }
169         }
170       }
171       default:
172         DCHECK(!Bytecodes::IsRegisterInputOperandType(operand_types[i]));
173         break;
174     }
175   }
176 }
177 
UpdateOutLiveness(Bytecode bytecode,BytecodeLivenessState & out_liveness,BytecodeLivenessState * next_bytecode_in_liveness,const BytecodeArrayAccessor & accessor,const BytecodeLivenessMap & liveness_map)178 void UpdateOutLiveness(Bytecode bytecode, BytecodeLivenessState& out_liveness,
179                        BytecodeLivenessState* next_bytecode_in_liveness,
180                        const BytecodeArrayAccessor& accessor,
181                        const BytecodeLivenessMap& liveness_map) {
182   int current_offset = accessor.current_offset();
183   const Handle<BytecodeArray>& bytecode_array = accessor.bytecode_array();
184 
185   // Update from jump target (if any). Skip loops, we update these manually in
186   // the liveness iterations.
187   if (Bytecodes::IsForwardJump(bytecode)) {
188     int target_offset = accessor.GetJumpTargetOffset();
189     out_liveness.Union(*liveness_map.GetInLiveness(target_offset));
190   }
191 
192   // Update from next bytecode (unless there isn't one or this is an
193   // unconditional jump).
194   if (next_bytecode_in_liveness != nullptr &&
195       !Bytecodes::IsUnconditionalJump(bytecode)) {
196     out_liveness.Union(*next_bytecode_in_liveness);
197   }
198 
199   // Update from exception handler (if any).
200   if (!interpreter::Bytecodes::IsWithoutExternalSideEffects(bytecode)) {
201     int handler_context;
202     // TODO(leszeks): We should look up this range only once per entry.
203     HandlerTable* table = HandlerTable::cast(bytecode_array->handler_table());
204     int handler_offset =
205         table->LookupRange(current_offset, &handler_context, nullptr);
206 
207     if (handler_offset != -1) {
208       out_liveness.Union(*liveness_map.GetInLiveness(handler_offset));
209       out_liveness.MarkRegisterLive(handler_context);
210     }
211   }
212 }
213 
UpdateAssignments(Bytecode bytecode,BytecodeLoopAssignments & assignments,const BytecodeArrayAccessor & accessor)214 void UpdateAssignments(Bytecode bytecode, BytecodeLoopAssignments& assignments,
215                        const BytecodeArrayAccessor& accessor) {
216   int num_operands = Bytecodes::NumberOfOperands(bytecode);
217   const OperandType* operand_types = Bytecodes::GetOperandTypes(bytecode);
218 
219   for (int i = 0; i < num_operands; ++i) {
220     switch (operand_types[i]) {
221       case OperandType::kRegOut: {
222         assignments.Add(accessor.GetRegisterOperand(i));
223         break;
224       }
225       case OperandType::kRegOutPair: {
226         assignments.AddPair(accessor.GetRegisterOperand(i));
227         break;
228       }
229       case OperandType::kRegOutTriple: {
230         assignments.AddTriple(accessor.GetRegisterOperand(i));
231         break;
232       }
233       default:
234         DCHECK(!Bytecodes::IsRegisterOutputOperandType(operand_types[i]));
235         break;
236     }
237   }
238 }
239 
240 }  // namespace
241 
Analyze(BailoutId osr_bailout_id)242 void BytecodeAnalysis::Analyze(BailoutId osr_bailout_id) {
243   loop_stack_.push({-1, nullptr});
244 
245   BytecodeLivenessState* next_bytecode_in_liveness = nullptr;
246 
247   int osr_loop_end_offset =
248       osr_bailout_id.IsNone() ? -1 : osr_bailout_id.ToInt();
249 
250   BytecodeArrayRandomIterator iterator(bytecode_array(), zone());
251   for (iterator.GoToEnd(); iterator.IsValid(); --iterator) {
252     Bytecode bytecode = iterator.current_bytecode();
253     int current_offset = iterator.current_offset();
254 
255     if (bytecode == Bytecode::kJumpLoop) {
256       // Every byte up to and including the last byte within the backwards jump
257       // instruction is considered part of the loop, set loop end accordingly.
258       int loop_end = current_offset + iterator.current_bytecode_size();
259       PushLoop(iterator.GetJumpTargetOffset(), loop_end);
260 
261       // Normally prefixed bytecodes are treated as if the prefix's offset was
262       // the actual bytecode's offset. However, the OSR id is the offset of the
263       // actual JumpLoop bytecode, so we need to find the location of that
264       // bytecode ignoring the prefix.
265       int jump_loop_offset = current_offset + iterator.current_prefix_offset();
266       bool is_osr_loop = (jump_loop_offset == osr_loop_end_offset);
267 
268       // Check that is_osr_loop is set iff the osr_loop_end_offset is within
269       // this bytecode.
270       DCHECK(!is_osr_loop ||
271              iterator.OffsetWithinBytecode(osr_loop_end_offset));
272 
273       // OSR "assigns" everything to OSR values on entry into an OSR loop, so we
274       // need to make sure to considered everything to be assigned.
275       if (is_osr_loop) {
276         loop_stack_.top().loop_info->assignments().AddAll();
277       }
278 
279       // Save the index so that we can do another pass later.
280       if (do_liveness_analysis_) {
281         loop_end_index_queue_.push_back(iterator.current_index());
282       }
283     } else if (loop_stack_.size() > 1) {
284       LoopStackEntry& current_loop = loop_stack_.top();
285       LoopInfo* current_loop_info = current_loop.loop_info;
286 
287       // TODO(leszeks): Ideally, we'd only set values that were assigned in
288       // the loop *and* are live when the loop exits. However, this requires
289       // tracking the out-liveness of *all* loop exits, which is not
290       // information we currently have.
291       UpdateAssignments(bytecode, current_loop_info->assignments(), iterator);
292 
293       if (current_offset == current_loop.header_offset) {
294         loop_stack_.pop();
295         if (loop_stack_.size() > 1) {
296           // Propagate inner loop assignments to outer loop.
297           loop_stack_.top().loop_info->assignments().Union(
298               current_loop_info->assignments());
299         }
300       }
301     }
302 
303     if (do_liveness_analysis_) {
304       BytecodeLiveness& liveness = liveness_map_.InitializeLiveness(
305           current_offset, bytecode_array()->register_count(), zone());
306 
307       UpdateOutLiveness(bytecode, *liveness.out, next_bytecode_in_liveness,
308                         iterator, liveness_map_);
309       liveness.in->CopyFrom(*liveness.out);
310       UpdateInLiveness(bytecode, *liveness.in, iterator);
311 
312       next_bytecode_in_liveness = liveness.in;
313     }
314   }
315 
316   DCHECK_EQ(loop_stack_.size(), 1u);
317   DCHECK_EQ(loop_stack_.top().header_offset, -1);
318 
319   if (!do_liveness_analysis_) return;
320 
321   // At this point, every bytecode has a valid in and out liveness, except for
322   // propagating liveness across back edges (i.e. JumpLoop). Subsequent liveness
323   // analysis iterations can only add additional liveness bits that are pulled
324   // across these back edges.
325   //
326   // Furthermore, a loop header's in-liveness can only change based on any
327   // bytecodes *after* the loop end --  it cannot change as a result of the
328   // JumpLoop liveness being updated, as the only liveness bits than can be
329   // added to the loop body are those of the loop header.
330   //
331   // So, if we know that the liveness of bytecodes after a loop header won't
332   // change (e.g. because there are no loops in them, or we have already ensured
333   // those loops are valid), we can safely update the loop end and pass over the
334   // loop body, and then never have to pass over that loop end again, because we
335   // have shown that its target, the loop header, can't change from the entries
336   // after the loop, and can't change from any loop body pass.
337   //
338   // This means that in a pass, we can iterate backwards over the bytecode
339   // array, process any loops that we encounter, and on subsequent passes we can
340   // skip processing those loops (though we still have to process inner loops).
341   //
342   // Equivalently, we can queue up loop ends from back to front, and pass over
343   // the loops in that order, as this preserves both the bottom-to-top and
344   // outer-to-inner requirements.
345 
346   for (int loop_end_index : loop_end_index_queue_) {
347     iterator.GoToIndex(loop_end_index);
348 
349     DCHECK_EQ(iterator.current_bytecode(), Bytecode::kJumpLoop);
350 
351     int header_offset = iterator.GetJumpTargetOffset();
352     int end_offset = iterator.current_offset();
353 
354     BytecodeLiveness& header_liveness =
355         liveness_map_.GetLiveness(header_offset);
356     BytecodeLiveness& end_liveness = liveness_map_.GetLiveness(end_offset);
357 
358     if (!end_liveness.out->UnionIsChanged(*header_liveness.in)) {
359       // Only update the loop body if the loop end liveness changed.
360       continue;
361     }
362     end_liveness.in->CopyFrom(*end_liveness.out);
363     next_bytecode_in_liveness = end_liveness.in;
364 
365     // Advance into the loop body.
366     --iterator;
367     for (; iterator.current_offset() > header_offset; --iterator) {
368       Bytecode bytecode = iterator.current_bytecode();
369 
370       int current_offset = iterator.current_offset();
371       BytecodeLiveness& liveness = liveness_map_.GetLiveness(current_offset);
372 
373       UpdateOutLiveness(bytecode, *liveness.out, next_bytecode_in_liveness,
374                         iterator, liveness_map_);
375       liveness.in->CopyFrom(*liveness.out);
376       UpdateInLiveness(bytecode, *liveness.in, iterator);
377 
378       next_bytecode_in_liveness = liveness.in;
379     }
380     // Now we are at the loop header. Since the in-liveness of the header
381     // can't change, we need only to update the out-liveness.
382     UpdateOutLiveness(iterator.current_bytecode(), *header_liveness.out,
383                       next_bytecode_in_liveness, iterator, liveness_map_);
384   }
385 
386   DCHECK(LivenessIsValid());
387 }
388 
PushLoop(int loop_header,int loop_end)389 void BytecodeAnalysis::PushLoop(int loop_header, int loop_end) {
390   DCHECK(loop_header < loop_end);
391   DCHECK(loop_stack_.top().header_offset < loop_header);
392   DCHECK(end_to_header_.find(loop_end) == end_to_header_.end());
393   DCHECK(header_to_info_.find(loop_header) == header_to_info_.end());
394 
395   int parent_offset = loop_stack_.top().header_offset;
396 
397   end_to_header_.insert({loop_end, loop_header});
398   auto it = header_to_info_.insert(
399       {loop_header, LoopInfo(parent_offset, bytecode_array_->parameter_count(),
400                              bytecode_array_->register_count(), zone_)});
401   // Get the loop info pointer from the output of insert.
402   LoopInfo* loop_info = &it.first->second;
403 
404   loop_stack_.push({loop_header, loop_info});
405 }
406 
IsLoopHeader(int offset) const407 bool BytecodeAnalysis::IsLoopHeader(int offset) const {
408   return header_to_info_.find(offset) != header_to_info_.end();
409 }
410 
GetLoopOffsetFor(int offset) const411 int BytecodeAnalysis::GetLoopOffsetFor(int offset) const {
412   auto loop_end_to_header = end_to_header_.upper_bound(offset);
413   // If there is no next end => offset is not in a loop.
414   if (loop_end_to_header == end_to_header_.end()) {
415     return -1;
416   }
417   // If the header preceeds the offset, this is the loop
418   //
419   //   .> header  <--loop_end_to_header
420   //   |
421   //   |  <--offset
422   //   |
423   //   `- end
424   if (loop_end_to_header->second <= offset) {
425     return loop_end_to_header->second;
426   }
427   // Otherwise there is a (potentially nested) loop after this offset.
428   //
429   //    <--offset
430   //
431   //   .> header
432   //   |
433   //   | .> header  <--loop_end_to_header
434   //   | |
435   //   | `- end
436   //   |
437   //   `- end
438   // We just return the parent of the next loop (might be -1).
439   DCHECK(header_to_info_.upper_bound(offset) != header_to_info_.end());
440 
441   return header_to_info_.upper_bound(offset)->second.parent_offset();
442 }
443 
GetLoopInfoFor(int header_offset) const444 const LoopInfo& BytecodeAnalysis::GetLoopInfoFor(int header_offset) const {
445   DCHECK(IsLoopHeader(header_offset));
446 
447   return header_to_info_.find(header_offset)->second;
448 }
449 
GetInLivenessFor(int offset) const450 const BytecodeLivenessState* BytecodeAnalysis::GetInLivenessFor(
451     int offset) const {
452   if (!do_liveness_analysis_) return nullptr;
453 
454   return liveness_map_.GetInLiveness(offset);
455 }
456 
GetOutLivenessFor(int offset) const457 const BytecodeLivenessState* BytecodeAnalysis::GetOutLivenessFor(
458     int offset) const {
459   if (!do_liveness_analysis_) return nullptr;
460 
461   return liveness_map_.GetOutLiveness(offset);
462 }
463 
PrintLivenessTo(std::ostream & os) const464 std::ostream& BytecodeAnalysis::PrintLivenessTo(std::ostream& os) const {
465   interpreter::BytecodeArrayIterator iterator(bytecode_array());
466 
467   for (; !iterator.done(); iterator.Advance()) {
468     int current_offset = iterator.current_offset();
469 
470     const BitVector& in_liveness =
471         GetInLivenessFor(current_offset)->bit_vector();
472     const BitVector& out_liveness =
473         GetOutLivenessFor(current_offset)->bit_vector();
474 
475     for (int i = 0; i < in_liveness.length(); ++i) {
476       os << (in_liveness.Contains(i) ? "L" : ".");
477     }
478     os << " -> ";
479 
480     for (int i = 0; i < out_liveness.length(); ++i) {
481       os << (out_liveness.Contains(i) ? "L" : ".");
482     }
483 
484     os << " | " << current_offset << ": ";
485     iterator.PrintTo(os) << std::endl;
486   }
487 
488   return os;
489 }
490 
491 #if DEBUG
LivenessIsValid()492 bool BytecodeAnalysis::LivenessIsValid() {
493   BytecodeArrayRandomIterator iterator(bytecode_array(), zone());
494 
495   BytecodeLivenessState previous_liveness(bytecode_array()->register_count(),
496                                           zone());
497 
498   int invalid_offset = -1;
499   int which_invalid = -1;
500 
501   BytecodeLivenessState* next_bytecode_in_liveness = nullptr;
502 
503   // Ensure that there are no liveness changes if we iterate one more time.
504   for (iterator.GoToEnd(); iterator.IsValid(); --iterator) {
505     Bytecode bytecode = iterator.current_bytecode();
506 
507     int current_offset = iterator.current_offset();
508 
509     BytecodeLiveness& liveness = liveness_map_.GetLiveness(current_offset);
510 
511     previous_liveness.CopyFrom(*liveness.out);
512 
513     UpdateOutLiveness(bytecode, *liveness.out, next_bytecode_in_liveness,
514                       iterator, liveness_map_);
515     // UpdateOutLiveness skips kJumpLoop, so we update it manually.
516     if (bytecode == Bytecode::kJumpLoop) {
517       int target_offset = iterator.GetJumpTargetOffset();
518       liveness.out->Union(*liveness_map_.GetInLiveness(target_offset));
519     }
520 
521     if (!liveness.out->Equals(previous_liveness)) {
522       // Reset the invalid liveness.
523       liveness.out->CopyFrom(previous_liveness);
524       invalid_offset = current_offset;
525       which_invalid = 1;
526       break;
527     }
528 
529     previous_liveness.CopyFrom(*liveness.in);
530 
531     liveness.in->CopyFrom(*liveness.out);
532     UpdateInLiveness(bytecode, *liveness.in, iterator);
533 
534     if (!liveness.in->Equals(previous_liveness)) {
535       // Reset the invalid liveness.
536       liveness.in->CopyFrom(previous_liveness);
537       invalid_offset = current_offset;
538       which_invalid = 0;
539       break;
540     }
541 
542     next_bytecode_in_liveness = liveness.in;
543   }
544 
545   if (invalid_offset != -1) {
546     OFStream of(stderr);
547     of << "Invalid liveness:" << std::endl;
548 
549     // Dump the bytecode, annotated with the liveness and marking loops.
550 
551     int loop_indent = 0;
552 
553     BytecodeArrayIterator forward_iterator(bytecode_array());
554     for (; !forward_iterator.done(); forward_iterator.Advance()) {
555       int current_offset = forward_iterator.current_offset();
556       const BitVector& in_liveness =
557           GetInLivenessFor(current_offset)->bit_vector();
558       const BitVector& out_liveness =
559           GetOutLivenessFor(current_offset)->bit_vector();
560 
561       for (int i = 0; i < in_liveness.length(); ++i) {
562         of << (in_liveness.Contains(i) ? 'L' : '.');
563       }
564 
565       of << " | ";
566 
567       for (int i = 0; i < out_liveness.length(); ++i) {
568         of << (out_liveness.Contains(i) ? 'L' : '.');
569       }
570 
571       of << " : " << current_offset << " : ";
572 
573       // Draw loop back edges by indentin everything between loop headers and
574       // jump loop instructions.
575       if (forward_iterator.current_bytecode() == Bytecode::kJumpLoop) {
576         loop_indent--;
577       }
578       for (int i = 0; i < loop_indent; ++i) {
579         of << " | ";
580       }
581       if (forward_iterator.current_bytecode() == Bytecode::kJumpLoop) {
582         of << " `-" << current_offset;
583       } else if (IsLoopHeader(current_offset)) {
584         of << " .>" << current_offset;
585         loop_indent++;
586       }
587       forward_iterator.PrintTo(of) << std::endl;
588 
589       if (current_offset == invalid_offset) {
590         // Underline the invalid liveness.
591         if (which_invalid == 0) {
592           for (int i = 0; i < in_liveness.length(); ++i) {
593             of << '^';
594           }
595         } else {
596           for (int i = 0; i < in_liveness.length() + 3; ++i) {
597             of << ' ';
598           }
599           for (int i = 0; i < out_liveness.length(); ++i) {
600             of << '^';
601           }
602         }
603 
604         // Make sure to draw the loop indentation marks on this additional line.
605         of << " : " << current_offset << " : ";
606         for (int i = 0; i < loop_indent; ++i) {
607           of << " | ";
608         }
609 
610         of << std::endl;
611       }
612     }
613   }
614 
615   return invalid_offset == -1;
616 }
617 #endif
618 
619 }  // namespace compiler
620 }  // namespace internal
621 }  // namespace v8
622