Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2)

Side by Side Diff: src/compiler/bytecode-analysis.cc

Issue 2523893003: Reland of [ignition/turbo] Perform liveness analysis on the bytecodes (Closed)
Patch Set: Add a test case for the nested loop with register killing Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 the V8 project authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "src/compiler/bytecode-analysis.h" 5 #include "src/compiler/bytecode-analysis.h"
6 6
7 #include "src/interpreter/bytecode-array-iterator.h"
7 #include "src/interpreter/bytecode-array-reverse-iterator.h" 8 #include "src/interpreter/bytecode-array-reverse-iterator.h"
8 #include "src/objects-inl.h" 9 #include "src/objects-inl.h"
9 10
10 namespace v8 { 11 namespace v8 {
11 namespace internal { 12 namespace internal {
12 namespace compiler { 13 namespace compiler {
13 14
15 using namespace interpreter;
16
14 BytecodeAnalysis::BytecodeAnalysis(Handle<BytecodeArray> bytecode_array, 17 BytecodeAnalysis::BytecodeAnalysis(Handle<BytecodeArray> bytecode_array,
15 Zone* zone) 18 Zone* zone, bool do_liveness_analysis)
16 : bytecode_array_(bytecode_array), 19 : bytecode_array_(bytecode_array),
20 do_liveness_analysis_(do_liveness_analysis),
17 zone_(zone), 21 zone_(zone),
18 loop_stack_(zone), 22 loop_stack_(zone),
19 end_to_header_(zone), 23 end_to_header_(zone),
20 header_to_parent_(zone) {} 24 header_to_parent_(zone),
25 liveness_map_(bytecode_array->length(), zone) {}
26
27 namespace {
28
29 void UpdateInLiveness(Bytecode bytecode, BitVector& in_liveness,
30 const BytecodeArrayAccessor& accessor) {
31 if (bytecode == Bytecode::kDebugger) {
32 // TODO(leszeks): Do we really need to keep everything alive for a debugger
33 // bytecode? Other ways of breaking the debugger here (such as a debugger
34 // statement in a called function) won't keep locals alive.
35 in_liveness.AddAll();
36 return;
37 }
rmcilroy 2016/11/28 15:16:37 I guess you can drop this now?
Leszek Swirski 2016/11/28 16:31:11 Done.
38
39 int num_operands = Bytecodes::NumberOfOperands(bytecode);
40 const OperandType* operand_types = Bytecodes::GetOperandTypes(bytecode);
41 AccumulatorUse accumulator_use = Bytecodes::GetAccumulatorUse(bytecode);
42
43 if (accumulator_use == AccumulatorUse::kWrite) {
44 in_liveness.Remove(in_liveness.length() - 1);
45 }
46 for (int i = 0; i < num_operands; ++i) {
47 switch (operand_types[i]) {
48 case OperandType::kRegOut: {
49 interpreter::Register r = accessor.GetRegisterOperand(i);
50 if (!r.is_parameter()) {
51 in_liveness.Remove(r.index());
52 }
53 break;
54 }
55 case OperandType::kRegOutPair: {
56 interpreter::Register r = accessor.GetRegisterOperand(i);
57 if (!r.is_parameter()) {
58 DCHECK(!interpreter::Register(r.index() + 1).is_parameter());
59 in_liveness.Remove(r.index());
60 in_liveness.Remove(r.index() + 1);
61 }
62 break;
63 }
64 case OperandType::kRegOutTriple: {
65 interpreter::Register r = accessor.GetRegisterOperand(i);
66 if (!r.is_parameter()) {
67 DCHECK(!interpreter::Register(r.index() + 1).is_parameter());
68 DCHECK(!interpreter::Register(r.index() + 2).is_parameter());
69 in_liveness.Remove(r.index());
70 in_liveness.Remove(r.index() + 1);
71 in_liveness.Remove(r.index() + 2);
72 }
73 break;
74 }
75 default:
76 DCHECK(!Bytecodes::IsRegisterOutputOperandType(operand_types[i]));
77 break;
78 }
79 }
80
81 if (accumulator_use == AccumulatorUse::kRead) {
82 in_liveness.Add(in_liveness.length() - 1);
83 }
84 for (int i = 0; i < num_operands; ++i) {
85 switch (operand_types[i]) {
86 case OperandType::kReg: {
87 interpreter::Register r = accessor.GetRegisterOperand(i);
88 if (!r.is_parameter()) {
89 in_liveness.Add(r.index());
90 }
91 break;
92 }
93 case OperandType::kRegPair: {
94 interpreter::Register r = accessor.GetRegisterOperand(i);
95 if (!r.is_parameter()) {
96 DCHECK(!interpreter::Register(r.index() + 1).is_parameter());
97 in_liveness.Add(r.index());
98 in_liveness.Add(r.index() + 1);
99 }
100 break;
101 }
102 case OperandType::kRegList: {
103 interpreter::Register r = accessor.GetRegisterOperand(i);
104 i++;
105 if (!r.is_parameter()) {
106 uint32_t reg_count = accessor.GetRegisterCountOperand(i);
107
108 for (uint32_t j = 0; j < reg_count; ++j) {
109 DCHECK(!interpreter::Register(r.index() + j).is_parameter());
110 in_liveness.Add(r.index() + j);
111 }
112 }
113 }
114 default:
115 DCHECK(!Bytecodes::IsRegisterInputOperandType(operand_types[i]));
116 break;
117 }
118 }
119 }
120
121 void UpdateOutLiveness(Bytecode bytecode, BitVector& out_liveness,
122 const BytecodeArrayAccessor& accessor,
123 const BytecodeLivenessMap& liveness_map) {
124 if (bytecode == Bytecode::kDebugger) {
125 // TODO(leszeks): Do we really need to keep everything alive for a debugger
126 // bytecode? Other ways of breaking the debugger here (such as a debugger
127 // statement in a called function) won't keep locals alive.
128 out_liveness.AddAll();
129 return;
130 }
rmcilroy 2016/11/28 15:16:37 ditto
Leszek Swirski 2016/11/28 16:31:11 Done.
131
132 int current_offset = accessor.current_offset();
133 const Handle<BytecodeArray>& bytecode_array = accessor.bytecode_array();
134
135 // Update from jump target (if any). Skip loops, we update these manually in
136 // the liveness iterations.
137 if (Bytecodes::IsForwardJump(bytecode)) {
138 int target_offset = accessor.GetJumpTargetOffset();
139 out_liveness.Union(*liveness_map.GetInLiveness(target_offset));
140 }
141
142 // Update from next bytecode (unless this is an unconditional jump).
143 if (!Bytecodes::IsUnconditionalJump(bytecode)) {
144 int next_offset = current_offset + accessor.current_bytecode_size();
145 if (next_offset < bytecode_array->length()) {
146 out_liveness.Union(*liveness_map.GetInLiveness(next_offset));
147 }
148 }
149
150 // Update from exception handler (if any).
151 if (!interpreter::Bytecodes::IsWithoutExternalSideEffects(bytecode)) {
152 int handler_context;
153 int handler_offset = bytecode_array->LookupRangeInHandlerTable(
rmcilroy 2016/11/28 15:16:37 (side thought) - I wonder if this ends up being ex
Leszek Swirski 2016/11/28 16:31:11 Yes, I've got this in the back of my head as one o
154 current_offset, &handler_context, nullptr);
155
156 if (handler_offset != -1) {
157 out_liveness.Union(*liveness_map.GetInLiveness(handler_offset));
158 out_liveness.Add(handler_context);
159 }
160 }
161 }
162
163 } // namespace
21 164
22 void BytecodeAnalysis::Analyze() { 165 void BytecodeAnalysis::Analyze() {
23 loop_stack_.push(-1); 166 loop_stack_.push(-1);
24 167
25 interpreter::BytecodeArrayReverseIterator iterator(bytecode_array(), zone()); 168 // The last JumpLoop that we haven't done a guaranteed valid liveness pass
26 while (!iterator.done()) { 169 // over. See the below wall of text for a more thorough explanation.
27 interpreter::Bytecode bytecode = iterator.current_bytecode(); 170 int last_invalid_jumploop_offset = -1;
28 if (bytecode == interpreter::Bytecode::kJumpLoop) { 171
29 PushLoop(iterator.GetJumpTargetOffset(), iterator.current_offset()); 172 BytecodeArrayReverseIterator iterator(bytecode_array(), zone());
30 } else if (iterator.current_offset() == loop_stack_.top()) { 173 for (; !iterator.done(); iterator.Advance()) {
174 Bytecode bytecode = iterator.current_bytecode();
175 int current_offset = iterator.current_offset();
176
177 if (bytecode == Bytecode::kJumpLoop) {
178 PushLoop(iterator.GetJumpTargetOffset(), current_offset);
179
180 // Save the last offset so that we can do another pass later.
181 if (last_invalid_jumploop_offset == -1) {
182 last_invalid_jumploop_offset = current_offset;
183 }
184 } else if (current_offset == loop_stack_.top()) {
31 loop_stack_.pop(); 185 loop_stack_.pop();
32 } 186 }
33 iterator.Advance(); 187
188 if (do_liveness_analysis_) {
189 // The liveness vector had bits for the liveness of the registers, and one
190 // more bit for the liveness of the accumulator.
191 Liveness& liveness = liveness_map_.InitializeLiveness(
192 current_offset, bytecode_array()->register_count() + 1, zone());
193
194 UpdateOutLiveness(bytecode, *liveness.out, iterator, liveness_map_);
195 liveness.in->CopyFrom(*liveness.out);
196 UpdateInLiveness(bytecode, *liveness.in, iterator);
197 }
34 } 198 }
35 199
36 DCHECK_EQ(loop_stack_.size(), 1u); 200 DCHECK_EQ(loop_stack_.size(), 1u);
37 DCHECK_EQ(loop_stack_.top(), -1); 201 DCHECK_EQ(loop_stack_.top(), -1);
202
203 if (!do_liveness_analysis_) return;
204
205 // At this point, every bytecode has a valid in and out liveness, except for
206 // propagating liveness across back edges (i.e. JumpLoop). Subsequent liveness
207 // analysis iterations can only add additional liveness bits that are pulled
208 // across these back edges.
209 //
210 // Furthermore, a loop header's in-liveness can only change based on any
211 // bytecodes *after* the loop end -- it cannot change as a result of the
212 // JumpLoop liveness being updated, as the only liveness bits than can be
213 // added to the loop body are those of the loop header.
214 //
215 // So, if we know that the liveness of bytecodes after a loop header won't
216 // change (e.g. because there are no loops in them, or we have already ensured
217 // those loops are valid), we can safely update the loop end and pass over the
218 // loop body, and then never have to pass over that loop end again, because we
219 // have shown that its target, the loop header, can't change from the entries
220 // after the loop, and can't change from any loop body pass.
221 //
222 // This means that in a pass, we can iterate backwards over the bytecode
223 // array, process any loops that we encounter, and on subsequent passes we can
224 // skip processing those loops (though we still have to process inner loops).
225
226 while (last_invalid_jumploop_offset != -1) {
227 // TODO(leszeks): We shouldn't need to iterate here, we should just have a
228 // random access iterator.
229 iterator.Reset();
230 while (last_invalid_jumploop_offset < iterator.current_offset()) {
231 iterator.Advance();
232 }
233 last_invalid_jumploop_offset = -1;
234
235 DCHECK_EQ(iterator.current_bytecode(), Bytecode::kJumpLoop);
236
237 for (; !iterator.done(); iterator.Advance()) {
238 Bytecode bytecode = iterator.current_bytecode();
239 if (bytecode != Bytecode::kJumpLoop) {
240 // Skip bytecodes until we hit a jumploop. This check isn't needed for
rmcilroy 2016/11/28 15:16:37 nit - JumpLoop
Leszek Swirski 2016/11/28 16:31:11 Done.
241 // the first loop we see (thanks to saving its offset), but it is for
242 // subsequent ones we want to process on this pass.
243 continue;
244 }
245
246 int header_offset = iterator.GetJumpTargetOffset();
247 int end_offset = iterator.current_offset();
248
249 Liveness& header_liveness = liveness_map_.GetLiveness(header_offset);
250 Liveness& end_liveness = liveness_map_.GetLiveness(end_offset);
251
252 if (end_liveness.out->UnionIsChanged(*header_liveness.in)) {
253 // Only update the loop body if the loop end liveness changed.
254 end_liveness.in->CopyFrom(*end_liveness.out);
255
256 // Go past loop end.
rmcilroy 2016/11/28 15:16:37 nit - comment is slightly confusing. How about "Ad
Leszek Swirski 2016/11/28 16:31:11 Done.
257 iterator.Advance();
258 for (; iterator.current_offset() > header_offset; iterator.Advance()) {
259 bytecode = iterator.current_bytecode();
260 if (bytecode == Bytecode::kJumpLoop) {
261 // We can't validate this loop at the moment because we can't
262 // guarantee that its header is valid yet. Save it for later.
263 if (last_invalid_jumploop_offset == -1) {
264 last_invalid_jumploop_offset = iterator.current_offset();
265 }
266 }
267
268 int current_offset = iterator.current_offset();
269 Liveness& liveness = liveness_map_.GetLiveness(current_offset);
270
271 UpdateOutLiveness(bytecode, *liveness.out, iterator, liveness_map_);
272 liveness.in->CopyFrom(*liveness.out);
273 UpdateInLiveness(bytecode, *liveness.in, iterator);
274 }
275 // Now we are at the loop header. Since the in-liveness of the header
276 // can't change, we need only to update the out-liveness.
277 bytecode = iterator.current_bytecode();
278 UpdateOutLiveness(bytecode, *header_liveness.out, iterator,
279 liveness_map_);
280 }
281
282 // Keep the iterator going so that we can find other loops.
rmcilroy 2016/11/28 15:16:37 As mentioned offline, I'm not super keen on the la
Leszek Swirski 2016/11/28 16:31:11 Yup, follow-up CL incoming, I just need to make su
283 }
284 }
285
286 DCHECK(LivenessIsValid());
38 } 287 }
39 288
40 void BytecodeAnalysis::PushLoop(int loop_header, int loop_end) { 289 void BytecodeAnalysis::PushLoop(int loop_header, int loop_end) {
41 DCHECK(loop_header < loop_end); 290 DCHECK(loop_header < loop_end);
42 DCHECK(loop_stack_.top() < loop_header); 291 DCHECK(loop_stack_.top() < loop_header);
43 DCHECK(end_to_header_.find(loop_end) == end_to_header_.end()); 292 DCHECK(end_to_header_.find(loop_end) == end_to_header_.end());
44 DCHECK(header_to_parent_.find(loop_header) == header_to_parent_.end()); 293 DCHECK(header_to_parent_.find(loop_header) == header_to_parent_.end());
45 294
46 end_to_header_.insert(ZoneMap<int, int>::value_type(loop_end, loop_header)); 295 end_to_header_.insert(ZoneMap<int, int>::value_type(loop_end, loop_header));
47 header_to_parent_.insert( 296 header_to_parent_.insert(
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
85 334
86 return header_to_parent_.upper_bound(offset)->second; 335 return header_to_parent_.upper_bound(offset)->second;
87 } 336 }
88 337
89 int BytecodeAnalysis::GetParentLoopFor(int header_offset) const { 338 int BytecodeAnalysis::GetParentLoopFor(int header_offset) const {
90 DCHECK(IsLoopHeader(header_offset)); 339 DCHECK(IsLoopHeader(header_offset));
91 340
92 return header_to_parent_.find(header_offset)->second; 341 return header_to_parent_.find(header_offset)->second;
93 } 342 }
94 343
344 const BitVector* BytecodeAnalysis::GetInLivenessFor(int offset) const {
345 if (!do_liveness_analysis_) return nullptr;
346
347 return liveness_map_.GetInLiveness(offset);
348 }
349
350 const BitVector* BytecodeAnalysis::GetOutLivenessFor(int offset) const {
351 if (!do_liveness_analysis_) return nullptr;
352
353 return liveness_map_.GetOutLiveness(offset);
354 }
355
356 std::ostream& BytecodeAnalysis::PrintLivenessTo(std::ostream& os) const {
357 interpreter::BytecodeArrayIterator iterator(bytecode_array());
358
359 for (; !iterator.done(); iterator.Advance()) {
360 int current_offset = iterator.current_offset();
361
362 const BitVector* in_liveness = GetInLivenessFor(current_offset);
363 const BitVector* out_liveness = GetOutLivenessFor(current_offset);
364
365 for (int i = 0; i < in_liveness->length(); ++i) {
366 os << (in_liveness->Contains(i) ? "L" : ".");
367 }
368 os << " -> ";
369
370 for (int i = 0; i < out_liveness->length(); ++i) {
371 os << (out_liveness->Contains(i) ? "L" : ".");
372 }
373
374 os << " | " << current_offset << ": ";
375 iterator.PrintTo(os) << std::endl;
376 }
377
378 return os;
379 }
380
381 #if DEBUG
382 bool BytecodeAnalysis::LivenessIsValid() {
383 BytecodeArrayReverseIterator iterator(bytecode_array(), zone());
384
385 BitVector previous_liveness(bytecode_array()->register_count() + 1, zone());
386
387 int invalid_offset = -1;
388 int which_invalid = -1;
389
390 // Ensure that there are no liveness changes if we iterate one more time.
391 for (iterator.Reset(); !iterator.done(); iterator.Advance()) {
392 Bytecode bytecode = iterator.current_bytecode();
393
394 int current_offset = iterator.current_offset();
395
396 Liveness& liveness = liveness_map_.GetLiveness(current_offset);
397
398 previous_liveness.CopyFrom(*liveness.out);
399
400 UpdateOutLiveness(bytecode, *liveness.out, iterator, liveness_map_);
401 // UpdateOutLiveness skips kJumpLoop, so we update it manually.
402 if (bytecode == Bytecode::kJumpLoop) {
403 int target_offset = iterator.GetJumpTargetOffset();
404 liveness.out->Union(*liveness_map_.GetInLiveness(target_offset));
405 }
406
407 if (!liveness.out->Equals(previous_liveness)) {
408 // Reset the invalid liveness.
409 liveness.out->CopyFrom(previous_liveness);
410 invalid_offset = current_offset;
411 which_invalid = 1;
412 break;
413 }
414
415 previous_liveness.CopyFrom(*liveness.in);
416
417 liveness.in->CopyFrom(*liveness.out);
418 UpdateInLiveness(bytecode, *liveness.in, iterator);
419
420 if (!liveness.in->Equals(previous_liveness)) {
421 // Reset the invalid liveness.
422 liveness.in->CopyFrom(previous_liveness);
423 invalid_offset = current_offset;
424 which_invalid = 0;
425 break;
426 }
427 }
428
429 if (invalid_offset != -1) {
430 OFStream of(stderr);
431 of << "Invalid liveness:" << std::endl;
432
433 // Dump the bytecode, annotated with the liveness and marking loops.
434
435 int loop_indent = 0;
436
437 BytecodeArrayIterator forward_iterator(bytecode_array());
438 for (; !forward_iterator.done(); forward_iterator.Advance()) {
439 int current_offset = forward_iterator.current_offset();
440 BitVector* in_liveness = liveness_map_.GetInLiveness(current_offset);
441 BitVector* out_liveness = liveness_map_.GetOutLiveness(current_offset);
442
443 for (int i = 0; i < in_liveness->length(); ++i) {
444 of << (in_liveness->Contains(i) ? 'L' : '.');
445 }
446
447 of << " | ";
448
449 for (int i = 0; i < out_liveness->length(); ++i) {
450 of << (out_liveness->Contains(i) ? 'L' : '.');
451 }
452
453 of << " : " << current_offset << " : ";
454
455 // Draw loop back edges by indentin everything between loop headers and
456 // jump loop instructions.
457 if (forward_iterator.current_bytecode() == Bytecode::kJumpLoop) {
458 loop_indent--;
459 }
460 for (int i = 0; i < loop_indent; ++i) {
461 of << " | ";
462 }
463 if (forward_iterator.current_bytecode() == Bytecode::kJumpLoop) {
464 of << " `-" << current_offset;
465 } else if (IsLoopHeader(current_offset)) {
466 of << " .>" << current_offset;
467 loop_indent++;
rmcilroy 2016/11/28 15:16:37 Could you share code with PrintLivenessTo here?
Leszek Swirski 2016/11/28 16:31:11 I potentially could, but there's some additional m
468 }
469 forward_iterator.PrintTo(of) << std::endl;
470
471 if (current_offset == invalid_offset) {
472 // Underline the invalid liveness.
473 if (which_invalid == 0) {
474 for (int i = 0; i < in_liveness->length(); ++i) {
475 of << '^';
476 }
477 } else {
478 for (int i = 0; i < in_liveness->length() + 3; ++i) {
479 of << ' ';
480 }
481 for (int i = 0; i < out_liveness->length(); ++i) {
482 of << '^';
483 }
484 }
485
486 // Make sure to draw the loop indentation marks on this additional line.
487 of << " : " << current_offset << " : ";
488 for (int i = 0; i < loop_indent; ++i) {
489 of << " | ";
490 }
491
492 of << std::endl;
493 }
494 }
495 }
496
497 return invalid_offset == -1;
498 }
499 #endif
500
95 } // namespace compiler 501 } // namespace compiler
96 } // namespace internal 502 } // namespace internal
97 } // namespace v8 503 } // namespace v8
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698