OLD | NEW |
| (Empty) |
1 // Copyright 2012 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 | |
6 #include "src/v8.h" | |
7 | |
8 #include "src/liveedit.h" | |
9 | |
10 #include "src/code-stubs.h" | |
11 #include "src/compilation-cache.h" | |
12 #include "src/compiler.h" | |
13 #include "src/debug.h" | |
14 #include "src/deoptimizer.h" | |
15 #include "src/global-handles.h" | |
16 #include "src/messages.h" | |
17 #include "src/parser.h" | |
18 #include "src/scopeinfo.h" | |
19 #include "src/scopes.h" | |
20 #include "src/v8memory.h" | |
21 | |
22 namespace v8 { | |
23 namespace internal { | |
24 | |
25 void SetElementSloppy(Handle<JSObject> object, | |
26 uint32_t index, | |
27 Handle<Object> value) { | |
28 // Ignore return value from SetElement. It can only be a failure if there | |
29 // are element setters causing exceptions and the debugger context has none | |
30 // of these. | |
31 Object::SetElement(object->GetIsolate(), object, index, value, SLOPPY) | |
32 .Assert(); | |
33 } | |
34 | |
35 | |
36 // A simple implementation of dynamic programming algorithm. It solves | |
37 // the problem of finding the difference of 2 arrays. It uses a table of results | |
38 // of subproblems. Each cell contains a number together with 2-bit flag | |
39 // that helps building the chunk list. | |
40 class Differencer { | |
41 public: | |
42 explicit Differencer(Comparator::Input* input) | |
43 : input_(input), len1_(input->GetLength1()), len2_(input->GetLength2()) { | |
44 buffer_ = NewArray<int>(len1_ * len2_); | |
45 } | |
46 ~Differencer() { | |
47 DeleteArray(buffer_); | |
48 } | |
49 | |
50 void Initialize() { | |
51 int array_size = len1_ * len2_; | |
52 for (int i = 0; i < array_size; i++) { | |
53 buffer_[i] = kEmptyCellValue; | |
54 } | |
55 } | |
56 | |
57 // Makes sure that result for the full problem is calculated and stored | |
58 // in the table together with flags showing a path through subproblems. | |
59 void FillTable() { | |
60 CompareUpToTail(0, 0); | |
61 } | |
62 | |
63 void SaveResult(Comparator::Output* chunk_writer) { | |
64 ResultWriter writer(chunk_writer); | |
65 | |
66 int pos1 = 0; | |
67 int pos2 = 0; | |
68 while (true) { | |
69 if (pos1 < len1_) { | |
70 if (pos2 < len2_) { | |
71 Direction dir = get_direction(pos1, pos2); | |
72 switch (dir) { | |
73 case EQ: | |
74 writer.eq(); | |
75 pos1++; | |
76 pos2++; | |
77 break; | |
78 case SKIP1: | |
79 writer.skip1(1); | |
80 pos1++; | |
81 break; | |
82 case SKIP2: | |
83 case SKIP_ANY: | |
84 writer.skip2(1); | |
85 pos2++; | |
86 break; | |
87 default: | |
88 UNREACHABLE(); | |
89 } | |
90 } else { | |
91 writer.skip1(len1_ - pos1); | |
92 break; | |
93 } | |
94 } else { | |
95 if (len2_ != pos2) { | |
96 writer.skip2(len2_ - pos2); | |
97 } | |
98 break; | |
99 } | |
100 } | |
101 writer.close(); | |
102 } | |
103 | |
104 private: | |
105 Comparator::Input* input_; | |
106 int* buffer_; | |
107 int len1_; | |
108 int len2_; | |
109 | |
110 enum Direction { | |
111 EQ = 0, | |
112 SKIP1, | |
113 SKIP2, | |
114 SKIP_ANY, | |
115 | |
116 MAX_DIRECTION_FLAG_VALUE = SKIP_ANY | |
117 }; | |
118 | |
119 // Computes result for a subtask and optionally caches it in the buffer table. | |
120 // All results values are shifted to make space for flags in the lower bits. | |
121 int CompareUpToTail(int pos1, int pos2) { | |
122 if (pos1 < len1_) { | |
123 if (pos2 < len2_) { | |
124 int cached_res = get_value4(pos1, pos2); | |
125 if (cached_res == kEmptyCellValue) { | |
126 Direction dir; | |
127 int res; | |
128 if (input_->Equals(pos1, pos2)) { | |
129 res = CompareUpToTail(pos1 + 1, pos2 + 1); | |
130 dir = EQ; | |
131 } else { | |
132 int res1 = CompareUpToTail(pos1 + 1, pos2) + | |
133 (1 << kDirectionSizeBits); | |
134 int res2 = CompareUpToTail(pos1, pos2 + 1) + | |
135 (1 << kDirectionSizeBits); | |
136 if (res1 == res2) { | |
137 res = res1; | |
138 dir = SKIP_ANY; | |
139 } else if (res1 < res2) { | |
140 res = res1; | |
141 dir = SKIP1; | |
142 } else { | |
143 res = res2; | |
144 dir = SKIP2; | |
145 } | |
146 } | |
147 set_value4_and_dir(pos1, pos2, res, dir); | |
148 cached_res = res; | |
149 } | |
150 return cached_res; | |
151 } else { | |
152 return (len1_ - pos1) << kDirectionSizeBits; | |
153 } | |
154 } else { | |
155 return (len2_ - pos2) << kDirectionSizeBits; | |
156 } | |
157 } | |
158 | |
159 inline int& get_cell(int i1, int i2) { | |
160 return buffer_[i1 + i2 * len1_]; | |
161 } | |
162 | |
163 // Each cell keeps a value plus direction. Value is multiplied by 4. | |
164 void set_value4_and_dir(int i1, int i2, int value4, Direction dir) { | |
165 DCHECK((value4 & kDirectionMask) == 0); | |
166 get_cell(i1, i2) = value4 | dir; | |
167 } | |
168 | |
169 int get_value4(int i1, int i2) { | |
170 return get_cell(i1, i2) & (kMaxUInt32 ^ kDirectionMask); | |
171 } | |
172 Direction get_direction(int i1, int i2) { | |
173 return static_cast<Direction>(get_cell(i1, i2) & kDirectionMask); | |
174 } | |
175 | |
176 static const int kDirectionSizeBits = 2; | |
177 static const int kDirectionMask = (1 << kDirectionSizeBits) - 1; | |
178 static const int kEmptyCellValue = ~0u << kDirectionSizeBits; | |
179 | |
180 // This method only holds static assert statement (unfortunately you cannot | |
181 // place one in class scope). | |
182 void StaticAssertHolder() { | |
183 STATIC_ASSERT(MAX_DIRECTION_FLAG_VALUE < (1 << kDirectionSizeBits)); | |
184 } | |
185 | |
186 class ResultWriter { | |
187 public: | |
188 explicit ResultWriter(Comparator::Output* chunk_writer) | |
189 : chunk_writer_(chunk_writer), pos1_(0), pos2_(0), | |
190 pos1_begin_(-1), pos2_begin_(-1), has_open_chunk_(false) { | |
191 } | |
192 void eq() { | |
193 FlushChunk(); | |
194 pos1_++; | |
195 pos2_++; | |
196 } | |
197 void skip1(int len1) { | |
198 StartChunk(); | |
199 pos1_ += len1; | |
200 } | |
201 void skip2(int len2) { | |
202 StartChunk(); | |
203 pos2_ += len2; | |
204 } | |
205 void close() { | |
206 FlushChunk(); | |
207 } | |
208 | |
209 private: | |
210 Comparator::Output* chunk_writer_; | |
211 int pos1_; | |
212 int pos2_; | |
213 int pos1_begin_; | |
214 int pos2_begin_; | |
215 bool has_open_chunk_; | |
216 | |
217 void StartChunk() { | |
218 if (!has_open_chunk_) { | |
219 pos1_begin_ = pos1_; | |
220 pos2_begin_ = pos2_; | |
221 has_open_chunk_ = true; | |
222 } | |
223 } | |
224 | |
225 void FlushChunk() { | |
226 if (has_open_chunk_) { | |
227 chunk_writer_->AddChunk(pos1_begin_, pos2_begin_, | |
228 pos1_ - pos1_begin_, pos2_ - pos2_begin_); | |
229 has_open_chunk_ = false; | |
230 } | |
231 } | |
232 }; | |
233 }; | |
234 | |
235 | |
236 void Comparator::CalculateDifference(Comparator::Input* input, | |
237 Comparator::Output* result_writer) { | |
238 Differencer differencer(input); | |
239 differencer.Initialize(); | |
240 differencer.FillTable(); | |
241 differencer.SaveResult(result_writer); | |
242 } | |
243 | |
244 | |
245 static bool CompareSubstrings(Handle<String> s1, int pos1, | |
246 Handle<String> s2, int pos2, int len) { | |
247 for (int i = 0; i < len; i++) { | |
248 if (s1->Get(i + pos1) != s2->Get(i + pos2)) { | |
249 return false; | |
250 } | |
251 } | |
252 return true; | |
253 } | |
254 | |
255 | |
256 // Additional to Input interface. Lets switch Input range to subrange. | |
257 // More elegant way would be to wrap one Input as another Input object | |
258 // and translate positions there, but that would cost us additional virtual | |
259 // call per comparison. | |
260 class SubrangableInput : public Comparator::Input { | |
261 public: | |
262 virtual void SetSubrange1(int offset, int len) = 0; | |
263 virtual void SetSubrange2(int offset, int len) = 0; | |
264 }; | |
265 | |
266 | |
267 class SubrangableOutput : public Comparator::Output { | |
268 public: | |
269 virtual void SetSubrange1(int offset, int len) = 0; | |
270 virtual void SetSubrange2(int offset, int len) = 0; | |
271 }; | |
272 | |
273 | |
274 static int min(int a, int b) { | |
275 return a < b ? a : b; | |
276 } | |
277 | |
278 | |
279 // Finds common prefix and suffix in input. This parts shouldn't take space in | |
280 // linear programming table. Enable subranging in input and output. | |
281 static void NarrowDownInput(SubrangableInput* input, | |
282 SubrangableOutput* output) { | |
283 const int len1 = input->GetLength1(); | |
284 const int len2 = input->GetLength2(); | |
285 | |
286 int common_prefix_len; | |
287 int common_suffix_len; | |
288 | |
289 { | |
290 common_prefix_len = 0; | |
291 int prefix_limit = min(len1, len2); | |
292 while (common_prefix_len < prefix_limit && | |
293 input->Equals(common_prefix_len, common_prefix_len)) { | |
294 common_prefix_len++; | |
295 } | |
296 | |
297 common_suffix_len = 0; | |
298 int suffix_limit = min(len1 - common_prefix_len, len2 - common_prefix_len); | |
299 | |
300 while (common_suffix_len < suffix_limit && | |
301 input->Equals(len1 - common_suffix_len - 1, | |
302 len2 - common_suffix_len - 1)) { | |
303 common_suffix_len++; | |
304 } | |
305 } | |
306 | |
307 if (common_prefix_len > 0 || common_suffix_len > 0) { | |
308 int new_len1 = len1 - common_suffix_len - common_prefix_len; | |
309 int new_len2 = len2 - common_suffix_len - common_prefix_len; | |
310 | |
311 input->SetSubrange1(common_prefix_len, new_len1); | |
312 input->SetSubrange2(common_prefix_len, new_len2); | |
313 | |
314 output->SetSubrange1(common_prefix_len, new_len1); | |
315 output->SetSubrange2(common_prefix_len, new_len2); | |
316 } | |
317 } | |
318 | |
319 | |
320 // A helper class that writes chunk numbers into JSArray. | |
321 // Each chunk is stored as 3 array elements: (pos1_begin, pos1_end, pos2_end). | |
322 class CompareOutputArrayWriter { | |
323 public: | |
324 explicit CompareOutputArrayWriter(Isolate* isolate) | |
325 : array_(isolate->factory()->NewJSArray(10)), current_size_(0) {} | |
326 | |
327 Handle<JSArray> GetResult() { | |
328 return array_; | |
329 } | |
330 | |
331 void WriteChunk(int char_pos1, int char_pos2, int char_len1, int char_len2) { | |
332 Isolate* isolate = array_->GetIsolate(); | |
333 SetElementSloppy(array_, | |
334 current_size_, | |
335 Handle<Object>(Smi::FromInt(char_pos1), isolate)); | |
336 SetElementSloppy(array_, | |
337 current_size_ + 1, | |
338 Handle<Object>(Smi::FromInt(char_pos1 + char_len1), | |
339 isolate)); | |
340 SetElementSloppy(array_, | |
341 current_size_ + 2, | |
342 Handle<Object>(Smi::FromInt(char_pos2 + char_len2), | |
343 isolate)); | |
344 current_size_ += 3; | |
345 } | |
346 | |
347 private: | |
348 Handle<JSArray> array_; | |
349 int current_size_; | |
350 }; | |
351 | |
352 | |
353 // Represents 2 strings as 2 arrays of tokens. | |
354 // TODO(LiveEdit): Currently it's actually an array of charactres. | |
355 // Make array of tokens instead. | |
356 class TokensCompareInput : public Comparator::Input { | |
357 public: | |
358 TokensCompareInput(Handle<String> s1, int offset1, int len1, | |
359 Handle<String> s2, int offset2, int len2) | |
360 : s1_(s1), offset1_(offset1), len1_(len1), | |
361 s2_(s2), offset2_(offset2), len2_(len2) { | |
362 } | |
363 virtual int GetLength1() { | |
364 return len1_; | |
365 } | |
366 virtual int GetLength2() { | |
367 return len2_; | |
368 } | |
369 bool Equals(int index1, int index2) { | |
370 return s1_->Get(offset1_ + index1) == s2_->Get(offset2_ + index2); | |
371 } | |
372 | |
373 private: | |
374 Handle<String> s1_; | |
375 int offset1_; | |
376 int len1_; | |
377 Handle<String> s2_; | |
378 int offset2_; | |
379 int len2_; | |
380 }; | |
381 | |
382 | |
383 // Stores compare result in JSArray. Converts substring positions | |
384 // to absolute positions. | |
385 class TokensCompareOutput : public Comparator::Output { | |
386 public: | |
387 TokensCompareOutput(CompareOutputArrayWriter* array_writer, | |
388 int offset1, int offset2) | |
389 : array_writer_(array_writer), offset1_(offset1), offset2_(offset2) { | |
390 } | |
391 | |
392 void AddChunk(int pos1, int pos2, int len1, int len2) { | |
393 array_writer_->WriteChunk(pos1 + offset1_, pos2 + offset2_, len1, len2); | |
394 } | |
395 | |
396 private: | |
397 CompareOutputArrayWriter* array_writer_; | |
398 int offset1_; | |
399 int offset2_; | |
400 }; | |
401 | |
402 | |
403 // Wraps raw n-elements line_ends array as a list of n+1 lines. The last line | |
404 // never has terminating new line character. | |
405 class LineEndsWrapper { | |
406 public: | |
407 explicit LineEndsWrapper(Handle<String> string) | |
408 : ends_array_(String::CalculateLineEnds(string, false)), | |
409 string_len_(string->length()) { | |
410 } | |
411 int length() { | |
412 return ends_array_->length() + 1; | |
413 } | |
414 // Returns start for any line including start of the imaginary line after | |
415 // the last line. | |
416 int GetLineStart(int index) { | |
417 if (index == 0) { | |
418 return 0; | |
419 } else { | |
420 return GetLineEnd(index - 1); | |
421 } | |
422 } | |
423 int GetLineEnd(int index) { | |
424 if (index == ends_array_->length()) { | |
425 // End of the last line is always an end of the whole string. | |
426 // If the string ends with a new line character, the last line is an | |
427 // empty string after this character. | |
428 return string_len_; | |
429 } else { | |
430 return GetPosAfterNewLine(index); | |
431 } | |
432 } | |
433 | |
434 private: | |
435 Handle<FixedArray> ends_array_; | |
436 int string_len_; | |
437 | |
438 int GetPosAfterNewLine(int index) { | |
439 return Smi::cast(ends_array_->get(index))->value() + 1; | |
440 } | |
441 }; | |
442 | |
443 | |
444 // Represents 2 strings as 2 arrays of lines. | |
445 class LineArrayCompareInput : public SubrangableInput { | |
446 public: | |
447 LineArrayCompareInput(Handle<String> s1, Handle<String> s2, | |
448 LineEndsWrapper line_ends1, LineEndsWrapper line_ends2) | |
449 : s1_(s1), s2_(s2), line_ends1_(line_ends1), | |
450 line_ends2_(line_ends2), | |
451 subrange_offset1_(0), subrange_offset2_(0), | |
452 subrange_len1_(line_ends1_.length()), | |
453 subrange_len2_(line_ends2_.length()) { | |
454 } | |
455 int GetLength1() { | |
456 return subrange_len1_; | |
457 } | |
458 int GetLength2() { | |
459 return subrange_len2_; | |
460 } | |
461 bool Equals(int index1, int index2) { | |
462 index1 += subrange_offset1_; | |
463 index2 += subrange_offset2_; | |
464 | |
465 int line_start1 = line_ends1_.GetLineStart(index1); | |
466 int line_start2 = line_ends2_.GetLineStart(index2); | |
467 int line_end1 = line_ends1_.GetLineEnd(index1); | |
468 int line_end2 = line_ends2_.GetLineEnd(index2); | |
469 int len1 = line_end1 - line_start1; | |
470 int len2 = line_end2 - line_start2; | |
471 if (len1 != len2) { | |
472 return false; | |
473 } | |
474 return CompareSubstrings(s1_, line_start1, s2_, line_start2, | |
475 len1); | |
476 } | |
477 void SetSubrange1(int offset, int len) { | |
478 subrange_offset1_ = offset; | |
479 subrange_len1_ = len; | |
480 } | |
481 void SetSubrange2(int offset, int len) { | |
482 subrange_offset2_ = offset; | |
483 subrange_len2_ = len; | |
484 } | |
485 | |
486 private: | |
487 Handle<String> s1_; | |
488 Handle<String> s2_; | |
489 LineEndsWrapper line_ends1_; | |
490 LineEndsWrapper line_ends2_; | |
491 int subrange_offset1_; | |
492 int subrange_offset2_; | |
493 int subrange_len1_; | |
494 int subrange_len2_; | |
495 }; | |
496 | |
497 | |
498 // Stores compare result in JSArray. For each chunk tries to conduct | |
499 // a fine-grained nested diff token-wise. | |
500 class TokenizingLineArrayCompareOutput : public SubrangableOutput { | |
501 public: | |
502 TokenizingLineArrayCompareOutput(LineEndsWrapper line_ends1, | |
503 LineEndsWrapper line_ends2, | |
504 Handle<String> s1, Handle<String> s2) | |
505 : array_writer_(s1->GetIsolate()), | |
506 line_ends1_(line_ends1), line_ends2_(line_ends2), s1_(s1), s2_(s2), | |
507 subrange_offset1_(0), subrange_offset2_(0) { | |
508 } | |
509 | |
510 void AddChunk(int line_pos1, int line_pos2, int line_len1, int line_len2) { | |
511 line_pos1 += subrange_offset1_; | |
512 line_pos2 += subrange_offset2_; | |
513 | |
514 int char_pos1 = line_ends1_.GetLineStart(line_pos1); | |
515 int char_pos2 = line_ends2_.GetLineStart(line_pos2); | |
516 int char_len1 = line_ends1_.GetLineStart(line_pos1 + line_len1) - char_pos1; | |
517 int char_len2 = line_ends2_.GetLineStart(line_pos2 + line_len2) - char_pos2; | |
518 | |
519 if (char_len1 < CHUNK_LEN_LIMIT && char_len2 < CHUNK_LEN_LIMIT) { | |
520 // Chunk is small enough to conduct a nested token-level diff. | |
521 HandleScope subTaskScope(s1_->GetIsolate()); | |
522 | |
523 TokensCompareInput tokens_input(s1_, char_pos1, char_len1, | |
524 s2_, char_pos2, char_len2); | |
525 TokensCompareOutput tokens_output(&array_writer_, char_pos1, | |
526 char_pos2); | |
527 | |
528 Comparator::CalculateDifference(&tokens_input, &tokens_output); | |
529 } else { | |
530 array_writer_.WriteChunk(char_pos1, char_pos2, char_len1, char_len2); | |
531 } | |
532 } | |
533 void SetSubrange1(int offset, int len) { | |
534 subrange_offset1_ = offset; | |
535 } | |
536 void SetSubrange2(int offset, int len) { | |
537 subrange_offset2_ = offset; | |
538 } | |
539 | |
540 Handle<JSArray> GetResult() { | |
541 return array_writer_.GetResult(); | |
542 } | |
543 | |
544 private: | |
545 static const int CHUNK_LEN_LIMIT = 800; | |
546 | |
547 CompareOutputArrayWriter array_writer_; | |
548 LineEndsWrapper line_ends1_; | |
549 LineEndsWrapper line_ends2_; | |
550 Handle<String> s1_; | |
551 Handle<String> s2_; | |
552 int subrange_offset1_; | |
553 int subrange_offset2_; | |
554 }; | |
555 | |
556 | |
557 Handle<JSArray> LiveEdit::CompareStrings(Handle<String> s1, | |
558 Handle<String> s2) { | |
559 s1 = String::Flatten(s1); | |
560 s2 = String::Flatten(s2); | |
561 | |
562 LineEndsWrapper line_ends1(s1); | |
563 LineEndsWrapper line_ends2(s2); | |
564 | |
565 LineArrayCompareInput input(s1, s2, line_ends1, line_ends2); | |
566 TokenizingLineArrayCompareOutput output(line_ends1, line_ends2, s1, s2); | |
567 | |
568 NarrowDownInput(&input, &output); | |
569 | |
570 Comparator::CalculateDifference(&input, &output); | |
571 | |
572 return output.GetResult(); | |
573 } | |
574 | |
575 | |
576 // Unwraps JSValue object, returning its field "value" | |
577 static Handle<Object> UnwrapJSValue(Handle<JSValue> jsValue) { | |
578 return Handle<Object>(jsValue->value(), jsValue->GetIsolate()); | |
579 } | |
580 | |
581 | |
582 // Wraps any object into a OpaqueReference, that will hide the object | |
583 // from JavaScript. | |
584 static Handle<JSValue> WrapInJSValue(Handle<HeapObject> object) { | |
585 Isolate* isolate = object->GetIsolate(); | |
586 Handle<JSFunction> constructor = isolate->opaque_reference_function(); | |
587 Handle<JSValue> result = | |
588 Handle<JSValue>::cast(isolate->factory()->NewJSObject(constructor)); | |
589 result->set_value(*object); | |
590 return result; | |
591 } | |
592 | |
593 | |
594 static Handle<SharedFunctionInfo> UnwrapSharedFunctionInfoFromJSValue( | |
595 Handle<JSValue> jsValue) { | |
596 Object* shared = jsValue->value(); | |
597 CHECK(shared->IsSharedFunctionInfo()); | |
598 return Handle<SharedFunctionInfo>(SharedFunctionInfo::cast(shared)); | |
599 } | |
600 | |
601 | |
602 static int GetArrayLength(Handle<JSArray> array) { | |
603 Object* length = array->length(); | |
604 CHECK(length->IsSmi()); | |
605 return Smi::cast(length)->value(); | |
606 } | |
607 | |
608 | |
609 void FunctionInfoWrapper::SetInitialProperties(Handle<String> name, | |
610 int start_position, | |
611 int end_position, int param_num, | |
612 int literal_count, | |
613 int parent_index) { | |
614 HandleScope scope(isolate()); | |
615 this->SetField(kFunctionNameOffset_, name); | |
616 this->SetSmiValueField(kStartPositionOffset_, start_position); | |
617 this->SetSmiValueField(kEndPositionOffset_, end_position); | |
618 this->SetSmiValueField(kParamNumOffset_, param_num); | |
619 this->SetSmiValueField(kLiteralNumOffset_, literal_count); | |
620 this->SetSmiValueField(kParentIndexOffset_, parent_index); | |
621 } | |
622 | |
623 | |
624 void FunctionInfoWrapper::SetFunctionCode(Handle<Code> function_code, | |
625 Handle<HeapObject> code_scope_info) { | |
626 Handle<JSValue> code_wrapper = WrapInJSValue(function_code); | |
627 this->SetField(kCodeOffset_, code_wrapper); | |
628 | |
629 Handle<JSValue> scope_wrapper = WrapInJSValue(code_scope_info); | |
630 this->SetField(kCodeScopeInfoOffset_, scope_wrapper); | |
631 } | |
632 | |
633 | |
634 void FunctionInfoWrapper::SetSharedFunctionInfo( | |
635 Handle<SharedFunctionInfo> info) { | |
636 Handle<JSValue> info_holder = WrapInJSValue(info); | |
637 this->SetField(kSharedFunctionInfoOffset_, info_holder); | |
638 } | |
639 | |
640 | |
641 Handle<Code> FunctionInfoWrapper::GetFunctionCode() { | |
642 Handle<Object> element = this->GetField(kCodeOffset_); | |
643 Handle<JSValue> value_wrapper = Handle<JSValue>::cast(element); | |
644 Handle<Object> raw_result = UnwrapJSValue(value_wrapper); | |
645 CHECK(raw_result->IsCode()); | |
646 return Handle<Code>::cast(raw_result); | |
647 } | |
648 | |
649 | |
650 MaybeHandle<TypeFeedbackVector> FunctionInfoWrapper::GetFeedbackVector() { | |
651 Handle<Object> element = this->GetField(kSharedFunctionInfoOffset_); | |
652 if (element->IsJSValue()) { | |
653 Handle<JSValue> value_wrapper = Handle<JSValue>::cast(element); | |
654 Handle<Object> raw_result = UnwrapJSValue(value_wrapper); | |
655 Handle<SharedFunctionInfo> shared = | |
656 Handle<SharedFunctionInfo>::cast(raw_result); | |
657 return Handle<TypeFeedbackVector>(shared->feedback_vector(), isolate()); | |
658 } else { | |
659 // Scripts may never have a SharedFunctionInfo created. | |
660 return MaybeHandle<TypeFeedbackVector>(); | |
661 } | |
662 } | |
663 | |
664 | |
665 Handle<Object> FunctionInfoWrapper::GetCodeScopeInfo() { | |
666 Handle<Object> element = this->GetField(kCodeScopeInfoOffset_); | |
667 return UnwrapJSValue(Handle<JSValue>::cast(element)); | |
668 } | |
669 | |
670 | |
671 void SharedInfoWrapper::SetProperties(Handle<String> name, | |
672 int start_position, | |
673 int end_position, | |
674 Handle<SharedFunctionInfo> info) { | |
675 HandleScope scope(isolate()); | |
676 this->SetField(kFunctionNameOffset_, name); | |
677 Handle<JSValue> info_holder = WrapInJSValue(info); | |
678 this->SetField(kSharedInfoOffset_, info_holder); | |
679 this->SetSmiValueField(kStartPositionOffset_, start_position); | |
680 this->SetSmiValueField(kEndPositionOffset_, end_position); | |
681 } | |
682 | |
683 | |
684 Handle<SharedFunctionInfo> SharedInfoWrapper::GetInfo() { | |
685 Handle<Object> element = this->GetField(kSharedInfoOffset_); | |
686 Handle<JSValue> value_wrapper = Handle<JSValue>::cast(element); | |
687 return UnwrapSharedFunctionInfoFromJSValue(value_wrapper); | |
688 } | |
689 | |
690 | |
691 class FunctionInfoListener { | |
692 public: | |
693 explicit FunctionInfoListener(Isolate* isolate) { | |
694 current_parent_index_ = -1; | |
695 len_ = 0; | |
696 result_ = isolate->factory()->NewJSArray(10); | |
697 } | |
698 | |
699 void FunctionStarted(FunctionLiteral* fun) { | |
700 HandleScope scope(isolate()); | |
701 FunctionInfoWrapper info = FunctionInfoWrapper::Create(isolate()); | |
702 info.SetInitialProperties(fun->name(), fun->start_position(), | |
703 fun->end_position(), fun->parameter_count(), | |
704 fun->materialized_literal_count(), | |
705 current_parent_index_); | |
706 current_parent_index_ = len_; | |
707 SetElementSloppy(result_, len_, info.GetJSArray()); | |
708 len_++; | |
709 } | |
710 | |
711 void FunctionDone() { | |
712 HandleScope scope(isolate()); | |
713 FunctionInfoWrapper info = | |
714 FunctionInfoWrapper::cast( | |
715 *Object::GetElement( | |
716 isolate(), result_, current_parent_index_).ToHandleChecked()); | |
717 current_parent_index_ = info.GetParentIndex(); | |
718 } | |
719 | |
720 // Saves only function code, because for a script function we | |
721 // may never create a SharedFunctionInfo object. | |
722 void FunctionCode(Handle<Code> function_code) { | |
723 FunctionInfoWrapper info = | |
724 FunctionInfoWrapper::cast( | |
725 *Object::GetElement( | |
726 isolate(), result_, current_parent_index_).ToHandleChecked()); | |
727 info.SetFunctionCode(function_code, | |
728 Handle<HeapObject>(isolate()->heap()->null_value())); | |
729 } | |
730 | |
731 // Saves full information about a function: its code, its scope info | |
732 // and a SharedFunctionInfo object. | |
733 void FunctionInfo(Handle<SharedFunctionInfo> shared, Scope* scope, | |
734 Zone* zone) { | |
735 if (!shared->IsSharedFunctionInfo()) { | |
736 return; | |
737 } | |
738 FunctionInfoWrapper info = | |
739 FunctionInfoWrapper::cast( | |
740 *Object::GetElement( | |
741 isolate(), result_, current_parent_index_).ToHandleChecked()); | |
742 info.SetFunctionCode(Handle<Code>(shared->code()), | |
743 Handle<HeapObject>(shared->scope_info())); | |
744 info.SetSharedFunctionInfo(shared); | |
745 | |
746 Handle<Object> scope_info_list = SerializeFunctionScope(scope, zone); | |
747 info.SetFunctionScopeInfo(scope_info_list); | |
748 } | |
749 | |
750 Handle<JSArray> GetResult() { return result_; } | |
751 | |
752 private: | |
753 Isolate* isolate() const { return result_->GetIsolate(); } | |
754 | |
755 Handle<Object> SerializeFunctionScope(Scope* scope, Zone* zone) { | |
756 Handle<JSArray> scope_info_list = isolate()->factory()->NewJSArray(10); | |
757 int scope_info_length = 0; | |
758 | |
759 // Saves some description of scope. It stores name and indexes of | |
760 // variables in the whole scope chain. Null-named slots delimit | |
761 // scopes of this chain. | |
762 Scope* current_scope = scope; | |
763 while (current_scope != NULL) { | |
764 HandleScope handle_scope(isolate()); | |
765 ZoneList<Variable*> stack_list(current_scope->StackLocalCount(), zone); | |
766 ZoneList<Variable*> context_list( | |
767 current_scope->ContextLocalCount(), zone); | |
768 ZoneList<Variable*> globals_list(current_scope->ContextGlobalCount(), | |
769 zone); | |
770 current_scope->CollectStackAndContextLocals(&stack_list, &context_list, | |
771 &globals_list); | |
772 context_list.Sort(&Variable::CompareIndex); | |
773 | |
774 for (int i = 0; i < context_list.length(); i++) { | |
775 SetElementSloppy(scope_info_list, | |
776 scope_info_length, | |
777 context_list[i]->name()); | |
778 scope_info_length++; | |
779 SetElementSloppy( | |
780 scope_info_list, | |
781 scope_info_length, | |
782 Handle<Smi>(Smi::FromInt(context_list[i]->index()), isolate())); | |
783 scope_info_length++; | |
784 } | |
785 SetElementSloppy(scope_info_list, | |
786 scope_info_length, | |
787 Handle<Object>(isolate()->heap()->null_value(), | |
788 isolate())); | |
789 scope_info_length++; | |
790 | |
791 current_scope = current_scope->outer_scope(); | |
792 } | |
793 | |
794 return scope_info_list; | |
795 } | |
796 | |
797 Handle<JSArray> result_; | |
798 int len_; | |
799 int current_parent_index_; | |
800 }; | |
801 | |
802 | |
803 void LiveEdit::InitializeThreadLocal(Debug* debug) { | |
804 debug->thread_local_.frame_drop_mode_ = LiveEdit::FRAMES_UNTOUCHED; | |
805 } | |
806 | |
807 | |
808 bool LiveEdit::SetAfterBreakTarget(Debug* debug) { | |
809 Code* code = NULL; | |
810 Isolate* isolate = debug->isolate_; | |
811 switch (debug->thread_local_.frame_drop_mode_) { | |
812 case FRAMES_UNTOUCHED: | |
813 return false; | |
814 case FRAME_DROPPED_IN_IC_CALL: | |
815 // We must have been calling IC stub. Do not go there anymore. | |
816 code = isolate->builtins()->builtin(Builtins::kPlainReturn_LiveEdit); | |
817 break; | |
818 case FRAME_DROPPED_IN_DEBUG_SLOT_CALL: | |
819 // Debug break slot stub does not return normally, instead it manually | |
820 // cleans the stack and jumps. We should patch the jump address. | |
821 code = isolate->builtins()->builtin(Builtins::kFrameDropper_LiveEdit); | |
822 break; | |
823 case FRAME_DROPPED_IN_DIRECT_CALL: | |
824 // Nothing to do, after_break_target is not used here. | |
825 return true; | |
826 case FRAME_DROPPED_IN_RETURN_CALL: | |
827 code = isolate->builtins()->builtin(Builtins::kFrameDropper_LiveEdit); | |
828 break; | |
829 case CURRENTLY_SET_MODE: | |
830 UNREACHABLE(); | |
831 break; | |
832 } | |
833 debug->after_break_target_ = code->entry(); | |
834 return true; | |
835 } | |
836 | |
837 | |
838 MaybeHandle<JSArray> LiveEdit::GatherCompileInfo(Handle<Script> script, | |
839 Handle<String> source) { | |
840 Isolate* isolate = script->GetIsolate(); | |
841 | |
842 FunctionInfoListener listener(isolate); | |
843 Handle<Object> original_source = | |
844 Handle<Object>(script->source(), isolate); | |
845 script->set_source(*source); | |
846 isolate->set_active_function_info_listener(&listener); | |
847 | |
848 { | |
849 // Creating verbose TryCatch from public API is currently the only way to | |
850 // force code save location. We do not use this the object directly. | |
851 v8::TryCatch try_catch(reinterpret_cast<v8::Isolate*>(isolate)); | |
852 try_catch.SetVerbose(true); | |
853 | |
854 // A logical 'try' section. | |
855 Compiler::CompileForLiveEdit(script); | |
856 } | |
857 | |
858 // A logical 'catch' section. | |
859 Handle<JSObject> rethrow_exception; | |
860 if (isolate->has_pending_exception()) { | |
861 Handle<Object> exception(isolate->pending_exception(), isolate); | |
862 MessageLocation message_location = isolate->GetMessageLocation(); | |
863 | |
864 isolate->clear_pending_message(); | |
865 isolate->clear_pending_exception(); | |
866 | |
867 // If possible, copy positions from message object to exception object. | |
868 if (exception->IsJSObject() && !message_location.script().is_null()) { | |
869 rethrow_exception = Handle<JSObject>::cast(exception); | |
870 | |
871 Factory* factory = isolate->factory(); | |
872 Handle<String> start_pos_key = factory->InternalizeOneByteString( | |
873 STATIC_CHAR_VECTOR("startPosition")); | |
874 Handle<String> end_pos_key = | |
875 factory->InternalizeOneByteString(STATIC_CHAR_VECTOR("endPosition")); | |
876 Handle<String> script_obj_key = | |
877 factory->InternalizeOneByteString(STATIC_CHAR_VECTOR("scriptObject")); | |
878 Handle<Smi> start_pos( | |
879 Smi::FromInt(message_location.start_pos()), isolate); | |
880 Handle<Smi> end_pos(Smi::FromInt(message_location.end_pos()), isolate); | |
881 Handle<JSObject> script_obj = | |
882 Script::GetWrapper(message_location.script()); | |
883 Object::SetProperty(rethrow_exception, start_pos_key, start_pos, SLOPPY) | |
884 .Assert(); | |
885 Object::SetProperty(rethrow_exception, end_pos_key, end_pos, SLOPPY) | |
886 .Assert(); | |
887 Object::SetProperty(rethrow_exception, script_obj_key, script_obj, SLOPPY) | |
888 .Assert(); | |
889 } | |
890 } | |
891 | |
892 // A logical 'finally' section. | |
893 isolate->set_active_function_info_listener(NULL); | |
894 script->set_source(*original_source); | |
895 | |
896 if (rethrow_exception.is_null()) { | |
897 return listener.GetResult(); | |
898 } else { | |
899 return isolate->Throw<JSArray>(rethrow_exception); | |
900 } | |
901 } | |
902 | |
903 | |
904 void LiveEdit::WrapSharedFunctionInfos(Handle<JSArray> array) { | |
905 Isolate* isolate = array->GetIsolate(); | |
906 HandleScope scope(isolate); | |
907 int len = GetArrayLength(array); | |
908 for (int i = 0; i < len; i++) { | |
909 Handle<SharedFunctionInfo> info( | |
910 SharedFunctionInfo::cast( | |
911 *Object::GetElement(isolate, array, i).ToHandleChecked())); | |
912 SharedInfoWrapper info_wrapper = SharedInfoWrapper::Create(isolate); | |
913 Handle<String> name_handle(String::cast(info->name())); | |
914 info_wrapper.SetProperties(name_handle, info->start_position(), | |
915 info->end_position(), info); | |
916 SetElementSloppy(array, i, info_wrapper.GetJSArray()); | |
917 } | |
918 } | |
919 | |
920 | |
921 // Visitor that finds all references to a particular code object, | |
922 // including "CODE_TARGET" references in other code objects and replaces | |
923 // them on the fly. | |
924 class ReplacingVisitor : public ObjectVisitor { | |
925 public: | |
926 explicit ReplacingVisitor(Code* original, Code* substitution) | |
927 : original_(original), substitution_(substitution) { | |
928 } | |
929 | |
930 virtual void VisitPointers(Object** start, Object** end) { | |
931 for (Object** p = start; p < end; p++) { | |
932 if (*p == original_) { | |
933 *p = substitution_; | |
934 } | |
935 } | |
936 } | |
937 | |
938 virtual void VisitCodeEntry(Address entry) { | |
939 if (Code::GetObjectFromEntryAddress(entry) == original_) { | |
940 Address substitution_entry = substitution_->instruction_start(); | |
941 Memory::Address_at(entry) = substitution_entry; | |
942 } | |
943 } | |
944 | |
945 virtual void VisitCodeTarget(RelocInfo* rinfo) { | |
946 if (RelocInfo::IsCodeTarget(rinfo->rmode()) && | |
947 Code::GetCodeFromTargetAddress(rinfo->target_address()) == original_) { | |
948 Address substitution_entry = substitution_->instruction_start(); | |
949 rinfo->set_target_address(substitution_entry); | |
950 } | |
951 } | |
952 | |
953 virtual void VisitDebugTarget(RelocInfo* rinfo) { | |
954 VisitCodeTarget(rinfo); | |
955 } | |
956 | |
957 private: | |
958 Code* original_; | |
959 Code* substitution_; | |
960 }; | |
961 | |
962 | |
963 // Finds all references to original and replaces them with substitution. | |
964 static void ReplaceCodeObject(Handle<Code> original, | |
965 Handle<Code> substitution) { | |
966 // Perform a full GC in order to ensure that we are not in the middle of an | |
967 // incremental marking phase when we are replacing the code object. | |
968 // Since we are not in an incremental marking phase we can write pointers | |
969 // to code objects (that are never in new space) without worrying about | |
970 // write barriers. | |
971 Heap* heap = original->GetHeap(); | |
972 HeapIterator iterator(heap); | |
973 | |
974 DCHECK(!heap->InNewSpace(*substitution)); | |
975 | |
976 ReplacingVisitor visitor(*original, *substitution); | |
977 | |
978 // Iterate over all roots. Stack frames may have pointer into original code, | |
979 // so temporary replace the pointers with offset numbers | |
980 // in prologue/epilogue. | |
981 heap->IterateRoots(&visitor, VISIT_ALL); | |
982 | |
983 // Now iterate over all pointers of all objects, including code_target | |
984 // implicit pointers. | |
985 for (HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next()) { | |
986 obj->Iterate(&visitor); | |
987 } | |
988 } | |
989 | |
990 | |
991 // Patch function literals. | |
992 // Name 'literals' is a misnomer. Rather it's a cache for complex object | |
993 // boilerplates and for a native context. We must clean cached values. | |
994 // Additionally we may need to allocate a new array if number of literals | |
995 // changed. | |
996 class LiteralFixer { | |
997 public: | |
998 static void PatchLiterals(FunctionInfoWrapper* compile_info_wrapper, | |
999 Handle<SharedFunctionInfo> shared_info, | |
1000 Isolate* isolate) { | |
1001 int new_literal_count = compile_info_wrapper->GetLiteralCount(); | |
1002 int old_literal_count = shared_info->num_literals(); | |
1003 | |
1004 if (old_literal_count == new_literal_count) { | |
1005 // If literal count didn't change, simply go over all functions | |
1006 // and clear literal arrays. | |
1007 ClearValuesVisitor visitor; | |
1008 IterateJSFunctions(shared_info, &visitor); | |
1009 } else { | |
1010 // When literal count changes, we have to create new array instances. | |
1011 // Since we cannot create instances when iterating heap, we should first | |
1012 // collect all functions and fix their literal arrays. | |
1013 Handle<FixedArray> function_instances = | |
1014 CollectJSFunctions(shared_info, isolate); | |
1015 for (int i = 0; i < function_instances->length(); i++) { | |
1016 Handle<JSFunction> fun(JSFunction::cast(function_instances->get(i))); | |
1017 Handle<FixedArray> new_literals = | |
1018 isolate->factory()->NewFixedArray(new_literal_count); | |
1019 fun->set_literals(*new_literals); | |
1020 } | |
1021 | |
1022 shared_info->set_num_literals(new_literal_count); | |
1023 } | |
1024 } | |
1025 | |
1026 private: | |
1027 // Iterates all function instances in the HEAP that refers to the | |
1028 // provided shared_info. | |
1029 template<typename Visitor> | |
1030 static void IterateJSFunctions(Handle<SharedFunctionInfo> shared_info, | |
1031 Visitor* visitor) { | |
1032 HeapIterator iterator(shared_info->GetHeap()); | |
1033 for (HeapObject* obj = iterator.next(); obj != NULL; | |
1034 obj = iterator.next()) { | |
1035 if (obj->IsJSFunction()) { | |
1036 JSFunction* function = JSFunction::cast(obj); | |
1037 if (function->shared() == *shared_info) { | |
1038 visitor->visit(function); | |
1039 } | |
1040 } | |
1041 } | |
1042 } | |
1043 | |
1044 // Finds all instances of JSFunction that refers to the provided shared_info | |
1045 // and returns array with them. | |
1046 static Handle<FixedArray> CollectJSFunctions( | |
1047 Handle<SharedFunctionInfo> shared_info, Isolate* isolate) { | |
1048 CountVisitor count_visitor; | |
1049 count_visitor.count = 0; | |
1050 IterateJSFunctions(shared_info, &count_visitor); | |
1051 int size = count_visitor.count; | |
1052 | |
1053 Handle<FixedArray> result = isolate->factory()->NewFixedArray(size); | |
1054 if (size > 0) { | |
1055 CollectVisitor collect_visitor(result); | |
1056 IterateJSFunctions(shared_info, &collect_visitor); | |
1057 } | |
1058 return result; | |
1059 } | |
1060 | |
1061 class ClearValuesVisitor { | |
1062 public: | |
1063 void visit(JSFunction* fun) { | |
1064 FixedArray* literals = fun->literals(); | |
1065 int len = literals->length(); | |
1066 for (int j = 0; j < len; j++) { | |
1067 literals->set_undefined(j); | |
1068 } | |
1069 } | |
1070 }; | |
1071 | |
1072 class CountVisitor { | |
1073 public: | |
1074 void visit(JSFunction* fun) { | |
1075 count++; | |
1076 } | |
1077 int count; | |
1078 }; | |
1079 | |
1080 class CollectVisitor { | |
1081 public: | |
1082 explicit CollectVisitor(Handle<FixedArray> output) | |
1083 : m_output(output), m_pos(0) {} | |
1084 | |
1085 void visit(JSFunction* fun) { | |
1086 m_output->set(m_pos, fun); | |
1087 m_pos++; | |
1088 } | |
1089 private: | |
1090 Handle<FixedArray> m_output; | |
1091 int m_pos; | |
1092 }; | |
1093 }; | |
1094 | |
1095 | |
1096 // Marks code that shares the same shared function info or has inlined | |
1097 // code that shares the same function info. | |
1098 class DependentFunctionMarker: public OptimizedFunctionVisitor { | |
1099 public: | |
1100 SharedFunctionInfo* shared_info_; | |
1101 bool found_; | |
1102 | |
1103 explicit DependentFunctionMarker(SharedFunctionInfo* shared_info) | |
1104 : shared_info_(shared_info), found_(false) { } | |
1105 | |
1106 virtual void EnterContext(Context* context) { } // Don't care. | |
1107 virtual void LeaveContext(Context* context) { } // Don't care. | |
1108 virtual void VisitFunction(JSFunction* function) { | |
1109 // It should be guaranteed by the iterator that everything is optimized. | |
1110 DCHECK(function->code()->kind() == Code::OPTIMIZED_FUNCTION); | |
1111 if (function->Inlines(shared_info_)) { | |
1112 // Mark the code for deoptimization. | |
1113 function->code()->set_marked_for_deoptimization(true); | |
1114 found_ = true; | |
1115 } | |
1116 } | |
1117 }; | |
1118 | |
1119 | |
1120 static void DeoptimizeDependentFunctions(SharedFunctionInfo* function_info) { | |
1121 DisallowHeapAllocation no_allocation; | |
1122 DependentFunctionMarker marker(function_info); | |
1123 // TODO(titzer): need to traverse all optimized code to find OSR code here. | |
1124 Deoptimizer::VisitAllOptimizedFunctions(function_info->GetIsolate(), &marker); | |
1125 | |
1126 if (marker.found_) { | |
1127 // Only go through with the deoptimization if something was found. | |
1128 Deoptimizer::DeoptimizeMarkedCode(function_info->GetIsolate()); | |
1129 } | |
1130 } | |
1131 | |
1132 | |
1133 void LiveEdit::ReplaceFunctionCode( | |
1134 Handle<JSArray> new_compile_info_array, | |
1135 Handle<JSArray> shared_info_array) { | |
1136 Isolate* isolate = new_compile_info_array->GetIsolate(); | |
1137 | |
1138 FunctionInfoWrapper compile_info_wrapper(new_compile_info_array); | |
1139 SharedInfoWrapper shared_info_wrapper(shared_info_array); | |
1140 | |
1141 Handle<SharedFunctionInfo> shared_info = shared_info_wrapper.GetInfo(); | |
1142 | |
1143 if (shared_info->code()->kind() == Code::FUNCTION) { | |
1144 Handle<Code> code = compile_info_wrapper.GetFunctionCode(); | |
1145 ReplaceCodeObject(Handle<Code>(shared_info->code()), code); | |
1146 Handle<Object> code_scope_info = compile_info_wrapper.GetCodeScopeInfo(); | |
1147 if (code_scope_info->IsFixedArray()) { | |
1148 shared_info->set_scope_info(ScopeInfo::cast(*code_scope_info)); | |
1149 } | |
1150 shared_info->DisableOptimization(kLiveEdit); | |
1151 // Update the type feedback vector, if needed. | |
1152 MaybeHandle<TypeFeedbackVector> feedback_vector = | |
1153 compile_info_wrapper.GetFeedbackVector(); | |
1154 if (!feedback_vector.is_null()) { | |
1155 shared_info->set_feedback_vector(*feedback_vector.ToHandleChecked()); | |
1156 } | |
1157 } | |
1158 | |
1159 int start_position = compile_info_wrapper.GetStartPosition(); | |
1160 int end_position = compile_info_wrapper.GetEndPosition(); | |
1161 shared_info->set_start_position(start_position); | |
1162 shared_info->set_end_position(end_position); | |
1163 | |
1164 LiteralFixer::PatchLiterals(&compile_info_wrapper, shared_info, isolate); | |
1165 | |
1166 shared_info->set_construct_stub( | |
1167 isolate->builtins()->builtin(Builtins::kJSConstructStubGeneric)); | |
1168 | |
1169 DeoptimizeDependentFunctions(*shared_info); | |
1170 isolate->compilation_cache()->Remove(shared_info); | |
1171 } | |
1172 | |
1173 | |
1174 void LiveEdit::FunctionSourceUpdated(Handle<JSArray> shared_info_array) { | |
1175 SharedInfoWrapper shared_info_wrapper(shared_info_array); | |
1176 Handle<SharedFunctionInfo> shared_info = shared_info_wrapper.GetInfo(); | |
1177 | |
1178 DeoptimizeDependentFunctions(*shared_info); | |
1179 shared_info_array->GetIsolate()->compilation_cache()->Remove(shared_info); | |
1180 } | |
1181 | |
1182 | |
1183 void LiveEdit::SetFunctionScript(Handle<JSValue> function_wrapper, | |
1184 Handle<Object> script_handle) { | |
1185 Handle<SharedFunctionInfo> shared_info = | |
1186 UnwrapSharedFunctionInfoFromJSValue(function_wrapper); | |
1187 CHECK(script_handle->IsScript() || script_handle->IsUndefined()); | |
1188 SharedFunctionInfo::SetScript(shared_info, script_handle); | |
1189 shared_info->DisableOptimization(kLiveEdit); | |
1190 | |
1191 function_wrapper->GetIsolate()->compilation_cache()->Remove(shared_info); | |
1192 } | |
1193 | |
1194 | |
1195 // For a script text change (defined as position_change_array), translates | |
1196 // position in unchanged text to position in changed text. | |
1197 // Text change is a set of non-overlapping regions in text, that have changed | |
1198 // their contents and length. It is specified as array of groups of 3 numbers: | |
1199 // (change_begin, change_end, change_end_new_position). | |
1200 // Each group describes a change in text; groups are sorted by change_begin. | |
1201 // Only position in text beyond any changes may be successfully translated. | |
1202 // If a positions is inside some region that changed, result is currently | |
1203 // undefined. | |
1204 static int TranslatePosition(int original_position, | |
1205 Handle<JSArray> position_change_array) { | |
1206 int position_diff = 0; | |
1207 int array_len = GetArrayLength(position_change_array); | |
1208 Isolate* isolate = position_change_array->GetIsolate(); | |
1209 // TODO(635): binary search may be used here | |
1210 for (int i = 0; i < array_len; i += 3) { | |
1211 HandleScope scope(isolate); | |
1212 Handle<Object> element = Object::GetElement( | |
1213 isolate, position_change_array, i).ToHandleChecked(); | |
1214 CHECK(element->IsSmi()); | |
1215 int chunk_start = Handle<Smi>::cast(element)->value(); | |
1216 if (original_position < chunk_start) { | |
1217 break; | |
1218 } | |
1219 element = Object::GetElement( | |
1220 isolate, position_change_array, i + 1).ToHandleChecked(); | |
1221 CHECK(element->IsSmi()); | |
1222 int chunk_end = Handle<Smi>::cast(element)->value(); | |
1223 // Position mustn't be inside a chunk. | |
1224 DCHECK(original_position >= chunk_end); | |
1225 element = Object::GetElement( | |
1226 isolate, position_change_array, i + 2).ToHandleChecked(); | |
1227 CHECK(element->IsSmi()); | |
1228 int chunk_changed_end = Handle<Smi>::cast(element)->value(); | |
1229 position_diff = chunk_changed_end - chunk_end; | |
1230 } | |
1231 | |
1232 return original_position + position_diff; | |
1233 } | |
1234 | |
1235 | |
1236 // Auto-growing buffer for writing relocation info code section. This buffer | |
1237 // is a simplified version of buffer from Assembler. Unlike Assembler, this | |
1238 // class is platform-independent and it works without dealing with instructions. | |
1239 // As specified by RelocInfo format, the buffer is filled in reversed order: | |
1240 // from upper to lower addresses. | |
1241 // It uses NewArray/DeleteArray for memory management. | |
1242 class RelocInfoBuffer { | |
1243 public: | |
1244 RelocInfoBuffer(int buffer_initial_capicity, byte* pc) { | |
1245 buffer_size_ = buffer_initial_capicity + kBufferGap; | |
1246 buffer_ = NewArray<byte>(buffer_size_); | |
1247 | |
1248 reloc_info_writer_.Reposition(buffer_ + buffer_size_, pc); | |
1249 } | |
1250 ~RelocInfoBuffer() { | |
1251 DeleteArray(buffer_); | |
1252 } | |
1253 | |
1254 // As specified by RelocInfo format, the buffer is filled in reversed order: | |
1255 // from upper to lower addresses. | |
1256 void Write(const RelocInfo* rinfo) { | |
1257 if (buffer_ + kBufferGap >= reloc_info_writer_.pos()) { | |
1258 Grow(); | |
1259 } | |
1260 reloc_info_writer_.Write(rinfo); | |
1261 } | |
1262 | |
1263 Vector<byte> GetResult() { | |
1264 // Return the bytes from pos up to end of buffer. | |
1265 int result_size = | |
1266 static_cast<int>((buffer_ + buffer_size_) - reloc_info_writer_.pos()); | |
1267 return Vector<byte>(reloc_info_writer_.pos(), result_size); | |
1268 } | |
1269 | |
1270 private: | |
1271 void Grow() { | |
1272 // Compute new buffer size. | |
1273 int new_buffer_size; | |
1274 if (buffer_size_ < 2 * KB) { | |
1275 new_buffer_size = 4 * KB; | |
1276 } else { | |
1277 new_buffer_size = 2 * buffer_size_; | |
1278 } | |
1279 // Some internal data structures overflow for very large buffers, | |
1280 // they must ensure that kMaximalBufferSize is not too large. | |
1281 if (new_buffer_size > kMaximalBufferSize) { | |
1282 V8::FatalProcessOutOfMemory("RelocInfoBuffer::GrowBuffer"); | |
1283 } | |
1284 | |
1285 // Set up new buffer. | |
1286 byte* new_buffer = NewArray<byte>(new_buffer_size); | |
1287 | |
1288 // Copy the data. | |
1289 int curently_used_size = | |
1290 static_cast<int>(buffer_ + buffer_size_ - reloc_info_writer_.pos()); | |
1291 MemMove(new_buffer + new_buffer_size - curently_used_size, | |
1292 reloc_info_writer_.pos(), curently_used_size); | |
1293 | |
1294 reloc_info_writer_.Reposition( | |
1295 new_buffer + new_buffer_size - curently_used_size, | |
1296 reloc_info_writer_.last_pc()); | |
1297 | |
1298 DeleteArray(buffer_); | |
1299 buffer_ = new_buffer; | |
1300 buffer_size_ = new_buffer_size; | |
1301 } | |
1302 | |
1303 RelocInfoWriter reloc_info_writer_; | |
1304 byte* buffer_; | |
1305 int buffer_size_; | |
1306 | |
1307 static const int kBufferGap = RelocInfoWriter::kMaxSize; | |
1308 static const int kMaximalBufferSize = 512*MB; | |
1309 }; | |
1310 | |
1311 | |
1312 // Patch positions in code (changes relocation info section) and possibly | |
1313 // returns new instance of code. | |
1314 static Handle<Code> PatchPositionsInCode( | |
1315 Handle<Code> code, | |
1316 Handle<JSArray> position_change_array) { | |
1317 Isolate* isolate = code->GetIsolate(); | |
1318 | |
1319 RelocInfoBuffer buffer_writer(code->relocation_size(), | |
1320 code->instruction_start()); | |
1321 | |
1322 { | |
1323 for (RelocIterator it(*code); !it.done(); it.next()) { | |
1324 RelocInfo* rinfo = it.rinfo(); | |
1325 if (RelocInfo::IsPosition(rinfo->rmode())) { | |
1326 int position = static_cast<int>(rinfo->data()); | |
1327 int new_position = TranslatePosition(position, | |
1328 position_change_array); | |
1329 if (position != new_position) { | |
1330 RelocInfo info_copy(rinfo->pc(), rinfo->rmode(), new_position, NULL); | |
1331 buffer_writer.Write(&info_copy); | |
1332 continue; | |
1333 } | |
1334 } | |
1335 if (RelocInfo::IsRealRelocMode(rinfo->rmode())) { | |
1336 buffer_writer.Write(it.rinfo()); | |
1337 } | |
1338 } | |
1339 } | |
1340 | |
1341 Vector<byte> buffer = buffer_writer.GetResult(); | |
1342 | |
1343 if (buffer.length() == code->relocation_size()) { | |
1344 // Simply patch relocation area of code. | |
1345 MemCopy(code->relocation_start(), buffer.start(), buffer.length()); | |
1346 return code; | |
1347 } else { | |
1348 // Relocation info section now has different size. We cannot simply | |
1349 // rewrite it inside code object. Instead we have to create a new | |
1350 // code object. | |
1351 Handle<Code> result(isolate->factory()->CopyCode(code, buffer)); | |
1352 return result; | |
1353 } | |
1354 } | |
1355 | |
1356 | |
1357 void LiveEdit::PatchFunctionPositions(Handle<JSArray> shared_info_array, | |
1358 Handle<JSArray> position_change_array) { | |
1359 SharedInfoWrapper shared_info_wrapper(shared_info_array); | |
1360 Handle<SharedFunctionInfo> info = shared_info_wrapper.GetInfo(); | |
1361 | |
1362 int old_function_start = info->start_position(); | |
1363 int new_function_start = TranslatePosition(old_function_start, | |
1364 position_change_array); | |
1365 int new_function_end = TranslatePosition(info->end_position(), | |
1366 position_change_array); | |
1367 int new_function_token_pos = | |
1368 TranslatePosition(info->function_token_position(), position_change_array); | |
1369 | |
1370 info->set_start_position(new_function_start); | |
1371 info->set_end_position(new_function_end); | |
1372 info->set_function_token_position(new_function_token_pos); | |
1373 | |
1374 if (info->code()->kind() == Code::FUNCTION) { | |
1375 // Patch relocation info section of the code. | |
1376 Handle<Code> patched_code = PatchPositionsInCode(Handle<Code>(info->code()), | |
1377 position_change_array); | |
1378 if (*patched_code != info->code()) { | |
1379 // Replace all references to the code across the heap. In particular, | |
1380 // some stubs may refer to this code and this code may be being executed | |
1381 // on stack (it is safe to substitute the code object on stack, because | |
1382 // we only change the structure of rinfo and leave instructions | |
1383 // untouched). | |
1384 ReplaceCodeObject(Handle<Code>(info->code()), patched_code); | |
1385 } | |
1386 } | |
1387 } | |
1388 | |
1389 | |
1390 static Handle<Script> CreateScriptCopy(Handle<Script> original) { | |
1391 Isolate* isolate = original->GetIsolate(); | |
1392 | |
1393 Handle<String> original_source(String::cast(original->source())); | |
1394 Handle<Script> copy = isolate->factory()->NewScript(original_source); | |
1395 | |
1396 copy->set_name(original->name()); | |
1397 copy->set_line_offset(original->line_offset()); | |
1398 copy->set_column_offset(original->column_offset()); | |
1399 copy->set_type(original->type()); | |
1400 copy->set_context_data(original->context_data()); | |
1401 copy->set_eval_from_shared(original->eval_from_shared()); | |
1402 copy->set_eval_from_instructions_offset( | |
1403 original->eval_from_instructions_offset()); | |
1404 | |
1405 // Copy all the flags, but clear compilation state. | |
1406 copy->set_flags(original->flags()); | |
1407 copy->set_compilation_state(Script::COMPILATION_STATE_INITIAL); | |
1408 | |
1409 return copy; | |
1410 } | |
1411 | |
1412 | |
1413 Handle<Object> LiveEdit::ChangeScriptSource(Handle<Script> original_script, | |
1414 Handle<String> new_source, | |
1415 Handle<Object> old_script_name) { | |
1416 Isolate* isolate = original_script->GetIsolate(); | |
1417 Handle<Object> old_script_object; | |
1418 if (old_script_name->IsString()) { | |
1419 Handle<Script> old_script = CreateScriptCopy(original_script); | |
1420 old_script->set_name(String::cast(*old_script_name)); | |
1421 old_script_object = old_script; | |
1422 isolate->debug()->OnAfterCompile(old_script); | |
1423 } else { | |
1424 old_script_object = isolate->factory()->null_value(); | |
1425 } | |
1426 | |
1427 original_script->set_source(*new_source); | |
1428 | |
1429 // Drop line ends so that they will be recalculated. | |
1430 original_script->set_line_ends(isolate->heap()->undefined_value()); | |
1431 | |
1432 return old_script_object; | |
1433 } | |
1434 | |
1435 | |
1436 | |
1437 void LiveEdit::ReplaceRefToNestedFunction( | |
1438 Handle<JSValue> parent_function_wrapper, | |
1439 Handle<JSValue> orig_function_wrapper, | |
1440 Handle<JSValue> subst_function_wrapper) { | |
1441 | |
1442 Handle<SharedFunctionInfo> parent_shared = | |
1443 UnwrapSharedFunctionInfoFromJSValue(parent_function_wrapper); | |
1444 Handle<SharedFunctionInfo> orig_shared = | |
1445 UnwrapSharedFunctionInfoFromJSValue(orig_function_wrapper); | |
1446 Handle<SharedFunctionInfo> subst_shared = | |
1447 UnwrapSharedFunctionInfoFromJSValue(subst_function_wrapper); | |
1448 | |
1449 for (RelocIterator it(parent_shared->code()); !it.done(); it.next()) { | |
1450 if (it.rinfo()->rmode() == RelocInfo::EMBEDDED_OBJECT) { | |
1451 if (it.rinfo()->target_object() == *orig_shared) { | |
1452 it.rinfo()->set_target_object(*subst_shared); | |
1453 } | |
1454 } | |
1455 } | |
1456 } | |
1457 | |
1458 | |
1459 // Check an activation against list of functions. If there is a function | |
1460 // that matches, its status in result array is changed to status argument value. | |
1461 static bool CheckActivation(Handle<JSArray> shared_info_array, | |
1462 Handle<JSArray> result, | |
1463 StackFrame* frame, | |
1464 LiveEdit::FunctionPatchabilityStatus status) { | |
1465 if (!frame->is_java_script()) return false; | |
1466 | |
1467 Handle<JSFunction> function(JavaScriptFrame::cast(frame)->function()); | |
1468 | |
1469 Isolate* isolate = shared_info_array->GetIsolate(); | |
1470 int len = GetArrayLength(shared_info_array); | |
1471 for (int i = 0; i < len; i++) { | |
1472 HandleScope scope(isolate); | |
1473 Handle<Object> element = | |
1474 Object::GetElement(isolate, shared_info_array, i).ToHandleChecked(); | |
1475 Handle<JSValue> jsvalue = Handle<JSValue>::cast(element); | |
1476 Handle<SharedFunctionInfo> shared = | |
1477 UnwrapSharedFunctionInfoFromJSValue(jsvalue); | |
1478 | |
1479 if (function->Inlines(*shared)) { | |
1480 SetElementSloppy(result, i, Handle<Smi>(Smi::FromInt(status), isolate)); | |
1481 return true; | |
1482 } | |
1483 } | |
1484 return false; | |
1485 } | |
1486 | |
1487 | |
1488 // Iterates over handler chain and removes all elements that are inside | |
1489 // frames being dropped. | |
1490 static bool FixTryCatchHandler(StackFrame* top_frame, | |
1491 StackFrame* bottom_frame) { | |
1492 Address* pointer_address = | |
1493 &Memory::Address_at(top_frame->isolate()->get_address_from_id( | |
1494 Isolate::kHandlerAddress)); | |
1495 | |
1496 while (*pointer_address < top_frame->sp()) { | |
1497 pointer_address = &Memory::Address_at(*pointer_address); | |
1498 } | |
1499 Address* above_frame_address = pointer_address; | |
1500 while (*pointer_address < bottom_frame->fp()) { | |
1501 pointer_address = &Memory::Address_at(*pointer_address); | |
1502 } | |
1503 bool change = *above_frame_address != *pointer_address; | |
1504 *above_frame_address = *pointer_address; | |
1505 return change; | |
1506 } | |
1507 | |
1508 | |
1509 // Initializes an artificial stack frame. The data it contains is used for: | |
1510 // a. successful work of frame dropper code which eventually gets control, | |
1511 // b. being compatible with regular stack structure for various stack | |
1512 // iterators. | |
1513 // Returns address of stack allocated pointer to restarted function, | |
1514 // the value that is called 'restarter_frame_function_pointer'. The value | |
1515 // at this address (possibly updated by GC) may be used later when preparing | |
1516 // 'step in' operation. | |
1517 // Frame structure (conforms InternalFrame structure): | |
1518 // -- code | |
1519 // -- SMI maker | |
1520 // -- function (slot is called "context") | |
1521 // -- frame base | |
1522 static Object** SetUpFrameDropperFrame(StackFrame* bottom_js_frame, | |
1523 Handle<Code> code) { | |
1524 DCHECK(bottom_js_frame->is_java_script()); | |
1525 | |
1526 Address fp = bottom_js_frame->fp(); | |
1527 | |
1528 // Move function pointer into "context" slot. | |
1529 Memory::Object_at(fp + StandardFrameConstants::kContextOffset) = | |
1530 Memory::Object_at(fp + JavaScriptFrameConstants::kFunctionOffset); | |
1531 | |
1532 Memory::Object_at(fp + InternalFrameConstants::kCodeOffset) = *code; | |
1533 Memory::Object_at(fp + StandardFrameConstants::kMarkerOffset) = | |
1534 Smi::FromInt(StackFrame::INTERNAL); | |
1535 | |
1536 return reinterpret_cast<Object**>(&Memory::Object_at( | |
1537 fp + StandardFrameConstants::kContextOffset)); | |
1538 } | |
1539 | |
1540 | |
1541 // Removes specified range of frames from stack. There may be 1 or more | |
1542 // frames in range. Anyway the bottom frame is restarted rather than dropped, | |
1543 // and therefore has to be a JavaScript frame. | |
1544 // Returns error message or NULL. | |
1545 static const char* DropFrames(Vector<StackFrame*> frames, | |
1546 int top_frame_index, | |
1547 int bottom_js_frame_index, | |
1548 LiveEdit::FrameDropMode* mode, | |
1549 Object*** restarter_frame_function_pointer) { | |
1550 if (!LiveEdit::kFrameDropperSupported) { | |
1551 return "Stack manipulations are not supported in this architecture."; | |
1552 } | |
1553 | |
1554 StackFrame* pre_top_frame = frames[top_frame_index - 1]; | |
1555 StackFrame* top_frame = frames[top_frame_index]; | |
1556 StackFrame* bottom_js_frame = frames[bottom_js_frame_index]; | |
1557 | |
1558 DCHECK(bottom_js_frame->is_java_script()); | |
1559 | |
1560 // Check the nature of the top frame. | |
1561 Isolate* isolate = bottom_js_frame->isolate(); | |
1562 Code* pre_top_frame_code = pre_top_frame->LookupCode(); | |
1563 bool frame_has_padding = true; | |
1564 if (pre_top_frame_code->is_inline_cache_stub() && | |
1565 pre_top_frame_code->is_debug_stub()) { | |
1566 // OK, we can drop inline cache calls. | |
1567 *mode = LiveEdit::FRAME_DROPPED_IN_IC_CALL; | |
1568 } else if (pre_top_frame_code == | |
1569 isolate->builtins()->builtin(Builtins::kSlot_DebugBreak)) { | |
1570 // OK, we can drop debug break slot. | |
1571 *mode = LiveEdit::FRAME_DROPPED_IN_DEBUG_SLOT_CALL; | |
1572 } else if (pre_top_frame_code == | |
1573 isolate->builtins()->builtin(Builtins::kFrameDropper_LiveEdit)) { | |
1574 // OK, we can drop our own code. | |
1575 pre_top_frame = frames[top_frame_index - 2]; | |
1576 top_frame = frames[top_frame_index - 1]; | |
1577 *mode = LiveEdit::CURRENTLY_SET_MODE; | |
1578 frame_has_padding = false; | |
1579 } else if (pre_top_frame_code == | |
1580 isolate->builtins()->builtin(Builtins::kReturn_DebugBreak)) { | |
1581 *mode = LiveEdit::FRAME_DROPPED_IN_RETURN_CALL; | |
1582 } else if (pre_top_frame_code->kind() == Code::STUB && | |
1583 CodeStub::GetMajorKey(pre_top_frame_code) == CodeStub::CEntry) { | |
1584 // Entry from our unit tests on 'debugger' statement. | |
1585 // It's fine, we support this case. | |
1586 *mode = LiveEdit::FRAME_DROPPED_IN_DIRECT_CALL; | |
1587 // We don't have a padding from 'debugger' statement call. | |
1588 // Here the stub is CEntry, it's not debug-only and can't be padded. | |
1589 // If anyone would complain, a proxy padded stub could be added. | |
1590 frame_has_padding = false; | |
1591 } else if (pre_top_frame->type() == StackFrame::ARGUMENTS_ADAPTOR) { | |
1592 // This must be adaptor that remain from the frame dropping that | |
1593 // is still on stack. A frame dropper frame must be above it. | |
1594 DCHECK(frames[top_frame_index - 2]->LookupCode() == | |
1595 isolate->builtins()->builtin(Builtins::kFrameDropper_LiveEdit)); | |
1596 pre_top_frame = frames[top_frame_index - 3]; | |
1597 top_frame = frames[top_frame_index - 2]; | |
1598 *mode = LiveEdit::CURRENTLY_SET_MODE; | |
1599 frame_has_padding = false; | |
1600 } else { | |
1601 return "Unknown structure of stack above changing function"; | |
1602 } | |
1603 | |
1604 Address unused_stack_top = top_frame->sp(); | |
1605 int new_frame_size = LiveEdit::kFrameDropperFrameSize * kPointerSize; | |
1606 Address unused_stack_bottom = bottom_js_frame->fp() | |
1607 - new_frame_size + kPointerSize; // Bigger address end is exclusive. | |
1608 | |
1609 Address* top_frame_pc_address = top_frame->pc_address(); | |
1610 | |
1611 // top_frame may be damaged below this point. Do not used it. | |
1612 DCHECK(!(top_frame = NULL)); | |
1613 | |
1614 if (unused_stack_top > unused_stack_bottom) { | |
1615 if (frame_has_padding) { | |
1616 int shortage_bytes = | |
1617 static_cast<int>(unused_stack_top - unused_stack_bottom); | |
1618 | |
1619 Address padding_start = pre_top_frame->fp() - | |
1620 LiveEdit::kFrameDropperFrameSize * kPointerSize; | |
1621 | |
1622 Address padding_pointer = padding_start; | |
1623 Smi* padding_object = Smi::FromInt(LiveEdit::kFramePaddingValue); | |
1624 while (Memory::Object_at(padding_pointer) == padding_object) { | |
1625 padding_pointer -= kPointerSize; | |
1626 } | |
1627 int padding_counter = | |
1628 Smi::cast(Memory::Object_at(padding_pointer))->value(); | |
1629 if (padding_counter * kPointerSize < shortage_bytes) { | |
1630 return "Not enough space for frame dropper frame " | |
1631 "(even with padding frame)"; | |
1632 } | |
1633 Memory::Object_at(padding_pointer) = | |
1634 Smi::FromInt(padding_counter - shortage_bytes / kPointerSize); | |
1635 | |
1636 StackFrame* pre_pre_frame = frames[top_frame_index - 2]; | |
1637 | |
1638 MemMove(padding_start + kPointerSize - shortage_bytes, | |
1639 padding_start + kPointerSize, | |
1640 LiveEdit::kFrameDropperFrameSize * kPointerSize); | |
1641 | |
1642 pre_top_frame->UpdateFp(pre_top_frame->fp() - shortage_bytes); | |
1643 pre_pre_frame->SetCallerFp(pre_top_frame->fp()); | |
1644 unused_stack_top -= shortage_bytes; | |
1645 | |
1646 STATIC_ASSERT(sizeof(Address) == kPointerSize); | |
1647 top_frame_pc_address -= shortage_bytes / kPointerSize; | |
1648 } else { | |
1649 return "Not enough space for frame dropper frame"; | |
1650 } | |
1651 } | |
1652 | |
1653 // Committing now. After this point we should return only NULL value. | |
1654 | |
1655 FixTryCatchHandler(pre_top_frame, bottom_js_frame); | |
1656 // Make sure FixTryCatchHandler is idempotent. | |
1657 DCHECK(!FixTryCatchHandler(pre_top_frame, bottom_js_frame)); | |
1658 | |
1659 Handle<Code> code = isolate->builtins()->FrameDropper_LiveEdit(); | |
1660 *top_frame_pc_address = code->entry(); | |
1661 pre_top_frame->SetCallerFp(bottom_js_frame->fp()); | |
1662 | |
1663 *restarter_frame_function_pointer = | |
1664 SetUpFrameDropperFrame(bottom_js_frame, code); | |
1665 | |
1666 DCHECK((**restarter_frame_function_pointer)->IsJSFunction()); | |
1667 | |
1668 for (Address a = unused_stack_top; | |
1669 a < unused_stack_bottom; | |
1670 a += kPointerSize) { | |
1671 Memory::Object_at(a) = Smi::FromInt(0); | |
1672 } | |
1673 | |
1674 return NULL; | |
1675 } | |
1676 | |
1677 | |
1678 // Describes a set of call frames that execute any of listed functions. | |
1679 // Finding no such frames does not mean error. | |
1680 class MultipleFunctionTarget { | |
1681 public: | |
1682 MultipleFunctionTarget(Handle<JSArray> shared_info_array, | |
1683 Handle<JSArray> result) | |
1684 : m_shared_info_array(shared_info_array), | |
1685 m_result(result) {} | |
1686 bool MatchActivation(StackFrame* frame, | |
1687 LiveEdit::FunctionPatchabilityStatus status) { | |
1688 return CheckActivation(m_shared_info_array, m_result, frame, status); | |
1689 } | |
1690 const char* GetNotFoundMessage() const { | |
1691 return NULL; | |
1692 } | |
1693 private: | |
1694 Handle<JSArray> m_shared_info_array; | |
1695 Handle<JSArray> m_result; | |
1696 }; | |
1697 | |
1698 | |
1699 // Drops all call frame matched by target and all frames above them. | |
1700 template <typename TARGET> | |
1701 static const char* DropActivationsInActiveThreadImpl(Isolate* isolate, | |
1702 TARGET& target, // NOLINT | |
1703 bool do_drop) { | |
1704 Debug* debug = isolate->debug(); | |
1705 Zone zone; | |
1706 Vector<StackFrame*> frames = CreateStackMap(isolate, &zone); | |
1707 | |
1708 | |
1709 int top_frame_index = -1; | |
1710 int frame_index = 0; | |
1711 for (; frame_index < frames.length(); frame_index++) { | |
1712 StackFrame* frame = frames[frame_index]; | |
1713 if (frame->id() == debug->break_frame_id()) { | |
1714 top_frame_index = frame_index; | |
1715 break; | |
1716 } | |
1717 if (target.MatchActivation( | |
1718 frame, LiveEdit::FUNCTION_BLOCKED_UNDER_NATIVE_CODE)) { | |
1719 // We are still above break_frame. It is not a target frame, | |
1720 // it is a problem. | |
1721 return "Debugger mark-up on stack is not found"; | |
1722 } | |
1723 } | |
1724 | |
1725 if (top_frame_index == -1) { | |
1726 // We haven't found break frame, but no function is blocking us anyway. | |
1727 return target.GetNotFoundMessage(); | |
1728 } | |
1729 | |
1730 bool target_frame_found = false; | |
1731 int bottom_js_frame_index = top_frame_index; | |
1732 bool non_droppable_frame_found = false; | |
1733 LiveEdit::FunctionPatchabilityStatus non_droppable_reason; | |
1734 | |
1735 for (; frame_index < frames.length(); frame_index++) { | |
1736 StackFrame* frame = frames[frame_index]; | |
1737 if (frame->is_exit()) { | |
1738 non_droppable_frame_found = true; | |
1739 non_droppable_reason = LiveEdit::FUNCTION_BLOCKED_UNDER_NATIVE_CODE; | |
1740 break; | |
1741 } | |
1742 if (frame->is_java_script() && | |
1743 JavaScriptFrame::cast(frame)->function()->shared()->is_generator()) { | |
1744 non_droppable_frame_found = true; | |
1745 non_droppable_reason = LiveEdit::FUNCTION_BLOCKED_UNDER_GENERATOR; | |
1746 break; | |
1747 } | |
1748 if (target.MatchActivation( | |
1749 frame, LiveEdit::FUNCTION_BLOCKED_ON_ACTIVE_STACK)) { | |
1750 target_frame_found = true; | |
1751 bottom_js_frame_index = frame_index; | |
1752 } | |
1753 } | |
1754 | |
1755 if (non_droppable_frame_found) { | |
1756 // There is a C or generator frame on stack. We can't drop C frames, and we | |
1757 // can't restart generators. Check that there are no target frames below | |
1758 // them. | |
1759 for (; frame_index < frames.length(); frame_index++) { | |
1760 StackFrame* frame = frames[frame_index]; | |
1761 if (frame->is_java_script()) { | |
1762 if (target.MatchActivation(frame, non_droppable_reason)) { | |
1763 // Fail. | |
1764 return NULL; | |
1765 } | |
1766 } | |
1767 } | |
1768 } | |
1769 | |
1770 if (!do_drop) { | |
1771 // We are in check-only mode. | |
1772 return NULL; | |
1773 } | |
1774 | |
1775 if (!target_frame_found) { | |
1776 // Nothing to drop. | |
1777 return target.GetNotFoundMessage(); | |
1778 } | |
1779 | |
1780 LiveEdit::FrameDropMode drop_mode = LiveEdit::FRAMES_UNTOUCHED; | |
1781 Object** restarter_frame_function_pointer = NULL; | |
1782 const char* error_message = DropFrames(frames, top_frame_index, | |
1783 bottom_js_frame_index, &drop_mode, | |
1784 &restarter_frame_function_pointer); | |
1785 | |
1786 if (error_message != NULL) { | |
1787 return error_message; | |
1788 } | |
1789 | |
1790 // Adjust break_frame after some frames has been dropped. | |
1791 StackFrame::Id new_id = StackFrame::NO_ID; | |
1792 for (int i = bottom_js_frame_index + 1; i < frames.length(); i++) { | |
1793 if (frames[i]->type() == StackFrame::JAVA_SCRIPT) { | |
1794 new_id = frames[i]->id(); | |
1795 break; | |
1796 } | |
1797 } | |
1798 debug->FramesHaveBeenDropped( | |
1799 new_id, drop_mode, restarter_frame_function_pointer); | |
1800 return NULL; | |
1801 } | |
1802 | |
1803 | |
1804 // Fills result array with statuses of functions. Modifies the stack | |
1805 // removing all listed function if possible and if do_drop is true. | |
1806 static const char* DropActivationsInActiveThread( | |
1807 Handle<JSArray> shared_info_array, Handle<JSArray> result, bool do_drop) { | |
1808 MultipleFunctionTarget target(shared_info_array, result); | |
1809 Isolate* isolate = shared_info_array->GetIsolate(); | |
1810 | |
1811 const char* message = | |
1812 DropActivationsInActiveThreadImpl(isolate, target, do_drop); | |
1813 if (message) { | |
1814 return message; | |
1815 } | |
1816 | |
1817 int array_len = GetArrayLength(shared_info_array); | |
1818 | |
1819 // Replace "blocked on active" with "replaced on active" status. | |
1820 for (int i = 0; i < array_len; i++) { | |
1821 Handle<Object> obj = | |
1822 Object::GetElement(isolate, result, i).ToHandleChecked(); | |
1823 if (*obj == Smi::FromInt(LiveEdit::FUNCTION_BLOCKED_ON_ACTIVE_STACK)) { | |
1824 Handle<Object> replaced( | |
1825 Smi::FromInt(LiveEdit::FUNCTION_REPLACED_ON_ACTIVE_STACK), isolate); | |
1826 SetElementSloppy(result, i, replaced); | |
1827 } | |
1828 } | |
1829 return NULL; | |
1830 } | |
1831 | |
1832 | |
1833 bool LiveEdit::FindActiveGenerators(Handle<FixedArray> shared_info_array, | |
1834 Handle<FixedArray> result, | |
1835 int len) { | |
1836 Isolate* isolate = shared_info_array->GetIsolate(); | |
1837 bool found_suspended_activations = false; | |
1838 | |
1839 DCHECK_LE(len, result->length()); | |
1840 | |
1841 FunctionPatchabilityStatus active = FUNCTION_BLOCKED_ACTIVE_GENERATOR; | |
1842 | |
1843 Heap* heap = isolate->heap(); | |
1844 HeapIterator iterator(heap); | |
1845 HeapObject* obj = NULL; | |
1846 while ((obj = iterator.next()) != NULL) { | |
1847 if (!obj->IsJSGeneratorObject()) continue; | |
1848 | |
1849 JSGeneratorObject* gen = JSGeneratorObject::cast(obj); | |
1850 if (gen->is_closed()) continue; | |
1851 | |
1852 HandleScope scope(isolate); | |
1853 | |
1854 for (int i = 0; i < len; i++) { | |
1855 Handle<JSValue> jsvalue = | |
1856 Handle<JSValue>::cast(FixedArray::get(shared_info_array, i)); | |
1857 Handle<SharedFunctionInfo> shared = | |
1858 UnwrapSharedFunctionInfoFromJSValue(jsvalue); | |
1859 | |
1860 if (gen->function()->shared() == *shared) { | |
1861 result->set(i, Smi::FromInt(active)); | |
1862 found_suspended_activations = true; | |
1863 } | |
1864 } | |
1865 } | |
1866 | |
1867 return found_suspended_activations; | |
1868 } | |
1869 | |
1870 | |
1871 class InactiveThreadActivationsChecker : public ThreadVisitor { | |
1872 public: | |
1873 InactiveThreadActivationsChecker(Handle<JSArray> shared_info_array, | |
1874 Handle<JSArray> result) | |
1875 : shared_info_array_(shared_info_array), result_(result), | |
1876 has_blocked_functions_(false) { | |
1877 } | |
1878 void VisitThread(Isolate* isolate, ThreadLocalTop* top) { | |
1879 for (StackFrameIterator it(isolate, top); !it.done(); it.Advance()) { | |
1880 has_blocked_functions_ |= CheckActivation( | |
1881 shared_info_array_, result_, it.frame(), | |
1882 LiveEdit::FUNCTION_BLOCKED_ON_OTHER_STACK); | |
1883 } | |
1884 } | |
1885 bool HasBlockedFunctions() { | |
1886 return has_blocked_functions_; | |
1887 } | |
1888 | |
1889 private: | |
1890 Handle<JSArray> shared_info_array_; | |
1891 Handle<JSArray> result_; | |
1892 bool has_blocked_functions_; | |
1893 }; | |
1894 | |
1895 | |
1896 Handle<JSArray> LiveEdit::CheckAndDropActivations( | |
1897 Handle<JSArray> shared_info_array, bool do_drop) { | |
1898 Isolate* isolate = shared_info_array->GetIsolate(); | |
1899 int len = GetArrayLength(shared_info_array); | |
1900 | |
1901 DCHECK(shared_info_array->HasFastElements()); | |
1902 Handle<FixedArray> shared_info_array_elements( | |
1903 FixedArray::cast(shared_info_array->elements())); | |
1904 | |
1905 Handle<JSArray> result = isolate->factory()->NewJSArray(len); | |
1906 Handle<FixedArray> result_elements = | |
1907 JSObject::EnsureWritableFastElements(result); | |
1908 | |
1909 // Fill the default values. | |
1910 for (int i = 0; i < len; i++) { | |
1911 FunctionPatchabilityStatus status = FUNCTION_AVAILABLE_FOR_PATCH; | |
1912 result_elements->set(i, Smi::FromInt(status)); | |
1913 } | |
1914 | |
1915 // Scan the heap for active generators -- those that are either currently | |
1916 // running (as we wouldn't want to restart them, because we don't know where | |
1917 // to restart them from) or suspended. Fail if any one corresponds to the set | |
1918 // of functions being edited. | |
1919 if (FindActiveGenerators(shared_info_array_elements, result_elements, len)) { | |
1920 return result; | |
1921 } | |
1922 | |
1923 // Check inactive threads. Fail if some functions are blocked there. | |
1924 InactiveThreadActivationsChecker inactive_threads_checker(shared_info_array, | |
1925 result); | |
1926 isolate->thread_manager()->IterateArchivedThreads( | |
1927 &inactive_threads_checker); | |
1928 if (inactive_threads_checker.HasBlockedFunctions()) { | |
1929 return result; | |
1930 } | |
1931 | |
1932 // Try to drop activations from the current stack. | |
1933 const char* error_message = | |
1934 DropActivationsInActiveThread(shared_info_array, result, do_drop); | |
1935 if (error_message != NULL) { | |
1936 // Add error message as an array extra element. | |
1937 Handle<String> str = | |
1938 isolate->factory()->NewStringFromAsciiChecked(error_message); | |
1939 SetElementSloppy(result, len, str); | |
1940 } | |
1941 return result; | |
1942 } | |
1943 | |
1944 | |
1945 // Describes a single callframe a target. Not finding this frame | |
1946 // means an error. | |
1947 class SingleFrameTarget { | |
1948 public: | |
1949 explicit SingleFrameTarget(JavaScriptFrame* frame) | |
1950 : m_frame(frame), | |
1951 m_saved_status(LiveEdit::FUNCTION_AVAILABLE_FOR_PATCH) {} | |
1952 | |
1953 bool MatchActivation(StackFrame* frame, | |
1954 LiveEdit::FunctionPatchabilityStatus status) { | |
1955 if (frame->fp() == m_frame->fp()) { | |
1956 m_saved_status = status; | |
1957 return true; | |
1958 } | |
1959 return false; | |
1960 } | |
1961 const char* GetNotFoundMessage() const { | |
1962 return "Failed to found requested frame"; | |
1963 } | |
1964 LiveEdit::FunctionPatchabilityStatus saved_status() { | |
1965 return m_saved_status; | |
1966 } | |
1967 private: | |
1968 JavaScriptFrame* m_frame; | |
1969 LiveEdit::FunctionPatchabilityStatus m_saved_status; | |
1970 }; | |
1971 | |
1972 | |
1973 // Finds a drops required frame and all frames above. | |
1974 // Returns error message or NULL. | |
1975 const char* LiveEdit::RestartFrame(JavaScriptFrame* frame) { | |
1976 SingleFrameTarget target(frame); | |
1977 | |
1978 const char* result = | |
1979 DropActivationsInActiveThreadImpl(frame->isolate(), target, true); | |
1980 if (result != NULL) { | |
1981 return result; | |
1982 } | |
1983 if (target.saved_status() == LiveEdit::FUNCTION_BLOCKED_UNDER_NATIVE_CODE) { | |
1984 return "Function is blocked under native code"; | |
1985 } | |
1986 if (target.saved_status() == LiveEdit::FUNCTION_BLOCKED_UNDER_GENERATOR) { | |
1987 return "Function is blocked under a generator activation"; | |
1988 } | |
1989 return NULL; | |
1990 } | |
1991 | |
1992 | |
1993 LiveEditFunctionTracker::LiveEditFunctionTracker(Isolate* isolate, | |
1994 FunctionLiteral* fun) | |
1995 : isolate_(isolate) { | |
1996 if (isolate_->active_function_info_listener() != NULL) { | |
1997 isolate_->active_function_info_listener()->FunctionStarted(fun); | |
1998 } | |
1999 } | |
2000 | |
2001 | |
2002 LiveEditFunctionTracker::~LiveEditFunctionTracker() { | |
2003 if (isolate_->active_function_info_listener() != NULL) { | |
2004 isolate_->active_function_info_listener()->FunctionDone(); | |
2005 } | |
2006 } | |
2007 | |
2008 | |
2009 void LiveEditFunctionTracker::RecordFunctionInfo( | |
2010 Handle<SharedFunctionInfo> info, FunctionLiteral* lit, | |
2011 Zone* zone) { | |
2012 if (isolate_->active_function_info_listener() != NULL) { | |
2013 isolate_->active_function_info_listener()->FunctionInfo(info, lit->scope(), | |
2014 zone); | |
2015 } | |
2016 } | |
2017 | |
2018 | |
2019 void LiveEditFunctionTracker::RecordRootFunctionInfo(Handle<Code> code) { | |
2020 isolate_->active_function_info_listener()->FunctionCode(code); | |
2021 } | |
2022 | |
2023 | |
2024 bool LiveEditFunctionTracker::IsActive(Isolate* isolate) { | |
2025 return isolate->active_function_info_listener() != NULL; | |
2026 } | |
2027 | |
2028 } // namespace internal | |
2029 } // namespace v8 | |
OLD | NEW |