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

Side by Side Diff: src/builtins.cc

Issue 1770793002: Move EnsureFastWritableElements into the elements accessor. (Closed) Base URL: https://chromium.googlesource.com/v8/v8.git@master
Patch Set: Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | src/elements.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2012 the V8 project authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "src/builtins.h" 5 #include "src/builtins.h"
6 6
7 #include "src/api.h" 7 #include "src/api.h"
8 #include "src/api-natives.h" 8 #include "src/api-natives.h"
9 #include "src/arguments.h" 9 #include "src/arguments.h"
10 #include "src/base/once.h" 10 #include "src/base/once.h"
(...skipping 230 matching lines...) Expand 10 before | Expand all | Expand 10 after
241 // Returns |false| if not applicable. 241 // Returns |false| if not applicable.
242 MUST_USE_RESULT 242 MUST_USE_RESULT
243 inline bool EnsureJSArrayWithWritableFastElements(Isolate* isolate, 243 inline bool EnsureJSArrayWithWritableFastElements(Isolate* isolate,
244 Handle<Object> receiver, 244 Handle<Object> receiver,
245 Arguments* args, 245 Arguments* args,
246 int first_added_arg) { 246 int first_added_arg) {
247 if (!receiver->IsJSArray()) return false; 247 if (!receiver->IsJSArray()) return false;
248 Handle<JSArray> array = Handle<JSArray>::cast(receiver); 248 Handle<JSArray> array = Handle<JSArray>::cast(receiver);
249 // If there may be elements accessors in the prototype chain, the fast path 249 // If there may be elements accessors in the prototype chain, the fast path
250 // cannot be used if there arguments to add to the array. 250 // cannot be used if there arguments to add to the array.
251 Heap* heap = isolate->heap(); 251 if (args != nullptr && !IsJSArrayFastElementMovingAllowed(isolate, *array)) {
252 if (args != NULL && !IsJSArrayFastElementMovingAllowed(isolate, *array)) {
253 return false; 252 return false;
254 } 253 }
254 ElementsKind origin_kind = array->map()->elements_kind();
255 if (IsDictionaryElementsKind(origin_kind)) return false;
255 if (array->map()->is_observed()) return false; 256 if (array->map()->is_observed()) return false;
256 if (!array->map()->is_extensible()) return false; 257 if (!array->map()->is_extensible()) return false;
257 Map* map = array->elements()->map(); 258 if (args == nullptr) return true;
258 if (map == heap->fixed_array_map()) {
259 if (args == NULL || array->HasFastObjectElements()) {
260 return true;
261 }
262 } else if (map == heap->fixed_cow_array_map()) {
263 // Use a short-lived HandleScope to avoid creating several copies of the
264 // elements handle which would cause issues when left-trimming later-on.
265 HandleScope scope(isolate);
266 // TODO(jkummerow/verwaest): Move this call (or this entire function?)
267 // into the ElementsAccessor so it's only done when needed (e.g. ArrayPush
268 // can skip it because it must grow the backing store anyway).
269 JSObject::EnsureWritableFastElements(array);
270 if (args == NULL || array->HasFastObjectElements()) {
271 return true;
272 }
273 } else if (map == heap->fixed_double_array_map()) {
274 if (args == NULL) {
275 return true;
276 }
277 } else {
278 return false;
279 }
280 259
281 // Adding elements to the array prototype would break code that makes sure 260 // Adding elements to the array prototype would break code that makes sure
282 // it has no elements. Handle that elsewhere. 261 // it has no elements. Handle that elsewhere.
283 if (isolate->IsAnyInitialArrayPrototype(array)) { 262 if (isolate->IsAnyInitialArrayPrototype(array)) return false;
284 return false;
285 }
286 263
287 // Need to ensure that the arguments passed in args can be contained in 264 // Need to ensure that the arguments passed in args can be contained in
288 // the array. 265 // the array.
289 int args_length = args->length(); 266 int args_length = args->length();
290 if (first_added_arg >= args_length) { 267 if (first_added_arg >= args_length) return true;
291 return true;
292 }
293 268
294 ElementsKind origin_kind = array->map()->elements_kind(); 269 if (IsFastObjectElementsKind(origin_kind)) return true;
295 DCHECK(!IsFastObjectElementsKind(origin_kind));
296 ElementsKind target_kind = origin_kind; 270 ElementsKind target_kind = origin_kind;
297 { 271 {
298 DisallowHeapAllocation no_gc; 272 DisallowHeapAllocation no_gc;
299 int arg_count = args_length - first_added_arg; 273 int arg_count = args_length - first_added_arg;
300 Object** arguments = args->arguments() - first_added_arg - (arg_count - 1); 274 Object** arguments = args->arguments() - first_added_arg - (arg_count - 1);
301 for (int i = 0; i < arg_count; i++) { 275 for (int i = 0; i < arg_count; i++) {
302 Object* arg = arguments[i]; 276 Object* arg = arguments[i];
303 if (arg->IsHeapObject()) { 277 if (arg->IsHeapObject()) {
304 if (arg->IsHeapNumber()) { 278 if (arg->IsHeapNumber()) {
305 target_kind = FAST_DOUBLE_ELEMENTS; 279 target_kind = FAST_DOUBLE_ELEMENTS;
(...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after
430 return isolate->heap()->false_value(); 404 return isolate->heap()->false_value();
431 } 405 }
432 406
433 BUILTIN(ArrayPush) { 407 BUILTIN(ArrayPush) {
434 HandleScope scope(isolate); 408 HandleScope scope(isolate);
435 Handle<Object> receiver = args.receiver(); 409 Handle<Object> receiver = args.receiver();
436 if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, &args, 1)) { 410 if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, &args, 1)) {
437 return CallJsIntrinsic(isolate, isolate->array_push(), args); 411 return CallJsIntrinsic(isolate, isolate->array_push(), args);
438 } 412 }
439 // Fast Elements Path 413 // Fast Elements Path
440 int push_size = args.length() - 1; 414 int to_add = args.length() - 1;
441 Handle<JSArray> array = Handle<JSArray>::cast(receiver); 415 Handle<JSArray> array = Handle<JSArray>::cast(receiver);
442 int len = Smi::cast(array->length())->value(); 416 int len = Smi::cast(array->length())->value();
443 if (push_size == 0) { 417 if (to_add == 0) return Smi::FromInt(len);
444 return Smi::FromInt(len); 418
445 } 419 // Currently fixed arrays cannot grow too big, so we should never hit this.
446 DCHECK(push_size > 0); 420 DCHECK_LE(to_add, Smi::kMaxValue - Smi::cast(array->length())->value());
421
447 if (JSArray::HasReadOnlyLength(array)) { 422 if (JSArray::HasReadOnlyLength(array)) {
448 return CallJsIntrinsic(isolate, isolate->array_push(), args); 423 return CallJsIntrinsic(isolate, isolate->array_push(), args);
449 } 424 }
450 DCHECK(!array->map()->is_observed()); 425
451 ElementsAccessor* accessor = array->GetElementsAccessor(); 426 ElementsAccessor* accessor = array->GetElementsAccessor();
452 int new_length = accessor->Push(array, handle(array->elements(), isolate), 427 int new_length = accessor->Push(array, &args, to_add);
453 &args, push_size);
454 return Smi::FromInt(new_length); 428 return Smi::FromInt(new_length);
455 } 429 }
456 430
457 431
458 BUILTIN(ArrayPop) { 432 BUILTIN(ArrayPop) {
459 HandleScope scope(isolate); 433 HandleScope scope(isolate);
460 Handle<Object> receiver = args.receiver(); 434 Handle<Object> receiver = args.receiver();
461 if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, NULL, 0)) { 435 if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, nullptr, 0)) {
462 return CallJsIntrinsic(isolate, isolate->array_pop(), args); 436 return CallJsIntrinsic(isolate, isolate->array_pop(), args);
463 } 437 }
464 438
465 Handle<JSArray> array = Handle<JSArray>::cast(receiver); 439 Handle<JSArray> array = Handle<JSArray>::cast(receiver);
466 DCHECK(!array->map()->is_observed()); 440 DCHECK(!array->map()->is_observed());
467 441
468 uint32_t len = static_cast<uint32_t>(Smi::cast(array->length())->value()); 442 uint32_t len = static_cast<uint32_t>(Smi::cast(array->length())->value());
469 if (len == 0) return isolate->heap()->undefined_value(); 443 if (len == 0) return isolate->heap()->undefined_value();
470 444
471 if (JSArray::HasReadOnlyLength(array)) { 445 if (JSArray::HasReadOnlyLength(array)) {
472 return CallJsIntrinsic(isolate, isolate->array_pop(), args); 446 return CallJsIntrinsic(isolate, isolate->array_pop(), args);
473 } 447 }
474 448
475 Handle<Object> result; 449 Handle<Object> result;
476 if (IsJSArrayFastElementMovingAllowed(isolate, JSArray::cast(*receiver))) { 450 if (IsJSArrayFastElementMovingAllowed(isolate, JSArray::cast(*receiver))) {
477 // Fast Elements Path 451 // Fast Elements Path
478 result = array->GetElementsAccessor()->Pop( 452 result = array->GetElementsAccessor()->Pop(array);
479 array, handle(array->elements(), isolate));
480 } else { 453 } else {
481 // Use Slow Lookup otherwise 454 // Use Slow Lookup otherwise
482 uint32_t new_length = len - 1; 455 uint32_t new_length = len - 1;
483 ASSIGN_RETURN_FAILURE_ON_EXCEPTION( 456 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
484 isolate, result, Object::GetElement(isolate, array, new_length)); 457 isolate, result, Object::GetElement(isolate, array, new_length));
485 JSArray::SetLength(array, new_length); 458 JSArray::SetLength(array, new_length);
486 } 459 }
487 return *result; 460 return *result;
488 } 461 }
489 462
490 463
491 BUILTIN(ArrayShift) { 464 BUILTIN(ArrayShift) {
492 HandleScope scope(isolate); 465 HandleScope scope(isolate);
493 Heap* heap = isolate->heap(); 466 Heap* heap = isolate->heap();
494 Handle<Object> receiver = args.receiver(); 467 Handle<Object> receiver = args.receiver();
495 if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, NULL, 0) || 468 if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, nullptr, 0) ||
496 !IsJSArrayFastElementMovingAllowed(isolate, JSArray::cast(*receiver))) { 469 !IsJSArrayFastElementMovingAllowed(isolate, JSArray::cast(*receiver))) {
497 return CallJsIntrinsic(isolate, isolate->array_shift(), args); 470 return CallJsIntrinsic(isolate, isolate->array_shift(), args);
498 } 471 }
499 Handle<JSArray> array = Handle<JSArray>::cast(receiver); 472 Handle<JSArray> array = Handle<JSArray>::cast(receiver);
500 DCHECK(!array->map()->is_observed()); 473 DCHECK(!array->map()->is_observed());
501 474
502 int len = Smi::cast(array->length())->value(); 475 int len = Smi::cast(array->length())->value();
503 if (len == 0) return heap->undefined_value(); 476 if (len == 0) return heap->undefined_value();
504 477
505 if (JSArray::HasReadOnlyLength(array)) { 478 if (JSArray::HasReadOnlyLength(array)) {
506 return CallJsIntrinsic(isolate, isolate->array_shift(), args); 479 return CallJsIntrinsic(isolate, isolate->array_shift(), args);
507 } 480 }
508 481
509 Handle<Object> first = array->GetElementsAccessor()->Shift( 482 Handle<Object> first = array->GetElementsAccessor()->Shift(array);
510 array, handle(array->elements(), isolate));
511 return *first; 483 return *first;
512 } 484 }
513 485
514 486
515 BUILTIN(ArrayUnshift) { 487 BUILTIN(ArrayUnshift) {
516 HandleScope scope(isolate); 488 HandleScope scope(isolate);
517 Handle<Object> receiver = args.receiver(); 489 Handle<Object> receiver = args.receiver();
518 if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, &args, 1)) { 490 if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, &args, 1)) {
519 return CallJsIntrinsic(isolate, isolate->array_unshift(), args); 491 return CallJsIntrinsic(isolate, isolate->array_unshift(), args);
520 } 492 }
521 Handle<JSArray> array = Handle<JSArray>::cast(receiver); 493 Handle<JSArray> array = Handle<JSArray>::cast(receiver);
522 DCHECK(!array->map()->is_observed()); 494 DCHECK(!array->map()->is_observed());
523 int to_add = args.length() - 1; 495 int to_add = args.length() - 1;
524 if (to_add == 0) { 496 if (to_add == 0) return array->length();
525 return array->length();
526 }
527 // Currently fixed arrays cannot grow too big, so
528 // we should never hit this case.
529 DCHECK(to_add <= (Smi::kMaxValue - Smi::cast(array->length())->value()));
530 497
531 if (to_add > 0 && JSArray::HasReadOnlyLength(array)) { 498 // Currently fixed arrays cannot grow too big, so we should never hit this.
499 DCHECK_LE(to_add, Smi::kMaxValue - Smi::cast(array->length())->value());
500
501 if (JSArray::HasReadOnlyLength(array)) {
532 return CallJsIntrinsic(isolate, isolate->array_unshift(), args); 502 return CallJsIntrinsic(isolate, isolate->array_unshift(), args);
533 } 503 }
534 504
535 ElementsAccessor* accessor = array->GetElementsAccessor(); 505 ElementsAccessor* accessor = array->GetElementsAccessor();
536 int new_length = accessor->Unshift(array, handle(array->elements(), isolate), 506 int new_length = accessor->Unshift(array, &args, to_add);
537 &args, to_add);
538 return Smi::FromInt(new_length); 507 return Smi::FromInt(new_length);
539 } 508 }
540 509
541 510
542 BUILTIN(ArraySlice) { 511 BUILTIN(ArraySlice) {
543 HandleScope scope(isolate); 512 HandleScope scope(isolate);
544 Handle<Object> receiver = args.receiver(); 513 Handle<Object> receiver = args.receiver();
545 Handle<JSObject> object;
546 Handle<FixedArrayBase> elms_obj;
547 int len = -1; 514 int len = -1;
548 int relative_start = 0; 515 int relative_start = 0;
549 int relative_end = 0; 516 int relative_end = 0;
550 bool is_sloppy_arguments = false;
551 517
552 if (receiver->IsJSArray()) { 518 if (receiver->IsJSArray()) {
553 DisallowHeapAllocation no_gc; 519 DisallowHeapAllocation no_gc;
554 JSArray* array = JSArray::cast(*receiver); 520 JSArray* array = JSArray::cast(*receiver);
555 if (!array->HasFastElements() || 521 if (!array->HasFastElements() ||
556 !IsJSArrayFastElementMovingAllowed(isolate, array) || 522 !IsJSArrayFastElementMovingAllowed(isolate, array) ||
557 !isolate->IsArraySpeciesLookupChainIntact() || 523 !isolate->IsArraySpeciesLookupChainIntact() ||
558 // If this is a subclass of Array, then call out to JS 524 // If this is a subclass of Array, then call out to JS
559 !array->map()->new_target_is_base()) { 525 !array->map()->new_target_is_base()) {
560 AllowHeapAllocation allow_allocation; 526 AllowHeapAllocation allow_allocation;
561 return CallJsIntrinsic(isolate, isolate->array_slice(), args); 527 return CallJsIntrinsic(isolate, isolate->array_slice(), args);
562 } 528 }
563 len = Smi::cast(array->length())->value(); 529 len = Smi::cast(array->length())->value();
564 object = Handle<JSObject>::cast(receiver);
565 elms_obj = handle(array->elements(), isolate);
566 } else if (receiver->IsJSObject() && 530 } else if (receiver->IsJSObject() &&
567 GetSloppyArgumentsLength(isolate, Handle<JSObject>::cast(receiver), 531 GetSloppyArgumentsLength(isolate, Handle<JSObject>::cast(receiver),
568 &len)) { 532 &len)) {
533 DCHECK_EQ(FAST_ELEMENTS, JSObject::cast(*receiver)->GetElementsKind());
569 // Array.prototype.slice(arguments, ...) is quite a common idiom 534 // Array.prototype.slice(arguments, ...) is quite a common idiom
570 // (notably more than 50% of invocations in Web apps). 535 // (notably more than 50% of invocations in Web apps).
571 // Treat it in C++ as well. 536 // Treat it in C++ as well.
572 is_sloppy_arguments = true;
573 object = Handle<JSObject>::cast(receiver);
574 elms_obj = handle(object->elements(), isolate);
575 } else { 537 } else {
576 AllowHeapAllocation allow_allocation; 538 AllowHeapAllocation allow_allocation;
577 return CallJsIntrinsic(isolate, isolate->array_slice(), args); 539 return CallJsIntrinsic(isolate, isolate->array_slice(), args);
578 } 540 }
579 DCHECK(len >= 0); 541 DCHECK_LE(0, len);
580 int argument_count = args.length() - 1; 542 int argument_count = args.length() - 1;
581 // Note carefully chosen defaults---if argument is missing, 543 // Note carefully chosen defaults---if argument is missing,
582 // it's undefined which gets converted to 0 for relative_start 544 // it's undefined which gets converted to 0 for relative_start
583 // and to len for relative_end. 545 // and to len for relative_end.
584 relative_start = 0; 546 relative_start = 0;
585 relative_end = len; 547 relative_end = len;
586 if (argument_count > 0) { 548 if (argument_count > 0) {
587 DisallowHeapAllocation no_gc; 549 DisallowHeapAllocation no_gc;
588 if (!ClampedToInteger(args[1], &relative_start)) { 550 if (!ClampedToInteger(args[1], &relative_start)) {
589 AllowHeapAllocation allow_allocation; 551 AllowHeapAllocation allow_allocation;
(...skipping 12 matching lines...) Expand all
602 } 564 }
603 565
604 // ECMAScript 232, 3rd Edition, Section 15.4.4.10, step 6. 566 // ECMAScript 232, 3rd Edition, Section 15.4.4.10, step 6.
605 uint32_t actual_start = (relative_start < 0) ? Max(len + relative_start, 0) 567 uint32_t actual_start = (relative_start < 0) ? Max(len + relative_start, 0)
606 : Min(relative_start, len); 568 : Min(relative_start, len);
607 569
608 // ECMAScript 232, 3rd Edition, Section 15.4.4.10, step 8. 570 // ECMAScript 232, 3rd Edition, Section 15.4.4.10, step 8.
609 uint32_t actual_end = 571 uint32_t actual_end =
610 (relative_end < 0) ? Max(len + relative_end, 0) : Min(relative_end, len); 572 (relative_end < 0) ? Max(len + relative_end, 0) : Min(relative_end, len);
611 573
612 if (actual_end <= actual_start) { 574 Handle<JSObject> object = Handle<JSObject>::cast(receiver);
613 Handle<JSArray> result_array = isolate->factory()->NewJSArray(
614 GetPackedElementsKind(object->GetElementsKind()), 0, 0);
615 return *result_array;
616 }
617
618 ElementsAccessor* accessor = object->GetElementsAccessor(); 575 ElementsAccessor* accessor = object->GetElementsAccessor();
619 if (is_sloppy_arguments && 576 return *accessor->Slice(object, actual_start, actual_end);
620 !accessor->IsPacked(object, elms_obj, actual_start, actual_end)) {
621 // Don't deal with arguments with holes in C++
622 AllowHeapAllocation allow_allocation;
623 return CallJsIntrinsic(isolate, isolate->array_slice(), args);
624 }
625 Handle<JSArray> result_array =
626 accessor->Slice(object, elms_obj, actual_start, actual_end);
627 return *result_array;
628 } 577 }
629 578
630 579
631 BUILTIN(ArraySplice) { 580 BUILTIN(ArraySplice) {
632 HandleScope scope(isolate); 581 HandleScope scope(isolate);
633 Handle<Object> receiver = args.receiver(); 582 Handle<Object> receiver = args.receiver();
634 if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, &args, 3) || 583 if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, &args, 3) ||
635 // If this is a subclass of Array, then call out to JS. 584 // If this is a subclass of Array, then call out to JS.
636 !JSArray::cast(*receiver)->map()->new_target_is_base() || 585 !JSArray::cast(*receiver)->map()->new_target_is_base() ||
637 // If anything with @@species has been messed with, call out to JS. 586 // If anything with @@species has been messed with, call out to JS.
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
676 } 625 }
677 626
678 int add_count = (argument_count > 1) ? (argument_count - 2) : 0; 627 int add_count = (argument_count > 1) ? (argument_count - 2) : 0;
679 int new_length = len - actual_delete_count + add_count; 628 int new_length = len - actual_delete_count + add_count;
680 629
681 if (new_length != len && JSArray::HasReadOnlyLength(array)) { 630 if (new_length != len && JSArray::HasReadOnlyLength(array)) {
682 AllowHeapAllocation allow_allocation; 631 AllowHeapAllocation allow_allocation;
683 return CallJsIntrinsic(isolate, isolate->array_splice(), args); 632 return CallJsIntrinsic(isolate, isolate->array_splice(), args);
684 } 633 }
685 ElementsAccessor* accessor = array->GetElementsAccessor(); 634 ElementsAccessor* accessor = array->GetElementsAccessor();
686 Handle<JSArray> result_array = 635 Handle<JSArray> result_array = accessor->Splice(
687 accessor->Splice(array, handle(array->elements(), isolate), actual_start, 636 array, actual_start, actual_delete_count, &args, add_count);
688 actual_delete_count, &args, add_count);
689 return *result_array; 637 return *result_array;
690 } 638 }
691 639
692 640
693 // Array Concat ------------------------------------------------------------- 641 // Array Concat -------------------------------------------------------------
694 642
695 namespace { 643 namespace {
696 644
697 /** 645 /**
698 * A simple visitor visits every element of Array's. 646 * A simple visitor visits every element of Array's.
(...skipping 3885 matching lines...) Expand 10 before | Expand all | Expand 10 after
4584 BUILTIN_LIST_C(DEFINE_BUILTIN_ACCESSOR_C) 4532 BUILTIN_LIST_C(DEFINE_BUILTIN_ACCESSOR_C)
4585 BUILTIN_LIST_A(DEFINE_BUILTIN_ACCESSOR_A) 4533 BUILTIN_LIST_A(DEFINE_BUILTIN_ACCESSOR_A)
4586 BUILTIN_LIST_H(DEFINE_BUILTIN_ACCESSOR_H) 4534 BUILTIN_LIST_H(DEFINE_BUILTIN_ACCESSOR_H)
4587 BUILTIN_LIST_DEBUG_A(DEFINE_BUILTIN_ACCESSOR_A) 4535 BUILTIN_LIST_DEBUG_A(DEFINE_BUILTIN_ACCESSOR_A)
4588 #undef DEFINE_BUILTIN_ACCESSOR_C 4536 #undef DEFINE_BUILTIN_ACCESSOR_C
4589 #undef DEFINE_BUILTIN_ACCESSOR_A 4537 #undef DEFINE_BUILTIN_ACCESSOR_A
4590 4538
4591 4539
4592 } // namespace internal 4540 } // namespace internal
4593 } // namespace v8 4541 } // namespace v8
OLDNEW
« no previous file with comments | « no previous file | src/elements.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698