• 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 <utility>
8 
9 #include "src/compiler/bytecode-liveness-map.h"
10 #include "src/interpreter/bytecode-array-iterator.h"
11 #include "src/interpreter/bytecode-array-random-iterator.h"
12 #include "src/objects/objects-inl.h"
13 #include "src/utils/ostreams.h"
14 
15 namespace v8 {
16 namespace internal {
17 namespace compiler {
18 
19 using interpreter::Bytecode;
20 using interpreter::BytecodeOperands;
21 using interpreter::Bytecodes;
22 using interpreter::ImplicitRegisterUse;
23 using interpreter::OperandType;
24 using interpreter::Register;
25 
BytecodeLoopAssignments(int parameter_count,int register_count,Zone * zone)26 BytecodeLoopAssignments::BytecodeLoopAssignments(int parameter_count,
27                                                  int register_count, Zone* zone)
28     : parameter_count_(parameter_count),
29       bit_vector_(
30           zone->New<BitVector>(parameter_count + register_count, zone)) {}
31 
Add(interpreter::Register r)32 void BytecodeLoopAssignments::Add(interpreter::Register r) {
33   if (r.is_parameter()) {
34     bit_vector_->Add(r.ToParameterIndex());
35   } else {
36     bit_vector_->Add(parameter_count_ + r.index());
37   }
38 }
39 
AddList(interpreter::Register r,uint32_t count)40 void BytecodeLoopAssignments::AddList(interpreter::Register r, uint32_t count) {
41   if (r.is_parameter()) {
42     for (uint32_t i = 0; i < count; i++) {
43       DCHECK(interpreter::Register(r.index() + i).is_parameter());
44       bit_vector_->Add(r.ToParameterIndex() + i);
45     }
46   } else {
47     for (uint32_t i = 0; i < count; i++) {
48       DCHECK(!interpreter::Register(r.index() + i).is_parameter());
49       bit_vector_->Add(parameter_count_ + r.index() + i);
50     }
51   }
52 }
53 
54 
Union(const BytecodeLoopAssignments & other)55 void BytecodeLoopAssignments::Union(const BytecodeLoopAssignments& other) {
56   bit_vector_->Union(*other.bit_vector_);
57 }
58 
ContainsParameter(int index) const59 bool BytecodeLoopAssignments::ContainsParameter(int index) const {
60   DCHECK_GE(index, 0);
61   DCHECK_LT(index, parameter_count());
62   return bit_vector_->Contains(index);
63 }
64 
ContainsLocal(int index) const65 bool BytecodeLoopAssignments::ContainsLocal(int index) const {
66   DCHECK_GE(index, 0);
67   DCHECK_LT(index, local_count());
68   return bit_vector_->Contains(parameter_count_ + index);
69 }
70 
ResumeJumpTarget(int suspend_id,int target_offset,int final_target_offset)71 ResumeJumpTarget::ResumeJumpTarget(int suspend_id, int target_offset,
72                                    int final_target_offset)
73     : suspend_id_(suspend_id),
74       target_offset_(target_offset),
75       final_target_offset_(final_target_offset) {}
76 
Leaf(int suspend_id,int target_offset)77 ResumeJumpTarget ResumeJumpTarget::Leaf(int suspend_id, int target_offset) {
78   return ResumeJumpTarget(suspend_id, target_offset, target_offset);
79 }
80 
AtLoopHeader(int loop_header_offset,const ResumeJumpTarget & next)81 ResumeJumpTarget ResumeJumpTarget::AtLoopHeader(int loop_header_offset,
82                                                 const ResumeJumpTarget& next) {
83   return ResumeJumpTarget(next.suspend_id(), loop_header_offset,
84                           next.target_offset());
85 }
86 
BytecodeAnalysis(Handle<BytecodeArray> bytecode_array,Zone * zone,BytecodeOffset osr_bailout_id,bool analyze_liveness)87 BytecodeAnalysis::BytecodeAnalysis(Handle<BytecodeArray> bytecode_array,
88                                    Zone* zone, BytecodeOffset osr_bailout_id,
89                                    bool analyze_liveness)
90     : bytecode_array_(bytecode_array),
91       zone_(zone),
92       osr_bailout_id_(osr_bailout_id),
93       analyze_liveness_(analyze_liveness),
94       loop_stack_(zone),
95       loop_end_index_queue_(zone),
96       resume_jump_targets_(zone),
97       end_to_header_(zone),
98       header_to_info_(zone),
99       osr_entry_point_(-1) {
100   Analyze();
101 }
102 
103 namespace {
104 
105 template <Bytecode bytecode, OperandType operand_type, size_t i>
UpdateInLivenessForOutOperand(BytecodeLivenessState * in_liveness,const interpreter::BytecodeArrayIterator & iterator)106 void UpdateInLivenessForOutOperand(
107     BytecodeLivenessState* in_liveness,
108     const interpreter::BytecodeArrayIterator& iterator) {
109   switch (operand_type) {
110     case OperandType::kRegOut: {
111       Register r = iterator.GetRegisterOperand(i);
112       if (!r.is_parameter()) {
113         in_liveness->MarkRegisterDead(r.index());
114       }
115       break;
116     }
117     case OperandType::kRegOutList: {
118       Register r = iterator.GetRegisterOperand(i);
119       uint32_t reg_count = iterator.GetRegisterCountOperand(i + 1);
120       if (!r.is_parameter()) {
121         for (uint32_t j = 0; j < reg_count; ++j) {
122           DCHECK(!Register(r.index() + j).is_parameter());
123           in_liveness->MarkRegisterDead(r.index() + j);
124         }
125       }
126       break;
127     }
128     case OperandType::kRegOutPair: {
129       Register r = iterator.GetRegisterOperand(i);
130       if (!r.is_parameter()) {
131         DCHECK(!Register(r.index() + 1).is_parameter());
132         in_liveness->MarkRegisterDead(r.index());
133         in_liveness->MarkRegisterDead(r.index() + 1);
134       }
135       break;
136     }
137     case OperandType::kRegOutTriple: {
138       Register r = iterator.GetRegisterOperand(i);
139       if (!r.is_parameter()) {
140         DCHECK(!Register(r.index() + 1).is_parameter());
141         DCHECK(!Register(r.index() + 2).is_parameter());
142         in_liveness->MarkRegisterDead(r.index());
143         in_liveness->MarkRegisterDead(r.index() + 1);
144         in_liveness->MarkRegisterDead(r.index() + 2);
145       }
146       break;
147     }
148     default:
149       DCHECK(!Bytecodes::IsRegisterOutputOperandType(operand_type));
150       break;
151   }
152 }
153 
154 template <Bytecode bytecode, OperandType operand_type, size_t i>
UpdateInLivenessForInOperand(BytecodeLivenessState * in_liveness,const interpreter::BytecodeArrayIterator & iterator)155 void UpdateInLivenessForInOperand(
156     BytecodeLivenessState* in_liveness,
157     const interpreter::BytecodeArrayIterator& iterator) {
158   switch (operand_type) {
159     case OperandType::kReg: {
160       Register r = iterator.GetRegisterOperand(i);
161       if (!r.is_parameter()) {
162         in_liveness->MarkRegisterLive(r.index());
163       }
164       break;
165     }
166     case OperandType::kRegPair: {
167       Register r = iterator.GetRegisterOperand(i);
168       if (!r.is_parameter()) {
169         DCHECK(!Register(r.index() + 1).is_parameter());
170         in_liveness->MarkRegisterLive(r.index());
171         in_liveness->MarkRegisterLive(r.index() + 1);
172       }
173       break;
174     }
175     case OperandType::kRegList: {
176       Register r = iterator.GetRegisterOperand(i);
177       uint32_t reg_count = iterator.GetRegisterCountOperand(i + 1);
178       if (!r.is_parameter()) {
179         for (uint32_t j = 0; j < reg_count; ++j) {
180           DCHECK(!interpreter::Register(r.index() + j).is_parameter());
181           in_liveness->MarkRegisterLive(r.index() + j);
182         }
183       }
184       break;
185     }
186     default:
187       DCHECK(!Bytecodes::IsRegisterInputOperandType(operand_type));
188       break;
189   }
190 }
191 
192 template <Bytecode bytecode, ImplicitRegisterUse implicit_register_use,
193           OperandType... operand_types, size_t... operand_index>
UpdateInLiveness(BytecodeLivenessState * in_liveness,const interpreter::BytecodeArrayIterator & iterator,std::index_sequence<operand_index...>)194 void UpdateInLiveness(BytecodeLivenessState* in_liveness,
195                       const interpreter::BytecodeArrayIterator& iterator,
196                       std::index_sequence<operand_index...>) {
197   // Special case Suspend and Resume to just pass through liveness.
198   if (bytecode == Bytecode::kSuspendGenerator) {
199     // The generator object has to be live.
200     in_liveness->MarkRegisterLive(iterator.GetRegisterOperand(0).index());
201     // Suspend additionally reads and returns the accumulator
202     DCHECK(Bytecodes::ReadsAccumulator(bytecode));
203     in_liveness->MarkAccumulatorLive();
204     return;
205   }
206   if (bytecode == Bytecode::kResumeGenerator) {
207     // The generator object has to be live.
208     in_liveness->MarkRegisterLive(iterator.GetRegisterOperand(0).index());
209     return;
210   }
211 
212   if (BytecodeOperands::WritesAccumulator(implicit_register_use)) {
213     in_liveness->MarkAccumulatorDead();
214   }
215   (UpdateInLivenessForOutOperand<bytecode, operand_types, operand_index>(
216        in_liveness, iterator),
217    ...);
218 
219   if (Bytecodes::WritesImplicitRegister(bytecode)) {
220     in_liveness->MarkRegisterDead(Register::FromShortStar(bytecode).index());
221   }
222 
223   if (BytecodeOperands::ReadsAccumulator(implicit_register_use)) {
224     in_liveness->MarkAccumulatorLive();
225   }
226   (UpdateInLivenessForInOperand<bytecode, operand_types, operand_index>(
227        in_liveness, iterator),
228    ...);
229 }
230 
231 template <Bytecode bytecode, ImplicitRegisterUse implicit_register_use,
232           OperandType... operand_types>
UpdateInLiveness(BytecodeLivenessState * in_liveness,const interpreter::BytecodeArrayIterator & iterator)233 void UpdateInLiveness(BytecodeLivenessState* in_liveness,
234                       const interpreter::BytecodeArrayIterator& iterator) {
235   UpdateInLiveness<bytecode, implicit_register_use, operand_types...>(
236       in_liveness, iterator,
237       std::make_index_sequence<sizeof...(operand_types)>());
238 }
239 
UpdateInLiveness(Bytecode bytecode,BytecodeLivenessState * in_liveness,const interpreter::BytecodeArrayIterator & iterator)240 void UpdateInLiveness(Bytecode bytecode, BytecodeLivenessState* in_liveness,
241                       const interpreter::BytecodeArrayIterator& iterator) {
242   switch (bytecode) {
243 #define BYTECODE_UPDATE_IN_LIVENESS(Name, ...)                           \
244   case Bytecode::k##Name:                                                \
245     return UpdateInLiveness<Bytecode::k##Name, __VA_ARGS__>(in_liveness, \
246                                                             iterator);
247     BYTECODE_LIST(BYTECODE_UPDATE_IN_LIVENESS)
248 #undef BYTECODE_UPDATE_IN_LIVENESS
249   }
250 }
251 
252 template <bool IsFirstUpdate = false>
EnsureOutLivenessIsNotAlias(BytecodeLiveness & liveness,BytecodeLivenessState * next_bytecode_in_liveness,Zone * zone)253 void EnsureOutLivenessIsNotAlias(
254     BytecodeLiveness& liveness,
255     BytecodeLivenessState* next_bytecode_in_liveness, Zone* zone) {
256   if (!IsFirstUpdate) {
257     // We should have copied the next bytecode's in liveness already in the
258     // first pass, so on subsequent passes this should already not be an alias.
259     DCHECK_NE(liveness.out, next_bytecode_in_liveness);
260     return;
261   }
262   if (liveness.out == next_bytecode_in_liveness) {
263     // If the out-liveness is aliasing the next bytecode's in-liveness,
264     // reallocate it and copy the data to the newly allocated state.
265     liveness.out =
266         zone->New<BytecodeLivenessState>(*next_bytecode_in_liveness, zone);
267   }
268 }
269 
270 template <bool IsFirstUpdate, Bytecode bytecode>
UpdateOutLiveness(BytecodeLiveness & liveness,BytecodeLivenessState * next_bytecode_in_liveness,const interpreter::BytecodeArrayIterator & iterator,Handle<BytecodeArray> bytecode_array,const BytecodeLivenessMap & liveness_map,Zone * zone)271 void UpdateOutLiveness(BytecodeLiveness& liveness,
272                        BytecodeLivenessState* next_bytecode_in_liveness,
273                        const interpreter::BytecodeArrayIterator& iterator,
274                        Handle<BytecodeArray> bytecode_array,
275                        const BytecodeLivenessMap& liveness_map, Zone* zone) {
276   // On subsequent updates, only update out-liveness manually if it isn't
277   // already aliasing the next bytecode's in-liveness.
278   if (!IsFirstUpdate && liveness.out == next_bytecode_in_liveness) return;
279 
280   // Special case Suspend and Resume to just pass through liveness.
281   if (bytecode == Bytecode::kSuspendGenerator ||
282       bytecode == Bytecode::kResumeGenerator) {
283     DCHECK_NOT_NULL(next_bytecode_in_liveness);
284     if (IsFirstUpdate) {
285       liveness.out = next_bytecode_in_liveness;
286     } else {
287       liveness.out->Union(*next_bytecode_in_liveness);
288     }
289     return;
290   }
291 
292   // Update from next bytecode (unless there isn't one or this is an
293   // unconditional jump).
294   if (next_bytecode_in_liveness != nullptr &&
295       !Bytecodes::IsUnconditionalJump(bytecode) &&
296       !Bytecodes::Returns(bytecode) &&
297       !Bytecodes::UnconditionallyThrows(bytecode)) {
298     if (IsFirstUpdate) {
299       // On first update, we can assume that this out-liveness is the same as
300       // the next liveness, and can directly alias it -- we'll allocate a new
301       // one using EnsureOutLivenessIsNotAlias if it needs to be mutated.
302       DCHECK_NULL(liveness.out);
303       liveness.out = next_bytecode_in_liveness;
304     } else {
305       liveness.out->Union(*next_bytecode_in_liveness);
306     }
307   } else if (IsFirstUpdate) {
308     // Otherwise, on the first allocation we need to make sure that there is an
309     // allocated out liveness.
310     DCHECK_NULL(liveness.out);
311     liveness.out = zone->New<BytecodeLivenessState>(
312         bytecode_array->register_count(), zone);
313   }
314 
315   DCHECK_NOT_NULL(liveness.out);
316 
317   // Update from jump target (if any). Skip loops, we update these manually in
318   // the liveness iterations.
319   if (Bytecodes::IsForwardJump(bytecode)) {
320     int target_offset = iterator.GetJumpTargetOffset();
321     EnsureOutLivenessIsNotAlias<IsFirstUpdate>(liveness,
322                                                next_bytecode_in_liveness, zone);
323     liveness.out->Union(*liveness_map.GetInLiveness(target_offset));
324   } else if (Bytecodes::IsSwitch(bytecode)) {
325     EnsureOutLivenessIsNotAlias<IsFirstUpdate>(liveness,
326                                                next_bytecode_in_liveness, zone);
327     for (interpreter::JumpTableTargetOffset entry :
328          iterator.GetJumpTableTargetOffsets()) {
329       liveness.out->Union(*liveness_map.GetInLiveness(entry.target_offset));
330     }
331   }
332 
333   // Update from exception handler (if any).
334   if (!interpreter::Bytecodes::IsWithoutExternalSideEffects(bytecode)) {
335     int handler_context;
336     // TODO(leszeks): We should look up this range only once per entry.
337     HandlerTable table(*bytecode_array);
338     int handler_offset =
339         table.LookupRange(iterator.current_offset(), &handler_context, nullptr);
340 
341     if (handler_offset != -1) {
342       EnsureOutLivenessIsNotAlias<IsFirstUpdate>(
343           liveness, next_bytecode_in_liveness, zone);
344       bool was_accumulator_live = liveness.out->AccumulatorIsLive();
345       liveness.out->Union(*liveness_map.GetInLiveness(handler_offset));
346       liveness.out->MarkRegisterLive(handler_context);
347       if (!was_accumulator_live) {
348         // The accumulator is reset to the exception on entry into a handler,
349         // and so shouldn't be considered live coming out of this bytecode just
350         // because it's live coming into the handler. So, kill the accumulator
351         // if the handler is the only thing that made it live.
352         liveness.out->MarkAccumulatorDead();
353 
354         // TODO(leszeks): Ideally the accumulator wouldn't be considered live at
355         // the start of the handler, but looking up if the current bytecode is
356         // the start of a handler is not free, so we should only do it if we
357         // decide it's necessary.
358       }
359     }
360   }
361 }
362 
363 template <bool IsFirstUpdate = false>
UpdateOutLiveness(Bytecode bytecode,BytecodeLiveness & liveness,BytecodeLivenessState * next_bytecode_in_liveness,const interpreter::BytecodeArrayIterator & iterator,Handle<BytecodeArray> bytecode_array,const BytecodeLivenessMap & liveness_map,Zone * zone)364 void UpdateOutLiveness(Bytecode bytecode, BytecodeLiveness& liveness,
365                        BytecodeLivenessState* next_bytecode_in_liveness,
366                        const interpreter::BytecodeArrayIterator& iterator,
367                        Handle<BytecodeArray> bytecode_array,
368                        const BytecodeLivenessMap& liveness_map, Zone* zone) {
369   switch (bytecode) {
370 #define BYTECODE_UPDATE_OUT_LIVENESS(Name, ...)                        \
371   case Bytecode::k##Name:                                              \
372     return UpdateOutLiveness<IsFirstUpdate, Bytecode::k##Name>(        \
373         liveness, next_bytecode_in_liveness, iterator, bytecode_array, \
374         liveness_map, zone);
375     BYTECODE_LIST(BYTECODE_UPDATE_OUT_LIVENESS)
376 #undef BYTECODE_UPDATE_OUT_LIVENESS
377   }
378 }
379 
380 template <bool IsFirstUpdate, Bytecode bytecode,
381           ImplicitRegisterUse implicit_register_use,
382           OperandType... operand_types>
UpdateLiveness(BytecodeLiveness & liveness,BytecodeLivenessState ** next_bytecode_in_liveness,const interpreter::BytecodeArrayIterator & iterator,Handle<BytecodeArray> bytecode_array,const BytecodeLivenessMap & liveness_map,Zone * zone)383 void UpdateLiveness(BytecodeLiveness& liveness,
384                     BytecodeLivenessState** next_bytecode_in_liveness,
385                     const interpreter::BytecodeArrayIterator& iterator,
386                     Handle<BytecodeArray> bytecode_array,
387                     const BytecodeLivenessMap& liveness_map, Zone* zone) {
388   UpdateOutLiveness<IsFirstUpdate, bytecode>(
389       liveness, *next_bytecode_in_liveness, iterator, bytecode_array,
390       liveness_map, zone);
391   if (IsFirstUpdate) {
392     // On the first update, allocate the in-liveness as a copy of the
393     // out-liveness.
394     DCHECK_NULL(liveness.in);
395     liveness.in = zone->New<BytecodeLivenessState>(*liveness.out, zone);
396   } else {
397     // On subsequent updates, copy liveness from the out vector.
398     // TODO(leszeks): If this copy doesn't change liveness, we could
399     // opportunistically terminate early.
400     liveness.in->CopyFrom(*liveness.out);
401   }
402   UpdateInLiveness<bytecode, implicit_register_use, operand_types...>(
403       liveness.in, iterator);
404 
405   *next_bytecode_in_liveness = liveness.in;
406 }
407 
408 template <bool IsFirstUpdate = false>
UpdateLiveness(Bytecode bytecode,BytecodeLiveness & liveness,BytecodeLivenessState ** next_bytecode_in_liveness,const interpreter::BytecodeArrayIterator & iterator,Handle<BytecodeArray> bytecode_array,const BytecodeLivenessMap & liveness_map,Zone * zone)409 void UpdateLiveness(Bytecode bytecode, BytecodeLiveness& liveness,
410                     BytecodeLivenessState** next_bytecode_in_liveness,
411                     const interpreter::BytecodeArrayIterator& iterator,
412                     Handle<BytecodeArray> bytecode_array,
413                     const BytecodeLivenessMap& liveness_map, Zone* zone) {
414   switch (bytecode) {
415 #define BYTECODE_UPDATE_LIVENESS(Name, ...)                               \
416   case Bytecode::k##Name:                                                 \
417     return UpdateLiveness<IsFirstUpdate, Bytecode::k##Name, __VA_ARGS__>( \
418         liveness, next_bytecode_in_liveness, iterator, bytecode_array,    \
419         liveness_map, zone);
420     BYTECODE_LIST(BYTECODE_UPDATE_LIVENESS)
421 #undef BYTECODE_UPDATE_LIVENESS
422   }
423 }
424 
UpdateAssignments(Bytecode bytecode,BytecodeLoopAssignments * assignments,const interpreter::BytecodeArrayIterator & iterator)425 void UpdateAssignments(Bytecode bytecode, BytecodeLoopAssignments* assignments,
426                        const interpreter::BytecodeArrayIterator& iterator) {
427   int num_operands = Bytecodes::NumberOfOperands(bytecode);
428   const OperandType* operand_types = Bytecodes::GetOperandTypes(bytecode);
429 
430   for (int i = 0; i < num_operands; ++i) {
431     switch (operand_types[i]) {
432       case OperandType::kRegOut: {
433         assignments->Add(iterator.GetRegisterOperand(i));
434         break;
435       }
436       case OperandType::kRegOutList: {
437         interpreter::Register r = iterator.GetRegisterOperand(i++);
438         uint32_t reg_count = iterator.GetRegisterCountOperand(i);
439         assignments->AddList(r, reg_count);
440         break;
441       }
442       case OperandType::kRegOutPair: {
443         assignments->AddList(iterator.GetRegisterOperand(i), 2);
444         break;
445       }
446       case OperandType::kRegOutTriple: {
447         assignments->AddList(iterator.GetRegisterOperand(i), 3);
448         break;
449       }
450       default:
451         DCHECK(!Bytecodes::IsRegisterOutputOperandType(operand_types[i]));
452         break;
453     }
454   }
455 
456   if (Bytecodes::WritesImplicitRegister(bytecode)) {
457     assignments->Add(interpreter::Register::FromShortStar(bytecode));
458   }
459 }
460 
461 }  // namespace
462 
Analyze()463 void BytecodeAnalysis::Analyze() {
464   loop_stack_.push({-1, nullptr});
465 
466   BytecodeLivenessState* next_bytecode_in_liveness = nullptr;
467   int generator_switch_index = -1;
468   int osr_loop_end_offset = osr_bailout_id_.ToInt();
469   DCHECK_EQ(osr_loop_end_offset < 0, osr_bailout_id_.IsNone());
470 
471   interpreter::BytecodeArrayRandomIterator iterator(bytecode_array(), zone());
472 
473   bytecode_count_ = iterator.size();
474   if (analyze_liveness_) {
475     liveness_map_.emplace(bytecode_array()->length(), zone());
476   }
477 
478   for (iterator.GoToEnd(); iterator.IsValid(); --iterator) {
479     Bytecode bytecode = iterator.current_bytecode();
480     int current_offset = iterator.current_offset();
481 
482     if (bytecode == Bytecode::kSwitchOnGeneratorState) {
483       DCHECK_EQ(generator_switch_index, -1);
484       generator_switch_index = iterator.current_index();
485     } else if (bytecode == Bytecode::kJumpLoop) {
486       // Every byte up to and including the last byte within the backwards jump
487       // instruction is considered part of the loop, set loop end accordingly.
488       int loop_end = current_offset + iterator.current_bytecode_size();
489       int loop_header = iterator.GetJumpTargetOffset();
490       PushLoop(loop_header, loop_end);
491 
492       if (current_offset == osr_loop_end_offset) {
493         osr_entry_point_ = loop_header;
494       } else if (current_offset < osr_loop_end_offset) {
495         // Assert that we've found the osr_entry_point if we've gone past the
496         // osr_loop_end_offset. Note, we are iterating the bytecode in reverse,
497         // so the less-than in the above condition is correct.
498         DCHECK_LE(0, osr_entry_point_);
499       }
500 
501       // Save the index so that we can do another pass later.
502       if (analyze_liveness_) {
503         loop_end_index_queue_.push_back(iterator.current_index());
504       }
505     }
506 
507     // We have to pop from loop_stack_ if:
508     // 1) We entered the body of the loop
509     // 2) If we have a JumpLoop that jumps to itself (i.e an empty loop)
510     bool pop_current_loop = loop_stack_.size() > 1 &&
511                             (bytecode != Bytecode::kJumpLoop ||
512                              iterator.GetJumpTargetOffset() == current_offset);
513 
514     if (pop_current_loop) {
515       LoopStackEntry& current_loop = loop_stack_.top();
516       LoopInfo* current_loop_info = current_loop.loop_info;
517 
518       // TODO(leszeks): Ideally, we'd only set values that were assigned in
519       // the loop *and* are live when the loop exits. However, this requires
520       // tracking the out-liveness of *all* loop exits, which is not
521       // information we currently have.
522       UpdateAssignments(bytecode, &current_loop_info->assignments(), iterator);
523 
524       // Update suspend counts for this loop.
525       if (bytecode == Bytecode::kSuspendGenerator) {
526         int suspend_id = iterator.GetUnsignedImmediateOperand(3);
527         int resume_offset = current_offset + iterator.current_bytecode_size();
528         current_loop_info->AddResumeTarget(
529             ResumeJumpTarget::Leaf(suspend_id, resume_offset));
530       }
531 
532       // If we've reached the header of the loop, pop it off the stack.
533       if (current_offset == current_loop.header_offset) {
534         loop_stack_.pop();
535         if (loop_stack_.size() > 1) {
536           // If there is still an outer loop, propagate inner loop assignments.
537           LoopInfo* parent_loop_info = loop_stack_.top().loop_info;
538 
539           parent_loop_info->assignments().Union(
540               current_loop_info->assignments());
541 
542           // Also, propagate resume targets. Instead of jumping to the target
543           // itself, the outer loop will jump to this loop header for any
544           // targets that are inside the current loop, so that this loop stays
545           // reducible. Hence, a nested loop of the form:
546           //
547           //                switch (#1 -> suspend1, #2 -> suspend2)
548           //                loop {
549           //     suspend1:    suspend #1
550           //                  loop {
551           //     suspend2:      suspend #2
552           //                  }
553           //                }
554           //
555           // becomes:
556           //
557           //                switch (#1 -> loop1, #2 -> loop1)
558           //     loop1:     loop {
559           //                  switch (#1 -> suspend1, #2 -> loop2)
560           //     suspend1:    suspend #1
561           //     loop2:       loop {
562           //                    switch (#2 -> suspend2)
563           //     suspend2:      suspend #2
564           //                  }
565           //                }
566           for (const auto& target : current_loop_info->resume_jump_targets()) {
567             parent_loop_info->AddResumeTarget(
568                 ResumeJumpTarget::AtLoopHeader(current_offset, target));
569           }
570 
571         } else {
572           // Otherwise, just propagate inner loop suspends to top-level.
573           for (const auto& target : current_loop_info->resume_jump_targets()) {
574             resume_jump_targets_.push_back(
575                 ResumeJumpTarget::AtLoopHeader(current_offset, target));
576           }
577         }
578       }
579     } else if (bytecode == Bytecode::kSuspendGenerator) {
580       // If we're not in a loop, we still need to look for suspends.
581       // TODO(leszeks): It would be nice to de-duplicate this with the in-loop
582       // case
583       int suspend_id = iterator.GetUnsignedImmediateOperand(3);
584       int resume_offset = current_offset + iterator.current_bytecode_size();
585       resume_jump_targets_.push_back(
586           ResumeJumpTarget::Leaf(suspend_id, resume_offset));
587     }
588 
589     if (analyze_liveness_) {
590       BytecodeLiveness& liveness =
591           liveness_map().InsertNewLiveness(current_offset);
592       UpdateLiveness<true>(bytecode, liveness, &next_bytecode_in_liveness,
593                            iterator, bytecode_array(), liveness_map(), zone());
594     }
595   }
596 
597   DCHECK_EQ(loop_stack_.size(), 1u);
598   DCHECK_EQ(loop_stack_.top().header_offset, -1);
599 
600   DCHECK(ResumeJumpTargetsAreValid());
601 
602   if (!analyze_liveness_) return;
603 
604   // At this point, every bytecode has a valid in and out liveness, except for
605   // propagating liveness across back edges (i.e. JumpLoop). Subsequent liveness
606   // analysis iterations can only add additional liveness bits that are pulled
607   // across these back edges.
608   //
609   // Furthermore, a loop header's in-liveness can only change based on any
610   // bytecodes *after* the loop end --  it cannot change as a result of the
611   // JumpLoop liveness being updated, as the only liveness bits than can be
612   // added to the loop body are those of the loop header.
613   //
614   // So, if we know that the liveness of bytecodes after a loop header won't
615   // change (e.g. because there are no loops in them, or we have already ensured
616   // those loops are valid), we can safely update the loop end and pass over the
617   // loop body, and then never have to pass over that loop end again, because we
618   // have shown that its target, the loop header, can't change from the entries
619   // after the loop, and can't change from any loop body pass.
620   //
621   // This means that in a pass, we can iterate backwards over the bytecode
622   // array, process any loops that we encounter, and on subsequent passes we can
623   // skip processing those loops (though we still have to process inner loops).
624   //
625   // Equivalently, we can queue up loop ends from back to front, and pass over
626   // the loops in that order, as this preserves both the bottom-to-top and
627   // outer-to-inner requirements.
628 
629   for (int loop_end_index : loop_end_index_queue_) {
630     iterator.GoToIndex(loop_end_index);
631 
632     DCHECK_EQ(iterator.current_bytecode(), Bytecode::kJumpLoop);
633 
634     int header_offset = iterator.GetJumpTargetOffset();
635     int end_offset = iterator.current_offset();
636 
637     BytecodeLiveness& header_liveness =
638         liveness_map().GetLiveness(header_offset);
639     BytecodeLiveness& end_liveness = liveness_map().GetLiveness(end_offset);
640 
641     if (!end_liveness.out->UnionIsChanged(*header_liveness.in)) {
642       // Only update the loop body if the loop end liveness changed.
643       continue;
644     }
645     end_liveness.in->CopyFrom(*end_liveness.out);
646     next_bytecode_in_liveness = end_liveness.in;
647 
648     // Advance into the loop body.
649     --iterator;
650     for (; iterator.current_offset() > header_offset; --iterator) {
651       Bytecode bytecode = iterator.current_bytecode();
652       int current_offset = iterator.current_offset();
653       BytecodeLiveness& liveness = liveness_map().GetLiveness(current_offset);
654       UpdateLiveness(bytecode, liveness, &next_bytecode_in_liveness, iterator,
655                      bytecode_array(), liveness_map(), zone());
656     }
657     // Now we are at the loop header. Since the in-liveness of the header
658     // can't change, we need only to update the out-liveness.
659     UpdateOutLiveness(iterator.current_bytecode(), header_liveness,
660                       next_bytecode_in_liveness, iterator, bytecode_array(),
661                       liveness_map(), zone());
662   }
663 
664   // Process the generator switch statement separately, once the loops are done.
665   // This has to be a separate pass because the generator switch can jump into
666   // the middle of loops (and is the only kind of jump that can jump across a
667   // loop header).
668   if (generator_switch_index != -1) {
669     iterator.GoToIndex(generator_switch_index);
670     DCHECK_EQ(iterator.current_bytecode(), Bytecode::kSwitchOnGeneratorState);
671 
672     int current_offset = iterator.current_offset();
673     BytecodeLiveness& switch_liveness =
674         liveness_map().GetLiveness(current_offset);
675 
676     bool any_changed = false;
677     for (interpreter::JumpTableTargetOffset entry :
678          iterator.GetJumpTableTargetOffsets()) {
679       if (switch_liveness.out->UnionIsChanged(
680               *liveness_map().GetInLiveness(entry.target_offset))) {
681         any_changed = true;
682       }
683     }
684 
685     // If the switch liveness changed, we have to propagate it up the remaining
686     // bytecodes before it.
687     if (any_changed) {
688       switch_liveness.in->CopyFrom(*switch_liveness.out);
689       UpdateInLiveness(Bytecode::kSwitchOnGeneratorState, switch_liveness.in,
690                        iterator);
691       next_bytecode_in_liveness = switch_liveness.in;
692       for (--iterator; iterator.IsValid(); --iterator) {
693         Bytecode bytecode = iterator.current_bytecode();
694         BytecodeLiveness& liveness =
695             liveness_map().GetLiveness(iterator.current_offset());
696 
697         // There shouldn't be any more loops.
698         DCHECK_NE(bytecode, Bytecode::kJumpLoop);
699 
700         UpdateLiveness(bytecode, liveness, &next_bytecode_in_liveness, iterator,
701                        bytecode_array(), liveness_map(), zone());
702       }
703     }
704   }
705 
706   DCHECK(analyze_liveness_);
707   if (FLAG_trace_environment_liveness) {
708     StdoutStream of;
709     PrintLivenessTo(of);
710   }
711 
712   DCHECK(LivenessIsValid());
713 }
714 
PushLoop(int loop_header,int loop_end)715 void BytecodeAnalysis::PushLoop(int loop_header, int loop_end) {
716   DCHECK_LT(loop_header, loop_end);
717   DCHECK_LT(loop_stack_.top().header_offset, loop_header);
718   DCHECK_EQ(end_to_header_.find(loop_end), end_to_header_.end());
719   DCHECK_EQ(header_to_info_.find(loop_header), header_to_info_.end());
720 
721   int parent_offset = loop_stack_.top().header_offset;
722 
723   end_to_header_.insert({loop_end, loop_header});
724   auto it = header_to_info_.insert(
725       {loop_header, LoopInfo(parent_offset, bytecode_array_->parameter_count(),
726                              bytecode_array_->register_count(), zone_)});
727   // Get the loop info pointer from the output of insert.
728   LoopInfo* loop_info = &it.first->second;
729 
730   loop_stack_.push({loop_header, loop_info});
731 }
732 
IsLoopHeader(int offset) const733 bool BytecodeAnalysis::IsLoopHeader(int offset) const {
734   return header_to_info_.find(offset) != header_to_info_.end();
735 }
736 
GetLoopOffsetFor(int offset) const737 int BytecodeAnalysis::GetLoopOffsetFor(int offset) const {
738   auto loop_end_to_header = end_to_header_.upper_bound(offset);
739   // If there is no next end => offset is not in a loop.
740   if (loop_end_to_header == end_to_header_.end()) {
741     return -1;
742   }
743   // If the header precedes the offset, this is the loop
744   //
745   //   .> header  <--loop_end_to_header
746   //   |
747   //   |  <--offset
748   //   |
749   //   `- end
750   if (loop_end_to_header->second <= offset) {
751     return loop_end_to_header->second;
752   }
753   // Otherwise there is a (potentially nested) loop after this offset.
754   //
755   //    <--offset
756   //
757   //   .> header
758   //   |
759   //   | .> header  <--loop_end_to_header
760   //   | |
761   //   | `- end
762   //   |
763   //   `- end
764   // We just return the parent of the next loop (might be -1).
765   DCHECK(header_to_info_.upper_bound(offset) != header_to_info_.end());
766 
767   return header_to_info_.upper_bound(offset)->second.parent_offset();
768 }
769 
GetLoopInfoFor(int header_offset) const770 const LoopInfo& BytecodeAnalysis::GetLoopInfoFor(int header_offset) const {
771   DCHECK(IsLoopHeader(header_offset));
772 
773   return header_to_info_.find(header_offset)->second;
774 }
775 
TryGetLoopInfoFor(int header_offset) const776 const LoopInfo* BytecodeAnalysis::TryGetLoopInfoFor(int header_offset) const {
777   auto it = header_to_info_.find(header_offset);
778   if (it == header_to_info_.end()) return nullptr;
779   return &it->second;
780 }
781 
GetInLivenessFor(int offset) const782 const BytecodeLivenessState* BytecodeAnalysis::GetInLivenessFor(
783     int offset) const {
784   if (!analyze_liveness_) return nullptr;
785 
786   return liveness_map().GetInLiveness(offset);
787 }
788 
GetOutLivenessFor(int offset) const789 const BytecodeLivenessState* BytecodeAnalysis::GetOutLivenessFor(
790     int offset) const {
791   if (!analyze_liveness_) return nullptr;
792 
793   return liveness_map().GetOutLiveness(offset);
794 }
795 
PrintLivenessTo(std::ostream & os) const796 std::ostream& BytecodeAnalysis::PrintLivenessTo(std::ostream& os) const {
797   interpreter::BytecodeArrayIterator iterator(bytecode_array());
798 
799   for (; !iterator.done(); iterator.Advance()) {
800     int current_offset = iterator.current_offset();
801 
802     const BytecodeLivenessState* in_liveness = GetInLivenessFor(current_offset);
803     const BytecodeLivenessState* out_liveness =
804         GetOutLivenessFor(current_offset);
805 
806     os << ToString(*in_liveness) << " -> " << ToString(*out_liveness) << " | "
807        << current_offset << ": ";
808     iterator.PrintTo(os) << std::endl;
809   }
810 
811   return os;
812 }
813 
814 #if DEBUG
ResumeJumpTargetsAreValid()815 bool BytecodeAnalysis::ResumeJumpTargetsAreValid() {
816   bool valid = true;
817 
818   // Find the generator switch.
819   interpreter::BytecodeArrayRandomIterator iterator(bytecode_array(), zone());
820   for (iterator.GoToStart(); iterator.IsValid(); ++iterator) {
821     if (iterator.current_bytecode() == Bytecode::kSwitchOnGeneratorState) {
822       break;
823     }
824   }
825 
826   // If the iterator is invalid, we've reached the end without finding the
827   // generator switch. So, ensure there are no jump targets and exit.
828   if (!iterator.IsValid()) {
829     // Check top-level.
830     if (!resume_jump_targets().empty()) {
831       PrintF(stderr,
832              "Found %zu top-level resume targets but no resume switch\n",
833              resume_jump_targets().size());
834       valid = false;
835     }
836     // Check loops.
837     for (const std::pair<const int, LoopInfo>& loop_info : header_to_info_) {
838       if (!loop_info.second.resume_jump_targets().empty()) {
839         PrintF(stderr,
840                "Found %zu resume targets at loop at offset %d, but no resume "
841                "switch\n",
842                loop_info.second.resume_jump_targets().size(), loop_info.first);
843         valid = false;
844       }
845     }
846 
847     return valid;
848   }
849 
850   // Otherwise, we've found the resume switch. Check that the top level jumps
851   // only to leaves and loop headers, then check that each loop header handles
852   // all the unresolved jumps, also jumping only to leaves and inner loop
853   // headers.
854 
855   // First collect all required suspend ids.
856   std::map<int, int> unresolved_suspend_ids;
857   for (interpreter::JumpTableTargetOffset offset :
858        iterator.GetJumpTableTargetOffsets()) {
859     int suspend_id = offset.case_value;
860     int resume_offset = offset.target_offset;
861 
862     unresolved_suspend_ids[suspend_id] = resume_offset;
863   }
864 
865   // Check top-level.
866   if (!ResumeJumpTargetLeavesResolveSuspendIds(-1, resume_jump_targets(),
867                                                &unresolved_suspend_ids)) {
868     valid = false;
869   }
870   // Check loops.
871   for (const std::pair<const int, LoopInfo>& loop_info : header_to_info_) {
872     if (!ResumeJumpTargetLeavesResolveSuspendIds(
873             loop_info.first, loop_info.second.resume_jump_targets(),
874             &unresolved_suspend_ids)) {
875       valid = false;
876     }
877   }
878 
879   // Check that everything is resolved.
880   if (!unresolved_suspend_ids.empty()) {
881     PrintF(stderr,
882            "Found suspend ids that are not resolved by a final leaf resume "
883            "jump:\n");
884 
885     for (const std::pair<const int, int>& target : unresolved_suspend_ids) {
886       PrintF(stderr, "  %d -> %d\n", target.first, target.second);
887     }
888     valid = false;
889   }
890 
891   return valid;
892 }
893 
ResumeJumpTargetLeavesResolveSuspendIds(int parent_offset,const ZoneVector<ResumeJumpTarget> & resume_jump_targets,std::map<int,int> * unresolved_suspend_ids)894 bool BytecodeAnalysis::ResumeJumpTargetLeavesResolveSuspendIds(
895     int parent_offset, const ZoneVector<ResumeJumpTarget>& resume_jump_targets,
896     std::map<int, int>* unresolved_suspend_ids) {
897   bool valid = true;
898   for (const ResumeJumpTarget& target : resume_jump_targets) {
899     std::map<int, int>::iterator it =
900         unresolved_suspend_ids->find(target.suspend_id());
901     if (it == unresolved_suspend_ids->end()) {
902       PrintF(
903           stderr,
904           "No unresolved suspend found for resume target with suspend id %d\n",
905           target.suspend_id());
906       valid = false;
907       continue;
908     }
909     int expected_target = it->second;
910 
911     if (target.is_leaf()) {
912       // Leaves should have the expected target as their target.
913       if (target.target_offset() != expected_target) {
914         PrintF(
915             stderr,
916             "Expected leaf resume target for id %d to have target offset %d, "
917             "but had %d\n",
918             target.suspend_id(), expected_target, target.target_offset());
919         valid = false;
920       } else {
921         // Make sure we're resuming to a Resume bytecode
922         interpreter::BytecodeArrayIterator iterator(bytecode_array(),
923                                                     target.target_offset());
924         if (iterator.current_bytecode() != Bytecode::kResumeGenerator) {
925           PrintF(stderr,
926                  "Expected resume target for id %d, offset %d, to be "
927                  "ResumeGenerator, but found %s\n",
928                  target.suspend_id(), target.target_offset(),
929                  Bytecodes::ToString(iterator.current_bytecode()));
930 
931           valid = false;
932         }
933       }
934       // We've resolved this suspend id, so erase it to make sure we don't
935       // resolve it twice.
936       unresolved_suspend_ids->erase(it);
937     } else {
938       // Non-leaves should have a direct inner loop header as their target.
939       if (!IsLoopHeader(target.target_offset())) {
940         PrintF(stderr,
941                "Expected non-leaf resume target for id %d to have a loop "
942                "header at target offset %d\n",
943                target.suspend_id(), target.target_offset());
944         valid = false;
945       } else {
946         LoopInfo loop_info = GetLoopInfoFor(target.target_offset());
947         if (loop_info.parent_offset() != parent_offset) {
948           PrintF(stderr,
949                  "Expected non-leaf resume target for id %d to have a direct "
950                  "inner loop at target offset %d\n",
951                  target.suspend_id(), target.target_offset());
952           valid = false;
953         }
954         // If the target loop is a valid inner loop, we'll check its validity
955         // when we analyze its resume targets.
956       }
957     }
958   }
959   return valid;
960 }
961 
LivenessIsValid()962 bool BytecodeAnalysis::LivenessIsValid() {
963   interpreter::BytecodeArrayRandomIterator iterator(bytecode_array(), zone());
964 
965   BytecodeLivenessState previous_liveness(bytecode_array()->register_count(),
966                                           zone());
967 
968   int invalid_offset = -1;
969   int which_invalid = -1;
970   BytecodeLivenessState invalid_liveness(bytecode_array()->register_count(),
971                                          zone());
972 
973   BytecodeLivenessState* next_bytecode_in_liveness = nullptr;
974 
975   // Ensure that there are no liveness changes if we iterate one more time.
976   for (iterator.GoToEnd(); iterator.IsValid(); --iterator) {
977     Bytecode bytecode = iterator.current_bytecode();
978 
979     int current_offset = iterator.current_offset();
980 
981     BytecodeLiveness& liveness = liveness_map().GetLiveness(current_offset);
982 
983     previous_liveness.CopyFrom(*liveness.out);
984 
985     UpdateOutLiveness(bytecode, liveness, next_bytecode_in_liveness, iterator,
986                       bytecode_array(), liveness_map(), zone());
987     // UpdateOutLiveness skips kJumpLoop, so we update it manually.
988     if (bytecode == Bytecode::kJumpLoop) {
989       int target_offset = iterator.GetJumpTargetOffset();
990       liveness.out->Union(*liveness_map().GetInLiveness(target_offset));
991     }
992 
993     if (!liveness.out->Equals(previous_liveness)) {
994       invalid_liveness.CopyFrom(*liveness.out);
995       // Reset the invalid liveness.
996       liveness.out->CopyFrom(previous_liveness);
997       invalid_offset = current_offset;
998       which_invalid = 1;
999       break;
1000     }
1001 
1002     previous_liveness.CopyFrom(*liveness.in);
1003 
1004     liveness.in->CopyFrom(*liveness.out);
1005     UpdateInLiveness(bytecode, liveness.in, iterator);
1006 
1007     if (!liveness.in->Equals(previous_liveness)) {
1008       invalid_liveness.CopyFrom(*liveness.in);
1009       // Reset the invalid liveness.
1010       liveness.in->CopyFrom(previous_liveness);
1011       invalid_offset = current_offset;
1012       which_invalid = 0;
1013       break;
1014     }
1015 
1016     next_bytecode_in_liveness = liveness.in;
1017   }
1018 
1019   // Ensure that the accumulator is not live when jumping out of a loop, or on
1020   // the back-edge of a loop.
1021   for (iterator.GoToStart(); iterator.IsValid() && invalid_offset == -1;
1022        ++iterator) {
1023     Bytecode bytecode = iterator.current_bytecode();
1024     int current_offset = iterator.current_offset();
1025     int loop_header = GetLoopOffsetFor(current_offset);
1026 
1027     // We only care if we're inside a loop.
1028     if (loop_header == -1) continue;
1029 
1030     // We only care about jumps.
1031     if (!Bytecodes::IsJump(bytecode)) continue;
1032 
1033     int jump_target = iterator.GetJumpTargetOffset();
1034 
1035     // If this is a forward jump to somewhere else in the same loop, ignore it.
1036     if (Bytecodes::IsForwardJump(bytecode) &&
1037         GetLoopOffsetFor(jump_target) == loop_header) {
1038       continue;
1039     }
1040 
1041     // The accumulator must be dead at the start of the target of the jump.
1042     if (liveness_map().GetLiveness(jump_target).in->AccumulatorIsLive()) {
1043       invalid_offset = jump_target;
1044       which_invalid = 0;
1045       break;
1046     }
1047   }
1048 
1049   if (invalid_offset != -1) {
1050     OFStream of(stderr);
1051     of << "Invalid liveness:" << std::endl;
1052 
1053     // Dump the bytecode, annotated with the liveness and marking loops.
1054 
1055     int loop_indent = 0;
1056 
1057     interpreter::BytecodeArrayIterator forward_iterator(bytecode_array());
1058     for (; !forward_iterator.done(); forward_iterator.Advance()) {
1059       int current_offset = forward_iterator.current_offset();
1060       const BytecodeLivenessState* in_liveness =
1061           GetInLivenessFor(current_offset);
1062       const BytecodeLivenessState* out_liveness =
1063           GetOutLivenessFor(current_offset);
1064 
1065       std::string in_liveness_str = ToString(*in_liveness);
1066       std::string out_liveness_str = ToString(*out_liveness);
1067 
1068       of << in_liveness_str << " | " << out_liveness_str << " : "
1069          << current_offset << " : ";
1070 
1071       // Draw loop back edges by indentin everything between loop headers and
1072       // jump loop instructions.
1073       if (forward_iterator.current_bytecode() == Bytecode::kJumpLoop) {
1074         loop_indent--;
1075       }
1076       for (int i = 0; i < loop_indent; ++i) {
1077         of << "| ";
1078       }
1079       if (forward_iterator.current_bytecode() == Bytecode::kJumpLoop) {
1080         of << "`-";
1081       } else if (IsLoopHeader(current_offset)) {
1082         of << ".>";
1083         loop_indent++;
1084       }
1085       forward_iterator.PrintTo(of);
1086       if (Bytecodes::IsJump(forward_iterator.current_bytecode())) {
1087         of << " (@" << forward_iterator.GetJumpTargetOffset() << ")";
1088       }
1089       of << std::endl;
1090 
1091       if (current_offset == invalid_offset) {
1092         // Underline the invalid liveness.
1093         char in_underline = which_invalid == 0 ? '^' : ' ';
1094         char out_underline = which_invalid == 0 ? ' ' : '^';
1095         of << std::string(in_liveness_str.size(), in_underline) << "   "
1096            << std::string(out_liveness_str.size(), out_underline);
1097 
1098         // Make sure to draw the loop indentation marks on this additional line.
1099         of << " : " << current_offset << " : ";
1100         for (int i = 0; i < loop_indent; ++i) {
1101           of << "| ";
1102         }
1103 
1104         of << std::endl;
1105 
1106         // Print the invalid liveness.
1107         if (which_invalid == 0) {
1108           of << ToString(invalid_liveness) << "   "
1109              << std::string(out_liveness_str.size(), ' ');
1110         } else {
1111           of << std::string(in_liveness_str.size(), ' ') << "   "
1112              << ToString(invalid_liveness);
1113         }
1114 
1115         // Make sure to draw the loop indentation marks on this additional line.
1116         of << " : " << current_offset << " : ";
1117         for (int i = 0; i < loop_indent; ++i) {
1118           of << "| ";
1119         }
1120 
1121         of << std::endl;
1122       }
1123     }
1124   }
1125 
1126   return invalid_offset == -1;
1127 }
1128 #endif
1129 
1130 }  // namespace compiler
1131 }  // namespace internal
1132 }  // namespace v8
1133