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