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

Side by Side Diff: lib/compiler/implementation/ssa/codegen.dart

Issue 10495013: Recognize logical operations before code generation. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 6 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
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 class SsaCodeGeneratorTask extends CompilerTask { 5 class SsaCodeGeneratorTask extends CompilerTask {
6 final JavaScriptBackend backend; 6 final JavaScriptBackend backend;
7 SsaCodeGeneratorTask(JavaScriptBackend backend) 7 SsaCodeGeneratorTask(JavaScriptBackend backend)
8 : this.backend = backend, 8 : this.backend = backend,
9 super(backend.compiler); 9 super(backend.compiler);
10 String get name() => 'SSA code generator'; 10 String get name() => 'SSA code generator';
(...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after
140 static final int TYPE_STATEMENT = 0; 140 static final int TYPE_STATEMENT = 0;
141 static final int TYPE_EXPRESSION = 1; 141 static final int TYPE_EXPRESSION = 1;
142 static final int TYPE_DECLARATION = 2; 142 static final int TYPE_DECLARATION = 2;
143 143
144 final JavaScriptBackend backend; 144 final JavaScriptBackend backend;
145 final WorkItem work; 145 final WorkItem work;
146 final StringBuffer buffer; 146 final StringBuffer buffer;
147 final String parameters; 147 final String parameters;
148 148
149 final Set<HInstruction> generateAtUseSite; 149 final Set<HInstruction> generateAtUseSite;
150 final Map<HPhi, String> logicalOperations; 150 final Set<HInstruction> logicalOperations;
151 final Map<Element, ElementAction> breakAction; 151 final Map<Element, ElementAction> breakAction;
152 final Map<Element, ElementAction> continueAction; 152 final Map<Element, ElementAction> continueAction;
153 final Map<Element, String> parameterNames; 153 final Map<Element, String> parameterNames;
154 154
155 /** 155 /**
156 * Contains the names of the instructions, as well as the parallel 156 * Contains the names of the instructions, as well as the parallel
157 * copies to perform on block transitioning. 157 * copies to perform on block transitioning.
158 */ 158 */
159 VariableNames variableNames; 159 VariableNames variableNames;
160 160
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
199 } 199 }
200 200
201 SsaCodeGenerator(this.backend, 201 SsaCodeGenerator(this.backend,
202 this.work, 202 this.work,
203 this.parameters, 203 this.parameters,
204 this.parameterNames) 204 this.parameterNames)
205 : declaredVariables = new Set<String>(), 205 : declaredVariables = new Set<String>(),
206 delayedVariablesDeclaration = new List<String>(), 206 delayedVariablesDeclaration = new List<String>(),
207 buffer = new StringBuffer(), 207 buffer = new StringBuffer(),
208 generateAtUseSite = new Set<HInstruction>(), 208 generateAtUseSite = new Set<HInstruction>(),
209 logicalOperations = new Map<HPhi, String>(), 209 logicalOperations = new Set<HInstruction>(),
210 breakAction = new Map<Element, ElementAction>(), 210 breakAction = new Map<Element, ElementAction>(),
211 continueAction = new Map<Element, ElementAction>(), 211 continueAction = new Map<Element, ElementAction>(),
212 unsignedShiftPrecedences = JSPrecedence.binary['>>>'] { 212 unsignedShiftPrecedences = JSPrecedence.binary['>>>'] {
213 213
214 Interceptors interceptors = backend.builder.interceptors; 214 Interceptors interceptors = backend.builder.interceptors;
215 equalsNullElement = interceptors.getEqualsNullInterceptor(); 215 equalsNullElement = interceptors.getEqualsNullInterceptor();
216 boolifiedEqualsNullElement = 216 boolifiedEqualsNullElement =
217 interceptors.getBoolifiedVersionOf(equalsNullElement); 217 interceptors.getBoolifiedVersionOf(equalsNullElement);
218 } 218 }
219 219
(...skipping 260 matching lines...) Expand 10 before | Expand all | Expand 10 after
480 buffer.add("var "); 480 buffer.add("var ");
481 } 481 }
482 buffer.add(variableName); 482 buffer.add(variableName);
483 } 483 }
484 484
485 void declareInstruction(HInstruction instruction) { 485 void declareInstruction(HInstruction instruction) {
486 declareVariable(variableNames.getName(instruction)); 486 declareVariable(variableNames.getName(instruction));
487 } 487 }
488 488
489 void define(HInstruction instruction) { 489 void define(HInstruction instruction) {
490 if (isGeneratingExpression()) {
491 addExpressionSeparator();
492 } else {
493 addIndentation();
494 }
490 if (instruction is !HCheck && variableNames.hasName(instruction)) { 495 if (instruction is !HCheck && variableNames.hasName(instruction)) {
491 declareInstruction(instruction); 496 declareInstruction(instruction);
492 buffer.add(" = "); 497 buffer.add(" = ");
493 visit(instruction, JSPrecedence.ASSIGNMENT_PRECEDENCE); 498 visit(instruction, JSPrecedence.ASSIGNMENT_PRECEDENCE);
494 } else { 499 } else {
495 visit(instruction, JSPrecedence.STATEMENT_PRECEDENCE); 500 visit(instruction, JSPrecedence.STATEMENT_PRECEDENCE);
496 } 501 }
502 if (!isGeneratingExpression()) buffer.add(';\n');
497 } 503 }
498 504
499 void use(HInstruction argument, int expectedPrecedenceForArgument) { 505 void use(HInstruction argument, int expectedPrecedenceForArgument) {
500 if (isGenerateAtUseSite(argument)) { 506 if (isGenerateAtUseSite(argument)) {
501 visit(argument, expectedPrecedenceForArgument); 507 visit(argument, expectedPrecedenceForArgument);
502 } else if (argument is HCheck) { 508 } else if (argument is HCheck) {
503 HCheck check = argument; 509 HCheck check = argument;
504 use(argument.checkedInput, expectedPrecedenceForArgument); 510 use(argument.checkedInput, expectedPrecedenceForArgument);
505 } else { 511 } else {
506 buffer.add(variableNames.getName(argument)); 512 buffer.add(variableNames.getName(argument));
(...skipping 19 matching lines...) Expand all
526 buffer.add(";\n"); 532 buffer.add(";\n");
527 } 533 }
528 534
529 void implicitBreakWithLabel(TargetElement target) { 535 void implicitBreakWithLabel(TargetElement target) {
530 addIndented("break "); 536 addIndented("break ");
531 writeImplicitLabel(target); 537 writeImplicitLabel(target);
532 buffer.add(";\n"); 538 buffer.add(";\n");
533 } 539 }
534 540
535 bool visitIfInfo(HIfBlockInformation info) { 541 bool visitIfInfo(HIfBlockInformation info) {
542 // If the [HIf] instruction is actually a logical operation, we
543 // let the flow-based traversal handle it.
544 if (logicalOperations.contains(info.condition.end.last)) return false;
536 HInstruction condition = info.condition.conditionExpression; 545 HInstruction condition = info.condition.conditionExpression;
537 if (condition.isConstant()) { 546 if (condition.isConstant()) {
538 // If the condition is constant, only generate one branch (if any). 547 // If the condition is constant, only generate one branch (if any).
539 HConstant constantCondition = condition; 548 HConstant constantCondition = condition;
540 Constant constant = constantCondition.constant; 549 Constant constant = constantCondition.constant;
541 generateStatements(info.condition); 550 generateStatements(info.condition);
542 if (constant.isTrue()) { 551 if (constant.isTrue()) {
543 generateStatements(info.thenGraph); 552 generateStatements(info.thenGraph);
544 } else if (info.elseGraph !== null) { 553 } else if (info.elseGraph !== null) {
545 generateStatements(info.elseGraph); 554 generateStatements(info.elseGraph);
(...skipping 256 matching lines...) Expand 10 before | Expand all | Expand 10 after
802 while (!continueOverrides.isEmpty()) { 811 while (!continueOverrides.isEmpty()) {
803 continueAction.remove(continueOverrides.head); 812 continueAction.remove(continueOverrides.head);
804 continueOverrides = continueOverrides.tail; 813 continueOverrides = continueOverrides.tail;
805 } 814 }
806 } else { 815 } else {
807 breakAction.remove(labeledBlockInfo.target); 816 breakAction.remove(labeledBlockInfo.target);
808 } 817 }
809 return true; 818 return true;
810 } 819 }
811 820
812 void emitLogicalOperation(HPhi node, String operation) {
813 JSBinaryOperatorPrecedence operatorPrecedence =
814 JSPrecedence.binary[operation];
815 beginExpression(operatorPrecedence.precedence);
816 use(node.inputs[0], operatorPrecedence.left);
817 buffer.add(" $operation ");
818 use(node.inputs[1], operatorPrecedence.right);
819 endExpression(operatorPrecedence.precedence);
820 }
821
822 // Wraps a loop body in a block to make continues have a target to break 821 // Wraps a loop body in a block to make continues have a target to break
823 // to (if necessary). 822 // to (if necessary).
824 void wrapLoopBodyForContinue(HLoopBlockInformation info) { 823 void wrapLoopBodyForContinue(HLoopBlockInformation info) {
825 TargetElement target = info.target; 824 TargetElement target = info.target;
826 if (target !== null && target.isContinueTarget) { 825 if (target !== null && target.isContinueTarget) {
827 addIndentation(); 826 addIndentation();
828 for (LabelElement label in info.labels) { 827 for (LabelElement label in info.labels) {
829 if (label.isContinueTarget) { 828 if (label.isContinueTarget) {
830 writeContinueLabel(label); 829 writeContinueLabel(label);
831 buffer.add(":"); 830 buffer.add(":");
(...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after
1005 buffer.add(' = '); 1004 buffer.add(' = ');
1006 use(copy.source, JSPrecedence.ASSIGNMENT_PRECEDENCE); 1005 use(copy.source, JSPrecedence.ASSIGNMENT_PRECEDENCE);
1007 if (!isGeneratingExpression()) { 1006 if (!isGeneratingExpression()) {
1008 buffer.add(';\n'); 1007 buffer.add(';\n');
1009 } 1008 }
1010 } 1009 }
1011 } 1010 }
1012 1011
1013 void iterateBasicBlock(HBasicBlock node) { 1012 void iterateBasicBlock(HBasicBlock node) {
1014 HInstruction instruction = node.first; 1013 HInstruction instruction = node.first;
1015 while (instruction != null) { 1014 while (instruction !== node.last) {
1016 if (instruction === node.last) { 1015 if (instruction is HTypeGuard) {
1017 assignPhisOfSuccessors(node);
1018 }
1019
1020 if (isGenerateAtUseSite(instruction)) {
1021 if (instruction is HIf) {
1022 HIf hif = instruction;
1023 // The "if" is implementing part of a logical expression.
1024 // Skip directly forward to to its latest successor, since everything
1025 // in-between must also be generateAtUseSite.
1026 assert(hif.trueBranch.id < hif.falseBranch.id);
1027 visitBasicBlock(hif.falseBranch);
1028 }
1029 } else if (instruction is HControlFlow) {
1030 if (instruction is HLoopBranch && isGeneratingExpression()) {
1031 addExpressionSeparator();
1032 }
1033 visit(instruction, JSPrecedence.STATEMENT_PRECEDENCE); 1016 visit(instruction, JSPrecedence.STATEMENT_PRECEDENCE);
1034 } else if (instruction is HTypeGuard) { 1017 } else if (!isGenerateAtUseSite(instruction)) {
1035 visit(instruction, JSPrecedence.STATEMENT_PRECEDENCE);
1036 } else {
1037 if (isGeneratingExpression()) {
1038 addExpressionSeparator();
1039 } else {
1040 addIndentation();
1041 }
1042 define(instruction); 1018 define(instruction);
1043 if (!isGeneratingExpression()) buffer.add(';\n');
1044 } 1019 }
1045 instruction = instruction.next; 1020 instruction = instruction.next;
1046 } 1021 }
1022 assignPhisOfSuccessors(node);
1023 if (instruction is HLoopBranch && isGeneratingExpression()) {
1024 addExpressionSeparator();
1025 }
1026 visit(instruction, JSPrecedence.STATEMENT_PRECEDENCE);
1047 } 1027 }
1048 1028
1049 visitInvokeBinary(HInvokeBinary node, String op) { 1029 visitInvokeBinary(HInvokeBinary node, String op) {
1050 if (node.builtin) { 1030 if (node.builtin) {
1051 JSBinaryOperatorPrecedence operatorPrecedences = JSPrecedence.binary[op]; 1031 JSBinaryOperatorPrecedence operatorPrecedences = JSPrecedence.binary[op];
1052 beginExpression(operatorPrecedences.precedence); 1032 beginExpression(operatorPrecedences.precedence);
1053 use(node.left, operatorPrecedences.left); 1033 use(node.left, operatorPrecedences.left);
1054 buffer.add(' $op '); 1034 buffer.add(' $op ');
1055 use(node.right, operatorPrecedences.right); 1035 use(node.right, operatorPrecedences.right);
1056 endExpression(operatorPrecedences.precedence); 1036 endExpression(operatorPrecedences.precedence);
(...skipping 199 matching lines...) Expand 10 before | Expand all | Expand 10 after
1256 compiler.internalError('visitTry should not be called', instruction: node); 1236 compiler.internalError('visitTry should not be called', instruction: node);
1257 } 1237 }
1258 1238
1259 bool isEmptyElse(HBasicBlock start, HBasicBlock end) { 1239 bool isEmptyElse(HBasicBlock start, HBasicBlock end) {
1260 if (start !== end) return false; 1240 if (start !== end) return false;
1261 if (start.last is !HGoto 1241 if (start.last is !HGoto
1262 || start.last is HBreak 1242 || start.last is HBreak
1263 || start.last is HContinue) { 1243 || start.last is HContinue) {
1264 return false; 1244 return false;
1265 } 1245 }
1266 HInstruction instruction = start.first;
1267 for (HInstruction instruction = start.first; 1246 for (HInstruction instruction = start.first;
1268 instruction != start.last; 1247 instruction != start.last;
1269 instruction = instruction.next) { 1248 instruction = instruction.next) {
1270 // Instructions generated at use site are okay because they do 1249 // Instructions generated at use site are okay because they do
1271 // not generate code in this else block. 1250 // not generate code in this else block.
1272 if (!isGenerateAtUseSite(instruction)) return false; 1251 if (!isGenerateAtUseSite(instruction)) return false;
1273 } 1252 }
1274 CopyHandler handler = variableNames.getCopyHandler(start); 1253 CopyHandler handler = variableNames.getCopyHandler(start);
1275 if (handler == null || handler.isEmpty()) return true; 1254 if (handler == null || handler.isEmpty()) return true;
1276 if (!handler.assignments.isEmpty()) return false; 1255 if (!handler.assignments.isEmpty()) return false;
1277 // If the block has a copy where the destination and source are 1256 // If the block has a copy where the destination and source are
1278 // different, we will emit that copy, and therefore the block is 1257 // different, we will emit that copy, and therefore the block is
1279 // not empty. 1258 // not empty.
1280 for (Copy copy in handler.copies) { 1259 for (Copy copy in handler.copies) {
1281 String sourceName = variableNames.getName(copy.source); 1260 String sourceName = variableNames.getName(copy.source);
1282 String destinationName = variableNames.getName(copy.destination); 1261 String destinationName = variableNames.getName(copy.destination);
1283 if (sourceName != destinationName) return false; 1262 if (sourceName != destinationName) return false;
1284 } 1263 }
1285 return true; 1264 return true;
1286 } 1265 }
1287 1266
1288 visitIf(HIf node) { 1267 visitIf(HIf node) {
1268 if (logicalOperations.contains(node)) {
1269 HPhi phi = node.joinBlock.phis.first;
1270 if (!isGenerateAtUseSite(phi)) define(phi);
1271 visitBasicBlock(node.joinBlock);
1272 return;
1273 }
1274
1289 if (subGraph !== null && node.block === subGraph.end) { 1275 if (subGraph !== null && node.block === subGraph.end) {
1290 if (isGeneratingExpression()) { 1276 if (isGeneratingExpression()) {
1291 use(node.inputs[0], JSPrecedence.EXPRESSION_PRECEDENCE); 1277 use(node.inputs[0], JSPrecedence.EXPRESSION_PRECEDENCE);
1292 } 1278 }
1293 return; 1279 return;
1294 } 1280 }
1281
1295 HInstruction condition = node.inputs[0]; 1282 HInstruction condition = node.inputs[0];
1296 int preVisitedBlocks = 0; 1283 int preVisitedBlocks = 0;
1297 List<HBasicBlock> dominated = node.block.dominatedBlocks; 1284 List<HBasicBlock> dominated = node.block.dominatedBlocks;
1298 HIfBlockInformation info = node.blockInformation.body; 1285 HIfBlockInformation info = node.blockInformation.body;
1299 HBasicBlock joinBlock = node.joinBlock; 1286 HBasicBlock joinBlock = node.joinBlock;
1300 if (condition.isConstant()) { 1287 if (condition.isConstant()) {
1301 HConstant constant = condition; 1288 HConstant constant = condition;
1302 if (constant.constant.isTrue()) { 1289 if (constant.constant.isTrue()) {
1303 generateStatements(info.thenGraph); 1290 generateStatements(info.thenGraph);
1304 } else if (node.hasElse) { 1291 } else if (node.hasElse) {
(...skipping 275 matching lines...) Expand 10 before | Expand all | Expand 10 after
1580 visitBasicBlock(branchBlock.successors[1]); 1567 visitBasicBlock(branchBlock.successors[1]);
1581 // With labeled breaks we can have more dominated blocks. 1568 // With labeled breaks we can have more dominated blocks.
1582 if (dominated.length >= 3) { 1569 if (dominated.length >= 3) {
1583 for (int i = 2; i < dominated.length; i++) { 1570 for (int i = 2; i < dominated.length; i++) {
1584 visitBasicBlock(dominated[i]); 1571 visitBasicBlock(dominated[i]);
1585 } 1572 }
1586 } 1573 }
1587 } 1574 }
1588 1575
1589 visitNot(HNot node) { 1576 visitNot(HNot node) {
1577 assert(node.inputs.length == 1);
1578 generateNot(node.inputs[0]);
1579 }
1580
1581
1582 void generateNot(HInstruction input) {
1590 bool isBuiltinRelational(HInstruction instruction) { 1583 bool isBuiltinRelational(HInstruction instruction) {
1591 if (instruction is !HRelational) return false; 1584 if (instruction is !HRelational) return false;
1592 HRelational relational = instruction; 1585 HRelational relational = instruction;
1593 return relational.builtin; 1586 return relational.builtin;
1594 } 1587 }
1595 1588
1596 assert(node.inputs.length == 1);
1597 HInstruction input = node.inputs[0];
1598 if (input is HBoolify && isGenerateAtUseSite(input)) { 1589 if (input is HBoolify && isGenerateAtUseSite(input)) {
1599 beginExpression(JSPrecedence.EQUALITY_PRECEDENCE); 1590 beginExpression(JSPrecedence.EQUALITY_PRECEDENCE);
1600 assert(node.inputs.length == 1);
1601 use(input.inputs[0], JSPrecedence.EQUALITY_PRECEDENCE); 1591 use(input.inputs[0], JSPrecedence.EQUALITY_PRECEDENCE);
1602 buffer.add(' !== true'); 1592 buffer.add(' !== true');
1603 endExpression(JSPrecedence.EQUALITY_PRECEDENCE); 1593 endExpression(JSPrecedence.EQUALITY_PRECEDENCE);
1604 } else if (isBuiltinRelational(input) && 1594 } else if (isBuiltinRelational(input) &&
1605 isGenerateAtUseSite(input) && 1595 isGenerateAtUseSite(input) &&
1606 input.inputs[0].propagatedType.isUseful() && 1596 input.inputs[0].propagatedType.isUseful() &&
1607 !input.inputs[0].isDouble() && 1597 !input.inputs[0].isDouble() &&
1608 input.inputs[1].propagatedType.isUseful() && 1598 input.inputs[1].propagatedType.isUseful() &&
1609 !input.inputs[1].isDouble()) { 1599 !input.inputs[1].isDouble()) {
1610 // This optimization doesn't work for NaN, so we only do it if the 1600 // This optimization doesn't work for NaN, so we only do it if the
(...skipping 18 matching lines...) Expand all
1629 endExpression(JSPrecedence.PREFIX_PRECEDENCE); 1619 endExpression(JSPrecedence.PREFIX_PRECEDENCE);
1630 } 1620 }
1631 } 1621 }
1632 1622
1633 visitParameterValue(HParameterValue node) { 1623 visitParameterValue(HParameterValue node) {
1634 assert(isGenerateAtUseSite(node)); 1624 assert(isGenerateAtUseSite(node));
1635 buffer.add(variableNames.getName(node)); 1625 buffer.add(variableNames.getName(node));
1636 } 1626 }
1637 1627
1638 visitPhi(HPhi node) { 1628 visitPhi(HPhi node) {
1639 String operation = logicalOperations[node]; 1629 HBasicBlock ifBlock = node.block.predecessors[0].predecessors[0];
1640 if (operation !== null) { 1630 // This method is only called for phis that are generated at use
1641 emitLogicalOperation(node, operation); 1631 // site. A phi can be generated at use site only if it is the
1632 // result of a logical operation.
1633 assert(logicalOperations.contains(ifBlock.last));
1634 HInstruction input = ifBlock.last.inputs[0];
1635 if (input.isConstantFalse()) {
1636 use(node.inputs[1], expectedPrecedence);
1637 } else if (input.isConstantTrue()) {
1638 use(node.inputs[0], expectedPrecedence);
1642 } else { 1639 } else {
1643 buffer.add('${variableNames.getName(node)}'); 1640 String operation = node.inputs[1].isConstantFalse() ? '&&' : '||';
1641 JSBinaryOperatorPrecedence operatorPrecedence =
1642 JSPrecedence.binary[operation];
1643 beginExpression(operatorPrecedence.precedence);
1644 if (operation == '||') {
1645 if (input is HNot) {
1646 use(input.inputs[0], operatorPrecedence.left);
1647 } else {
1648 generateNot(input);
1649 }
1650 } else {
1651 use(input, operatorPrecedence.left);
1652 }
1653 buffer.add(" $operation ");
1654 use(node.inputs[0], operatorPrecedence.right);
1655 endExpression(operatorPrecedence.precedence);
1644 } 1656 }
1645 } 1657 }
1646 1658
1647 visitReturn(HReturn node) { 1659 visitReturn(HReturn node) {
1648 addIndentation(); 1660 addIndentation();
1649 assert(node.inputs.length == 1); 1661 assert(node.inputs.length == 1);
1650 HInstruction input = node.inputs[0]; 1662 HInstruction input = node.inputs[0];
1651 if (input.isConstantNull()) { 1663 if (input.isConstantNull()) {
1652 buffer.add('return;\n'); 1664 buffer.add('return;\n');
1653 } else { 1665 } else {
(...skipping 851 matching lines...) Expand 10 before | Expand all | Expand 10 after
2505 startBailoutSwitch(); 2517 startBailoutSwitch();
2506 } 2518 }
2507 } 2519 }
2508 2520
2509 void endLabeledBlock(HLabeledBlockInformation labeledBlockInfo) { 2521 void endLabeledBlock(HLabeledBlockInformation labeledBlockInfo) {
2510 if (labeledBlockInfo.body.start.hasGuards()) { 2522 if (labeledBlockInfo.body.start.hasGuards()) {
2511 endBailoutSwitch(); 2523 endBailoutSwitch();
2512 } 2524 }
2513 } 2525 }
2514 } 2526 }
OLDNEW
« no previous file with comments | « lib/compiler/implementation/ssa/builder.dart ('k') | lib/compiler/implementation/ssa/codegen_helpers.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698