| Index: pkg/compiler/lib/src/ssa/codegen.dart
|
| diff --git a/pkg/compiler/lib/src/ssa/codegen.dart b/pkg/compiler/lib/src/ssa/codegen.dart
|
| deleted file mode 100644
|
| index f67fe0030254043ea8bd3846ff4f2304649a6cf7..0000000000000000000000000000000000000000
|
| --- a/pkg/compiler/lib/src/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);
|
| - }
|
| -}
|
|
|