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

Side by Side Diff: src/ia32/full-codegen-ia32.cc

Issue 660095: Merge revision 3813 to 3930 from bleeding_edge to partial snapshots branch. (Closed) Base URL: http://v8.googlecode.com/svn/branches/experimental/partial_snapshots/
Patch Set: '' Created 10 years, 10 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 | « src/ia32/fast-codegen-ia32.cc ('k') | src/ia32/ic-ia32.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 2009 the V8 project authors. All rights reserved. 1 // Copyright 2009 the V8 project authors. All rights reserved.
2 // Redistribution and use in source and binary forms, with or without 2 // Redistribution and use in source and binary forms, with or without
3 // modification, are permitted provided that the following conditions are 3 // modification, are permitted provided that the following conditions are
4 // met: 4 // met:
5 // 5 //
6 // * Redistributions of source code must retain the above copyright 6 // * Redistributions of source code must retain the above copyright
7 // notice, this list of conditions and the following disclaimer. 7 // notice, this list of conditions and the following disclaimer.
8 // * Redistributions in binary form must reproduce the above 8 // * Redistributions in binary form must reproduce the above
9 // copyright notice, this list of conditions and the following 9 // copyright notice, this list of conditions and the following
10 // disclaimer in the documentation and/or other materials provided 10 // disclaimer in the documentation and/or other materials provided
(...skipping 790 matching lines...) Expand 10 before | Expand all | Expand 10 after
801 // Four cases: non-this global variables, lookup slots, all other 801 // Four cases: non-this global variables, lookup slots, all other
802 // types of slots, and parameters that rewrite to explicit property 802 // types of slots, and parameters that rewrite to explicit property
803 // accesses on the arguments object. 803 // accesses on the arguments object.
804 Slot* slot = var->slot(); 804 Slot* slot = var->slot();
805 Property* property = var->AsProperty(); 805 Property* property = var->AsProperty();
806 806
807 if (var->is_global() && !var->is_this()) { 807 if (var->is_global() && !var->is_this()) {
808 Comment cmnt(masm_, "Global variable"); 808 Comment cmnt(masm_, "Global variable");
809 // Use inline caching. Variable name is passed in ecx and the global 809 // Use inline caching. Variable name is passed in ecx and the global
810 // object on the stack. 810 // object on the stack.
811 __ push(CodeGenerator::GlobalObject()); 811 __ mov(eax, CodeGenerator::GlobalObject());
812 __ mov(ecx, var->name()); 812 __ mov(ecx, var->name());
813 Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize)); 813 Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize));
814 __ call(ic, RelocInfo::CODE_TARGET_CONTEXT); 814 __ call(ic, RelocInfo::CODE_TARGET_CONTEXT);
815 // By emitting a nop we make sure that we do not have a test eax 815 // By emitting a nop we make sure that we do not have a test eax
816 // instruction after the call it is treated specially by the LoadIC code 816 // instruction after the call it is treated specially by the LoadIC code
817 // Remember that the assembler may choose to do peephole optimization 817 // Remember that the assembler may choose to do peephole optimization
818 // (eg, push/pop elimination). 818 // (eg, push/pop elimination).
819 __ nop(); 819 __ nop();
820 DropAndApply(1, context, eax); 820 Apply(context, eax);
821 821
822 } else if (slot != NULL && slot->type() == Slot::LOOKUP) { 822 } else if (slot != NULL && slot->type() == Slot::LOOKUP) {
823 Comment cmnt(masm_, "Lookup slot"); 823 Comment cmnt(masm_, "Lookup slot");
824 __ push(esi); // Context. 824 __ push(esi); // Context.
825 __ push(Immediate(var->name())); 825 __ push(Immediate(var->name()));
826 __ CallRuntime(Runtime::kLoadContextSlot, 2); 826 __ CallRuntime(Runtime::kLoadContextSlot, 2);
827 Apply(context, eax); 827 Apply(context, eax);
828 828
829 } else if (slot != NULL) { 829 } else if (slot != NULL) {
830 Comment cmnt(masm_, (slot->type() == Slot::CONTEXT) 830 Comment cmnt(masm_, (slot->type() == Slot::CONTEXT)
831 ? "Context slot" 831 ? "Context slot"
832 : "Stack slot"); 832 : "Stack slot");
833 Apply(context, slot); 833 Apply(context, slot);
834 834
835 } else { 835 } else {
836 Comment cmnt(masm_, "Rewritten parameter"); 836 Comment cmnt(masm_, "Rewritten parameter");
837 ASSERT_NOT_NULL(property); 837 ASSERT_NOT_NULL(property);
838 // Rewritten parameter accesses are of the form "slot[literal]". 838 // Rewritten parameter accesses are of the form "slot[literal]".
839 839
840 // Assert that the object is in a slot. 840 // Assert that the object is in a slot.
841 Variable* object_var = property->obj()->AsVariableProxy()->AsVariable(); 841 Variable* object_var = property->obj()->AsVariableProxy()->AsVariable();
842 ASSERT_NOT_NULL(object_var); 842 ASSERT_NOT_NULL(object_var);
843 Slot* object_slot = object_var->slot(); 843 Slot* object_slot = object_var->slot();
844 ASSERT_NOT_NULL(object_slot); 844 ASSERT_NOT_NULL(object_slot);
845 845
846 // Load the object. 846 // Load the object.
847 MemOperand object_loc = EmitSlotSearch(object_slot, eax); 847 MemOperand object_loc = EmitSlotSearch(object_slot, eax);
848 __ push(object_loc); 848 __ mov(edx, object_loc);
849 849
850 // Assert that the key is a smi. 850 // Assert that the key is a smi.
851 Literal* key_literal = property->key()->AsLiteral(); 851 Literal* key_literal = property->key()->AsLiteral();
852 ASSERT_NOT_NULL(key_literal); 852 ASSERT_NOT_NULL(key_literal);
853 ASSERT(key_literal->handle()->IsSmi()); 853 ASSERT(key_literal->handle()->IsSmi());
854 854
855 // Load the key. 855 // Load the key.
856 __ push(Immediate(key_literal->handle())); 856 __ mov(eax, Immediate(key_literal->handle()));
857 857
858 // Do a keyed property load. 858 // Do a keyed property load.
859 Handle<Code> ic(Builtins::builtin(Builtins::KeyedLoadIC_Initialize)); 859 Handle<Code> ic(Builtins::builtin(Builtins::KeyedLoadIC_Initialize));
860 __ call(ic, RelocInfo::CODE_TARGET); 860 __ call(ic, RelocInfo::CODE_TARGET);
861 // Notice: We must not have a "test eax, ..." instruction after the 861 // Notice: We must not have a "test eax, ..." instruction after the
862 // call. It is treated specially by the LoadIC code. 862 // call. It is treated specially by the LoadIC code.
863 __ nop(); 863 __ nop();
864 // Drop key and object left on the stack by IC. 864 // Drop key and object left on the stack by IC.
865 DropAndApply(2, context, eax); 865 Apply(context, eax);
866 } 866 }
867 } 867 }
868 868
869 869
870 void FullCodeGenerator::VisitRegExpLiteral(RegExpLiteral* expr) { 870 void FullCodeGenerator::VisitRegExpLiteral(RegExpLiteral* expr) {
871 Comment cmnt(masm_, "[ RegExpLiteral"); 871 Comment cmnt(masm_, "[ RegExpLiteral");
872 Label done; 872 Label done;
873 // Registers will be used as follows: 873 // Registers will be used as follows:
874 // edi = JS function. 874 // edi = JS function.
875 // ebx = literals array. 875 // ebx = literals array.
(...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after
1006 } 1006 }
1007 1007
1008 if (result_saved) { 1008 if (result_saved) {
1009 ApplyTOS(context_); 1009 ApplyTOS(context_);
1010 } else { 1010 } else {
1011 Apply(context_, eax); 1011 Apply(context_, eax);
1012 } 1012 }
1013 } 1013 }
1014 1014
1015 1015
1016 void FullCodeGenerator::VisitAssignment(Assignment* expr) {
1017 Comment cmnt(masm_, "[ Assignment");
1018 ASSERT(expr->op() != Token::INIT_CONST);
1019 // Left-hand side can only be a property, a global or a (parameter or local)
1020 // slot. Variables with rewrite to .arguments are treated as KEYED_PROPERTY.
1021 enum LhsKind { VARIABLE, NAMED_PROPERTY, KEYED_PROPERTY };
1022 LhsKind assign_type = VARIABLE;
1023 Property* prop = expr->target()->AsProperty();
1024 if (prop != NULL) {
1025 assign_type =
1026 (prop->key()->IsPropertyName()) ? NAMED_PROPERTY : KEYED_PROPERTY;
1027 }
1028
1029 // Evaluate LHS expression.
1030 switch (assign_type) {
1031 case VARIABLE:
1032 // Nothing to do here.
1033 break;
1034 case NAMED_PROPERTY:
1035 if (expr->is_compound()) {
1036 // We need the receiver both on the stack and in the accumulator.
1037 VisitForValue(prop->obj(), kAccumulator);
1038 __ push(result_register());
1039 } else {
1040 VisitForValue(prop->obj(), kStack);
1041 }
1042 break;
1043 case KEYED_PROPERTY:
1044 if (expr->is_compound()) {
1045 VisitForValue(prop->obj(), kStack);
1046 VisitForValue(prop->key(), kAccumulator);
1047 __ mov(edx, Operand(esp, 0));
1048 __ push(eax);
1049 } else {
1050 VisitForValue(prop->obj(), kStack);
1051 VisitForValue(prop->key(), kStack);
1052 }
1053 break;
1054 }
1055
1056 // If we have a compound assignment: Get value of LHS expression and
1057 // store in on top of the stack.
1058 if (expr->is_compound()) {
1059 Location saved_location = location_;
1060 location_ = kStack;
1061 switch (assign_type) {
1062 case VARIABLE:
1063 EmitVariableLoad(expr->target()->AsVariableProxy()->var(),
1064 Expression::kValue);
1065 break;
1066 case NAMED_PROPERTY:
1067 EmitNamedPropertyLoad(prop);
1068 __ push(result_register());
1069 break;
1070 case KEYED_PROPERTY:
1071 EmitKeyedPropertyLoad(prop);
1072 __ push(result_register());
1073 break;
1074 }
1075 location_ = saved_location;
1076 }
1077
1078 // Evaluate RHS expression.
1079 Expression* rhs = expr->value();
1080 VisitForValue(rhs, kAccumulator);
1081
1082 // If we have a compound assignment: Apply operator.
1083 if (expr->is_compound()) {
1084 Location saved_location = location_;
1085 location_ = kAccumulator;
1086 EmitBinaryOp(expr->binary_op(), Expression::kValue);
1087 location_ = saved_location;
1088 }
1089
1090 // Record source position before possible IC call.
1091 SetSourcePosition(expr->position());
1092
1093 // Store the value.
1094 switch (assign_type) {
1095 case VARIABLE:
1096 EmitVariableAssignment(expr->target()->AsVariableProxy()->var(),
1097 context_);
1098 break;
1099 case NAMED_PROPERTY:
1100 EmitNamedPropertyAssignment(expr);
1101 break;
1102 case KEYED_PROPERTY:
1103 EmitKeyedPropertyAssignment(expr);
1104 break;
1105 }
1106 }
1107
1108
1016 void FullCodeGenerator::EmitNamedPropertyLoad(Property* prop) { 1109 void FullCodeGenerator::EmitNamedPropertyLoad(Property* prop) {
1017 SetSourcePosition(prop->position()); 1110 SetSourcePosition(prop->position());
1018 Literal* key = prop->key()->AsLiteral(); 1111 Literal* key = prop->key()->AsLiteral();
1019 __ mov(ecx, Immediate(key->handle())); 1112 __ mov(ecx, Immediate(key->handle()));
1020 Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize)); 1113 Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize));
1021 __ call(ic, RelocInfo::CODE_TARGET); 1114 __ call(ic, RelocInfo::CODE_TARGET);
1022 __ nop(); 1115 __ nop();
1023 } 1116 }
1024 1117
1025 1118
(...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after
1176 1269
1177 // Receiver and key are still on stack. 1270 // Receiver and key are still on stack.
1178 DropAndApply(2, context_, eax); 1271 DropAndApply(2, context_, eax);
1179 } 1272 }
1180 1273
1181 1274
1182 void FullCodeGenerator::VisitProperty(Property* expr) { 1275 void FullCodeGenerator::VisitProperty(Property* expr) {
1183 Comment cmnt(masm_, "[ Property"); 1276 Comment cmnt(masm_, "[ Property");
1184 Expression* key = expr->key(); 1277 Expression* key = expr->key();
1185 1278
1186 // Evaluate the receiver.
1187 VisitForValue(expr->obj(), kStack);
1188
1189 if (key->IsPropertyName()) { 1279 if (key->IsPropertyName()) {
1280 VisitForValue(expr->obj(), kAccumulator);
1190 EmitNamedPropertyLoad(expr); 1281 EmitNamedPropertyLoad(expr);
1191 // Drop receiver left on the stack by IC. 1282 Apply(context_, eax);
1192 DropAndApply(1, context_, eax);
1193 } else { 1283 } else {
1194 VisitForValue(expr->key(), kStack); 1284 VisitForValue(expr->obj(), kStack);
1285 VisitForValue(expr->key(), kAccumulator);
1286 __ pop(edx);
1195 EmitKeyedPropertyLoad(expr); 1287 EmitKeyedPropertyLoad(expr);
1196 // Drop key and receiver left on the stack by IC. 1288 Apply(context_, eax);
1197 DropAndApply(2, context_, eax);
1198 } 1289 }
1199 } 1290 }
1200 1291
1201 1292
1202 void FullCodeGenerator::EmitCallWithIC(Call* expr, 1293 void FullCodeGenerator::EmitCallWithIC(Call* expr,
1203 Handle<Object> name, 1294 Handle<Object> name,
1204 RelocInfo::Mode mode) { 1295 RelocInfo::Mode mode) {
1205 // Code common for calls using the IC. 1296 // Code common for calls using the IC.
1206 ZoneList<Expression*>* args = expr->arguments(); 1297 ZoneList<Expression*>* args = expr->arguments();
1207 int arg_count = args->length(); 1298 int arg_count = args->length();
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
1258 Property* prop = fun->AsProperty(); 1349 Property* prop = fun->AsProperty();
1259 Literal* key = prop->key()->AsLiteral(); 1350 Literal* key = prop->key()->AsLiteral();
1260 if (key != NULL && key->handle()->IsSymbol()) { 1351 if (key != NULL && key->handle()->IsSymbol()) {
1261 // Call to a named property, use call IC. 1352 // Call to a named property, use call IC.
1262 VisitForValue(prop->obj(), kStack); 1353 VisitForValue(prop->obj(), kStack);
1263 EmitCallWithIC(expr, key->handle(), RelocInfo::CODE_TARGET); 1354 EmitCallWithIC(expr, key->handle(), RelocInfo::CODE_TARGET);
1264 } else { 1355 } else {
1265 // Call to a keyed property, use keyed load IC followed by function 1356 // Call to a keyed property, use keyed load IC followed by function
1266 // call. 1357 // call.
1267 VisitForValue(prop->obj(), kStack); 1358 VisitForValue(prop->obj(), kStack);
1268 VisitForValue(prop->key(), kStack); 1359 VisitForValue(prop->key(), kAccumulator);
1269 // Record source code position for IC call. 1360 // Record source code position for IC call.
1270 SetSourcePosition(prop->position()); 1361 SetSourcePosition(prop->position());
1362 if (prop->is_synthetic()) {
1363 __ pop(edx); // We do not need to keep the receiver.
1364 } else {
1365 __ mov(edx, Operand(esp, 0)); // Keep receiver, to call function on.
1366 }
1367
1271 Handle<Code> ic(Builtins::builtin(Builtins::KeyedLoadIC_Initialize)); 1368 Handle<Code> ic(Builtins::builtin(Builtins::KeyedLoadIC_Initialize));
1272 __ call(ic, RelocInfo::CODE_TARGET); 1369 __ call(ic, RelocInfo::CODE_TARGET);
1273 // By emitting a nop we make sure that we do not have a "test eax,..." 1370 // By emitting a nop we make sure that we do not have a "test eax,..."
1274 // instruction after the call it is treated specially by the LoadIC code. 1371 // instruction after the call it is treated specially by the LoadIC code.
1275 __ nop(); 1372 __ nop();
1276 // Drop key left on the stack by IC.
1277 __ Drop(1);
1278 // Pop receiver.
1279 __ pop(ebx);
1280 // Push result (function).
1281 __ push(eax);
1282 // Push receiver object on stack.
1283 if (prop->is_synthetic()) { 1373 if (prop->is_synthetic()) {
1374 // Push result (function).
1375 __ push(eax);
1376 // Push Global receiver.
1284 __ mov(ecx, CodeGenerator::GlobalObject()); 1377 __ mov(ecx, CodeGenerator::GlobalObject());
1285 __ push(FieldOperand(ecx, GlobalObject::kGlobalReceiverOffset)); 1378 __ push(FieldOperand(ecx, GlobalObject::kGlobalReceiverOffset));
1286 } else { 1379 } else {
1380 // Pop receiver.
1381 __ pop(ebx);
1382 // Push result (function).
1383 __ push(eax);
1287 __ push(ebx); 1384 __ push(ebx);
1288 } 1385 }
1289 EmitCallWithStub(expr); 1386 EmitCallWithStub(expr);
1290 } 1387 }
1291 } else { 1388 } else {
1292 // Call to some other expression. If the expression is an anonymous 1389 // Call to some other expression. If the expression is an anonymous
1293 // function literal not called in a loop, mark it as one that should 1390 // function literal not called in a loop, mark it as one that should
1294 // also use the full code generator. 1391 // also use the full code generator.
1295 FunctionLiteral* lit = fun->AsFunctionLiteral(); 1392 FunctionLiteral* lit = fun->AsFunctionLiteral();
1296 if (lit != NULL && 1393 if (lit != NULL &&
(...skipping 151 matching lines...) Expand 10 before | Expand all | Expand 10 after
1448 break; 1545 break;
1449 } 1546 }
1450 1547
1451 case Token::TYPEOF: { 1548 case Token::TYPEOF: {
1452 Comment cmnt(masm_, "[ UnaryOperation (TYPEOF)"); 1549 Comment cmnt(masm_, "[ UnaryOperation (TYPEOF)");
1453 VariableProxy* proxy = expr->expression()->AsVariableProxy(); 1550 VariableProxy* proxy = expr->expression()->AsVariableProxy();
1454 if (proxy != NULL && 1551 if (proxy != NULL &&
1455 !proxy->var()->is_this() && 1552 !proxy->var()->is_this() &&
1456 proxy->var()->is_global()) { 1553 proxy->var()->is_global()) {
1457 Comment cmnt(masm_, "Global variable"); 1554 Comment cmnt(masm_, "Global variable");
1458 __ push(CodeGenerator::GlobalObject()); 1555 __ mov(eax, CodeGenerator::GlobalObject());
1459 __ mov(ecx, Immediate(proxy->name())); 1556 __ mov(ecx, Immediate(proxy->name()));
1460 Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize)); 1557 Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize));
1461 // Use a regular load, not a contextual load, to avoid a reference 1558 // Use a regular load, not a contextual load, to avoid a reference
1462 // error. 1559 // error.
1463 __ call(ic, RelocInfo::CODE_TARGET); 1560 __ call(ic, RelocInfo::CODE_TARGET);
1464 __ mov(Operand(esp, 0), eax); 1561 __ push(eax);
1465 } else if (proxy != NULL && 1562 } else if (proxy != NULL &&
1466 proxy->var()->slot() != NULL && 1563 proxy->var()->slot() != NULL &&
1467 proxy->var()->slot()->type() == Slot::LOOKUP) { 1564 proxy->var()->slot()->type() == Slot::LOOKUP) {
1468 __ push(esi); 1565 __ push(esi);
1469 __ push(Immediate(proxy->name())); 1566 __ push(Immediate(proxy->name()));
1470 __ CallRuntime(Runtime::kLoadContextSlotNoReferenceError, 2); 1567 __ CallRuntime(Runtime::kLoadContextSlotNoReferenceError, 2);
1471 __ push(eax); 1568 __ push(eax);
1472 } else { 1569 } else {
1473 // This expression cannot throw a reference error at the top level. 1570 // This expression cannot throw a reference error at the top level.
1474 VisitForValue(expr->expression(), kStack); 1571 VisitForValue(expr->expression(), kStack);
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after
1558 Location saved_location = location_; 1655 Location saved_location = location_;
1559 location_ = kAccumulator; 1656 location_ = kAccumulator;
1560 EmitVariableLoad(expr->expression()->AsVariableProxy()->var(), 1657 EmitVariableLoad(expr->expression()->AsVariableProxy()->var(),
1561 Expression::kValue); 1658 Expression::kValue);
1562 location_ = saved_location; 1659 location_ = saved_location;
1563 } else { 1660 } else {
1564 // Reserve space for result of postfix operation. 1661 // Reserve space for result of postfix operation.
1565 if (expr->is_postfix() && context_ != Expression::kEffect) { 1662 if (expr->is_postfix() && context_ != Expression::kEffect) {
1566 __ push(Immediate(Smi::FromInt(0))); 1663 __ push(Immediate(Smi::FromInt(0)));
1567 } 1664 }
1568 VisitForValue(prop->obj(), kStack);
1569 if (assign_type == NAMED_PROPERTY) { 1665 if (assign_type == NAMED_PROPERTY) {
1666 // Put the object both on the stack and in the accumulator.
1667 VisitForValue(prop->obj(), kAccumulator);
1668 __ push(eax);
1570 EmitNamedPropertyLoad(prop); 1669 EmitNamedPropertyLoad(prop);
1571 } else { 1670 } else {
1572 VisitForValue(prop->key(), kStack); 1671 VisitForValue(prop->obj(), kStack);
1672 VisitForValue(prop->key(), kAccumulator);
1673 __ mov(edx, Operand(esp, 0));
1674 __ push(eax);
1573 EmitKeyedPropertyLoad(prop); 1675 EmitKeyedPropertyLoad(prop);
1574 } 1676 }
1575 } 1677 }
1576 1678
1577 // Call ToNumber only if operand is not a smi. 1679 // Call ToNumber only if operand is not a smi.
1578 Label no_conversion; 1680 Label no_conversion;
1579 __ test(eax, Immediate(kSmiTagMask)); 1681 __ test(eax, Immediate(kSmiTagMask));
1580 __ j(zero, &no_conversion); 1682 __ j(zero, &no_conversion);
1581 __ push(eax); 1683 __ push(eax);
1582 __ InvokeBuiltin(Builtins::TO_NUMBER, CALL_FUNCTION); 1684 __ InvokeBuiltin(Builtins::TO_NUMBER, CALL_FUNCTION);
(...skipping 310 matching lines...) Expand 10 before | Expand all | Expand 10 after
1893 __ add(Operand(edx), Immediate(masm_->CodeObject())); 1995 __ add(Operand(edx), Immediate(masm_->CodeObject()));
1894 __ mov(Operand(esp, 0), edx); 1996 __ mov(Operand(esp, 0), edx);
1895 // And return. 1997 // And return.
1896 __ ret(0); 1998 __ ret(0);
1897 } 1999 }
1898 2000
1899 2001
1900 #undef __ 2002 #undef __
1901 2003
1902 } } // namespace v8::internal 2004 } } // namespace v8::internal
OLDNEW
« no previous file with comments | « src/ia32/fast-codegen-ia32.cc ('k') | src/ia32/ic-ia32.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698