| 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 |