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