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

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

Issue 1274133002: Don't zone-register async callbacks for every await call in the VM. (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: load async-op var. Created 5 years, 4 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 | « runtime/vm/ast_transformer.cc ('k') | runtime/vm/symbols.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 (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/parser.h" 5 #include "vm/parser.h"
6 6
7 #include "lib/invocation_mirror.h" 7 #include "lib/invocation_mirror.h"
8 #include "platform/utils.h" 8 #include "platform/utils.h"
9 #include "vm/ast_transformer.h" 9 #include "vm/ast_transformer.h"
10 #include "vm/bootstrap.h" 10 #include "vm/bootstrap.h"
(...skipping 6724 matching lines...) Expand 10 before | Expand all | Expand 10 after
6735 Scanner::kNoSourcePos, Symbols::AwaitContextVar(), dynamic_type); 6735 Scanner::kNoSourcePos, Symbols::AwaitContextVar(), dynamic_type);
6736 current_block_->scope->AddVariable(await_ctx_var); 6736 current_block_->scope->AddVariable(await_ctx_var);
6737 current_block_->scope->CaptureVariable(Symbols::AwaitContextVar()); 6737 current_block_->scope->CaptureVariable(Symbols::AwaitContextVar());
6738 await_ctx_var->set_is_captured(); 6738 await_ctx_var->set_is_captured();
6739 } 6739 }
6740 6740
6741 6741
6742 void Parser::AddAsyncClosureVariables() { 6742 void Parser::AddAsyncClosureVariables() {
6743 // Add to current block's scope: 6743 // Add to current block's scope:
6744 // var :async_op; 6744 // var :async_op;
6745 // var :async_then_callback;
6746 // var :async_catch_error_callback;
6745 // var :async_completer; 6747 // var :async_completer;
6746 const Type& dynamic_type = Type::ZoneHandle(Z, Type::DynamicType()); 6748 const Type& dynamic_type = Type::ZoneHandle(Z, Type::DynamicType());
6747 LocalVariable* async_op_var = new(Z) LocalVariable( 6749 LocalVariable* async_op_var = new(Z) LocalVariable(
6748 Scanner::kNoSourcePos, Symbols::AsyncOperation(), dynamic_type); 6750 Scanner::kNoSourcePos, Symbols::AsyncOperation(), dynamic_type);
6749 current_block_->scope->AddVariable(async_op_var); 6751 current_block_->scope->AddVariable(async_op_var);
6750 current_block_->scope->CaptureVariable(Symbols::AsyncOperation()); 6752 current_block_->scope->CaptureVariable(Symbols::AsyncOperation());
6751 async_op_var->set_is_captured(); 6753 async_op_var->set_is_captured();
6754 LocalVariable* async_then_callback_var = new(Z) LocalVariable(
6755 Scanner::kNoSourcePos, Symbols::AsyncThenCallback(), dynamic_type);
6756 current_block_->scope->AddVariable(async_then_callback_var);
6757 current_block_->scope->CaptureVariable(Symbols::AsyncThenCallback());
6758 async_then_callback_var->set_is_captured();
6759 LocalVariable* async_catch_error_callback_var = new(Z) LocalVariable(
6760 Scanner::kNoSourcePos, Symbols::AsyncCatchErrorCallback(), dynamic_type);
6761 current_block_->scope->AddVariable(async_catch_error_callback_var);
6762 current_block_->scope->CaptureVariable(Symbols::AsyncCatchErrorCallback());
6763 async_catch_error_callback_var->set_is_captured();
6752 LocalVariable* async_completer = new(Z) LocalVariable( 6764 LocalVariable* async_completer = new(Z) LocalVariable(
6753 Scanner::kNoSourcePos, Symbols::AsyncCompleter(), dynamic_type); 6765 Scanner::kNoSourcePos, Symbols::AsyncCompleter(), dynamic_type);
6754 current_block_->scope->AddVariable(async_completer); 6766 current_block_->scope->AddVariable(async_completer);
6755 current_block_->scope->CaptureVariable(Symbols::AsyncCompleter()); 6767 current_block_->scope->CaptureVariable(Symbols::AsyncCompleter());
6756 async_completer->set_is_captured(); 6768 async_completer->set_is_captured();
6757 } 6769 }
6758 6770
6759 6771
6760 void Parser::AddAsyncGeneratorVariables() { 6772 void Parser::AddAsyncGeneratorVariables() {
6761 // Add to current block's scope: 6773 // Add to current block's scope:
6762 // var :controller; 6774 // var :controller;
6763 // The :controller variable is used by the async generator closure to 6775 // The :controller variable is used by the async generator closure to
6764 // store the StreamController object to which the yielded expressions 6776 // store the StreamController object to which the yielded expressions
6765 // are added. 6777 // are added.
6766 // var :async_op; 6778 // var :async_op;
6767 // This variable is used to store the async generator closure containing 6779 // var :async_then_callback;
6768 // the body of the async* function. It is used by the await operator. 6780 // var :async_catch_error_callback;
6781 // These variables are used to store the async generator closure containing
6782 // the body of the async* function. They are used by the await operator.
6769 const Type& dynamic_type = Type::ZoneHandle(Z, Type::DynamicType()); 6783 const Type& dynamic_type = Type::ZoneHandle(Z, Type::DynamicType());
6770 LocalVariable* controller_var = new(Z) LocalVariable( 6784 LocalVariable* controller_var = new(Z) LocalVariable(
6771 Scanner::kNoSourcePos, Symbols::Controller(), dynamic_type); 6785 Scanner::kNoSourcePos, Symbols::Controller(), dynamic_type);
6772 current_block_->scope->AddVariable(controller_var); 6786 current_block_->scope->AddVariable(controller_var);
6773 current_block_->scope->CaptureVariable(Symbols::Controller()); 6787 current_block_->scope->CaptureVariable(Symbols::Controller());
6774 controller_var->set_is_captured(); 6788 controller_var->set_is_captured();
6775 6789
6776 LocalVariable* async_op_var = new(Z) LocalVariable( 6790 LocalVariable* async_op_var = new(Z) LocalVariable(
6777 Scanner::kNoSourcePos, Symbols::AsyncOperation(), dynamic_type); 6791 Scanner::kNoSourcePos, Symbols::AsyncOperation(), dynamic_type);
6778 current_block_->scope->AddVariable(async_op_var); 6792 current_block_->scope->AddVariable(async_op_var);
6779 current_block_->scope->CaptureVariable(Symbols::AsyncOperation()); 6793 current_block_->scope->CaptureVariable(Symbols::AsyncOperation());
6780 async_op_var->set_is_captured(); 6794 async_op_var->set_is_captured();
6795 LocalVariable* async_then_callback_var = new(Z) LocalVariable(
6796 Scanner::kNoSourcePos, Symbols::AsyncThenCallback(), dynamic_type);
6797 current_block_->scope->AddVariable(async_then_callback_var);
6798 current_block_->scope->CaptureVariable(Symbols::AsyncThenCallback());
6799 async_then_callback_var->set_is_captured();
6800 LocalVariable* async_catch_error_callback_var = new(Z) LocalVariable(
6801 Scanner::kNoSourcePos, Symbols::AsyncCatchErrorCallback(), dynamic_type);
6802 current_block_->scope->AddVariable(async_catch_error_callback_var);
6803 current_block_->scope->CaptureVariable(Symbols::AsyncCatchErrorCallback());
6804 async_catch_error_callback_var->set_is_captured();
6781 } 6805 }
6782 6806
6783 6807
6784 RawFunction* Parser::OpenAsyncGeneratorFunction(intptr_t async_func_pos) { 6808 RawFunction* Parser::OpenAsyncGeneratorFunction(intptr_t async_func_pos) {
6785 TRACE_PARSER("OpenAsyncGeneratorFunction"); 6809 TRACE_PARSER("OpenAsyncGeneratorFunction");
6786 AddContinuationVariables(); 6810 AddContinuationVariables();
6787 AddAsyncGeneratorVariables(); 6811 AddAsyncGeneratorVariables();
6788 6812
6789 Function& closure = Function::Handle(Z); 6813 Function& closure = Function::Handle(Z);
6790 bool is_new_closure = false; 6814 bool is_new_closure = false;
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after
6852 // Generate the Ast nodes for the implicit code of the async* function. 6876 // Generate the Ast nodes for the implicit code of the async* function.
6853 // 6877 //
6854 // f(...) async* { 6878 // f(...) async* {
6855 // var :controller; 6879 // var :controller;
6856 // var :await_jump_var = -1; 6880 // var :await_jump_var = -1;
6857 // var :await_context_var; 6881 // var :await_context_var;
6858 // f_async_body() { 6882 // f_async_body() {
6859 // ... source code of f ... 6883 // ... source code of f ...
6860 // } 6884 // }
6861 // var :async_op = f_async_body; 6885 // var :async_op = f_async_body;
6886 // var :async_then_callback = _asyncThenWrapperHelper(:async_op);
6887 // var :async_catch_error_callback = _asyncCatchErrorWrapperHelper(:async_op);
6862 // :controller = new _AsyncStarStreamController(:async_op); 6888 // :controller = new _AsyncStarStreamController(:async_op);
6863 // return :controller.stream; 6889 // return :controller.stream;
6864 // } 6890 // }
6865 SequenceNode* Parser::CloseAsyncGeneratorFunction(const Function& closure_func, 6891 SequenceNode* Parser::CloseAsyncGeneratorFunction(const Function& closure_func,
6866 SequenceNode* closure_body) { 6892 SequenceNode* closure_body) {
6867 TRACE_PARSER("CloseAsyncGeneratorFunction"); 6893 TRACE_PARSER("CloseAsyncGeneratorFunction");
6868 ASSERT(!closure_func.IsNull()); 6894 ASSERT(!closure_func.IsNull());
6869 ASSERT(closure_body != NULL); 6895 ASSERT(closure_body != NULL);
6870 6896
6871 // The block for the async closure body has already been closed. Close the 6897 // The block for the async closure body has already been closed. Close the
6872 // corresponding function block. 6898 // corresponding function block.
6873 CloseBlock(); 6899 CloseBlock();
6874 6900
6875 // Make sure the implicit variables of the async generator function 6901 // Make sure the implicit variables of the async generator function
6876 // are captured. 6902 // are captured.
6877 closure_body->scope()->LookupVariable(Symbols::AwaitJumpVar(), false); 6903 closure_body->scope()->LookupVariable(Symbols::AwaitJumpVar(), false);
6878 closure_body->scope()->LookupVariable(Symbols::AwaitContextVar(), false); 6904 closure_body->scope()->LookupVariable(Symbols::AwaitContextVar(), false);
6879 closure_body->scope()->LookupVariable(Symbols::Controller(), false); 6905 closure_body->scope()->LookupVariable(Symbols::Controller(), false);
6880 closure_body->scope()->LookupVariable(Symbols::AsyncOperation(), false); 6906 closure_body->scope()->LookupVariable(Symbols::AsyncOperation(), false);
6907 closure_body->scope()->LookupVariable(Symbols::AsyncThenCallback(), false);
6908 closure_body->scope()->LookupVariable(
6909 Symbols::AsyncCatchErrorCallback(), false);
6910
6911 const Library& async_lib = Library::Handle(Library::AsyncLibrary());
6881 6912
6882 const Class& controller_class = Class::Handle(Z, 6913 const Class& controller_class = Class::Handle(Z,
6883 Library::LookupCoreClass(Symbols::_AsyncStarStreamController())); 6914 async_lib.LookupClassAllowPrivate(
6915 Symbols::_AsyncStarStreamController()));
6884 ASSERT(!controller_class.IsNull()); 6916 ASSERT(!controller_class.IsNull());
6885 const Function& controller_constructor = Function::ZoneHandle(Z, 6917 const Function& controller_constructor = Function::ZoneHandle(Z,
6886 controller_class.LookupConstructorAllowPrivate( 6918 controller_class.LookupConstructorAllowPrivate(
6887 Symbols::_AsyncStarStreamControllerConstructor())); 6919 Symbols::_AsyncStarStreamControllerConstructor()));
6888 6920
6889 // :await_jump_var = -1; 6921 // :await_jump_var = -1;
6890 LocalVariable* jump_var = 6922 LocalVariable* jump_var =
6891 current_block_->scope->LookupVariable(Symbols::AwaitJumpVar(), false); 6923 current_block_->scope->LookupVariable(Symbols::AwaitJumpVar(), false);
6892 LiteralNode* init_value = 6924 LiteralNode* init_value =
6893 new(Z) LiteralNode(Scanner::kNoSourcePos, Smi::ZoneHandle(Smi::New(-1))); 6925 new(Z) LiteralNode(Scanner::kNoSourcePos, Smi::ZoneHandle(Smi::New(-1)));
6894 current_block_->statements->Add( 6926 current_block_->statements->Add(
6895 new(Z) StoreLocalNode(Scanner::kNoSourcePos, jump_var, init_value)); 6927 new(Z) StoreLocalNode(Scanner::kNoSourcePos, jump_var, init_value));
6896 6928
6897 // Add to AST: 6929 // Add to AST:
6898 // :async_op = <closure>; (containing the original body) 6930 // :async_op = <closure>; (containing the original body)
6899 LocalVariable* async_op_var = 6931 LocalVariable* async_op_var =
6900 current_block_->scope->LookupVariable(Symbols::AsyncOperation(), false); 6932 current_block_->scope->LookupVariable(Symbols::AsyncOperation(), false);
6901 ClosureNode* closure_obj = new(Z) ClosureNode( 6933 ClosureNode* closure_obj = new(Z) ClosureNode(
6902 Scanner::kNoSourcePos, closure_func, NULL, closure_body->scope()); 6934 Scanner::kNoSourcePos, closure_func, NULL, closure_body->scope());
6903 StoreLocalNode* store_async_op = new (Z) StoreLocalNode( 6935 StoreLocalNode* store_async_op = new (Z) StoreLocalNode(
6904 Scanner::kNoSourcePos, 6936 Scanner::kNoSourcePos,
6905 async_op_var, 6937 async_op_var,
6906 closure_obj); 6938 closure_obj);
6939
6907 current_block_->statements->Add(store_async_op); 6940 current_block_->statements->Add(store_async_op);
6908 6941
6942 // :async_then_callback = _asyncThenWrapperHelper(:async_op)
6943 const Function& async_then_wrapper_helper = Function::ZoneHandle(
6944 Z, async_lib.LookupFunctionAllowPrivate(
6945 Symbols::AsyncThenWrapperHelper()));
6946 ASSERT(!async_then_wrapper_helper.IsNull());
6947 ArgumentListNode* async_then_wrapper_helper_args = new (Z) ArgumentListNode(
6948 Scanner::kNoSourcePos);
6949 async_then_wrapper_helper_args->Add(
6950 new (Z) LoadLocalNode(Scanner::kNoSourcePos, async_op_var));
6951 StaticCallNode* then_wrapper_call = new (Z) StaticCallNode(
6952 Scanner::kNoSourcePos,
6953 async_then_wrapper_helper,
6954 async_then_wrapper_helper_args);
6955 LocalVariable* async_then_callback_var =
6956 current_block_->scope->LookupVariable(
6957 Symbols::AsyncThenCallback(), false);
6958 StoreLocalNode* store_async_then_callback = new (Z) StoreLocalNode(
6959 Scanner::kNoSourcePos,
6960 async_then_callback_var,
6961 then_wrapper_call);
6962
6963 current_block_->statements->Add(store_async_then_callback);
6964
6965 // :async_catch_error_callback = _asyncErrorWrapperHelper(:async_op)
6966
6967 const Function& async_error_wrapper_helper = Function::ZoneHandle(
6968 Z, async_lib.LookupFunctionAllowPrivate(
6969 Symbols::AsyncErrorWrapperHelper()));
6970 ASSERT(!async_error_wrapper_helper.IsNull());
6971 ArgumentListNode* async_error_wrapper_helper_args = new (Z) ArgumentListNode(
6972 Scanner::kNoSourcePos);
6973 async_error_wrapper_helper_args->Add(
6974 new (Z) LoadLocalNode(Scanner::kNoSourcePos, async_op_var));
6975 StaticCallNode* error_wrapper_call = new (Z) StaticCallNode(
6976 Scanner::kNoSourcePos,
6977 async_error_wrapper_helper,
6978 async_error_wrapper_helper_args);
6979 LocalVariable* async_catch_error_callback_var =
6980 current_block_->scope->LookupVariable(
6981 Symbols::AsyncCatchErrorCallback(), false);
6982 StoreLocalNode* store_async_catch_error_callback = new (Z) StoreLocalNode(
6983 Scanner::kNoSourcePos,
6984 async_catch_error_callback_var,
6985 error_wrapper_call);
6986
6987 current_block_->statements->Add(store_async_catch_error_callback);
6988
6909 // :controller = new _AsyncStarStreamController(body_closure); 6989 // :controller = new _AsyncStarStreamController(body_closure);
6910 ArgumentListNode* arguments = new(Z) ArgumentListNode(Scanner::kNoSourcePos); 6990 ArgumentListNode* arguments = new(Z) ArgumentListNode(Scanner::kNoSourcePos);
6911 arguments->Add(new (Z) LoadLocalNode(Scanner::kNoSourcePos, async_op_var)); 6991 arguments->Add(new (Z) LoadLocalNode(Scanner::kNoSourcePos, async_op_var));
6912 ConstructorCallNode* controller_constructor_call = 6992 ConstructorCallNode* controller_constructor_call =
6913 new(Z) ConstructorCallNode(Scanner::kNoSourcePos, 6993 new(Z) ConstructorCallNode(Scanner::kNoSourcePos,
6914 TypeArguments::ZoneHandle(Z), 6994 TypeArguments::ZoneHandle(Z),
6915 controller_constructor, 6995 controller_constructor,
6916 arguments); 6996 arguments);
6917 LocalVariable* controller_var = 6997 LocalVariable* controller_var =
6918 current_block_->scope->LookupVariable(Symbols::Controller(), false); 6998 current_block_->scope->LookupVariable(Symbols::Controller(), false);
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after
7055 LocalVariable* async_op_var = current_block_->scope->LookupVariable( 7135 LocalVariable* async_op_var = current_block_->scope->LookupVariable(
7056 Symbols::AsyncOperation(), false); 7136 Symbols::AsyncOperation(), false);
7057 ClosureNode* cn = new(Z) ClosureNode( 7137 ClosureNode* cn = new(Z) ClosureNode(
7058 Scanner::kNoSourcePos, closure, NULL, closure_body->scope()); 7138 Scanner::kNoSourcePos, closure, NULL, closure_body->scope());
7059 StoreLocalNode* store_async_op = new (Z) StoreLocalNode( 7139 StoreLocalNode* store_async_op = new (Z) StoreLocalNode(
7060 Scanner::kNoSourcePos, 7140 Scanner::kNoSourcePos,
7061 async_op_var, 7141 async_op_var,
7062 cn); 7142 cn);
7063 current_block_->statements->Add(store_async_op); 7143 current_block_->statements->Add(store_async_op);
7064 7144
7145 const Library& async_lib = Library::Handle(Library::AsyncLibrary());
7146 // :async_then_callback = _asyncThenWrapperHelper(:async_op)
7147 const Function& async_then_wrapper_helper = Function::ZoneHandle(
7148 Z, async_lib.LookupFunctionAllowPrivate(
7149 Symbols::AsyncThenWrapperHelper()));
7150 ASSERT(!async_then_wrapper_helper.IsNull());
7151 ArgumentListNode* async_then_wrapper_helper_args = new (Z) ArgumentListNode(
7152 Scanner::kNoSourcePos);
7153 async_then_wrapper_helper_args->Add(
7154 new (Z) LoadLocalNode(Scanner::kNoSourcePos, async_op_var));
7155 StaticCallNode* then_wrapper_call = new (Z) StaticCallNode(
7156 Scanner::kNoSourcePos,
7157 async_then_wrapper_helper,
7158 async_then_wrapper_helper_args);
7159 LocalVariable* async_then_callback_var =
7160 current_block_->scope->LookupVariable(
7161 Symbols::AsyncThenCallback(), false);
7162 StoreLocalNode* store_async_then_callback = new (Z) StoreLocalNode(
7163 Scanner::kNoSourcePos,
7164 async_then_callback_var,
7165 then_wrapper_call);
7166
7167 current_block_->statements->Add(store_async_then_callback);
7168
7169 // :async_catch_error_callback = _asyncErrorWrapperHelper(:async_op)
7170
7171 const Function& async_error_wrapper_helper = Function::ZoneHandle(
7172 Z, async_lib.LookupFunctionAllowPrivate(
7173 Symbols::AsyncErrorWrapperHelper()));
7174 ASSERT(!async_error_wrapper_helper.IsNull());
7175 ArgumentListNode* async_error_wrapper_helper_args = new (Z) ArgumentListNode(
7176 Scanner::kNoSourcePos);
7177 async_error_wrapper_helper_args->Add(
7178 new (Z) LoadLocalNode(Scanner::kNoSourcePos, async_op_var));
7179 StaticCallNode* error_wrapper_call = new (Z) StaticCallNode(
7180 Scanner::kNoSourcePos,
7181 async_error_wrapper_helper,
7182 async_error_wrapper_helper_args);
7183 LocalVariable* async_catch_error_callback_var =
7184 current_block_->scope->LookupVariable(
7185 Symbols::AsyncCatchErrorCallback(), false);
7186 StoreLocalNode* store_async_catch_error_callback = new (Z) StoreLocalNode(
7187 Scanner::kNoSourcePos,
7188 async_catch_error_callback_var,
7189 error_wrapper_call);
7190
7191 current_block_->statements->Add(store_async_catch_error_callback);
7192
7065 // Add to AST: 7193 // Add to AST:
7066 // new Future.microtask(:async_op); 7194 // new Future.microtask(:async_op);
7067 ArgumentListNode* arguments = new (Z) ArgumentListNode(Scanner::kNoSourcePos); 7195 ArgumentListNode* arguments = new (Z) ArgumentListNode(Scanner::kNoSourcePos);
7068 arguments->Add(new (Z) LoadLocalNode( 7196 arguments->Add(new (Z) LoadLocalNode(
7069 Scanner::kNoSourcePos, async_op_var)); 7197 Scanner::kNoSourcePos, async_op_var));
7070 ConstructorCallNode* future_node = new (Z) ConstructorCallNode( 7198 ConstructorCallNode* future_node = new (Z) ConstructorCallNode(
7071 Scanner::kNoSourcePos, TypeArguments::ZoneHandle(Z), constructor, 7199 Scanner::kNoSourcePos, TypeArguments::ZoneHandle(Z), constructor,
7072 arguments); 7200 arguments);
7073 current_block_->statements->Add(future_node); 7201 current_block_->statements->Add(future_node);
7074 7202
(...skipping 6993 matching lines...) Expand 10 before | Expand all | Expand 10 after
14068 void Parser::SkipQualIdent() { 14196 void Parser::SkipQualIdent() {
14069 ASSERT(IsIdentifier()); 14197 ASSERT(IsIdentifier());
14070 ConsumeToken(); 14198 ConsumeToken();
14071 if (CurrentToken() == Token::kPERIOD) { 14199 if (CurrentToken() == Token::kPERIOD) {
14072 ConsumeToken(); // Consume the kPERIOD token. 14200 ConsumeToken(); // Consume the kPERIOD token.
14073 ExpectIdentifier("identifier expected after '.'"); 14201 ExpectIdentifier("identifier expected after '.'");
14074 } 14202 }
14075 } 14203 }
14076 14204
14077 } // namespace dart 14205 } // namespace dart
OLDNEW
« no previous file with comments | « runtime/vm/ast_transformer.cc ('k') | runtime/vm/symbols.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698