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

Side by Side Diff: src/rewriter.cc

Issue 6811012: Remove some dead code. (Closed) Base URL: https://v8.googlecode.com/svn/branches/bleeding_edge
Patch Set: Created 9 years, 8 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/rewriter.h ('k') | src/variables.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 2010 the V8 project authors. All rights reserved. 1 // Copyright 2011 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
11 // with the distribution. 11 // with the distribution.
(...skipping 17 matching lines...) Expand all
29 29
30 #include "rewriter.h" 30 #include "rewriter.h"
31 31
32 #include "ast.h" 32 #include "ast.h"
33 #include "compiler.h" 33 #include "compiler.h"
34 #include "scopes.h" 34 #include "scopes.h"
35 35
36 namespace v8 { 36 namespace v8 {
37 namespace internal { 37 namespace internal {
38 38
39 class AstOptimizer: public AstVisitor {
40 public:
41 explicit AstOptimizer() : has_function_literal_(false) {}
42
43 void Optimize(ZoneList<Statement*>* statements);
44
45 private:
46 // Used for loop condition analysis. Cleared before visiting a loop
47 // condition, set when a function literal is visited.
48 bool has_function_literal_;
49
50 // Helpers
51 void OptimizeArguments(ZoneList<Expression*>* arguments);
52
53 // Node visitors.
54 #define DEF_VISIT(type) \
55 virtual void Visit##type(type* node);
56 AST_NODE_LIST(DEF_VISIT)
57 #undef DEF_VISIT
58
59 DISALLOW_COPY_AND_ASSIGN(AstOptimizer);
60 };
61
62
63 void AstOptimizer::Optimize(ZoneList<Statement*>* statements) {
64 int len = statements->length();
65 for (int i = 0; i < len; i++) {
66 Visit(statements->at(i));
67 }
68 }
69
70
71 void AstOptimizer::OptimizeArguments(ZoneList<Expression*>* arguments) {
72 for (int i = 0; i < arguments->length(); i++) {
73 Visit(arguments->at(i));
74 }
75 }
76
77
78 void AstOptimizer::VisitBlock(Block* node) {
79 Optimize(node->statements());
80 }
81
82
83 void AstOptimizer::VisitExpressionStatement(ExpressionStatement* node) {
84 node->expression()->set_no_negative_zero(true);
85 Visit(node->expression());
86 }
87
88
89 void AstOptimizer::VisitIfStatement(IfStatement* node) {
90 node->condition()->set_no_negative_zero(true);
91 Visit(node->condition());
92 Visit(node->then_statement());
93 if (node->HasElseStatement()) {
94 Visit(node->else_statement());
95 }
96 }
97
98
99 void AstOptimizer::VisitDoWhileStatement(DoWhileStatement* node) {
100 node->cond()->set_no_negative_zero(true);
101 Visit(node->cond());
102 Visit(node->body());
103 }
104
105
106 void AstOptimizer::VisitWhileStatement(WhileStatement* node) {
107 has_function_literal_ = false;
108 node->cond()->set_no_negative_zero(true);
109 Visit(node->cond());
110 node->set_may_have_function_literal(has_function_literal_);
111 Visit(node->body());
112 }
113
114
115 void AstOptimizer::VisitForStatement(ForStatement* node) {
116 if (node->init() != NULL) {
117 Visit(node->init());
118 }
119 if (node->cond() != NULL) {
120 has_function_literal_ = false;
121 node->cond()->set_no_negative_zero(true);
122 Visit(node->cond());
123 node->set_may_have_function_literal(has_function_literal_);
124 }
125 Visit(node->body());
126 if (node->next() != NULL) {
127 Visit(node->next());
128 }
129 }
130
131
132 void AstOptimizer::VisitForInStatement(ForInStatement* node) {
133 Visit(node->each());
134 Visit(node->enumerable());
135 Visit(node->body());
136 }
137
138
139 void AstOptimizer::VisitTryCatchStatement(TryCatchStatement* node) {
140 Visit(node->try_block());
141 Visit(node->catch_var());
142 Visit(node->catch_block());
143 }
144
145
146 void AstOptimizer::VisitTryFinallyStatement(TryFinallyStatement* node) {
147 Visit(node->try_block());
148 Visit(node->finally_block());
149 }
150
151
152 void AstOptimizer::VisitSwitchStatement(SwitchStatement* node) {
153 node->tag()->set_no_negative_zero(true);
154 Visit(node->tag());
155 for (int i = 0; i < node->cases()->length(); i++) {
156 CaseClause* clause = node->cases()->at(i);
157 if (!clause->is_default()) {
158 Visit(clause->label());
159 }
160 Optimize(clause->statements());
161 }
162 }
163
164
165 void AstOptimizer::VisitContinueStatement(ContinueStatement* node) {
166 USE(node);
167 }
168
169
170 void AstOptimizer::VisitBreakStatement(BreakStatement* node) {
171 USE(node);
172 }
173
174
175 void AstOptimizer::VisitDeclaration(Declaration* node) {
176 // Will not be reached by the current optimizations.
177 USE(node);
178 }
179
180
181 void AstOptimizer::VisitEmptyStatement(EmptyStatement* node) {
182 USE(node);
183 }
184
185
186 void AstOptimizer::VisitReturnStatement(ReturnStatement* node) {
187 Visit(node->expression());
188 }
189
190
191 void AstOptimizer::VisitWithEnterStatement(WithEnterStatement* node) {
192 Visit(node->expression());
193 }
194
195
196 void AstOptimizer::VisitWithExitStatement(WithExitStatement* node) {
197 USE(node);
198 }
199
200
201 void AstOptimizer::VisitDebuggerStatement(DebuggerStatement* node) {
202 USE(node);
203 }
204
205
206 void AstOptimizer::VisitFunctionLiteral(FunctionLiteral* node) {
207 has_function_literal_ = true;
208 }
209
210
211 void AstOptimizer::VisitSharedFunctionInfoLiteral(
212 SharedFunctionInfoLiteral* node) {
213 USE(node);
214 }
215
216
217 void AstOptimizer::VisitConditional(Conditional* node) {
218 node->condition()->set_no_negative_zero(true);
219 Visit(node->condition());
220 Visit(node->then_expression());
221 Visit(node->else_expression());
222 }
223
224
225 void AstOptimizer::VisitVariableProxy(VariableProxy* node) {
226 Variable* var = node->AsVariable();
227 if (var != NULL) {
228 if (var->type()->IsKnown()) {
229 node->type()->CopyFrom(var->type());
230 } else if (node->type()->IsLikelySmi()) {
231 var->type()->SetAsLikelySmi();
232 }
233
234 if (FLAG_safe_int32_compiler) {
235 if (var->IsStackAllocated() &&
236 !var->is_arguments() &&
237 var->mode() != Variable::CONST) {
238 node->set_side_effect_free(true);
239 }
240 }
241 }
242 }
243
244
245 void AstOptimizer::VisitLiteral(Literal* node) {
246 Handle<Object> literal = node->handle();
247 if (literal->IsSmi()) {
248 node->type()->SetAsLikelySmi();
249 node->set_side_effect_free(true);
250 } else if (literal->IsHeapNumber()) {
251 if (node->to_int32()) {
252 // Any HeapNumber has an int32 value if it is the input to a bit op.
253 node->set_side_effect_free(true);
254 } else {
255 double double_value = HeapNumber::cast(*literal)->value();
256 int32_t int32_value = DoubleToInt32(double_value);
257 node->set_side_effect_free(double_value == int32_value);
258 }
259 }
260 }
261
262
263 void AstOptimizer::VisitRegExpLiteral(RegExpLiteral* node) {
264 USE(node);
265 }
266
267
268 void AstOptimizer::VisitArrayLiteral(ArrayLiteral* node) {
269 for (int i = 0; i < node->values()->length(); i++) {
270 Visit(node->values()->at(i));
271 }
272 }
273
274 void AstOptimizer::VisitObjectLiteral(ObjectLiteral* node) {
275 for (int i = 0; i < node->properties()->length(); i++) {
276 Visit(node->properties()->at(i)->key());
277 Visit(node->properties()->at(i)->value());
278 }
279 }
280
281
282 void AstOptimizer::VisitCatchExtensionObject(CatchExtensionObject* node) {
283 Visit(node->key());
284 Visit(node->value());
285 }
286
287
288 void AstOptimizer::VisitAssignment(Assignment* node) {
289 switch (node->op()) {
290 case Token::INIT_VAR:
291 case Token::INIT_CONST:
292 case Token::ASSIGN:
293 // No type can be infered from the general assignment.
294 break;
295 case Token::ASSIGN_BIT_OR:
296 case Token::ASSIGN_BIT_XOR:
297 case Token::ASSIGN_BIT_AND:
298 case Token::ASSIGN_SHL:
299 case Token::ASSIGN_SAR:
300 case Token::ASSIGN_SHR:
301 node->type()->SetAsLikelySmiIfUnknown();
302 node->target()->type()->SetAsLikelySmiIfUnknown();
303 node->value()->type()->SetAsLikelySmiIfUnknown();
304 node->value()->set_to_int32(true);
305 node->value()->set_no_negative_zero(true);
306 break;
307 case Token::ASSIGN_ADD:
308 case Token::ASSIGN_SUB:
309 case Token::ASSIGN_MUL:
310 case Token::ASSIGN_DIV:
311 case Token::ASSIGN_MOD:
312 if (node->type()->IsLikelySmi()) {
313 node->target()->type()->SetAsLikelySmiIfUnknown();
314 node->value()->type()->SetAsLikelySmiIfUnknown();
315 }
316 break;
317 default:
318 UNREACHABLE();
319 break;
320 }
321
322 Visit(node->target());
323 Visit(node->value());
324
325 switch (node->op()) {
326 case Token::INIT_VAR:
327 case Token::INIT_CONST:
328 case Token::ASSIGN:
329 // Pure assignment copies the type from the value.
330 node->type()->CopyFrom(node->value()->type());
331 break;
332 case Token::ASSIGN_BIT_OR:
333 case Token::ASSIGN_BIT_XOR:
334 case Token::ASSIGN_BIT_AND:
335 case Token::ASSIGN_SHL:
336 case Token::ASSIGN_SAR:
337 case Token::ASSIGN_SHR:
338 // Should have been setup above already.
339 break;
340 case Token::ASSIGN_ADD:
341 case Token::ASSIGN_SUB:
342 case Token::ASSIGN_MUL:
343 case Token::ASSIGN_DIV:
344 case Token::ASSIGN_MOD:
345 if (node->type()->IsUnknown()) {
346 if (node->target()->type()->IsLikelySmi() ||
347 node->value()->type()->IsLikelySmi()) {
348 node->type()->SetAsLikelySmi();
349 }
350 }
351 break;
352 default:
353 UNREACHABLE();
354 break;
355 }
356
357 // Since this is an assignment. We have to propagate this node's type to the
358 // variable.
359 VariableProxy* proxy = node->target()->AsVariableProxy();
360 if (proxy != NULL) {
361 Variable* var = proxy->AsVariable();
362 if (var != NULL) {
363 StaticType* var_type = var->type();
364 if (var_type->IsUnknown()) {
365 var_type->CopyFrom(node->type());
366 } else if (var_type->IsLikelySmi()) {
367 // We do not reset likely types to Unknown.
368 }
369 }
370 }
371 }
372
373
374 void AstOptimizer::VisitThrow(Throw* node) {
375 Visit(node->exception());
376 }
377
378
379 void AstOptimizer::VisitProperty(Property* node) {
380 node->key()->set_no_negative_zero(true);
381 Visit(node->obj());
382 Visit(node->key());
383 }
384
385
386 void AstOptimizer::VisitCall(Call* node) {
387 Visit(node->expression());
388 OptimizeArguments(node->arguments());
389 }
390
391
392 void AstOptimizer::VisitCallNew(CallNew* node) {
393 Visit(node->expression());
394 OptimizeArguments(node->arguments());
395 }
396
397
398 void AstOptimizer::VisitCallRuntime(CallRuntime* node) {
399 OptimizeArguments(node->arguments());
400 }
401
402
403 void AstOptimizer::VisitUnaryOperation(UnaryOperation* node) {
404 if (node->op() == Token::ADD || node->op() == Token::SUB) {
405 node->expression()->set_no_negative_zero(node->no_negative_zero());
406 } else {
407 node->expression()->set_no_negative_zero(true);
408 }
409 Visit(node->expression());
410 if (FLAG_safe_int32_compiler) {
411 switch (node->op()) {
412 case Token::BIT_NOT:
413 node->expression()->set_no_negative_zero(true);
414 node->expression()->set_to_int32(true);
415 // Fall through.
416 case Token::ADD:
417 case Token::SUB:
418 node->set_side_effect_free(node->expression()->side_effect_free());
419 break;
420 case Token::NOT:
421 case Token::DELETE:
422 case Token::TYPEOF:
423 case Token::VOID:
424 break;
425 default:
426 UNREACHABLE();
427 break;
428 }
429 } else if (node->op() == Token::BIT_NOT) {
430 node->expression()->set_to_int32(true);
431 }
432 }
433
434
435 void AstOptimizer::VisitIncrementOperation(IncrementOperation* node) {
436 UNREACHABLE();
437 }
438
439
440 void AstOptimizer::VisitCountOperation(CountOperation* node) {
441 // Count operations assume that they work on Smis.
442 node->expression()->set_no_negative_zero(node->is_prefix() ?
443 true :
444 node->no_negative_zero());
445 node->type()->SetAsLikelySmiIfUnknown();
446 node->expression()->type()->SetAsLikelySmiIfUnknown();
447 Visit(node->expression());
448 }
449
450
451 static bool CouldBeNegativeZero(AstNode* node) {
452 Literal* literal = node->AsLiteral();
453 if (literal != NULL) {
454 Handle<Object> handle = literal->handle();
455 if (handle->IsString() || handle->IsSmi()) {
456 return false;
457 } else if (handle->IsHeapNumber()) {
458 double double_value = HeapNumber::cast(*handle)->value();
459 if (double_value != 0) {
460 return false;
461 }
462 }
463 }
464 BinaryOperation* binary = node->AsBinaryOperation();
465 if (binary != NULL && Token::IsBitOp(binary->op())) {
466 return false;
467 }
468 return true;
469 }
470
471
472 static bool CouldBePositiveZero(AstNode* node) {
473 Literal* literal = node->AsLiteral();
474 if (literal != NULL) {
475 Handle<Object> handle = literal->handle();
476 if (handle->IsSmi()) {
477 if (Smi::cast(*handle) != Smi::FromInt(0)) {
478 return false;
479 }
480 } else if (handle->IsHeapNumber()) {
481 // Heap number literal can't be +0, because that's a Smi.
482 return false;
483 }
484 }
485 return true;
486 }
487
488
489 void AstOptimizer::VisitBinaryOperation(BinaryOperation* node) {
490 // Depending on the operation we can propagate this node's type down the
491 // AST nodes.
492 Token::Value op = node->op();
493 switch (op) {
494 case Token::COMMA:
495 case Token::OR:
496 node->left()->set_no_negative_zero(true);
497 node->right()->set_no_negative_zero(node->no_negative_zero());
498 break;
499 case Token::AND:
500 node->left()->set_no_negative_zero(node->no_negative_zero());
501 node->right()->set_no_negative_zero(node->no_negative_zero());
502 break;
503 case Token::BIT_OR:
504 case Token::BIT_XOR:
505 case Token::BIT_AND:
506 case Token::SHL:
507 case Token::SAR:
508 case Token::SHR:
509 node->type()->SetAsLikelySmiIfUnknown();
510 node->left()->type()->SetAsLikelySmiIfUnknown();
511 node->right()->type()->SetAsLikelySmiIfUnknown();
512 node->left()->set_to_int32(true);
513 node->right()->set_to_int32(true);
514 node->left()->set_no_negative_zero(true);
515 node->right()->set_no_negative_zero(true);
516 break;
517 case Token::MUL: {
518 VariableProxy* lvar_proxy = node->left()->AsVariableProxy();
519 VariableProxy* rvar_proxy = node->right()->AsVariableProxy();
520 if (lvar_proxy != NULL && rvar_proxy != NULL) {
521 Variable* lvar = lvar_proxy->AsVariable();
522 Variable* rvar = rvar_proxy->AsVariable();
523 if (lvar != NULL && rvar != NULL) {
524 if (lvar->mode() == Variable::VAR && rvar->mode() == Variable::VAR) {
525 Slot* lslot = lvar->AsSlot();
526 Slot* rslot = rvar->AsSlot();
527 if (lslot->type() == rslot->type() &&
528 (lslot->type() == Slot::PARAMETER ||
529 lslot->type() == Slot::LOCAL) &&
530 lslot->index() == rslot->index()) {
531 // A number squared doesn't give negative zero.
532 node->set_no_negative_zero(true);
533 }
534 }
535 }
536 }
537 }
538 case Token::ADD:
539 case Token::SUB:
540 case Token::DIV:
541 case Token::MOD: {
542 if (node->type()->IsLikelySmi()) {
543 node->left()->type()->SetAsLikelySmiIfUnknown();
544 node->right()->type()->SetAsLikelySmiIfUnknown();
545 }
546 if (op == Token::ADD && (!CouldBeNegativeZero(node->left()) ||
547 !CouldBeNegativeZero(node->right()))) {
548 node->left()->set_no_negative_zero(true);
549 node->right()->set_no_negative_zero(true);
550 } else if (op == Token::SUB && (!CouldBeNegativeZero(node->left()) ||
551 !CouldBePositiveZero(node->right()))) {
552 node->left()->set_no_negative_zero(true);
553 node->right()->set_no_negative_zero(true);
554 } else {
555 node->left()->set_no_negative_zero(node->no_negative_zero());
556 node->right()->set_no_negative_zero(node->no_negative_zero());
557 }
558 if (node->op() == Token::DIV) {
559 node->right()->set_no_negative_zero(false);
560 } else if (node->op() == Token::MOD) {
561 node->right()->set_no_negative_zero(true);
562 }
563 break;
564 }
565 default:
566 UNREACHABLE();
567 break;
568 }
569
570 Visit(node->left());
571 Visit(node->right());
572
573 // After visiting the operand nodes we have to check if this node's type
574 // can be updated. If it does, then we can push that information down
575 // towards the leaves again if the new information is an upgrade over the
576 // previous type of the operand nodes.
577 if (node->type()->IsUnknown()) {
578 if (node->left()->type()->IsLikelySmi() ||
579 node->right()->type()->IsLikelySmi()) {
580 node->type()->SetAsLikelySmi();
581 }
582 if (node->type()->IsLikelySmi()) {
583 // The type of this node changed to LIKELY_SMI. Propagate this knowledge
584 // down through the nodes.
585 if (node->left()->type()->IsUnknown()) {
586 node->left()->type()->SetAsLikelySmi();
587 Visit(node->left());
588 }
589 if (node->right()->type()->IsUnknown()) {
590 node->right()->type()->SetAsLikelySmi();
591 Visit(node->right());
592 }
593 }
594 }
595
596 if (FLAG_safe_int32_compiler) {
597 switch (node->op()) {
598 case Token::COMMA:
599 case Token::OR:
600 case Token::AND:
601 break;
602 case Token::BIT_OR:
603 case Token::BIT_XOR:
604 case Token::BIT_AND:
605 case Token::SHL:
606 case Token::SAR:
607 case Token::SHR:
608 // Add one to the number of bit operations in this expression.
609 node->set_num_bit_ops(1);
610 // Fall through.
611 case Token::ADD:
612 case Token::SUB:
613 case Token::MUL:
614 case Token::DIV:
615 case Token::MOD:
616 node->set_side_effect_free(node->left()->side_effect_free() &&
617 node->right()->side_effect_free());
618 node->set_num_bit_ops(node->num_bit_ops() +
619 node->left()->num_bit_ops() +
620 node->right()->num_bit_ops());
621 if (!node->no_negative_zero() && node->op() == Token::MUL) {
622 node->set_side_effect_free(false);
623 }
624 break;
625 default:
626 UNREACHABLE();
627 break;
628 }
629 }
630 }
631
632
633 void AstOptimizer::VisitCompareOperation(CompareOperation* node) {
634 if (node->type()->IsKnown()) {
635 // Propagate useful information down towards the leaves.
636 node->left()->type()->SetAsLikelySmiIfUnknown();
637 node->right()->type()->SetAsLikelySmiIfUnknown();
638 }
639
640 node->left()->set_no_negative_zero(true);
641 // Only [[HasInstance]] has the right argument passed unchanged to it.
642 node->right()->set_no_negative_zero(true);
643
644 Visit(node->left());
645 Visit(node->right());
646
647 // After visiting the operand nodes we have to check if this node's type
648 // can be updated. If it does, then we can push that information down
649 // towards the leaves again if the new information is an upgrade over the
650 // previous type of the operand nodes.
651 if (node->type()->IsUnknown()) {
652 if (node->left()->type()->IsLikelySmi() ||
653 node->right()->type()->IsLikelySmi()) {
654 node->type()->SetAsLikelySmi();
655 }
656 if (node->type()->IsLikelySmi()) {
657 // The type of this node changed to LIKELY_SMI. Propagate this knowledge
658 // down through the nodes.
659 if (node->left()->type()->IsUnknown()) {
660 node->left()->type()->SetAsLikelySmi();
661 Visit(node->left());
662 }
663 if (node->right()->type()->IsUnknown()) {
664 node->right()->type()->SetAsLikelySmi();
665 Visit(node->right());
666 }
667 }
668 }
669 }
670
671
672 void AstOptimizer::VisitCompareToNull(CompareToNull* node) {
673 Visit(node->expression());
674 }
675
676
677 void AstOptimizer::VisitThisFunction(ThisFunction* node) {
678 USE(node);
679 }
680
681
682 class Processor: public AstVisitor { 39 class Processor: public AstVisitor {
683 public: 40 public:
684 explicit Processor(Variable* result) 41 explicit Processor(Variable* result)
685 : result_(result), 42 : result_(result),
686 result_assigned_(false), 43 result_assigned_(false),
687 is_set_(false), 44 is_set_(false),
688 in_try_(false) { 45 in_try_(false) {
689 } 46 }
690 47
691 void Process(ZoneList<Statement*>* statements); 48 void Process(ZoneList<Statement*>* statements);
(...skipping 306 matching lines...) Expand 10 before | Expand all | Expand 10 after
998 if (processor.result_assigned()) { 355 if (processor.result_assigned()) {
999 VariableProxy* result_proxy = new VariableProxy(result); 356 VariableProxy* result_proxy = new VariableProxy(result);
1000 body->Add(new ReturnStatement(result_proxy)); 357 body->Add(new ReturnStatement(result_proxy));
1001 } 358 }
1002 } 359 }
1003 360
1004 return true; 361 return true;
1005 } 362 }
1006 363
1007 364
1008 // Assumes code has been parsed and scopes have been analyzed. Mutates the
1009 // AST, so the AST should not continue to be used in the case of failure.
1010 bool Rewriter::Analyze(CompilationInfo* info) {
1011 FunctionLiteral* function = info->function();
1012 ASSERT(function != NULL && function->scope() != NULL);
1013
1014 ZoneList<Statement*>* body = function->body();
1015 if (FLAG_optimize_ast && !body->is_empty()) {
1016 AstOptimizer optimizer;
1017 optimizer.Optimize(body);
1018 if (optimizer.HasStackOverflow()) return false;
1019 }
1020 return true;
1021 }
1022
1023
1024 } } // namespace v8::internal 365 } } // namespace v8::internal
OLDNEW
« no previous file with comments | « src/rewriter.h ('k') | src/variables.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698