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

Unified Diff: sdk/lib/_internal/compiler/implementation/ssa/codegen.dart

Issue 694353007: Move dart2js from sdk/lib/_internal/compiler to pkg/compiler (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 1 month 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 side-by-side diff with in-line comments
Download patch
Index: sdk/lib/_internal/compiler/implementation/ssa/codegen.dart
diff --git a/sdk/lib/_internal/compiler/implementation/ssa/codegen.dart b/sdk/lib/_internal/compiler/implementation/ssa/codegen.dart
deleted file mode 100644
index f67fe0030254043ea8bd3846ff4f2304649a6cf7..0000000000000000000000000000000000000000
--- a/sdk/lib/_internal/compiler/implementation/ssa/codegen.dart
+++ /dev/null
@@ -1,2722 +0,0 @@
-// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-part of ssa;
-
-class SsaCodeGeneratorTask extends CompilerTask {
-
- final JavaScriptBackend backend;
-
- SsaCodeGeneratorTask(JavaScriptBackend backend)
- : this.backend = backend,
- super(backend.compiler);
- String get name => 'SSA code generator';
- NativeEmitter get nativeEmitter => backend.emitter.nativeEmitter;
-
-
- js.Node attachPosition(js.Node node, AstElement element) {
- // TODO(sra): Attaching positions might be cleaner if the source position
- // was on a wrapping node.
- SourceFile sourceFile = sourceFileOfElement(element);
- String name = element.name;
- AstElement implementation = element.implementation;
- ast.Node expression = implementation.node;
- Token beginToken;
- Token endToken;
- if (expression == null) {
- // Synthesized node. Use the enclosing element for the location.
- beginToken = endToken = element.position;
- } else {
- beginToken = expression.getBeginToken();
- endToken = expression.getEndToken();
- }
- // TODO(podivilov): find the right sourceFile here and remove offset
- // checks below.
- var sourcePosition, endSourcePosition;
- if (beginToken.charOffset < sourceFile.length) {
- sourcePosition =
- new TokenSourceFileLocation(sourceFile, beginToken, name);
- }
- if (endToken.charOffset < sourceFile.length) {
- endSourcePosition =
- new TokenSourceFileLocation(sourceFile, endToken, name);
- }
- return node.withPosition(sourcePosition, endSourcePosition);
- }
-
- SourceFile sourceFileOfElement(Element element) {
- return element.implementation.compilationUnit.script.file;
- }
-
- js.Fun buildJavaScriptFunction(FunctionElement element,
- List<js.Parameter> parameters,
- js.Block body) {
- return attachPosition(new js.Fun(parameters, body), element);
- }
-
- js.Expression generateCode(CodegenWorkItem work, HGraph graph) {
- if (work.element.isField) {
- return generateLazyInitializer(work, graph);
- } else {
- return generateMethod(work, graph);
- }
- }
-
- js.Expression generateLazyInitializer(work, graph) {
- return measure(() {
- compiler.tracer.traceGraph("codegen", graph);
- SsaCodeGenerator codegen = new SsaCodeGenerator(backend, work);
- codegen.visitGraph(graph);
- return new js.Fun(codegen.parameters,
- attachPosition(codegen.body, work.element));
- });
- }
-
- js.Expression generateMethod(CodegenWorkItem work, HGraph graph) {
- return measure(() {
- SsaCodeGenerator codegen = new SsaCodeGenerator(backend, work);
- codegen.visitGraph(graph);
- compiler.tracer.traceGraph("codegen", graph);
- FunctionElement element = work.element;
- return buildJavaScriptFunction(element, codegen.parameters, codegen.body);
- });
- }
-}
-
-typedef void EntityAction(Entity element);
-
-class SsaCodeGenerator implements HVisitor, HBlockInformationVisitor {
- /**
- * Returned by [expressionType] to tell how code can be generated for
- * a subgraph.
- * - [TYPE_STATEMENT] means that the graph must be generated as a statement,
- * which is always possible.
- * - [TYPE_EXPRESSION] means that the graph can be generated as an expression,
- * or possibly several comma-separated expressions.
- * - [TYPE_DECLARATION] means that the graph can be generated as an
- * expression, and that it only generates expressions of the form
- * variable = expression
- * which are also valid as parts of a "var" declaration.
- */
- static const int TYPE_STATEMENT = 0;
- static const int TYPE_EXPRESSION = 1;
- static const int TYPE_DECLARATION = 2;
-
- /**
- * Whether we are currently generating expressions instead of statements.
- * This includes declarations, which are generated as expressions.
- */
- bool isGeneratingExpression = false;
-
- final JavaScriptBackend backend;
- final CodegenWorkItem work;
-
- final Set<HInstruction> generateAtUseSite;
- final Set<HInstruction> controlFlowOperators;
- final Map<Entity, EntityAction> breakAction;
- final Map<Entity, EntityAction> continueAction;
- final List<js.Parameter> parameters;
-
- js.Block currentContainer;
- js.Block get body => currentContainer;
- List<js.Expression> expressionStack;
- List<js.Block> oldContainerStack;
-
- /**
- * Contains the names of the instructions, as well as the parallel
- * copies to perform on block transitioning.
- */
- VariableNames variableNames;
- bool shouldGroupVarDeclarations = false;
-
- /**
- * While generating expressions, we can't insert variable declarations.
- * Instead we declare them at the start of the function. When minifying
- * we do this most of the time, because it reduces the size unless there
- * is only one variable.
- */
- final Set<String> collectedVariableDeclarations;
-
- /**
- * Set of variables and parameters that have already been declared.
- */
- final Set<String> declaredLocals;
-
- HGraph currentGraph;
-
- // Records a block-information that is being handled specially.
- // Used to break bad recursion.
- HBlockInformation currentBlockInformation;
- // The subgraph is used to delimit traversal for some constructions, e.g.,
- // if branches.
- SubGraph subGraph;
-
- SsaCodeGenerator(this.backend, CodegenWorkItem work)
- : this.work = work,
- declaredLocals = new Set<String>(),
- collectedVariableDeclarations = new Set<String>(),
- currentContainer = new js.Block.empty(),
- parameters = <js.Parameter>[],
- expressionStack = <js.Expression>[],
- oldContainerStack = <js.Block>[],
- generateAtUseSite = new Set<HInstruction>(),
- controlFlowOperators = new Set<HInstruction>(),
- breakAction = new Map<Entity, EntityAction>(),
- continueAction = new Map<Entity, EntityAction>();
-
- Compiler get compiler => backend.compiler;
- NativeEmitter get nativeEmitter => backend.emitter.nativeEmitter;
- CodegenRegistry get registry => work.registry;
-
- bool isGenerateAtUseSite(HInstruction instruction) {
- return generateAtUseSite.contains(instruction);
- }
-
- bool hasNonBitOpUser(HInstruction instruction, Set<HPhi> phiSet) {
- for (HInstruction user in instruction.usedBy) {
- if (user is HPhi) {
- if (!phiSet.contains(user)) {
- phiSet.add(user);
- if (hasNonBitOpUser(user, phiSet)) return true;
- }
- } else if (user is! HBitNot && user is! HBinaryBitOp) {
- return true;
- }
- }
- return false;
- }
-
- bool requiresUintConversion(instruction) {
- if (instruction.isUInt31(compiler)) return false;
- // If the result of a bit-operation is only used by other bit
- // operations, we do not have to convert to an unsigned integer.
- return hasNonBitOpUser(instruction, new Set<HPhi>());
- }
-
- /**
- * If the [instruction] is not `null` it will be used to attach the position
- * to the [statement].
- */
- void pushStatement(js.Statement statement, [HInstruction instruction]) {
- assert(expressionStack.isEmpty);
- if (instruction != null) {
- statement = attachLocation(statement, instruction);
- }
- currentContainer.statements.add(statement);
- }
-
- void insertStatementAtStart(js.Statement statement) {
- currentContainer.statements.insert(0, statement);
- }
-
- /**
- * If the [instruction] is not `null` it will be used to attach the position
- * to the [expression].
- */
- pushExpressionAsStatement(js.Expression expression,
- [HInstruction instruction]) {
- pushStatement(new js.ExpressionStatement(expression), instruction);
- }
-
- /**
- * If the [instruction] is not `null` it will be used to attach the position
- * to the [expression].
- */
- push(js.Expression expression, [HInstruction instruction]) {
- if (instruction != null) {
- expression = attachLocation(expression, instruction);
- }
- expressionStack.add(expression);
- }
-
- js.Expression pop() {
- return expressionStack.removeLast();
- }
-
- attachLocationToLast(HInstruction instruction) {
- int index = expressionStack.length - 1;
- expressionStack[index] =
- attachLocation(expressionStack[index], instruction);
- }
-
- js.Node attachLocation(js.Node jsNode, HInstruction instruction) {
- return jsNode.withLocation(instruction.sourcePosition);
- }
-
- js.Node attachLocationRange(js.Node jsNode,
- SourceFileLocation sourcePosition,
- SourceFileLocation endSourcePosition) {
- return jsNode.withPosition(sourcePosition, endSourcePosition);
- }
-
- void preGenerateMethod(HGraph graph) {
- new SsaInstructionSelection(compiler).visitGraph(graph);
- new SsaTypeKnownRemover().visitGraph(graph);
- new SsaInstructionMerger(generateAtUseSite, compiler).visitGraph(graph);
- new SsaConditionMerger(
- generateAtUseSite, controlFlowOperators).visitGraph(graph);
- SsaLiveIntervalBuilder intervalBuilder = new SsaLiveIntervalBuilder(
- compiler, generateAtUseSite, controlFlowOperators);
- intervalBuilder.visitGraph(graph);
- SsaVariableAllocator allocator = new SsaVariableAllocator(
- compiler,
- intervalBuilder.liveInstructions,
- intervalBuilder.liveIntervals,
- generateAtUseSite);
- allocator.visitGraph(graph);
- variableNames = allocator.names;
- shouldGroupVarDeclarations = allocator.names.numberOfVariables > 1;
- }
-
- void handleDelayedVariableDeclarations() {
- // If we have only one variable declaration and the first statement is an
- // assignment to that variable then we can merge the two. We count the
- // number of variables in the variable allocator to try to avoid this issue,
- // but it sometimes happens that the variable allocator introduces a
- // temporary variable that it later eliminates.
- if (!collectedVariableDeclarations.isEmpty) {
- if (collectedVariableDeclarations.length == 1 &&
- currentContainer.statements.length >= 1 &&
- currentContainer.statements[0] is js.ExpressionStatement) {
- String name = collectedVariableDeclarations.first;
- js.ExpressionStatement statement = currentContainer.statements[0];
- if (statement.expression is js.Assignment) {
- js.Assignment assignment = statement.expression;
- if (!assignment.isCompound &&
- assignment.leftHandSide is js.VariableReference) {
- js.VariableReference variableReference = assignment.leftHandSide;
- if (variableReference.name == name) {
- js.VariableDeclaration decl = new js.VariableDeclaration(name);
- js.VariableInitialization initialization =
- new js.VariableInitialization(decl, assignment.value);
- currentContainer.statements[0] = new js.ExpressionStatement(
- new js.VariableDeclarationList([initialization]));
- return;
- }
- }
- }
- }
- // If we can't merge the declaration with the first assignment then we
- // just do it with a new var z,y,x; statement.
- List<js.VariableInitialization> declarations =
- <js.VariableInitialization>[];
- collectedVariableDeclarations.forEach((String name) {
- declarations.add(new js.VariableInitialization(
- new js.VariableDeclaration(name), null));
- });
- var declarationList = new js.VariableDeclarationList(declarations);
- insertStatementAtStart(new js.ExpressionStatement(declarationList));
- }
- }
-
- visitGraph(HGraph graph) {
- preGenerateMethod(graph);
- currentGraph = graph;
- subGraph = new SubGraph(graph.entry, graph.exit);
- visitBasicBlock(graph.entry);
- handleDelayedVariableDeclarations();
- }
-
- void visitSubGraph(SubGraph newSubGraph) {
- SubGraph oldSubGraph = subGraph;
- subGraph = newSubGraph;
- visitBasicBlock(subGraph.start);
- subGraph = oldSubGraph;
- }
-
- /**
- * Check whether a sub-graph can be generated as an expression, or even
- * as a declaration, or if it has to fall back to being generated as
- * a statement.
- * Expressions are anything that doesn't generate control flow constructs.
- * Declarations must only generate assignments on the form "id = expression",
- * and not, e.g., expressions where the value isn't assigned, or where it's
- * assigned to something that's not a simple variable.
- */
- int expressionType(HExpressionInformation info) {
- // The only HExpressionInformation used as part of a HBlockInformation is
- // current HSubExpressionBlockInformation, so it's the only one reaching
- // here. If we start using the other HExpressionInformation types too,
- // this code should be generalized.
- assert(info is HSubExpressionBlockInformation);
- HSubExpressionBlockInformation expressionInfo = info;
- SubGraph limits = expressionInfo.subExpression;
-
- // Start assuming that we can generate declarations. If we find a
- // counter-example, we degrade our assumption to either expression or
- // statement, and in the latter case, we can return immediately since
- // it can't get any worse. E.g., a function call where the return value
- // isn't used can't be in a declaration.
- int result = TYPE_DECLARATION;
- HBasicBlock basicBlock = limits.start;
- do {
- HInstruction current = basicBlock.first;
- while (current != basicBlock.last) {
- // E.g, bounds check.
- if (current.isControlFlow()) {
- return TYPE_STATEMENT;
- }
- // HFieldSet generates code on the form x.y = ..., which isn't
- // valid in a declaration, but it also always have no uses, so
- // it's caught by that test too.
- assert(current is! HFieldSet || current.usedBy.isEmpty);
- if (current.usedBy.isEmpty) {
- result = TYPE_EXPRESSION;
- }
- current = current.next;
- }
- if (current is HGoto) {
- basicBlock = basicBlock.successors[0];
- } else if (current is HConditionalBranch) {
- if (generateAtUseSite.contains(current)) {
- // Short-circuit control flow operator trickery.
- // Check the second half, which will continue into the join.
- // (The first half is [inputs[0]], the second half is [successors[0]],
- // and [successors[1]] is the join-block).
- basicBlock = basicBlock.successors[0];
- } else {
- // We allow an expression to end on an HIf (a condition expression).
- return identical(basicBlock, limits.end) ? result : TYPE_STATEMENT;
- }
- } else {
- // Expression-incompatible control flow.
- return TYPE_STATEMENT;
- }
- } while (limits.contains(basicBlock));
- return result;
- }
-
- bool isJSExpression(HExpressionInformation info) {
- return !identical(expressionType(info), TYPE_STATEMENT);
- }
-
- bool isJSCondition(HExpressionInformation info) {
- HSubExpressionBlockInformation graph = info;
- SubExpression limits = graph.subExpression;
- return !identical(expressionType(info), TYPE_STATEMENT) &&
- (limits.end.last is HConditionalBranch);
- }
-
- /**
- * Generate statements from block information.
- * If the block information contains expressions, generate only
- * assignments, and if it ends in a conditional branch, don't generate
- * the condition.
- */
- void generateStatements(HBlockInformation block) {
- if (block is HStatementInformation) {
- block.accept(this);
- } else {
- HSubExpressionBlockInformation expression = block;
- visitSubGraph(expression.subExpression);
- }
- }
-
- js.Block generateStatementsInNewBlock(HBlockInformation block) {
- js.Block result = new js.Block.empty();
- js.Block oldContainer = currentContainer;
- currentContainer = result;
- generateStatements(block);
- currentContainer = oldContainer;
- return result;
- }
-
- /**
- * If the [block] only contains one statement returns that statement. If the
- * that statement itself is a block, recursively calls this method.
- *
- * If the block is empty, returns a new instance of [js.NOP].
- */
- js.Statement unwrapStatement(js.Block block) {
- int len = block.statements.length;
- if (len == 0) return new js.EmptyStatement();
- if (len == 1) {
- js.Statement result = block.statements[0];
- if (result is ast.Block) return unwrapStatement(result);
- return result;
- }
- return block;
- }
-
- /**
- * Generate expressions from block information.
- */
- js.Expression generateExpression(HExpressionInformation expression) {
- // Currently we only handle sub-expression graphs.
- assert(expression is HSubExpressionBlockInformation);
-
- bool oldIsGeneratingExpression = isGeneratingExpression;
- isGeneratingExpression = true;
- List<js.Expression> oldExpressionStack = expressionStack;
- List<js.Expression> sequenceElements = <js.Expression>[];
- expressionStack = sequenceElements;
- HSubExpressionBlockInformation expressionSubGraph = expression;
- visitSubGraph(expressionSubGraph.subExpression);
- expressionStack = oldExpressionStack;
- isGeneratingExpression = oldIsGeneratingExpression;
- if (sequenceElements.isEmpty) {
- // Happens when the initializer, condition or update of a loop is empty.
- return null;
- } else if (sequenceElements.length == 1) {
- return sequenceElements[0];
- } else {
- js.Expression result = sequenceElements.removeLast();
- while (sequenceElements.isNotEmpty) {
- result = new js.Binary(',', sequenceElements.removeLast(), result);
- }
- return result;
- }
- }
-
- /**
- * Only visits the arguments starting at inputs[HInvoke.ARGUMENTS_OFFSET].
- */
- List<js.Expression> visitArguments(List<HInstruction> inputs,
- {int start: HInvoke.ARGUMENTS_OFFSET}) {
- assert(inputs.length >= start);
- List<js.Expression> result = new List<js.Expression>(inputs.length - start);
- for (int i = start; i < inputs.length; i++) {
- use(inputs[i]);
- result[i - start] = pop();
- }
- return result;
- }
-
- bool isVariableDeclared(String variableName) {
- return declaredLocals.contains(variableName) ||
- collectedVariableDeclarations.contains(variableName);
- }
-
- js.Expression generateExpressionAssignment(String variableName,
- js.Expression value) {
- if (value is js.Binary) {
- js.Binary binary = value;
- String op = binary.op;
- if (op == '+' || op == '-' || op == '/' || op == '*' || op == '%' ||
- op == '^' || op == '&' || op == '|') {
- if (binary.left is js.VariableUse &&
- (binary.left as js.VariableUse).name == variableName) {
- // We know now, that we can shorten x = x + y into x += y.
- // Also check for the shortcut where y equals 1: x++ and x--.
- if ((op == '+' || op == '-') &&
- binary.right is js.LiteralNumber &&
- (binary.right as js.LiteralNumber).value == "1") {
- return new js.Prefix(op == '+' ? '++' : '--', binary.left);
- }
- return new js.Assignment.compound(binary.left, op, binary.right);
- }
- }
- }
- return new js.Assignment(new js.VariableUse(variableName), value);
- }
-
- void assignVariable(String variableName, js.Expression value) {
- if (isGeneratingExpression) {
- // If we are in an expression then we can't declare the variable here.
- // We have no choice, but to use it and then declare it separately.
- if (!isVariableDeclared(variableName)) {
- collectedVariableDeclarations.add(variableName);
- }
- push(generateExpressionAssignment(variableName, value));
- // Otherwise if we are trying to declare inline and we are in a statement
- // then we declare (unless it was already declared).
- } else if (!shouldGroupVarDeclarations &&
- !declaredLocals.contains(variableName)) {
- // It may be necessary to remove it from the ones to be declared later.
- collectedVariableDeclarations.remove(variableName);
- declaredLocals.add(variableName);
- js.VariableDeclaration decl = new js.VariableDeclaration(variableName);
- js.VariableInitialization initialization =
- new js.VariableInitialization(decl, value);
-
- pushExpressionAsStatement(new js.VariableDeclarationList(
- <js.VariableInitialization>[initialization]));
- } else {
- // Otherwise we are just going to use it. If we have not already declared
- // it then we make sure we will declare it later.
- if (!declaredLocals.contains(variableName)) {
- collectedVariableDeclarations.add(variableName);
- }
- pushExpressionAsStatement(
- generateExpressionAssignment(variableName, value));
- }
- }
-
- void define(HInstruction instruction) {
- // For simple type checks like i = intTypeCheck(i), we don't have to
- // emit an assignment, because the intTypeCheck just returns its
- // argument.
- bool needsAssignment = true;
- if (instruction is HTypeConversion) {
- HTypeConversion typeConversion = instruction;
- String inputName = variableNames.getName(typeConversion.checkedInput);
- if (variableNames.getName(instruction) == inputName) {
- needsAssignment = false;
- }
- }
- if (instruction is HLocalValue) {
- needsAssignment = false;
- }
-
- if (needsAssignment &&
- !instruction.isControlFlow() && variableNames.hasName(instruction)) {
- visitExpression(instruction);
- assignVariable(variableNames.getName(instruction), pop());
- return;
- }
-
- if (isGeneratingExpression) {
- visitExpression(instruction);
- } else {
- visitStatement(instruction);
- }
- }
-
- void use(HInstruction argument) {
- if (isGenerateAtUseSite(argument)) {
- visitExpression(argument);
- } else if (argument is HCheck && !variableNames.hasName(argument)) {
- HCheck check = argument;
- // This can only happen if the checked node does not have a name.
- assert(!variableNames.hasName(check.checkedInput));
- use(check.checkedInput);
- } else {
- assert(variableNames.hasName(argument));
- push(new js.VariableUse(variableNames.getName(argument)));
- }
- }
-
- visit(HInstruction node) {
- node.accept(this);
- }
-
- visitExpression(HInstruction node) {
- bool oldIsGeneratingExpression = isGeneratingExpression;
- isGeneratingExpression = true;
- visit(node);
- isGeneratingExpression = oldIsGeneratingExpression;
- }
-
- visitStatement(HInstruction node) {
- assert(!isGeneratingExpression);
- visit(node);
- if (!expressionStack.isEmpty) {
- assert(expressionStack.length == 1);
- pushExpressionAsStatement(pop());
- }
- }
-
- void continueAsBreak(LabelDefinition target) {
- pushStatement(new js.Break(backend.namer.continueLabelName(target)));
- }
-
- void implicitContinueAsBreak(JumpTarget target) {
- pushStatement(new js.Break(
- backend.namer.implicitContinueLabelName(target)));
- }
-
- void implicitBreakWithLabel(JumpTarget target) {
- pushStatement(new js.Break(backend.namer.implicitBreakLabelName(target)));
- }
-
- js.Statement wrapIntoLabels(js.Statement result, List<LabelDefinition> labels) {
- for (LabelDefinition label in labels) {
- if (label.isTarget) {
- String breakLabelString = backend.namer.breakLabelName(label);
- result = new js.LabeledStatement(breakLabelString, result);
- }
- }
- return result;
- }
-
-
- // The regular [visitIf] method implements the needed logic.
- bool visitIfInfo(HIfBlockInformation info) => false;
-
- bool visitSwitchInfo(HSwitchBlockInformation info) {
- bool isExpression = isJSExpression(info.expression);
- if (!isExpression) {
- generateStatements(info.expression);
- }
-
- if (isExpression) {
- push(generateExpression(info.expression));
- } else {
- use(info.expression.conditionExpression);
- }
- js.Expression key = pop();
- List<js.SwitchClause> cases = <js.SwitchClause>[];
- HSwitch switchInstruction = info.expression.end.last;
- List<HInstruction> inputs = switchInstruction.inputs;
- List<HBasicBlock> successors = switchInstruction.block.successors;
-
- js.Block oldContainer = currentContainer;
- for (int inputIndex = 1, statementIndex = 0;
- inputIndex < inputs.length;
- statementIndex++) {
- HBasicBlock successor = successors[inputIndex - 1];
- // If liveness analysis has figured out that this case is dead,
- // omit the code for it.
- if (successor.isLive) {
- do {
- visit(inputs[inputIndex]);
- currentContainer = new js.Block.empty();
- cases.add(new js.Case(pop(), currentContainer));
- inputIndex++;
- } while ((successors[inputIndex - 1] == successor)
- && (inputIndex < inputs.length));
-
- generateStatements(info.statements[statementIndex]);
- } else {
- // Skip all the case statements that belong to this
- // block.
- while ((successors[inputIndex - 1] == successor)
- && (inputIndex < inputs.length)) {
- ++inputIndex;
- }
- }
- }
-
- // If the default case is dead, we omit it. Likewise, if it is an
- // empty block, we omit it, too.
- if (info.statements.last.start.isLive) {
- currentContainer = new js.Block.empty();
- generateStatements(info.statements.last);
- if (currentContainer.statements.isNotEmpty) {
- cases.add(new js.Default(currentContainer));
- }
- }
-
- currentContainer = oldContainer;
-
- js.Statement result = new js.Switch(key, cases);
- pushStatement(wrapIntoLabels(result, info.labels));
- return true;
- }
-
- bool visitSequenceInfo(HStatementSequenceInformation info) {
- return false;
- }
-
- bool visitSubGraphInfo(HSubGraphBlockInformation info) {
- visitSubGraph(info.subGraph);
- return true;
- }
-
- bool visitSubExpressionInfo(HSubExpressionBlockInformation info) {
- return false;
- }
-
- bool visitAndOrInfo(HAndOrBlockInformation info) {
- return false;
- }
-
- bool visitTryInfo(HTryBlockInformation info) {
- js.Block body = generateStatementsInNewBlock(info.body);
- js.Catch catchPart = null;
- js.Block finallyPart = null;
- if (info.catchBlock != null) {
- void register(ClassElement classElement) {
- if (classElement != null) {
- registry.registerInstantiatedClass(classElement);
- }
- }
- register(backend.jsPlainJavaScriptObjectClass);
- register(backend.jsUnknownJavaScriptObjectClass);
-
- HLocalValue exception = info.catchVariable;
- String name = variableNames.getName(exception);
- js.VariableDeclaration decl = new js.VariableDeclaration(name);
- js.Block catchBlock = generateStatementsInNewBlock(info.catchBlock);
- catchPart = new js.Catch(decl, catchBlock);
- }
- if (info.finallyBlock != null) {
- finallyPart = generateStatementsInNewBlock(info.finallyBlock);
- }
- pushStatement(new js.Try(body, catchPart, finallyPart));
- return true;
- }
-
- void visitBodyIgnoreLabels(HLoopBlockInformation info) {
- if (info.body.start.isLabeledBlock()) {
- HBlockInformation oldInfo = currentBlockInformation;
- currentBlockInformation = info.body.start.blockFlow.body;
- generateStatements(info.body);
- currentBlockInformation = oldInfo;
- } else {
- generateStatements(info.body);
- }
- }
-
- bool visitLoopInfo(HLoopBlockInformation info) {
- HExpressionInformation condition = info.condition;
- bool isConditionExpression = isJSCondition(condition);
-
- js.Loop loop;
-
- switch (info.kind) {
- // Treate all three "test-first" loops the same way.
- case HLoopBlockInformation.FOR_LOOP:
- case HLoopBlockInformation.WHILE_LOOP:
- case HLoopBlockInformation.FOR_IN_LOOP:
- case HLoopBlockInformation.SWITCH_CONTINUE_LOOP:
- HBlockInformation initialization = info.initializer;
- int initializationType = TYPE_STATEMENT;
- if (initialization != null) {
- initializationType = expressionType(initialization);
- if (initializationType == TYPE_STATEMENT) {
- generateStatements(initialization);
- initialization = null;
- }
- }
-
- // We inserted a basic block to avoid critical edges. This block is
- // part of the LoopBlockInformation and must therefore be handled here.
- js.Block oldContainer = currentContainer;
- js.Block avoidContainer = new js.Block.empty();
- currentContainer = avoidContainer;
- assignPhisOfSuccessors(condition.end.successors.last);
- bool hasPhiUpdates = !avoidContainer.statements.isEmpty;
- currentContainer = oldContainer;
-
- if (isConditionExpression &&
- !hasPhiUpdates &&
- info.updates != null && isJSExpression(info.updates)) {
- // If we have an updates graph, and it's expressible as an
- // expression, generate a for-loop.
- js.Expression jsInitialization = null;
- if (initialization != null) {
- int delayedVariablesCount = collectedVariableDeclarations.length;
- jsInitialization = generateExpression(initialization);
- if (!shouldGroupVarDeclarations &&
- delayedVariablesCount < collectedVariableDeclarations.length) {
- // We just added a new delayed variable-declaration. See if we can
- // put in a 'var' in front of the initialization to make it go
- // away. We walk the 'tree' of comma-operators to find the
- // expressions and see if they are all assignments that can be
- // converted into declarations.
-
- List<js.Assignment> assignments;
-
- bool allSimpleAssignments(js.Expression expression) {
- if (expression is js.Assignment) {
- js.Assignment assignment = expression;
- if (assignment.leftHandSide is js.VariableUse &&
- !assignment.isCompound) {
- if (assignments == null) assignments = <js.Assignment>[];
- assignments.add(expression);
- return true;
- }
- } else if (expression.isCommaOperator) {
- js.Binary binary = expression;
- return allSimpleAssignments(binary.left)
- && allSimpleAssignments(binary.right);
- }
- return false;
- }
-
- if (allSimpleAssignments(jsInitialization)) {
- List<js.VariableInitialization> inits =
- <js.VariableInitialization>[];
- for (js.Assignment assignment in assignments) {
- String id = (assignment.leftHandSide as js.VariableUse).name;
- js.Node declaration = new js.VariableDeclaration(id);
- inits.add(new js.VariableInitialization(declaration,
- assignment.value));
- collectedVariableDeclarations.remove(id);
- declaredLocals.add(id);
- }
- jsInitialization = new js.VariableDeclarationList(inits);
- }
- }
- }
- js.Expression jsCondition = generateExpression(condition);
- js.Expression jsUpdates = generateExpression(info.updates);
- // The body might be labeled. Ignore this when recursing on the
- // subgraph.
- // TODO(lrn): Remove this extra labeling when handling all loops
- // using subgraphs.
- oldContainer = currentContainer;
- js.Statement body = new js.Block.empty();
- currentContainer = body;
- visitBodyIgnoreLabels(info);
- currentContainer = oldContainer;
- body = unwrapStatement(body);
- loop = new js.For(jsInitialization, jsCondition, jsUpdates, body);
- } else {
- // We have either no update graph, or it's too complex to
- // put in an expression.
- if (initialization != null) {
- generateStatements(initialization);
- }
- js.Expression jsCondition;
- js.Block oldContainer = currentContainer;
- js.Statement body = new js.Block.empty();
- if (isConditionExpression && !hasPhiUpdates) {
- jsCondition = generateExpression(condition);
- currentContainer = body;
- } else {
- jsCondition = newLiteralBool(true);
- currentContainer = body;
- generateStatements(condition);
- use(condition.conditionExpression);
- js.Expression ifTest = new js.Prefix("!", pop());
- js.Statement jsBreak = new js.Break(null);
- js.Statement exitLoop;
- if (avoidContainer.statements.isEmpty) {
- exitLoop = jsBreak;
- } else {
- avoidContainer.statements.add(jsBreak);
- exitLoop = avoidContainer;
- }
- pushStatement(new js.If.noElse(ifTest, exitLoop));
- }
- if (info.updates != null) {
- wrapLoopBodyForContinue(info);
- generateStatements(info.updates);
- } else {
- visitBodyIgnoreLabels(info);
- }
- currentContainer = oldContainer;
- body = unwrapStatement(body);
- loop = new js.While(jsCondition, body);
- }
- break;
- case HLoopBlockInformation.DO_WHILE_LOOP:
- if (info.initializer != null) {
- generateStatements(info.initializer);
- }
- // We inserted a basic block to avoid critical edges. This block is
- // part of the LoopBlockInformation and must therefore be handled here.
- js.Block oldContainer = currentContainer;
- js.Block exitAvoidContainer = new js.Block.empty();
- currentContainer = exitAvoidContainer;
- assignPhisOfSuccessors(condition.end.successors.last);
- bool hasExitPhiUpdates = !exitAvoidContainer.statements.isEmpty;
- currentContainer = oldContainer;
-
-
- oldContainer = currentContainer;
- js.Block body = new js.Block.empty();
- // If there are phi copies in the block that jumps to the
- // loop entry, we must emit the condition like this:
- // do {
- // body;
- // if (condition) {
- // phi updates;
- // continue;
- // } else {
- // break;
- // }
- // } while (true);
- HBasicBlock avoidEdge = info.end.successors[0];
- js.Block updateBody = new js.Block.empty();
- currentContainer = updateBody;
- assignPhisOfSuccessors(avoidEdge);
- bool hasPhiUpdates = !updateBody.statements.isEmpty;
- currentContainer = body;
- visitBodyIgnoreLabels(info);
- if (info.updates != null) {
- generateStatements(info.updates);
- }
- if (isConditionExpression) {
- push(generateExpression(condition));
- } else {
- generateStatements(condition);
- use(condition.conditionExpression);
- }
- js.Expression jsCondition = pop();
- if (jsCondition == null) {
- // If the condition is dead code, we turn the do-while into
- // a simpler while because we will never reach the condition
- // at the end of the loop anyway.
- loop = new js.While(newLiteralBool(true), unwrapStatement(body));
- } else {
- if (hasPhiUpdates || hasExitPhiUpdates) {
- updateBody.statements.add(new js.Continue(null));
- js.Statement jsBreak = new js.Break(null);
- js.Statement exitLoop;
- if (exitAvoidContainer.statements.isEmpty) {
- exitLoop = jsBreak;
- } else {
- exitAvoidContainer.statements.add(jsBreak);
- exitLoop = exitAvoidContainer;
- }
- body.statements.add(
- new js.If(jsCondition, updateBody, exitLoop));
- jsCondition = newLiteralBool(true);
- }
- loop = new js.Do(unwrapStatement(body), jsCondition);
- }
- currentContainer = oldContainer;
- break;
- default:
- compiler.internalError(condition.conditionExpression,
- 'Unexpected loop kind: ${info.kind}.');
- }
- js.Statement result =
- attachLocationRange(loop, info.sourcePosition, info.endSourcePosition);
- if (info.kind == HLoopBlockInformation.SWITCH_CONTINUE_LOOP) {
- String continueLabelString =
- backend.namer.implicitContinueLabelName(info.target);
- result = new js.LabeledStatement(continueLabelString, result);
- }
- pushStatement(wrapIntoLabels(result, info.labels));
- return true;
- }
-
- bool visitLabeledBlockInfo(HLabeledBlockInformation labeledBlockInfo) {
- Link<Entity> continueOverrides = const Link<Entity>();
-
- js.Block oldContainer = currentContainer;
- js.Block body = new js.Block.empty();
- js.Statement result = body;
-
- currentContainer = body;
-
- // If [labeledBlockInfo.isContinue], the block is an artificial
- // block around the body of a loop with an update block, so that
- // continues of the loop can be written as breaks of the body
- // block.
- if (labeledBlockInfo.isContinue) {
- for (LabelDefinition label in labeledBlockInfo.labels) {
- if (label.isContinueTarget) {
- String labelName = backend.namer.continueLabelName(label);
- result = new js.LabeledStatement(labelName, result);
- continueAction[label] = continueAsBreak;
- continueOverrides = continueOverrides.prepend(label);
- }
- }
- // For handling unlabeled continues from the body of a loop.
- // TODO(lrn): Consider recording whether the target is in fact
- // a target of an unlabeled continue, and not generate this if it isn't.
- JumpTarget target = labeledBlockInfo.target;
- String labelName = backend.namer.implicitContinueLabelName(target);
- result = new js.LabeledStatement(labelName, result);
- continueAction[target] = implicitContinueAsBreak;
- continueOverrides = continueOverrides.prepend(target);
- } else {
- for (LabelDefinition label in labeledBlockInfo.labels) {
- if (label.isBreakTarget) {
- String labelName = backend.namer.breakLabelName(label);
- result = new js.LabeledStatement(labelName, result);
- }
- }
- }
- JumpTarget target = labeledBlockInfo.target;
- if (target.isSwitch) {
- // This is an extra block around a switch that is generated
- // as a nested if/else chain. We add an extra break target
- // so that case code can break.
- String labelName = backend.namer.implicitBreakLabelName(target);
- result = new js.LabeledStatement(labelName, result);
- breakAction[target] = implicitBreakWithLabel;
- }
-
- currentContainer = body;
- generateStatements(labeledBlockInfo.body);
-
- if (labeledBlockInfo.isContinue) {
- while (!continueOverrides.isEmpty) {
- continueAction.remove(continueOverrides.head);
- continueOverrides = continueOverrides.tail;
- }
- } else {
- breakAction.remove(labeledBlockInfo.target);
- }
-
- currentContainer = oldContainer;
- pushStatement(result);
- return true;
- }
-
- // Wraps a loop body in a block to make continues have a target to break
- // to (if necessary).
- void wrapLoopBodyForContinue(HLoopBlockInformation info) {
- JumpTarget target = info.target;
- if (target != null && target.isContinueTarget) {
- js.Block oldContainer = currentContainer;
- js.Block body = new js.Block.empty();
- currentContainer = body;
- js.Statement result = body;
- for (LabelDefinition label in info.labels) {
- if (label.isContinueTarget) {
- String labelName = backend.namer.continueLabelName(label);
- result = new js.LabeledStatement(labelName, result);
- continueAction[label] = continueAsBreak;
- }
- }
- String labelName = backend.namer.implicitContinueLabelName(target);
- result = new js.LabeledStatement(labelName, result);
- continueAction[info.target] = implicitContinueAsBreak;
- visitBodyIgnoreLabels(info);
- continueAction.remove(info.target);
- for (LabelDefinition label in info.labels) {
- if (label.isContinueTarget) {
- continueAction.remove(label);
- }
- }
- currentContainer = oldContainer;
- pushStatement(result);
- } else {
- // Loop body contains no continues, so we don't need a break target.
- generateStatements(info.body);
- }
- }
-
- bool handleBlockFlow(HBlockFlow block) {
- HBlockInformation info = block.body;
- // If we reach here again while handling the attached information,
- // e.g., because we call visitSubGraph on a subgraph starting on
- // the same block, don't handle it again.
- // When the structure graph is complete, we will be able to have
- // different structures starting on the same basic block (e.g., an
- // "if" and its condition).
- if (identical(info, currentBlockInformation)) return false;
-
- HBlockInformation oldBlockInformation = currentBlockInformation;
- currentBlockInformation = info;
- bool success = info.accept(this);
- currentBlockInformation = oldBlockInformation;
- if (success) {
- HBasicBlock continuation = block.continuation;
- if (continuation != null) {
- visitBasicBlock(continuation);
- }
- }
- return success;
- }
-
- void visitBasicBlock(HBasicBlock node) {
- if (!node.isLive) return;
-
- // Abort traversal if we are leaving the currently active sub-graph.
- if (!subGraph.contains(node)) return;
-
- // If this node has block-structure based information attached,
- // try using that to traverse from here.
- if (node.blockFlow != null && handleBlockFlow(node.blockFlow)) {
- return;
- }
- iterateBasicBlock(node);
- }
-
- void emitAssignment(String destination, String source) {
- assignVariable(destination, new js.VariableUse(source));
- }
-
- /**
- * Sequentialize a list of conceptually parallel copies. Parallel
- * copies may contain cycles, that this method breaks.
- */
- void sequentializeCopies(Iterable<Copy> copies,
- String tempName,
- void doAssignment(String target, String source)) {
- // Map to keep track of the current location (ie the variable that
- // holds the initial value) of a variable.
- Map<String, String> currentLocation = new Map<String, String>();
-
- // Map to keep track of the initial value of a variable.
- Map<String, String> initialValue = new Map<String, String>();
-
- // List of variables to assign a value.
- List<String> worklist = <String>[];
-
- // List of variables that we can assign a value to (ie are not
- // being used anymore).
- List<String> ready = <String>[];
-
- // Prune [copies] by removing self-copies.
- List<Copy> prunedCopies = <Copy>[];
- for (Copy copy in copies) {
- if (copy.source != copy.destination) {
- prunedCopies.add(copy);
- }
- }
- copies = prunedCopies;
-
-
- // For each copy, set the current location of the source to
- // itself, and the initial value of the destination to the source.
- // Add the destination to the list of copies to make.
- for (Copy copy in copies) {
- currentLocation[copy.source] = copy.source;
- initialValue[copy.destination] = copy.source;
- worklist.add(copy.destination);
- }
-
- // For each copy, if the destination does not have a current
- // location, then we can safely assign to it.
- for (Copy copy in copies) {
- if (currentLocation[copy.destination] == null) {
- ready.add(copy.destination);
- }
- }
-
- while (!worklist.isEmpty) {
- while (!ready.isEmpty) {
- String destination = ready.removeLast();
- String source = initialValue[destination];
- // Since [source] might have been updated, use the current
- // location of [source]
- String copy = currentLocation[source];
- doAssignment(destination, copy);
- // Now [destination] is the current location of [source].
- currentLocation[source] = destination;
- // If [source] hasn't been updated and needs to have a value,
- // add it to the list of variables that can be updated. Copies
- // of [source] will now use [destination].
- if (source == copy && initialValue[source] != null) {
- ready.add(source);
- }
- }
-
- // Check if we have a cycle.
- String current = worklist.removeLast();
- // If [current] is used as a source, and the assignment has been
- // done, we are done with this variable. Otherwise there is a
- // cycle that we break by using a temporary name.
- if (currentLocation[current] != null
- && current != currentLocation[initialValue[current]]) {
- doAssignment(tempName, current);
- currentLocation[current] = tempName;
- // [current] can now be safely updated. Copies of [current]
- // will now use [tempName].
- ready.add(current);
- }
- }
- }
-
- void assignPhisOfSuccessors(HBasicBlock node) {
- CopyHandler handler = variableNames.getCopyHandler(node);
- if (handler == null) return;
-
- // Map the instructions to strings.
- Iterable<Copy> copies = handler.copies.map((Copy copy) {
- return new Copy(variableNames.getName(copy.source),
- variableNames.getName(copy.destination));
- });
-
- sequentializeCopies(copies, variableNames.getSwapTemp(), emitAssignment);
-
- for (Copy copy in handler.assignments) {
- String name = variableNames.getName(copy.destination);
- use(copy.source);
- assignVariable(name, pop());
- }
- }
-
- void iterateBasicBlock(HBasicBlock node) {
- HInstruction instruction = node.first;
- while (!identical(instruction, node.last)) {
- if (!isGenerateAtUseSite(instruction)) {
- define(instruction);
- }
- instruction = instruction.next;
- }
- assignPhisOfSuccessors(node);
- visit(instruction);
- }
-
- visitInvokeBinary(HInvokeBinary node, String op) {
- use(node.left);
- js.Expression jsLeft = pop();
- use(node.right);
- push(new js.Binary(op, jsLeft, pop()), node);
- }
-
- visitRelational(HRelational node, String op) => visitInvokeBinary(node, op);
-
- // We want the outcome of bit-operations to be positive. We use the unsigned
- // shift operator to achieve this.
- visitBitInvokeBinary(HBinaryBitOp node, String op) {
- visitInvokeBinary(node, op);
- if (op != '>>>' && requiresUintConversion(node)) {
- push(new js.Binary(">>>", pop(), new js.LiteralNumber("0")), node);
- }
- }
-
- visitInvokeUnary(HInvokeUnary node, String op) {
- use(node.operand);
- push(new js.Prefix(op, pop()), node);
- }
-
- // We want the outcome of bit-operations to be positive. We use the unsigned
- // shift operator to achieve this.
- visitBitInvokeUnary(HInvokeUnary node, String op) {
- visitInvokeUnary(node, op);
- if (requiresUintConversion(node)) {
- push(new js.Binary(">>>", pop(), new js.LiteralNumber("0")), node);
- }
- }
-
- void emitIdentityComparison(HIdentity instruction, bool inverse) {
- String op = instruction.singleComparisonOp;
- HInstruction left = instruction.left;
- HInstruction right = instruction.right;
- if (op != null) {
- use(left);
- js.Expression jsLeft = pop();
- use(right);
- push(new js.Binary(mapRelationalOperator(op, inverse), jsLeft, pop()));
- } else {
- assert(NullConstantValue.JsNull == 'null');
- use(left);
- js.Binary leftEqualsNull =
- new js.Binary("==", pop(), new js.LiteralNull());
- use(right);
- js.Binary rightEqualsNull =
- new js.Binary(mapRelationalOperator("==", inverse),
- pop(), new js.LiteralNull());
- use(right);
- use(left);
- js.Binary tripleEq = new js.Binary(mapRelationalOperator("===", inverse),
- pop(), pop());
-
- push(new js.Conditional(leftEqualsNull, rightEqualsNull, tripleEq));
- }
- }
-
- visitIdentity(HIdentity node) {
- emitIdentityComparison(node, false);
- }
-
- visitAdd(HAdd node) => visitInvokeBinary(node, '+');
- visitDivide(HDivide node) => visitInvokeBinary(node, '/');
- visitMultiply(HMultiply node) => visitInvokeBinary(node, '*');
- visitSubtract(HSubtract node) => visitInvokeBinary(node, '-');
- visitBitAnd(HBitAnd node) => visitBitInvokeBinary(node, '&');
- visitBitNot(HBitNot node) => visitBitInvokeUnary(node, '~');
- visitBitOr(HBitOr node) => visitBitInvokeBinary(node, '|');
- visitBitXor(HBitXor node) => visitBitInvokeBinary(node, '^');
- visitShiftLeft(HShiftLeft node) => visitBitInvokeBinary(node, '<<');
- visitShiftRight(HShiftRight node) => visitBitInvokeBinary(node, '>>>');
-
- visitTruncatingDivide(HTruncatingDivide node) {
- assert(node.left.isUInt31(compiler));
- assert(node.right.isPositiveInteger(compiler));
- use(node.left);
- js.Expression jsLeft = pop();
- use(node.right);
- push(new js.Binary('/', jsLeft, pop()), node);
- push(new js.Binary('|', pop(), new js.LiteralNumber("0")), node);
- }
-
- visitNegate(HNegate node) => visitInvokeUnary(node, '-');
-
- visitLess(HLess node) => visitRelational(node, '<');
- visitLessEqual(HLessEqual node) => visitRelational(node, '<=');
- visitGreater(HGreater node) => visitRelational(node, '>');
- visitGreaterEqual(HGreaterEqual node) => visitRelational(node, '>=');
-
- visitBoolify(HBoolify node) {
- assert(node.inputs.length == 1);
- use(node.inputs[0]);
- push(new js.Binary('===', pop(), newLiteralBool(true)), node);
- }
-
- visitExit(HExit node) {
- // Don't do anything.
- }
-
- visitGoto(HGoto node) {
- HBasicBlock block = node.block;
- assert(block.successors.length == 1);
- List<HBasicBlock> dominated = block.dominatedBlocks;
- // With the exception of the entry-node which dominates its successor
- // and the exit node, no block finishing with a 'goto' can have more than
- // one dominated block (since it has only one successor).
- // If the successor is dominated by another block, then the other block
- // is responsible for visiting the successor.
- if (dominated.isEmpty) return;
- if (dominated.length > 2) {
- compiler.internalError(node, 'dominated.length = ${dominated.length}');
- }
- if (dominated.length == 2 && block != currentGraph.entry) {
- compiler.internalError(node, 'node.block != currentGraph.entry');
- }
- assert(dominated[0] == block.successors[0]);
- visitBasicBlock(dominated[0]);
- }
-
- visitLoopBranch(HLoopBranch node) {
- assert(node.block == subGraph.end);
- // We are generating code for a loop condition.
- // If we are generating the subgraph as an expression, the
- // condition will be generated as the expression.
- // Otherwise, we don't generate the expression, and leave that
- // to the code that called [visitSubGraph].
- if (isGeneratingExpression) {
- use(node.inputs[0]);
- }
- }
-
- /**
- * Checks if [map] contains an [EntityAction] for [entity], and
- * if so calls that action and returns true.
- * Otherwise returns false.
- */
- bool tryCallAction(Map<Entity, EntityAction> map, Entity entity) {
- EntityAction action = map[entity];
- if (action == null) return false;
- action(entity);
- return true;
- }
-
- visitBreak(HBreak node) {
- assert(node.block.successors.length == 1);
- if (node.label != null) {
- LabelDefinition label = node.label;
- if (!tryCallAction(breakAction, label)) {
- pushStatement(new js.Break(backend.namer.breakLabelName(label)), node);
- }
- } else {
- JumpTarget target = node.target;
- if (!tryCallAction(breakAction, target)) {
- if (node.breakSwitchContinueLoop) {
- pushStatement(new js.Break(
- backend.namer.implicitContinueLabelName(target)), node);
- } else {
- pushStatement(new js.Break(null), node);
- }
- }
- }
- }
-
- visitContinue(HContinue node) {
- assert(node.block.successors.length == 1);
- if (node.label != null) {
- LabelDefinition label = node.label;
- if (!tryCallAction(continueAction, label)) {
- // TODO(floitsch): should this really be the breakLabelName?
- pushStatement(new js.Continue(backend.namer.breakLabelName(label)),
- node);
- }
- } else {
- JumpTarget target = node.target;
- if (!tryCallAction(continueAction, target)) {
- if (target.statement is ast.SwitchStatement) {
- pushStatement(new js.Continue(
- backend.namer.implicitContinueLabelName(target)), node);
- } else {
- pushStatement(new js.Continue(null), node);
- }
- }
- }
- }
-
- visitExitTry(HExitTry node) {
- // An [HExitTry] is used to represent the control flow graph of a
- // try/catch block, ie the try body is always a predecessor
- // of the catch and finally. Here, we continue visiting the try
- // body by visiting the block that contains the user-level control
- // flow instruction.
- visitBasicBlock(node.bodyTrySuccessor);
- }
-
- visitTry(HTry node) {
- // We should never get here. Try/catch/finally is always handled using block
- // information in [visitTryInfo].
- compiler.internalError(node, 'visitTry should not be called.');
- }
-
- bool tryControlFlowOperation(HIf node) {
- if (!controlFlowOperators.contains(node)) return false;
- HPhi phi = node.joinBlock.phis.first;
- bool atUseSite = isGenerateAtUseSite(phi);
- // Don't generate a conditional operator in this situation:
- // i = condition ? bar() : i;
- // But generate this instead:
- // if (condition) i = bar();
- // Usually, the variable name is longer than 'if' and it takes up
- // more space to duplicate the name.
- if (!atUseSite
- && variableNames.getName(phi) == variableNames.getName(phi.inputs[1])) {
- return false;
- }
- if (!atUseSite) define(phi);
- visitBasicBlock(node.joinBlock);
- return true;
- }
-
- void generateIf(HIf node, HIfBlockInformation info) {
- use(node.inputs[0]);
- js.Expression test = pop();
-
- HStatementInformation thenGraph = info.thenGraph;
- HStatementInformation elseGraph = info.elseGraph;
- js.Statement thenPart =
- unwrapStatement(generateStatementsInNewBlock(thenGraph));
- js.Statement elsePart =
- unwrapStatement(generateStatementsInNewBlock(elseGraph));
-
- pushStatement(new js.If(test, thenPart, elsePart), node);
- }
-
- visitIf(HIf node) {
- if (tryControlFlowOperation(node)) return;
-
- HInstruction condition = node.inputs[0];
- HIfBlockInformation info = node.blockInformation.body;
-
- if (condition.isConstant()) {
- HConstant constant = condition;
- if (constant.constant.isTrue) {
- generateStatements(info.thenGraph);
- } else {
- generateStatements(info.elseGraph);
- }
- } else {
- generateIf(node, info);
- }
-
- HBasicBlock joinBlock = node.joinBlock;
- if (joinBlock != null && !identical(joinBlock.dominator, node.block)) {
- // The join block is dominated by a block in one of the branches.
- // The subgraph traversal never reached it, so we visit it here
- // instead.
- visitBasicBlock(joinBlock);
- }
-
- // Visit all the dominated blocks that are not part of the then or else
- // branches, and is not the join block.
- // Depending on how the then/else branches terminate
- // (e.g., return/throw/break) there can be any number of these.
- List<HBasicBlock> dominated = node.block.dominatedBlocks;
- for (int i = 2; i < dominated.length; i++) {
- visitBasicBlock(dominated[i]);
- }
- }
-
- js.Call jsPropertyCall(js.Expression receiver,
- String fieldName,
- List<js.Expression> arguments) {
- return new js.Call(new js.PropertyAccess.field(receiver, fieldName),
- arguments);
- }
-
- void visitInterceptor(HInterceptor node) {
- registry.registerSpecializedGetInterceptor(node.interceptedClasses);
- String name = backend.namer.getInterceptorName(
- backend.getInterceptorMethod, node.interceptedClasses);
- var isolate = new js.VariableUse(
- backend.namer.globalObjectFor(backend.interceptorsLibrary));
- use(node.receiver);
- List<js.Expression> arguments = <js.Expression>[pop()];
- push(jsPropertyCall(isolate, name, arguments), node);
- registry.registerUseInterceptor();
- }
-
- visitInvokeDynamicMethod(HInvokeDynamicMethod node) {
- use(node.receiver);
- js.Expression object = pop();
- String name = node.selector.name;
- String methodName;
- List<js.Expression> arguments = visitArguments(node.inputs);
- Element target = node.element;
-
- if (target != null && !node.isInterceptedCall) {
- if (target == backend.jsArrayAdd) {
- methodName = 'push';
- } else if (target == backend.jsArrayRemoveLast) {
- methodName = 'pop';
- } else if (target == backend.jsStringSplit) {
- methodName = 'split';
- // Split returns a List, so we make sure the backend knows the
- // list class is instantiated.
- registry.registerInstantiatedClass(compiler.listClass);
- } else if (target.isNative && target.isFunction
- && !node.isInterceptedCall) {
- // A direct (i.e. non-interceptor) native call is the result of
- // optimization. The optimization ensures any type checks or
- // conversions have been satisified.
- methodName = target.fixedBackendName;
- }
- }
-
- if (methodName == null) {
- methodName = backend.namer.invocationName(node.selector);
- registerMethodInvoke(node);
- }
- push(jsPropertyCall(object, methodName, arguments), node);
- }
-
- void visitInvokeConstructorBody(HInvokeConstructorBody node) {
- use(node.inputs[0]);
- js.Expression object = pop();
- String methodName = backend.namer.getNameOfInstanceMember(node.element);
- List<js.Expression> arguments = visitArguments(node.inputs);
- push(jsPropertyCall(object, methodName, arguments), node);
- registry.registerStaticUse(node.element);
- }
-
- void visitOneShotInterceptor(HOneShotInterceptor node) {
- List<js.Expression> arguments = visitArguments(node.inputs);
- var isolate = new js.VariableUse(
- backend.namer.globalObjectFor(backend.interceptorsLibrary));
- Selector selector = getOptimizedSelectorFor(node, node.selector);
- String methodName = backend.registerOneShotInterceptor(selector);
- push(jsPropertyCall(isolate, methodName, arguments), node);
- if (selector.isGetter) {
- registerGetter(node);
- } else if (selector.isSetter) {
- registerSetter(node);
- } else {
- registerMethodInvoke(node);
- }
- registry.registerUseInterceptor();
- }
-
- Selector getOptimizedSelectorFor(HInvokeDynamic node, Selector selector) {
- if (node.element != null) {
- // Create an artificial type mask to make sure only
- // [node.element] will be enqueued. We're not using the receiver
- // type because our optimizations might end up in a state where the
- // invoke dynamic knows more than the receiver.
- ClassElement enclosing = node.element.enclosingClass;
- TypeMask receiverType =
- new TypeMask.nonNullExact(enclosing.declaration, compiler.world);
- return new TypedSelector(receiverType, selector, compiler.world);
- }
- // If [JSInvocationMirror._invokeOn] is enabled, and this call
- // might hit a `noSuchMethod`, we register an untyped selector.
- return selector.extendIfReachesAll(compiler);
- }
-
- void registerMethodInvoke(HInvokeDynamic node) {
- Selector selector = getOptimizedSelectorFor(node, node.selector);
-
- // If we don't know what we're calling or if we are calling a getter,
- // we need to register that fact that we may be calling a closure
- // with the same arguments.
- Element target = node.element;
- if (target == null || target.isGetter) {
- // TODO(kasperl): If we have a typed selector for the call, we
- // may know something about the types of closures that need
- // the specific closure call method.
- Selector call = new Selector.callClosureFrom(selector);
- registry.registerDynamicInvocation(call);
- }
- registry.registerDynamicInvocation(selector);
- }
-
- void registerSetter(HInvokeDynamic node) {
- Selector selector = getOptimizedSelectorFor(node, node.selector);
- registry.registerDynamicSetter(selector);
- }
-
- void registerGetter(HInvokeDynamic node) {
- Selector selector = getOptimizedSelectorFor(node, node.selector);
- registry.registerDynamicGetter(selector);
- }
-
- visitInvokeDynamicSetter(HInvokeDynamicSetter node) {
- use(node.receiver);
- String name = backend.namer.invocationName(node.selector);
- push(jsPropertyCall(pop(), name, visitArguments(node.inputs)), node);
- registerSetter(node);
- }
-
- visitInvokeDynamicGetter(HInvokeDynamicGetter node) {
- use(node.receiver);
- String name = backend.namer.invocationName(node.selector);
- push(jsPropertyCall(pop(), name, visitArguments(node.inputs)), node);
- registerGetter(node);
- }
-
- visitInvokeClosure(HInvokeClosure node) {
- Selector call = new Selector.callClosureFrom(node.selector);
- use(node.receiver);
- push(jsPropertyCall(pop(),
- backend.namer.invocationName(call),
- visitArguments(node.inputs)),
- node);
- registry.registerDynamicInvocation(call);
- }
-
- visitInvokeStatic(HInvokeStatic node) {
- Element element = node.element;
- ClassElement cls = element.enclosingClass;
- List<DartType> instantiatedTypes = node.instantiatedTypes;
-
- registry.registerStaticUse(element);
-
- if (instantiatedTypes != null && !instantiatedTypes.isEmpty) {
- instantiatedTypes.forEach((type) {
- registry.registerInstantiatedType(type);
- });
- }
-
- push(backend.namer.elementAccess(node.element));
- push(new js.Call(pop(), visitArguments(node.inputs, start: 0)), node);
- }
-
- visitInvokeSuper(HInvokeSuper node) {
- Element superMethod = node.element;
- registry.registerStaticUse(superMethod);
- ClassElement superClass = superMethod.enclosingClass;
- if (superMethod.kind == ElementKind.FIELD) {
- String fieldName = backend.namer.instanceFieldPropertyName(superMethod);
- use(node.inputs[0]);
- js.PropertyAccess access =
- new js.PropertyAccess.field(pop(), fieldName);
- if (node.isSetter) {
- use(node.value);
- push(new js.Assignment(access, pop()), node);
- } else {
- push(access, node);
- }
- } else {
- Selector selector = node.selector;
- String methodName;
- if (selector.isGetter) {
- // If the selector we need to register a typed getter to the
- // [world]. The emitter needs to know if it needs to emit a
- // bound closure for a method.
- TypeMask receiverType =
- new TypeMask.nonNullExact(superClass, compiler.world);
- selector = new TypedSelector(receiverType, selector, compiler.world);
- // TODO(floitsch): we know the target. We shouldn't register a
- // dynamic getter.
- registry.registerDynamicGetter(selector);
- registry.registerGetterForSuperMethod(node.element);
- methodName = backend.namer.invocationName(selector);
- } else {
- methodName = backend.namer.getNameOfInstanceMember(superMethod);
- }
- push(
- js.js('#.prototype.#.call(#)', [
- backend.namer.elementAccess(superClass),
- methodName, visitArguments(node.inputs, start: 0)]),
- node);
- }
- }
-
- visitFieldGet(HFieldGet node) {
- use(node.receiver);
- Element element = node.element;
- if (node.isNullCheck) {
- // We access a JavaScript member we know all objects besides
- // null and undefined have: V8 does not like accessing a member
- // that does not exist.
- push(new js.PropertyAccess.field(pop(), 'toString'), node);
- } else if (element == backend.jsIndexableLength) {
- // We're accessing a native JavaScript property called 'length'
- // on a JS String or a JS array. Therefore, the name of that
- // property should not be mangled.
- push(new js.PropertyAccess.field(pop(), 'length'), node);
- } else {
- String name = backend.namer.instanceFieldPropertyName(element);
- push(new js.PropertyAccess.field(pop(), name), node);
- registry.registerFieldGetter(element);
- }
- }
-
- visitFieldSet(HFieldSet node) {
- Element element = node.element;
- registry.registerFieldSetter(element);
- String name = backend.namer.instanceFieldPropertyName(element);
- use(node.receiver);
- js.Expression receiver = pop();
- use(node.value);
- push(new js.Assignment(new js.PropertyAccess.field(receiver, name), pop()),
- node);
- }
-
- visitReadModifyWrite(HReadModifyWrite node) {
- Element element = node.element;
- registry.registerFieldSetter(element);
- String name = backend.namer.instanceFieldPropertyName(element);
- use(node.receiver);
- js.Expression fieldReference = new js.PropertyAccess.field(pop(), name);
- if (node.isPreOp) {
- push(new js.Prefix(node.jsOp, fieldReference), node);
- } else if (node.isPostOp) {
- push(new js.Postfix(node.jsOp, fieldReference), node);
- } else {
- use(node.value);
- push(new js.Assignment.compound(fieldReference, node.jsOp, pop()), node);
- }
- }
-
- visitLocalGet(HLocalGet node) {
- use(node.receiver);
- }
-
- visitLocalSet(HLocalSet node) {
- use(node.value);
- assignVariable(variableNames.getName(node.receiver), pop());
- }
-
- void registerForeignTypes(HForeign node) {
- native.NativeBehavior nativeBehavior = node.nativeBehavior;
- if (nativeBehavior == null) return;
- nativeBehavior.typesReturned.forEach((type) {
- if (type is InterfaceType) {
- registry.registerInstantiatedType(type);
- }
- });
- }
-
- visitForeign(HForeign node) {
- List<HInstruction> inputs = node.inputs;
- if (node.isJsStatement()) {
- List<js.Expression> interpolatedExpressions = <js.Expression>[];
- for (int i = 0; i < inputs.length; i++) {
- use(inputs[i]);
- interpolatedExpressions.add(pop());
- }
- pushStatement(node.codeTemplate.instantiate(interpolatedExpressions));
- } else {
- List<js.Expression> interpolatedExpressions = <js.Expression>[];
- for (int i = 0; i < inputs.length; i++) {
- use(inputs[i]);
- interpolatedExpressions.add(pop());
- }
- push(node.codeTemplate.instantiate(interpolatedExpressions));
- }
-
- // TODO(sra): Tell world.nativeEnqueuer about the types created here.
- registerForeignTypes(node);
- }
-
- visitForeignNew(HForeignNew node) {
- js.Expression jsClassReference = backend.namer.elementAccess(node.element);
- List<js.Expression> arguments = visitArguments(node.inputs, start: 0);
- push(new js.New(jsClassReference, arguments), node);
- registerForeignTypes(node);
- if (node.instantiatedTypes == null) {
- return;
- }
- node.instantiatedTypes.forEach((type) {
- registry.registerInstantiatedType(type);
- });
- }
-
- js.Expression newLiteralBool(bool value) {
- if (compiler.enableMinification) {
- // Use !0 for true, !1 for false.
- return new js.Prefix("!", new js.LiteralNumber(value ? "0" : "1"));
- } else {
- return new js.LiteralBool(value);
- }
- }
-
- void generateConstant(ConstantValue constant) {
- if (constant.isFunction) {
- FunctionConstantValue function = constant;
- registry.registerStaticUse(function.element);
- }
- if (constant.isType) {
- // If the type is a web component, we need to ensure the constructors are
- // available to 'upgrade' the native object.
- TypeConstantValue type = constant;
- Element element = type.representedType.element;
- if (element != null && element.isClass) {
- registry.registerTypeConstant(element);
- }
- }
- push(backend.emitter.constantReference(constant));
- }
-
- visitConstant(HConstant node) {
- assert(isGenerateAtUseSite(node));
- generateConstant(node.constant);
-
- registry.registerCompileTimeConstant(node.constant);
- backend.constants.addCompileTimeConstantForEmission(node.constant);
- }
-
- visitNot(HNot node) {
- assert(node.inputs.length == 1);
- generateNot(node.inputs[0]);
- attachLocationToLast(node);
- }
-
- static String mapRelationalOperator(String op, bool inverse) {
- Map<String, String> inverseOperator = const <String, String>{
- "==" : "!=",
- "!=" : "==",
- "===": "!==",
- "!==": "===",
- "<" : ">=",
- "<=" : ">",
- ">" : "<=",
- ">=" : "<"
- };
- return inverse ? inverseOperator[op] : op;
- }
-
- void generateNot(HInstruction input) {
- bool canGenerateOptimizedComparison(HInstruction instruction) {
- if (instruction is !HRelational) return false;
-
- HRelational relational = instruction;
- BinaryOperation operation = relational.operation(backend.constantSystem);
-
- HInstruction left = relational.left;
- HInstruction right = relational.right;
- if (left.isStringOrNull(compiler) && right.isStringOrNull(compiler)) {
- return true;
- }
-
- // This optimization doesn't work for NaN, so we only do it if the
- // type is known to be an integer.
- return left.isInteger(compiler) && right.isInteger(compiler);
- }
-
- bool handledBySpecialCase = false;
- if (isGenerateAtUseSite(input)) {
- handledBySpecialCase = true;
- if (input is HIs) {
- emitIs(input, '!==');
- } else if (input is HIsViaInterceptor) {
- emitIsViaInterceptor(input, true);
- } else if (input is HNot) {
- use(input.inputs[0]);
- } else if (input is HIdentity) {
- emitIdentityComparison(input, true);
- } else if (input is HBoolify) {
- use(input.inputs[0]);
- push(new js.Binary("!==", pop(), newLiteralBool(true)), input);
- } else if (canGenerateOptimizedComparison(input)) {
- HRelational relational = input;
- BinaryOperation operation =
- relational.operation(backend.constantSystem);
- String op = mapRelationalOperator(operation.name, true);
- visitRelational(input, op);
- } else {
- handledBySpecialCase = false;
- }
- }
- if (!handledBySpecialCase) {
- use(input);
- push(new js.Prefix("!", pop()));
- }
- }
-
- visitParameterValue(HParameterValue node) {
- assert(!isGenerateAtUseSite(node));
- String name = variableNames.getName(node);
- parameters.add(new js.Parameter(name));
- declaredLocals.add(name);
- }
-
- visitLocalValue(HLocalValue node) {
- assert(!isGenerateAtUseSite(node));
- String name = variableNames.getName(node);
- collectedVariableDeclarations.add(name);
- }
-
- visitPhi(HPhi node) {
- // This method is only called for phis that are generated at use
- // site. A phi can be generated at use site only if it is the
- // result of a control flow operation.
- HBasicBlock ifBlock = node.block.dominator;
- assert(controlFlowOperators.contains(ifBlock.last));
- HInstruction input = ifBlock.last.inputs[0];
- if (input.isConstantFalse()) {
- use(node.inputs[1]);
- } else if (input.isConstantTrue()) {
- use(node.inputs[0]);
- } else if (node.inputs[1].isConstantBoolean()) {
- String operation = node.inputs[1].isConstantFalse() ? '&&' : '||';
- if (operation == '||') {
- generateNot(input);
- } else {
- use(input);
- }
- js.Expression left = pop();
- use(node.inputs[0]);
- push(new js.Binary(operation, left, pop()));
- } else {
- use(input);
- js.Expression test = pop();
- use(node.inputs[0]);
- js.Expression then = pop();
- use(node.inputs[1]);
- push(new js.Conditional(test, then, pop()));
- }
- }
-
- visitReturn(HReturn node) {
- assert(node.inputs.length == 1);
- HInstruction input = node.inputs[0];
- if (input.isConstantNull()) {
- pushStatement(new js.Return(null), node);
- } else {
- use(node.inputs[0]);
- pushStatement(new js.Return(pop()), node);
- }
- }
-
- visitThis(HThis node) {
- push(new js.This());
- }
-
- visitThrow(HThrow node) {
- if (node.isRethrow) {
- use(node.inputs[0]);
- pushStatement(new js.Throw(pop()), node);
- } else {
- generateThrowWithHelper('wrapException', node.inputs[0]);
- }
- }
-
- visitRangeConversion(HRangeConversion node) {
- // Range conversion instructions are removed by the value range
- // analyzer.
- assert(false);
- }
-
- visitBoundsCheck(HBoundsCheck node) {
- // TODO(ngeoffray): Separate the two checks of the bounds check, so,
- // e.g., the zero checks can be shared if possible.
-
- // If the checks always succeeds, we would have removed the bounds check
- // completely.
- assert(node.staticChecks != HBoundsCheck.ALWAYS_TRUE);
- if (node.staticChecks != HBoundsCheck.ALWAYS_FALSE) {
- js.Expression under;
- js.Expression over;
- if (node.staticChecks != HBoundsCheck.ALWAYS_ABOVE_ZERO) {
- use(node.index);
- if (node.index.isInteger(compiler)) {
- under = js.js("# < 0", pop());
- } else {
- js.Expression jsIndex = pop();
- under = js.js("# >>> 0 !== #", [jsIndex, jsIndex]);
- }
- } else if (!node.index.isInteger(compiler)) {
- checkInt(node.index, '!==');
- under = pop();
- }
- if (node.staticChecks != HBoundsCheck.ALWAYS_BELOW_LENGTH) {
- var index = node.index;
- use(index);
- js.Expression jsIndex = pop();
- use(node.length);
- over = new js.Binary(">=", jsIndex, pop());
- }
- assert(over != null || under != null);
- js.Expression underOver = under == null
- ? over
- : over == null
- ? under
- : new js.Binary("||", under, over);
- js.Statement thenBody = new js.Block.empty();
- js.Block oldContainer = currentContainer;
- currentContainer = thenBody;
- generateThrowWithHelper('ioore', [node.array, node.index]);
- currentContainer = oldContainer;
- thenBody = unwrapStatement(thenBody);
- pushStatement(new js.If.noElse(underOver, thenBody), node);
- } else {
- generateThrowWithHelper('ioore', [node.array, node.index]);
- }
- }
-
- void generateThrowWithHelper(String helperName, argument) {
- Element helper = backend.findHelper(helperName);
- registry.registerStaticUse(helper);
- js.Expression jsHelper = backend.namer.elementAccess(helper);
- List arguments = [];
- var location;
- if (argument is List) {
- location = argument[0];
- argument.forEach((instruction) {
- use(instruction);
- arguments.add(pop());
- });
- } else {
- location = argument;
- use(argument);
- arguments.add(pop());
- }
- js.Call value = new js.Call(jsHelper, arguments.toList(growable: false));
- value = attachLocation(value, location);
- // BUG(4906): Using throw/return here adds to the size of the generated code
- // but it has the advantage of explicitly telling the JS engine that
- // this code path will terminate abruptly. Needs more work.
- if (helperName == 'wrapException') {
- pushStatement(new js.Throw(value));
- } else {
- pushStatement(new js.Return(value));
- }
- }
-
- visitThrowExpression(HThrowExpression node) {
- HInstruction argument = node.inputs[0];
- use(argument);
-
- Element helper = backend.findHelper("throwExpression");
- registry.registerStaticUse(helper);
-
- js.Expression jsHelper = backend.namer.elementAccess(helper);
- js.Call value = new js.Call(jsHelper, [pop()]);
- value = attachLocation(value, argument);
- push(value, node);
- }
-
- void visitSwitch(HSwitch node) {
- // Switches are handled using [visitSwitchInfo].
- }
-
- void visitStatic(HStatic node) {
- Element element = node.element;
- if (element.isFunction) {
- push(backend.namer.isolateStaticClosureAccess(node.element));
- } else {
- push(backend.namer.elementAccess(node.element));
- }
- registry.registerStaticUse(element);
- }
-
- void visitLazyStatic(HLazyStatic node) {
- Element element = node.element;
- registry.registerStaticUse(element);
- js.Expression lazyGetter =
- backend.namer.isolateLazyInitializerAccess(element);
- js.Call call = new js.Call(lazyGetter, <js.Expression>[]);
- push(call, node);
- }
-
- void visitStaticStore(HStaticStore node) {
- registry.registerStaticUse(node.element);
- js.Node variable = backend.namer.elementAccess(node.element);
- use(node.inputs[0]);
- push(new js.Assignment(variable, pop()), node);
- }
-
- void visitStringConcat(HStringConcat node) {
- use(node.left);
- js.Expression jsLeft = pop();
- use(node.right);
- push(new js.Binary('+', jsLeft, pop()), node);
- }
-
- void visitStringify(HStringify node) {
- HInstruction input = node.inputs.first;
- if (input.isString(compiler)) {
- use(input);
- } else if (input.isInteger(compiler) || input.isBoolean(compiler)) {
- // JavaScript's + operator with a string for the left operand will convert
- // the right operand to a string, and the conversion result is correct.
- use(input);
- if (node.usedBy.length == 1
- && node.usedBy[0] is HStringConcat
- && node.usedBy[0].inputs[1] == node) {
- // The context is already <string> + value.
- } else {
- // Force an empty string for the first operand.
- push(new js.Binary('+', js.string(""), pop()), node);
- }
- } else {
- Element convertToString = backend.getStringInterpolationHelper();
- registry.registerStaticUse(convertToString);
- js.Expression jsHelper = backend.namer.elementAccess(convertToString);
- use(input);
- push(new js.Call(jsHelper, <js.Expression>[pop()]), node);
- }
- }
-
- void visitLiteralList(HLiteralList node) {
- registry.registerInstantiatedClass(compiler.listClass);
- generateArrayLiteral(node);
- }
-
- void generateArrayLiteral(HLiteralList node) {
- int len = node.inputs.length;
- List<js.ArrayElement> elements = <js.ArrayElement>[];
- for (int i = 0; i < len; i++) {
- use(node.inputs[i]);
- elements.add(new js.ArrayElement(i, pop()));
- }
- push(new js.ArrayInitializer(len, elements), node);
- }
-
- void visitIndex(HIndex node) {
- use(node.receiver);
- js.Expression receiver = pop();
- use(node.index);
- push(new js.PropertyAccess(receiver, pop()), node);
- }
-
- void visitIndexAssign(HIndexAssign node) {
- use(node.receiver);
- js.Expression receiver = pop();
- use(node.index);
- js.Expression index = pop();
- use(node.value);
- push(new js.Assignment(new js.PropertyAccess(receiver, index), pop()),
- node);
- }
-
- void checkInt(HInstruction input, String cmp) {
- use(input);
- js.Expression left = pop();
- use(input);
- js.Expression or0 = new js.Binary("|", pop(), new js.LiteralNumber("0"));
- push(new js.Binary(cmp, left, or0));
- }
-
- void checkBigInt(HInstruction input, String cmp) {
- use(input);
- js.Expression left = pop();
- use(input);
- js.Expression right = pop();
- // TODO(4984): Deal with infinity and -0.0.
- push(js.js('Math.floor(#) $cmp #', <js.Expression>[left, right]));
- }
-
- void checkTypeOf(HInstruction input, String cmp, String typeName) {
- use(input);
- js.Expression typeOf = new js.Prefix("typeof", pop());
- push(new js.Binary(cmp, typeOf, js.string(typeName)));
- }
-
- void checkNum(HInstruction input, String cmp)
- => checkTypeOf(input, cmp, 'number');
-
- void checkDouble(HInstruction input, String cmp) => checkNum(input, cmp);
-
- void checkString(HInstruction input, String cmp)
- => checkTypeOf(input, cmp, 'string');
-
- void checkBool(HInstruction input, String cmp)
- => checkTypeOf(input, cmp, 'boolean');
-
- void checkObject(HInstruction input, String cmp) {
- assert(NullConstantValue.JsNull == 'null');
- if (cmp == "===") {
- checkTypeOf(input, '===', 'object');
- js.Expression left = pop();
- use(input);
- js.Expression notNull = new js.Binary("!==", pop(), new js.LiteralNull());
- push(new js.Binary("&&", left, notNull));
- } else {
- assert(cmp == "!==");
- checkTypeOf(input, '!==', 'object');
- js.Expression left = pop();
- use(input);
- js.Expression eqNull = new js.Binary("===", pop(), new js.LiteralNull());
- push(new js.Binary("||", left, eqNull));
- }
- }
-
- void checkArray(HInstruction input, String cmp) {
- use(input);
- js.PropertyAccess constructor =
- new js.PropertyAccess.field(pop(), 'constructor');
- push(new js.Binary(cmp, constructor, new js.VariableUse('Array')));
- }
-
- void checkFieldExists(HInstruction input, String fieldName) {
- use(input);
- js.PropertyAccess field = new js.PropertyAccess.field(pop(), fieldName);
- // Double negate to boolify the result.
- push(new js.Prefix('!', new js.Prefix('!', field)));
- }
-
- void checkFieldDoesNotExist(HInstruction input, String fieldName) {
- use(input);
- js.PropertyAccess field = new js.PropertyAccess.field(pop(), fieldName);
- push(new js.Prefix('!', field));
- }
-
- void checkImmutableArray(HInstruction input) {
- checkFieldExists(input, 'immutable\$list');
- }
-
- void checkMutableArray(HInstruction input) {
- checkFieldDoesNotExist(input, 'immutable\$list');
- }
-
- void checkExtendableArray(HInstruction input) {
- checkFieldDoesNotExist(input, 'fixed\$length');
- }
-
- void checkFixedArray(HInstruction input) {
- checkFieldExists(input, 'fixed\$length');
- }
-
- void checkNull(HInstruction input) {
- use(input);
- push(new js.Binary('==', pop(), new js.LiteralNull()));
- }
-
- void checkNonNull(HInstruction input) {
- use(input);
- push(new js.Binary('!=', pop(), new js.LiteralNull()));
- }
-
- bool checkIndexingBehavior(HInstruction input, {bool negative: false}) {
- if (!compiler.resolverWorld.isInstantiated(
- backend.jsIndexingBehaviorInterface)) {
- return false;
- }
-
- use(input);
- js.Expression object1 = pop();
- use(input);
- js.Expression object2 = pop();
- push(backend.generateIsJsIndexableCall(object1, object2));
- if (negative) push(new js.Prefix('!', pop()));
- return true;
- }
-
- void checkType(HInstruction input, HInstruction interceptor,
- DartType type, {bool negative: false}) {
- Element element = type.element;
- if (element == backend.jsArrayClass) {
- checkArray(input, negative ? '!==': '===');
- return;
- } else if (element == backend.jsMutableArrayClass) {
- if (negative) {
- checkImmutableArray(input);
- } else {
- checkMutableArray(input);
- }
- return;
- } else if (element == backend.jsExtendableArrayClass) {
- if (negative) {
- checkFixedArray(input);
- } else {
- checkExtendableArray(input);
- }
- return;
- } else if (element == backend.jsFixedArrayClass) {
- if (negative) {
- checkExtendableArray(input);
- } else {
- checkFixedArray(input);
- }
- return;
- }
- if (interceptor != null) {
- checkTypeViaProperty(interceptor, type, negative);
- } else {
- checkTypeViaProperty(input, type, negative);
- }
- }
-
- void checkTypeViaProperty(HInstruction input, DartType type, bool negative) {
- registry.registerIsCheck(type);
-
- use(input);
-
- js.PropertyAccess field =
- new js.PropertyAccess.field(pop(), backend.namer.operatorIsType(type));
- // We always negate at least once so that the result is boolified.
- push(new js.Prefix('!', field));
- // If the result is not negated, put another '!' in front.
- if (!negative) push(new js.Prefix('!', pop()));
- }
-
- void handleNumberOrStringSupertypeCheck(HInstruction input,
- HInstruction interceptor,
- DartType type,
- { bool negative: false }) {
- assert(!identical(type.element, compiler.listClass)
- && !Elements.isListSupertype(type.element, compiler)
- && !Elements.isStringOnlySupertype(type.element, compiler));
- String relation = negative ? '!==' : '===';
- checkNum(input, relation);
- js.Expression numberTest = pop();
- checkString(input, relation);
- js.Expression stringTest = pop();
- checkObject(input, relation);
- js.Expression objectTest = pop();
- checkType(input, interceptor, type, negative: negative);
- String combiner = negative ? '&&' : '||';
- String combiner2 = negative ? '||' : '&&';
- push(new js.Binary(combiner,
- new js.Binary(combiner, numberTest, stringTest),
- new js.Binary(combiner2, objectTest, pop())));
- }
-
- void handleStringSupertypeCheck(HInstruction input,
- HInstruction interceptor,
- DartType type,
- { bool negative: false }) {
- assert(!identical(type.element, compiler.listClass)
- && !Elements.isListSupertype(type.element, compiler)
- && !Elements.isNumberOrStringSupertype(type.element, compiler));
- String relation = negative ? '!==' : '===';
- checkString(input, relation);
- js.Expression stringTest = pop();
- checkObject(input, relation);
- js.Expression objectTest = pop();
- checkType(input, interceptor, type, negative: negative);
- String combiner = negative ? '||' : '&&';
- push(new js.Binary(negative ? '&&' : '||',
- stringTest,
- new js.Binary(combiner, objectTest, pop())));
- }
-
- void handleListOrSupertypeCheck(HInstruction input,
- HInstruction interceptor,
- DartType type,
- { bool negative: false }) {
- assert(!identical(type.element, compiler.stringClass)
- && !Elements.isStringOnlySupertype(type.element, compiler)
- && !Elements.isNumberOrStringSupertype(type.element, compiler));
- String relation = negative ? '!==' : '===';
- checkObject(input, relation);
- js.Expression objectTest = pop();
- checkArray(input, relation);
- js.Expression arrayTest = pop();
- checkType(input, interceptor, type, negative: negative);
- String combiner = negative ? '&&' : '||';
- push(new js.Binary(negative ? '||' : '&&',
- objectTest,
- new js.Binary(combiner, arrayTest, pop())));
- }
-
- void visitIs(HIs node) {
- emitIs(node, "===");
- }
-
- void visitIsViaInterceptor(HIsViaInterceptor node) {
- emitIsViaInterceptor(node, false);
- }
-
- void emitIs(HIs node, String relation) {
- DartType type = node.typeExpression;
- registry.registerIsCheck(type);
- HInstruction input = node.expression;
-
- // If this is changed to single == there are several places below that must
- // be changed to match.
- assert(relation == '===' || relation == '!==');
- bool negative = relation == '!==';
-
- if (node.isVariableCheck || node.isCompoundCheck) {
- use(node.checkCall);
- if (negative) push(new js.Prefix('!', pop()));
- } else {
- assert(node.isRawCheck);
- HInstruction interceptor = node.interceptor;
- LibraryElement coreLibrary = compiler.coreLibrary;
- ClassElement objectClass = compiler.objectClass;
- Element element = type.element;
- if (element == compiler.nullClass) {
- if (negative) {
- checkNonNull(input);
- } else {
- checkNull(input);
- }
- } else if (identical(element, objectClass) || type.treatAsDynamic) {
- // The constant folder also does this optimization, but we make
- // it safe by assuming it may have not run.
- push(newLiteralBool(!negative), node);
- } else if (element == compiler.stringClass) {
- checkString(input, relation);
- attachLocationToLast(node);
- } else if (element == compiler.doubleClass) {
- checkDouble(input, relation);
- attachLocationToLast(node);
- } else if (element == compiler.numClass) {
- checkNum(input, relation);
- attachLocationToLast(node);
- } else if (element == compiler.boolClass) {
- checkBool(input, relation);
- attachLocationToLast(node);
- } else if (element == compiler.intClass) {
- // The is check in the code tells us that it might not be an
- // int. So we do a typeof first to avoid possible
- // deoptimizations on the JS engine due to the Math.floor check.
- checkNum(input, relation);
- js.Expression numTest = pop();
- checkBigInt(input, relation);
- push(new js.Binary(negative ? '||' : '&&', numTest, pop()), node);
- } else if (Elements.isNumberOrStringSupertype(element, compiler)) {
- handleNumberOrStringSupertypeCheck(
- input, interceptor, type, negative: negative);
- attachLocationToLast(node);
- } else if (Elements.isStringOnlySupertype(element, compiler)) {
- handleStringSupertypeCheck(
- input, interceptor, type, negative: negative);
- attachLocationToLast(node);
- } else if (identical(element, compiler.listClass)
- || Elements.isListSupertype(element, compiler)) {
- handleListOrSupertypeCheck(
- input, interceptor, type, negative: negative);
- attachLocationToLast(node);
- } else if (type.isFunctionType) {
- checkType(input, interceptor, type, negative: negative);
- attachLocationToLast(node);
- } else if ((input.canBePrimitive(compiler)
- && !input.canBePrimitiveArray(compiler))
- || input.canBeNull()) {
- checkObject(input, relation);
- js.Expression objectTest = pop();
- checkType(input, interceptor, type, negative: negative);
- push(new js.Binary(negative ? '||' : '&&', objectTest, pop()), node);
- } else {
- checkType(input, interceptor, type, negative: negative);
- attachLocationToLast(node);
- }
- }
- }
-
- void emitIsViaInterceptor(HIsViaInterceptor node, bool negative) {
- checkTypeViaProperty(node.interceptor, node.typeExpression, negative);
- attachLocationToLast(node);
- }
-
- js.Expression generateTest(HInstruction input, TypeMask checkedType) {
- ClassWorld classWorld = compiler.world;
- TypeMask receiver = input.instructionType;
- // Figure out if it is beneficial to turn this into a null check.
- // V8 generally prefers 'typeof' checks, but for integers and
- // indexable primitives we cannot compile this test into a single
- // typeof check so the null check is cheaper.
- bool turnIntoNumCheck = input.isIntegerOrNull(compiler)
- && checkedType.containsOnlyInt(classWorld);
- bool turnIntoNullCheck = !turnIntoNumCheck
- && (checkedType.nullable() == receiver)
- && (checkedType.containsOnlyInt(classWorld)
- || checkedType.satisfies(backend.jsIndexableClass, classWorld));
- js.Expression test;
- if (turnIntoNullCheck) {
- use(input);
- test = new js.Binary("==", pop(), new js.LiteralNull());
- } else if (checkedType.containsOnlyInt(classWorld) && !turnIntoNumCheck) {
- // input is !int
- checkInt(input, '!==');
- test = pop();
- } else if (checkedType.containsOnlyNum(classWorld) || turnIntoNumCheck) {
- // input is !num
- checkNum(input, '!==');
- test = pop();
- } else if (checkedType.containsOnlyBool(classWorld)) {
- // input is !bool
- checkBool(input, '!==');
- test = pop();
- } else if (checkedType.containsOnlyString(classWorld)) {
- // input is !string
- checkString(input, '!==');
- test = pop();
- } else if (checkedType.satisfies(backend.jsExtendableArrayClass,
- classWorld)) {
- // input is !Object || input is !Array || input.isFixed
- checkObject(input, '!==');
- js.Expression objectTest = pop();
- checkArray(input, '!==');
- js.Expression arrayTest = pop();
- checkFixedArray(input);
- test = new js.Binary('||', objectTest, arrayTest);
- test = new js.Binary('||', test, pop());
- } else if (checkedType.satisfies(backend.jsMutableArrayClass, classWorld)) {
- // input is !Object
- // || ((input is !Array || input.isImmutable)
- // && input is !JsIndexingBehavior)
- checkObject(input, '!==');
- js.Expression objectTest = pop();
- checkArray(input, '!==');
- js.Expression arrayTest = pop();
- checkImmutableArray(input);
- js.Binary notArrayOrImmutable = new js.Binary('||', arrayTest, pop());
-
- js.Binary notIndexing = checkIndexingBehavior(input, negative: true)
- ? new js.Binary('&&', notArrayOrImmutable, pop())
- : notArrayOrImmutable;
- test = new js.Binary('||', objectTest, notIndexing);
- } else if (checkedType.satisfies(backend.jsArrayClass, classWorld)) {
- // input is !Object
- // || (input is !Array && input is !JsIndexingBehavior)
- checkObject(input, '!==');
- js.Expression objectTest = pop();
- checkArray(input, '!==');
- js.Expression arrayTest = pop();
-
- js.Expression notIndexing = checkIndexingBehavior(input, negative: true)
- ? new js.Binary('&&', arrayTest, pop())
- : arrayTest;
- test = new js.Binary('||', objectTest, notIndexing);
- } else if (checkedType.satisfies(backend.jsIndexableClass, classWorld)) {
- // input is !String
- // && (input is !Object
- // || (input is !Array && input is !JsIndexingBehavior))
- checkString(input, '!==');
- js.Expression stringTest = pop();
- checkObject(input, '!==');
- js.Expression objectTest = pop();
- checkArray(input, '!==');
- js.Expression arrayTest = pop();
-
- js.Binary notIndexingTest = checkIndexingBehavior(input, negative: true)
- ? new js.Binary('&&', arrayTest, pop())
- : arrayTest;
- js.Binary notObjectOrIndexingTest =
- new js.Binary('||', objectTest, notIndexingTest);
- test = new js.Binary('&&', stringTest, notObjectOrIndexingTest);
- } else {
- compiler.internalError(input, 'Unexpected check.');
- }
- return test;
- }
-
- void visitTypeConversion(HTypeConversion node) {
- if (node.isArgumentTypeCheck || node.isReceiverTypeCheck) {
- ClassWorld classWorld = compiler.world;
- // An int check if the input is not int or null, is not
- // sufficient for doing a argument or receiver check.
- assert(!node.checkedType.containsOnlyInt(classWorld) ||
- node.checkedInput.isIntegerOrNull(compiler));
- js.Expression test = generateTest(node.checkedInput, node.checkedType);
- js.Block oldContainer = currentContainer;
- js.Statement body = new js.Block.empty();
- currentContainer = body;
- if (node.isArgumentTypeCheck) {
- generateThrowWithHelper('iae', node.checkedInput);
- } else if (node.isReceiverTypeCheck) {
- use(node.checkedInput);
- String methodName =
- backend.namer.invocationName(node.receiverTypeCheckSelector);
- js.Expression call = jsPropertyCall(pop(), methodName, []);
- pushStatement(new js.Return(call));
- }
- currentContainer = oldContainer;
- body = unwrapStatement(body);
- pushStatement(new js.If.noElse(test, body), node);
- return;
- }
-
- assert(node.isCheckedModeCheck || node.isCastTypeCheck);
- DartType type = node.typeExpression;
- assert(type.kind != TypeKind.TYPEDEF);
- if (type.isFunctionType) {
- // TODO(5022): We currently generate $isFunction checks for
- // function types.
- registry.registerIsCheck(compiler.functionClass.rawType);
- }
- registry.registerIsCheck(type);
-
- CheckedModeHelper helper;
- if (node.isBooleanConversionCheck) {
- helper =
- const CheckedModeHelper('boolConversionCheck');
- } else {
- helper =
- backend.getCheckedModeHelper(type, typeCast: node.isCastTypeCheck);
- }
-
- if (helper == null) {
- assert(type.isFunctionType);
- use(node.inputs[0]);
- } else {
- push(helper.generateCall(this, node));
- }
- }
-
- void visitTypeKnown(HTypeKnown node) {
- // [HTypeKnown] instructions are removed before generating code.
- assert(false);
- }
-
- void visitFunctionType(HFunctionType node) {
- FunctionType type = node.dartType;
- int inputCount = 0;
- use(node.inputs[inputCount++]);
- js.Expression returnType = pop();
-
- List<js.Expression> parameterTypes = <js.Expression>[];
- for (var _ in type.parameterTypes) {
- use(node.inputs[inputCount++]);
- parameterTypes.add(pop());
- }
-
- List<js.Expression> optionalParameterTypes = <js.Expression>[];
- for (var _ in type.optionalParameterTypes) {
- use(node.inputs[inputCount++]);
- optionalParameterTypes.add(pop());
- }
-
- List<js.Property> namedParameters = <js.Property>[];
- for (var _ in type.namedParameters) {
- use(node.inputs[inputCount++]);
- js.Expression name = pop();
- use(node.inputs[inputCount++]);
- namedParameters.add(new js.Property(name, pop()));
- }
-
- if (namedParameters.isEmpty) {
- var arguments = [returnType];
- if (!parameterTypes.isEmpty || !optionalParameterTypes.isEmpty) {
- arguments.add(new js.ArrayInitializer.from(parameterTypes));
- }
- if (!optionalParameterTypes.isEmpty) {
- arguments.add(new js.ArrayInitializer.from(optionalParameterTypes));
- }
- push(js.js('#(#)', [accessHelper('buildFunctionType'), arguments]));
- } else {
- var arguments = [
- returnType,
- new js.ArrayInitializer.from(parameterTypes),
- new js.ObjectInitializer(namedParameters)];
- push(js.js('#(#)', [accessHelper('buildNamedFunctionType'), arguments]));
- }
- }
-
- void visitReadTypeVariable(HReadTypeVariable node) {
- TypeVariableElement element = node.dartType.element;
- Element helperElement = backend.findHelper('convertRtiToRuntimeType');
- registry.registerStaticUse(helperElement);
-
- use(node.inputs[0]);
- if (node.hasReceiver) {
- if (backend.isInterceptorClass(element.enclosingClass)) {
- int index = RuntimeTypes.getTypeVariableIndex(element);
- js.Expression receiver = pop();
- js.Expression helper = backend.namer.elementAccess(helperElement);
- push(js.js(r'#(#.$builtinTypeInfo && #.$builtinTypeInfo[#])',
- [helper, receiver, receiver, js.js.number(index)]));
- } else {
- backend.emitter.registerReadTypeVariable(element);
- push(js.js('#.#()',
- [pop(), backend.namer.readTypeVariableName(element)]));
- }
- } else {
- push(js.js('#(#)', [
- backend.namer.elementAccess(
- backend.findHelper('convertRtiToRuntimeType')),
- pop()]));
- }
- }
-
- void visitInterfaceType(HInterfaceType node) {
- List<js.Expression> typeArguments = <js.Expression>[];
- for (HInstruction type in node.inputs) {
- use(type);
- typeArguments.add(pop());
- }
-
- ClassElement cls = node.dartType.element;
- var arguments = [backend.namer.elementAccess(cls)];
- if (!typeArguments.isEmpty) {
- arguments.add(new js.ArrayInitializer.from(typeArguments));
- }
- push(js.js('#(#)', [accessHelper('buildInterfaceType'), arguments]));
- }
-
- void visitVoidType(HVoidType node) {
- push(js.js('#()', accessHelper('getVoidRuntimeType')));
- }
-
- void visitDynamicType(HDynamicType node) {
- push(js.js('#()', accessHelper('getDynamicRuntimeType')));
- }
-
- js.PropertyAccess accessHelper(String name) {
- Element helper = backend.findHelper(name);
- if (helper == null) {
- // For mocked-up tests.
- return js.js('(void 0).$name');
- }
- registry.registerStaticUse(helper);
- return backend.namer.elementAccess(helper);
- }
-}

Powered by Google App Engine
This is Rietveld 408576698