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

Side by Side Diff: runtime/vm/compiler.cc

Issue 108383007: Replaces LongJump with LongJumpScope. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 6 years, 11 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 | Annotate | Revision Log
« no previous file with comments | « runtime/vm/code_descriptors_test.cc ('k') | runtime/vm/debugger.cc » ('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 (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 #include "vm/compiler.h" 5 #include "vm/compiler.h"
6 6
7 #include "vm/assembler.h" 7 #include "vm/assembler.h"
8 8
9 #include "vm/ast_printer.h" 9 #include "vm/ast_printer.h"
10 #include "vm/block_scheduler.h" 10 #include "vm/block_scheduler.h"
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
67 const Error& error = Error::Handle(Compiler::CompileFunction(function)); 67 const Error& error = Error::Handle(Compiler::CompileFunction(function));
68 if (!error.IsNull()) { 68 if (!error.IsNull()) {
69 Exceptions::PropagateError(error); 69 Exceptions::PropagateError(error);
70 } 70 }
71 } 71 }
72 72
73 73
74 RawError* Compiler::Compile(const Library& library, const Script& script) { 74 RawError* Compiler::Compile(const Library& library, const Script& script) {
75 Isolate* isolate = Isolate::Current(); 75 Isolate* isolate = Isolate::Current();
76 StackZone zone(isolate); 76 StackZone zone(isolate);
77 LongJump* base = isolate->long_jump_base(); 77 LongJumpScope jump;
78 LongJump jump;
79 isolate->set_long_jump_base(&jump);
80 if (setjmp(*jump.Set()) == 0) { 78 if (setjmp(*jump.Set()) == 0) {
81 if (FLAG_trace_compiler) { 79 if (FLAG_trace_compiler) {
82 const String& script_url = String::Handle(script.url()); 80 const String& script_url = String::Handle(script.url());
83 // TODO(iposva): Extract script kind. 81 // TODO(iposva): Extract script kind.
84 OS::Print("Compiling %s '%s'\n", "", script_url.ToCString()); 82 OS::Print("Compiling %s '%s'\n", "", script_url.ToCString());
85 } 83 }
86 const String& library_key = String::Handle(library.private_key()); 84 const String& library_key = String::Handle(library.private_key());
87 script.Tokenize(library_key); 85 script.Tokenize(library_key);
88 Parser::ParseCompilationUnit(library, script); 86 Parser::ParseCompilationUnit(library, script);
89 isolate->set_long_jump_base(base);
90 return Error::null(); 87 return Error::null();
91 } else { 88 } else {
92 Error& error = Error::Handle(); 89 Error& error = Error::Handle();
93 error = isolate->object_store()->sticky_error(); 90 error = isolate->object_store()->sticky_error();
94 isolate->object_store()->clear_sticky_error(); 91 isolate->object_store()->clear_sticky_error();
95 isolate->set_long_jump_base(base);
96 return error.raw(); 92 return error.raw();
97 } 93 }
98 UNREACHABLE(); 94 UNREACHABLE();
99 return Error::null(); 95 return Error::null();
100 } 96 }
101 97
102 98
103 static void AddRelatedClassesToList(const Class& cls, 99 static void AddRelatedClassesToList(const Class& cls,
104 const GrowableObjectArray& parse_list, 100 const GrowableObjectArray& parse_list,
105 const GrowableObjectArray& patch_list) { 101 const GrowableObjectArray& patch_list) {
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
161 // also allows us to reset the marked_for_parsing state in case we see an 157 // also allows us to reset the marked_for_parsing state in case we see an
162 // error. 158 // error.
163 Class& parse_class = Class::Handle(); 159 Class& parse_class = Class::Handle();
164 const GrowableObjectArray& parse_list = 160 const GrowableObjectArray& parse_list =
165 GrowableObjectArray::Handle(GrowableObjectArray::New(4)); 161 GrowableObjectArray::Handle(GrowableObjectArray::New(4));
166 const GrowableObjectArray& patch_list = 162 const GrowableObjectArray& patch_list =
167 GrowableObjectArray::Handle(GrowableObjectArray::New(4)); 163 GrowableObjectArray::Handle(GrowableObjectArray::New(4));
168 164
169 // Parse the class and all the interfaces it implements and super classes. 165 // Parse the class and all the interfaces it implements and super classes.
170 StackZone zone(isolate); 166 StackZone zone(isolate);
171 LongJump* base = isolate->long_jump_base(); 167 LongJumpScope jump;
172 LongJump jump;
173 isolate->set_long_jump_base(&jump);
174 if (setjmp(*jump.Set()) == 0) { 168 if (setjmp(*jump.Set()) == 0) {
175 if (FLAG_trace_compiler) { 169 if (FLAG_trace_compiler) {
176 OS::Print("Compiling Class %s '%s'\n", "", cls.ToCString()); 170 OS::Print("Compiling Class %s '%s'\n", "", cls.ToCString());
177 } 171 }
178 172
179 // Add the primary class which needs to be parsed to the parse list. 173 // Add the primary class which needs to be parsed to the parse list.
180 // Mark the class as parsed so that we don't recursively add the same 174 // Mark the class as parsed so that we don't recursively add the same
181 // class back into the list. 175 // class back into the list.
182 parse_list.Add(cls); 176 parse_list.Add(cls);
183 cls.set_is_marked_for_parsing(); 177 cls.set_is_marked_for_parsing();
(...skipping 24 matching lines...) Expand all
208 } 202 }
209 203
210 // Finalize these classes. 204 // Finalize these classes.
211 for (intptr_t i = (parse_list.Length() - 1); i >=0 ; i--) { 205 for (intptr_t i = (parse_list.Length() - 1); i >=0 ; i--) {
212 parse_class ^= parse_list.At(i); 206 parse_class ^= parse_list.At(i);
213 ASSERT(!parse_class.IsNull()); 207 ASSERT(!parse_class.IsNull());
214 ClassFinalizer::FinalizeClass(parse_class); 208 ClassFinalizer::FinalizeClass(parse_class);
215 parse_class.reset_is_marked_for_parsing(); 209 parse_class.reset_is_marked_for_parsing();
216 } 210 }
217 211
218 isolate->set_long_jump_base(base);
219 return Error::null(); 212 return Error::null();
220 } else { 213 } else {
221 // Reset the marked for parsing flags. 214 // Reset the marked for parsing flags.
222 for (intptr_t i = 0; i < parse_list.Length(); i++) { 215 for (intptr_t i = 0; i < parse_list.Length(); i++) {
223 parse_class ^= parse_list.At(i); 216 parse_class ^= parse_list.At(i);
224 if (parse_class.is_marked_for_parsing()) { 217 if (parse_class.is_marked_for_parsing()) {
225 parse_class.reset_is_marked_for_parsing(); 218 parse_class.reset_is_marked_for_parsing();
226 } 219 }
227 } 220 }
228 for (intptr_t i = 0; i < patch_list.Length(); i++) { 221 for (intptr_t i = 0; i < patch_list.Length(); i++) {
229 parse_class ^= patch_list.At(i); 222 parse_class ^= patch_list.At(i);
230 if (parse_class.is_marked_for_parsing()) { 223 if (parse_class.is_marked_for_parsing()) {
231 parse_class.reset_is_marked_for_parsing(); 224 parse_class.reset_is_marked_for_parsing();
232 } 225 }
233 } 226 }
234 227
235 Error& error = Error::Handle(); 228 Error& error = Error::Handle();
236 error = isolate->object_store()->sticky_error(); 229 error = isolate->object_store()->sticky_error();
237 isolate->object_store()->clear_sticky_error(); 230 isolate->object_store()->clear_sticky_error();
238 isolate->set_long_jump_base(base);
239 return error.raw(); 231 return error.raw();
240 } 232 }
241 UNREACHABLE(); 233 UNREACHABLE();
242 return Error::null(); 234 return Error::null();
243 } 235 }
244 236
245 237
246 static void InstallUnoptimizedCode(const Function& function) { 238 static void InstallUnoptimizedCode(const Function& function) {
247 // Disable optimized code. 239 // Disable optimized code.
248 ASSERT(function.HasOptimizedCode()); 240 ASSERT(function.HasOptimizedCode());
(...skipping 27 matching lines...) Expand all
276 // done is set to false, and use_far_branches is set to true if there is a 268 // done is set to false, and use_far_branches is set to true if there is a
277 // longjmp from the ARM or MIPS assemblers. In all other paths through this 269 // longjmp from the ARM or MIPS assemblers. In all other paths through this
278 // while loop, done is set to true. use_far_branches is always false on ia32 270 // while loop, done is set to true. use_far_branches is always false on ia32
279 // and x64. 271 // and x64.
280 bool done = false; 272 bool done = false;
281 // volatile because the variable may be clobbered by a longjmp. 273 // volatile because the variable may be clobbered by a longjmp.
282 volatile bool use_far_branches = false; 274 volatile bool use_far_branches = false;
283 while (!done) { 275 while (!done) {
284 const intptr_t prev_deopt_id = isolate->deopt_id(); 276 const intptr_t prev_deopt_id = isolate->deopt_id();
285 isolate->set_deopt_id(0); 277 isolate->set_deopt_id(0);
286 LongJump* old_base = isolate->long_jump_base(); 278 LongJumpScope jump;
287 LongJump bailout_jump; 279 if (setjmp(*jump.Set()) == 0) {
288 isolate->set_long_jump_base(&bailout_jump);
289 if (setjmp(*bailout_jump.Set()) == 0) {
290 FlowGraph* flow_graph = NULL; 280 FlowGraph* flow_graph = NULL;
291 // TimerScope needs an isolate to be properly terminated in case of a 281 // TimerScope needs an isolate to be properly terminated in case of a
292 // LongJump. 282 // LongJump.
293 { 283 {
294 TimerScope timer(FLAG_compiler_stats, 284 TimerScope timer(FLAG_compiler_stats,
295 &CompilerStats::graphbuilder_timer, 285 &CompilerStats::graphbuilder_timer,
296 isolate); 286 isolate);
297 Array& ic_data_array = Array::Handle(); 287 Array& ic_data_array = Array::Handle();
298 if (optimized) { 288 if (optimized) {
299 ASSERT(function.HasCode()); 289 ASSERT(function.HasCode());
(...skipping 301 matching lines...) Expand 10 before | Expand all | Expand 10 after
601 OS::Print("%s\n", bailout_error.ToErrorCString()); 591 OS::Print("%s\n", bailout_error.ToErrorCString());
602 } 592 }
603 done = true; 593 done = true;
604 ASSERT(optimized); 594 ASSERT(optimized);
605 } 595 }
606 596
607 isolate->object_store()->clear_sticky_error(); 597 isolate->object_store()->clear_sticky_error();
608 is_compiled = false; 598 is_compiled = false;
609 } 599 }
610 // Reset global isolate state. 600 // Reset global isolate state.
611 isolate->set_long_jump_base(old_base);
612 isolate->set_deopt_id(prev_deopt_id); 601 isolate->set_deopt_id(prev_deopt_id);
613 } 602 }
614 return is_compiled; 603 return is_compiled;
615 } 604 }
616 605
617 606
618 static void DisassembleCode(const Function& function, bool optimized) { 607 static void DisassembleCode(const Function& function, bool optimized) {
619 const char* function_fullname = function.ToFullyQualifiedCString(); 608 const char* function_fullname = function.ToFullyQualifiedCString();
620 OS::Print("Code for %sfunction '%s' {\n", 609 OS::Print("Code for %sfunction '%s' {\n",
621 optimized ? "optimized " : "", 610 optimized ? "optimized " : "",
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after
738 OS::Print("}\n"); 727 OS::Print("}\n");
739 } 728 }
740 } 729 }
741 730
742 731
743 static RawError* CompileFunctionHelper(const Function& function, 732 static RawError* CompileFunctionHelper(const Function& function,
744 bool optimized, 733 bool optimized,
745 intptr_t osr_id) { 734 intptr_t osr_id) {
746 Isolate* isolate = Isolate::Current(); 735 Isolate* isolate = Isolate::Current();
747 StackZone zone(isolate); 736 StackZone zone(isolate);
748 LongJump* base = isolate->long_jump_base(); 737 LongJumpScope jump;
749 LongJump jump;
750 isolate->set_long_jump_base(&jump);
751 // Make sure unoptimized code is not collected while we are compiling. 738 // Make sure unoptimized code is not collected while we are compiling.
752 const Code& unoptimized_code = Code::ZoneHandle(function.unoptimized_code()); 739 const Code& unoptimized_code = Code::ZoneHandle(function.unoptimized_code());
753 // Skips parsing if we need to only install unoptimized code. 740 // Skips parsing if we need to only install unoptimized code.
754 if (!optimized && !unoptimized_code.IsNull()) { 741 if (!optimized && !unoptimized_code.IsNull()) {
755 InstallUnoptimizedCode(function); 742 InstallUnoptimizedCode(function);
756 isolate->set_long_jump_base(base);
757 return Error::null(); 743 return Error::null();
758 } 744 }
759 if (setjmp(*jump.Set()) == 0) { 745 if (setjmp(*jump.Set()) == 0) {
760 TIMERSCOPE(time_compilation); 746 TIMERSCOPE(time_compilation);
761 Timer per_compile_timer(FLAG_trace_compiler, "Compilation time"); 747 Timer per_compile_timer(FLAG_trace_compiler, "Compilation time");
762 per_compile_timer.Start(); 748 per_compile_timer.Start();
763 ParsedFunction* parsed_function = 749 ParsedFunction* parsed_function =
764 new ParsedFunction(Function::ZoneHandle(function.raw())); 750 new ParsedFunction(Function::ZoneHandle(function.raw()));
765 if (FLAG_trace_compiler) { 751 if (FLAG_trace_compiler) {
766 OS::Print("Compiling %s%sfunction: '%s' @ token %" Pd ", size %" Pd "\n", 752 OS::Print("Compiling %s%sfunction: '%s' @ token %" Pd ", size %" Pd "\n",
(...skipping 13 matching lines...) Expand all
780 CompileParsedFunctionHelper(parsed_function, optimized, osr_id); 766 CompileParsedFunctionHelper(parsed_function, optimized, osr_id);
781 if (optimized && !success) { 767 if (optimized && !success) {
782 // Optimizer bailed out. Disable optimizations and to never try again. 768 // Optimizer bailed out. Disable optimizations and to never try again.
783 if (FLAG_trace_compiler) { 769 if (FLAG_trace_compiler) {
784 OS::Print("--> disabling optimizations for '%s'\n", 770 OS::Print("--> disabling optimizations for '%s'\n",
785 function.ToFullyQualifiedCString()); 771 function.ToFullyQualifiedCString());
786 } else if (FLAG_trace_failed_optimization_attempts) { 772 } else if (FLAG_trace_failed_optimization_attempts) {
787 OS::Print("Cannot optimize: %s\n", function.ToFullyQualifiedCString()); 773 OS::Print("Cannot optimize: %s\n", function.ToFullyQualifiedCString());
788 } 774 }
789 function.SetIsOptimizable(false); 775 function.SetIsOptimizable(false);
790 isolate->set_long_jump_base(base);
791 return Error::null(); 776 return Error::null();
792 } 777 }
793 778
794 ASSERT(success); 779 ASSERT(success);
795 per_compile_timer.Stop(); 780 per_compile_timer.Stop();
796 781
797 if (FLAG_trace_compiler) { 782 if (FLAG_trace_compiler) {
798 OS::Print("--> '%s' entry: %#" Px " size: %" Pd " time: %" Pd64 " us\n", 783 OS::Print("--> '%s' entry: %#" Px " size: %" Pd " time: %" Pd64 " us\n",
799 function.ToFullyQualifiedCString(), 784 function.ToFullyQualifiedCString(),
800 Code::Handle(function.CurrentCode()).EntryPoint(), 785 Code::Handle(function.CurrentCode()).EntryPoint(),
801 Code::Handle(function.CurrentCode()).Size(), 786 Code::Handle(function.CurrentCode()).Size(),
802 per_compile_timer.TotalElapsedTime()); 787 per_compile_timer.TotalElapsedTime());
803 } 788 }
804 789
805 isolate->debugger()->NotifyCompilation(function); 790 isolate->debugger()->NotifyCompilation(function);
806 791
807 if (FLAG_disassemble) { 792 if (FLAG_disassemble) {
808 DisassembleCode(function, optimized); 793 DisassembleCode(function, optimized);
809 } else if (FLAG_disassemble_optimized && optimized) { 794 } else if (FLAG_disassemble_optimized && optimized) {
810 // TODO(fschneider): Print unoptimized code along with the optimized code. 795 // TODO(fschneider): Print unoptimized code along with the optimized code.
811 OS::Print("*** BEGIN CODE\n"); 796 OS::Print("*** BEGIN CODE\n");
812 DisassembleCode(function, true); 797 DisassembleCode(function, true);
813 OS::Print("*** END CODE\n"); 798 OS::Print("*** END CODE\n");
814 } 799 }
815 800
816 isolate->set_long_jump_base(base);
817 return Error::null(); 801 return Error::null();
818 } else { 802 } else {
819 Error& error = Error::Handle(); 803 Error& error = Error::Handle();
820 // We got an error during compilation. 804 // We got an error during compilation.
821 error = isolate->object_store()->sticky_error(); 805 error = isolate->object_store()->sticky_error();
822 isolate->object_store()->clear_sticky_error(); 806 isolate->object_store()->clear_sticky_error();
823 isolate->set_long_jump_base(base);
824 return error.raw(); 807 return error.raw();
825 } 808 }
826 UNREACHABLE(); 809 UNREACHABLE();
827 return Error::null(); 810 return Error::null();
828 } 811 }
829 812
830 813
831 RawError* Compiler::CompileFunction(const Function& function) { 814 RawError* Compiler::CompileFunction(const Function& function) {
832 return CompileFunctionHelper(function, false, Isolate::kNoDeoptId); 815 return CompileFunctionHelper(function, false, Isolate::kNoDeoptId);
833 } 816 }
834 817
835 818
836 RawError* Compiler::CompileOptimizedFunction(const Function& function, 819 RawError* Compiler::CompileOptimizedFunction(const Function& function,
837 intptr_t osr_id) { 820 intptr_t osr_id) {
838 return CompileFunctionHelper(function, true, osr_id); 821 return CompileFunctionHelper(function, true, osr_id);
839 } 822 }
840 823
841 824
842 RawError* Compiler::CompileParsedFunction( 825 RawError* Compiler::CompileParsedFunction(
843 ParsedFunction* parsed_function) { 826 ParsedFunction* parsed_function) {
844 Isolate* isolate = Isolate::Current(); 827 Isolate* isolate = Isolate::Current();
845 LongJump* base = isolate->long_jump_base(); 828 LongJumpScope jump;
846 LongJump jump;
847 isolate->set_long_jump_base(&jump);
848 if (setjmp(*jump.Set()) == 0) { 829 if (setjmp(*jump.Set()) == 0) {
849 // Non-optimized code generator. 830 // Non-optimized code generator.
850 CompileParsedFunctionHelper(parsed_function, false, Isolate::kNoDeoptId); 831 CompileParsedFunctionHelper(parsed_function, false, Isolate::kNoDeoptId);
851 if (FLAG_disassemble) { 832 if (FLAG_disassemble) {
852 DisassembleCode(parsed_function->function(), false); 833 DisassembleCode(parsed_function->function(), false);
853 } 834 }
854 isolate->set_long_jump_base(base);
855 return Error::null(); 835 return Error::null();
856 } else { 836 } else {
857 Error& error = Error::Handle(); 837 Error& error = Error::Handle();
858 // We got an error during compilation. 838 // We got an error during compilation.
859 error = isolate->object_store()->sticky_error(); 839 error = isolate->object_store()->sticky_error();
860 isolate->object_store()->clear_sticky_error(); 840 isolate->object_store()->clear_sticky_error();
861 isolate->set_long_jump_base(base);
862 return error.raw(); 841 return error.raw();
863 } 842 }
864 UNREACHABLE(); 843 UNREACHABLE();
865 return Error::null(); 844 return Error::null();
866 } 845 }
867 846
868 847
869 RawError* Compiler::CompileAllFunctions(const Class& cls) { 848 RawError* Compiler::CompileAllFunctions(const Class& cls) {
870 Error& error = Error::Handle(); 849 Error& error = Error::Handle();
871 Array& functions = Array::Handle(cls.functions()); 850 Array& functions = Array::Handle(cls.functions());
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
904 } 883 }
905 } 884 }
906 } 885 }
907 } 886 }
908 return error.raw(); 887 return error.raw();
909 } 888 }
910 889
911 890
912 RawObject* Compiler::ExecuteOnce(SequenceNode* fragment) { 891 RawObject* Compiler::ExecuteOnce(SequenceNode* fragment) {
913 Isolate* isolate = Isolate::Current(); 892 Isolate* isolate = Isolate::Current();
914 LongJump* base = isolate->long_jump_base(); 893 LongJumpScope jump;
915 LongJump jump;
916 isolate->set_long_jump_base(&jump);
917 if (setjmp(*jump.Set()) == 0) { 894 if (setjmp(*jump.Set()) == 0) {
918 if (FLAG_trace_compiler) { 895 if (FLAG_trace_compiler) {
919 OS::Print("compiling expression: "); 896 OS::Print("compiling expression: ");
920 AstPrinter::PrintNode(fragment); 897 AstPrinter::PrintNode(fragment);
921 } 898 }
922 899
923 // Create a dummy function object for the code generator. 900 // Create a dummy function object for the code generator.
924 // The function needs to be associated with a named Class: the interface 901 // The function needs to be associated with a named Class: the interface
925 // Function fits the bill. 902 // Function fits the bill.
926 const char* kEvalConst = "eval_const"; 903 const char* kEvalConst = "eval_const";
(...skipping 22 matching lines...) Expand all
949 parsed_function->set_default_parameter_values(Object::null_array()); 926 parsed_function->set_default_parameter_values(Object::null_array());
950 parsed_function->EnsureExpressionTemp(); 927 parsed_function->EnsureExpressionTemp();
951 fragment->scope()->AddVariable(parsed_function->expression_temp_var()); 928 fragment->scope()->AddVariable(parsed_function->expression_temp_var());
952 parsed_function->AllocateVariables(); 929 parsed_function->AllocateVariables();
953 930
954 // Non-optimized code generator. 931 // Non-optimized code generator.
955 CompileParsedFunctionHelper(parsed_function, false, Isolate::kNoDeoptId); 932 CompileParsedFunctionHelper(parsed_function, false, Isolate::kNoDeoptId);
956 933
957 const Object& result = Object::Handle( 934 const Object& result = Object::Handle(
958 DartEntry::InvokeFunction(func, Object::empty_array())); 935 DartEntry::InvokeFunction(func, Object::empty_array()));
959 isolate->set_long_jump_base(base);
960 return result.raw(); 936 return result.raw();
961 } else { 937 } else {
962 const Object& result = 938 const Object& result =
963 Object::Handle(isolate->object_store()->sticky_error()); 939 Object::Handle(isolate->object_store()->sticky_error());
964 isolate->object_store()->clear_sticky_error(); 940 isolate->object_store()->clear_sticky_error();
965 isolate->set_long_jump_base(base);
966 return result.raw(); 941 return result.raw();
967 } 942 }
968 UNREACHABLE(); 943 UNREACHABLE();
969 return Object::null(); 944 return Object::null();
970 } 945 }
971 946
972 } // namespace dart 947 } // namespace dart
OLDNEW
« no previous file with comments | « runtime/vm/code_descriptors_test.cc ('k') | runtime/vm/debugger.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698