Home | History | Annotate | Download | only in compiler
      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 
     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 
     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 
     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 
     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 
     59 void BytecodeLoopAssignments::AddAll() { bit_vector_->AddAll(); }
     60 
     61 void BytecodeLoopAssignments::Union(const BytecodeLoopAssignments& other) {
     62   bit_vector_->Union(*other.bit_vector_);
     63 }
     64 
     65 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 
     71 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 
     77 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 
     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 
     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 
    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 
    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 
    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 
    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 
    407 bool BytecodeAnalysis::IsLoopHeader(int offset) const {
    408   return header_to_info_.find(offset) != header_to_info_.end();
    409 }
    410 
    411 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 
    444 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 
    450 const BytecodeLivenessState* BytecodeAnalysis::GetInLivenessFor(
    451     int offset) const {
    452   if (!do_liveness_analysis_) return nullptr;
    453 
    454   return liveness_map_.GetInLiveness(offset);
    455 }
    456 
    457 const BytecodeLivenessState* BytecodeAnalysis::GetOutLivenessFor(
    458     int offset) const {
    459   if (!do_liveness_analysis_) return nullptr;
    460 
    461   return liveness_map_.GetOutLiveness(offset);
    462 }
    463 
    464 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
    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