| Index: pkg/compiler/lib/src/js_backend/codegen/codegen.dart
|
| diff --git a/pkg/compiler/lib/src/js_backend/codegen/codegen.dart b/pkg/compiler/lib/src/js_backend/codegen/codegen.dart
|
| deleted file mode 100644
|
| index add58cefc82e14171e28a6f3809fd333762c0529..0000000000000000000000000000000000000000
|
| --- a/pkg/compiler/lib/src/js_backend/codegen/codegen.dart
|
| +++ /dev/null
|
| @@ -1,1240 +0,0 @@
|
| -// Copyright (c) 2014, 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.
|
| -
|
| -library code_generator;
|
| -
|
| -import '../../closure.dart' show ClosureClassElement;
|
| -import '../../common/codegen.dart' show CodegenRegistry;
|
| -import '../../constants/values.dart';
|
| -import '../../dart_types.dart';
|
| -import '../../elements/elements.dart';
|
| -import '../../io/source_information.dart' show SourceInformation;
|
| -import '../../js/js.dart' as js;
|
| -import '../../tree_ir/tree_ir_nodes.dart' as tree_ir;
|
| -import '../../tree_ir/tree_ir_nodes.dart'
|
| - show BuiltinMethod, BuiltinOperator, isCompoundableOperator;
|
| -import '../../types/types.dart' show TypeMask;
|
| -import '../../universe/call_structure.dart' show CallStructure;
|
| -import '../../universe/selector.dart' show Selector;
|
| -import '../../universe/use.dart' show DynamicUse, StaticUse, TypeUse;
|
| -import '../../util/maplet.dart';
|
| -import 'glue.dart';
|
| -
|
| -class CodegenBailout {
|
| - final tree_ir.Node node;
|
| - final String reason;
|
| - CodegenBailout(this.node, this.reason);
|
| - String get message {
|
| - return 'bailout${node != null ? " on $node" : ""}: $reason';
|
| - }
|
| -}
|
| -
|
| -class CodeGenerator extends tree_ir.StatementVisitor
|
| - with tree_ir.ExpressionVisitor<js.Expression> {
|
| - final CodegenRegistry registry;
|
| -
|
| - final Glue glue;
|
| -
|
| - ExecutableElement currentFunction;
|
| -
|
| - /// Maps variables to their name.
|
| - Map<tree_ir.Variable, String> variableNames = <tree_ir.Variable, String>{};
|
| -
|
| - /// Maps local constants to their name.
|
| - Maplet<VariableElement, String> constantNames =
|
| - new Maplet<VariableElement, String>();
|
| -
|
| - /// Variable names that have already been used. Used to avoid name clashes.
|
| - Set<String> usedVariableNames = new Set<String>();
|
| -
|
| - final tree_ir.FallthroughStack fallthrough = new tree_ir.FallthroughStack();
|
| -
|
| - /// Stacks whose top element is the current target of an unlabeled break
|
| - /// or continue. For continues, this is the loop node itself.
|
| - final tree_ir.FallthroughStack shortBreak = new tree_ir.FallthroughStack();
|
| - final tree_ir.FallthroughStack shortContinue = new tree_ir.FallthroughStack();
|
| -
|
| - /// When the top element is true, [Unreachable] statements will be emitted
|
| - /// as [Return]s, otherwise they are emitted as empty because they are
|
| - /// followed by the end of the method.
|
| - ///
|
| - /// Note on why the [fallthrough] stack should not be used for this:
|
| - /// Ordinary statements may choose whether to use the [fallthrough] target,
|
| - /// and the choice to do so may disable an optimization in [visitIf].
|
| - /// But omitting an unreachable 'return' should have lower priority than
|
| - /// the optimizations in [visitIf], so [visitIf] will instead tell the
|
| - /// [Unreachable] statements whether they may use fallthrough or not.
|
| - List<bool> emitUnreachableAsReturn = <bool>[false];
|
| -
|
| - final Map<tree_ir.Label, String> labelNames = <tree_ir.Label, String>{};
|
| -
|
| - List<js.Statement> accumulator = new List<js.Statement>();
|
| -
|
| - CodeGenerator(this.glue, this.registry);
|
| -
|
| - /// Generates JavaScript code for the body of [function].
|
| - js.Fun buildFunction(tree_ir.FunctionDefinition function) {
|
| - registerDefaultParameterValues(function.element);
|
| - currentFunction = function.element;
|
| - tree_ir.Statement statement = function.body;
|
| - while (statement != null) {
|
| - statement = visitStatement(statement);
|
| - }
|
| -
|
| - List<js.Parameter> parameters = new List<js.Parameter>();
|
| - Set<tree_ir.Variable> parameterSet = new Set<tree_ir.Variable>();
|
| - Set<String> declaredVariables = new Set<String>();
|
| -
|
| - for (tree_ir.Variable parameter in function.parameters) {
|
| - String name = getVariableName(parameter);
|
| - parameters.add(new js.Parameter(name));
|
| - parameterSet.add(parameter);
|
| - declaredVariables.add(name);
|
| - }
|
| -
|
| - List<js.VariableInitialization> jsVariables = <js.VariableInitialization>[];
|
| -
|
| - // Declare variables with an initializer. Pull statements into the
|
| - // initializer until we find a statement that cannot be pulled in.
|
| - int accumulatorIndex = 0;
|
| - while (accumulatorIndex < accumulator.length) {
|
| - js.Node node = accumulator[accumulatorIndex];
|
| -
|
| - // Check that node is an assignment to a local variable.
|
| - if (node is! js.ExpressionStatement) break;
|
| - js.ExpressionStatement stmt = node;
|
| - if (stmt.expression is! js.Assignment) break;
|
| - js.Assignment assign = stmt.expression;
|
| - if (assign.leftHandSide is! js.VariableUse) break;
|
| - if (assign.op != null) break; // Compound assignment.
|
| - js.VariableUse use = assign.leftHandSide;
|
| -
|
| - // Do not touch non-local variables.
|
| - if (!usedVariableNames.contains(use.name)) break;
|
| -
|
| - // We cannot declare a variable more than once.
|
| - if (!declaredVariables.add(use.name)) break;
|
| -
|
| - js.VariableInitialization jsVariable = new js.VariableInitialization(
|
| - new js.VariableDeclaration(use.name), assign.value);
|
| - jsVariables.add(jsVariable);
|
| -
|
| - ++accumulatorIndex;
|
| - }
|
| -
|
| - // If the last statement is a for loop with an initializer expression, try
|
| - // to pull that expression into an initializer as well.
|
| - pullFromForLoop: if (accumulatorIndex < accumulator.length &&
|
| - accumulator[accumulatorIndex] is js.For) {
|
| - js.For forLoop = accumulator[accumulatorIndex];
|
| - if (forLoop.init is! js.Assignment) break pullFromForLoop;
|
| - js.Assignment assign = forLoop.init;
|
| - if (assign.leftHandSide is! js.VariableUse) break pullFromForLoop;
|
| - if (assign.op != null) break pullFromForLoop; // Compound assignment.
|
| - js.VariableUse use = assign.leftHandSide;
|
| -
|
| - // Do not touch non-local variables.
|
| - if (!usedVariableNames.contains(use.name)) break pullFromForLoop;
|
| -
|
| - // We cannot declare a variable more than once.
|
| - if (!declaredVariables.add(use.name)) break pullFromForLoop;
|
| -
|
| - js.VariableInitialization jsVariable = new js.VariableInitialization(
|
| - new js.VariableDeclaration(use.name), assign.value);
|
| - jsVariables.add(jsVariable);
|
| -
|
| - // Remove the initializer from the for loop.
|
| - accumulator[accumulatorIndex] =
|
| - new js.For(null, forLoop.condition, forLoop.update, forLoop.body);
|
| - }
|
| -
|
| - // Discard the statements that were pulled in the initializer.
|
| - if (accumulatorIndex > 0) {
|
| - accumulator = accumulator.sublist(accumulatorIndex);
|
| - }
|
| -
|
| - // Declare remaining variables.
|
| - for (tree_ir.Variable variable in variableNames.keys) {
|
| - String name = getVariableName(variable);
|
| - if (declaredVariables.contains(name)) continue;
|
| - js.VariableInitialization jsVariable =
|
| - new js.VariableInitialization(new js.VariableDeclaration(name), null);
|
| - jsVariables.add(jsVariable);
|
| - }
|
| -
|
| - if (jsVariables.length > 0) {
|
| - // Would be nice to avoid inserting at the beginning of list.
|
| - accumulator.insert(
|
| - 0,
|
| - new js.ExpressionStatement(new js.VariableDeclarationList(jsVariables)
|
| - .withSourceInformation(function.sourceInformation)));
|
| - }
|
| - return new js.Fun(parameters, new js.Block(accumulator));
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitExpression(tree_ir.Expression node) {
|
| - js.Expression result = node.accept(this);
|
| - if (result == null) {
|
| - glue.reportInternalError('$node did not produce code.');
|
| - }
|
| - return result;
|
| - }
|
| -
|
| - /// Generates a name for the given variable. First trying with the name of
|
| - /// the [Variable.element] if it is non-null.
|
| - String getVariableName(tree_ir.Variable variable) {
|
| - // Functions are not nested in the JS backend.
|
| - assert(variable.host == currentFunction);
|
| -
|
| - // Get the name if we already have one.
|
| - String name = variableNames[variable];
|
| - if (name != null) {
|
| - return name;
|
| - }
|
| -
|
| - // Synthesize a variable name that isn't used elsewhere.
|
| - String prefix = variable.element == null ? 'v' : variable.element.name;
|
| - int counter = 0;
|
| - name = glue.safeVariableName(
|
| - variable.element == null ? '$prefix$counter' : variable.element.name);
|
| - while (!usedVariableNames.add(name)) {
|
| - ++counter;
|
| - name = '$prefix$counter';
|
| - }
|
| - variableNames[variable] = name;
|
| -
|
| - return name;
|
| - }
|
| -
|
| - List<js.Expression> visitExpressionList(
|
| - List<tree_ir.Expression> expressions) {
|
| - List<js.Expression> result = new List<js.Expression>(expressions.length);
|
| - for (int i = 0; i < expressions.length; ++i) {
|
| - result[i] = visitExpression(expressions[i]);
|
| - }
|
| - return result;
|
| - }
|
| -
|
| - giveup(tree_ir.Node node,
|
| - [String reason = 'unimplemented in CodeGenerator']) {
|
| - throw new CodegenBailout(node, reason);
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitConditional(tree_ir.Conditional node) {
|
| - return new js.Conditional(
|
| - visitExpression(node.condition),
|
| - visitExpression(node.thenExpression),
|
| - visitExpression(node.elseExpression));
|
| - }
|
| -
|
| - js.Expression buildConstant(ConstantValue constant,
|
| - {SourceInformation sourceInformation}) {
|
| - registry.registerCompileTimeConstant(constant);
|
| - return glue
|
| - .constantReference(constant)
|
| - .withSourceInformation(sourceInformation);
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitConstant(tree_ir.Constant node) {
|
| - return buildConstant(node.value, sourceInformation: node.sourceInformation);
|
| - }
|
| -
|
| - js.Expression buildStaticInvoke(Element target, List<js.Expression> arguments,
|
| - {SourceInformation sourceInformation}) {
|
| - if (target.isConstructor) {
|
| - // TODO(johnniwinther): Avoid dependency on [isGenerativeConstructor] by
|
| - // using backend-specific [StatisUse] classes.
|
| - registry.registerStaticUse(new StaticUse.constructorInvoke(
|
| - target.declaration, new CallStructure.unnamed(arguments.length)));
|
| - } else {
|
| - registry.registerStaticUse(new StaticUse.staticInvoke(
|
| - target.declaration, new CallStructure.unnamed(arguments.length)));
|
| - }
|
| - js.Expression elementAccess = glue.staticFunctionAccess(target);
|
| - return new js.Call(elementAccess, arguments,
|
| - sourceInformation: sourceInformation);
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitInvokeConstructor(tree_ir.InvokeConstructor node) {
|
| - if (node.constant != null) return giveup(node);
|
| -
|
| - registry.registerInstantiation(node.type);
|
| - FunctionElement target = node.target;
|
| - List<js.Expression> arguments = visitExpressionList(node.arguments);
|
| - return buildStaticInvoke(target, arguments,
|
| - sourceInformation: node.sourceInformation);
|
| - }
|
| -
|
| - void registerMethodInvoke(Selector selector, TypeMask receiverType) {
|
| - registry.registerDynamicUse(new DynamicUse(selector, receiverType));
|
| - if (!selector.isGetter && !selector.isSetter) {
|
| - // TODO(sigurdm): We should find a better place to register the call.
|
| - Selector call = new Selector.callClosureFrom(selector);
|
| - registry.registerDynamicUse(new DynamicUse(call, null));
|
| - }
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitInvokeMethod(tree_ir.InvokeMethod node) {
|
| - TypeMask mask = glue.extendMaskIfReachesAll(node.selector, node.mask);
|
| - registerMethodInvoke(node.selector, mask);
|
| - return js
|
| - .propertyCall(
|
| - visitExpression(node.receiver),
|
| - glue.invocationName(node.selector),
|
| - visitExpressionList(node.arguments))
|
| - .withSourceInformation(node.sourceInformation);
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitInvokeStatic(tree_ir.InvokeStatic node) {
|
| - FunctionElement target = node.target;
|
| - List<js.Expression> arguments = visitExpressionList(node.arguments);
|
| - return buildStaticInvoke(target, arguments,
|
| - sourceInformation: node.sourceInformation);
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitInvokeMethodDirectly(tree_ir.InvokeMethodDirectly node) {
|
| - if (node.isTearOff) {
|
| - // If this is a tear-off, register the fact that a tear-off closure
|
| - // will be created, and that this tear-off must bypass ordinary
|
| - // dispatch to ensure the super method is invoked.
|
| - registry.registerStaticUse(new StaticUse.staticInvoke(
|
| - glue.closureFromTearOff,
|
| - new CallStructure.unnamed(
|
| - glue.closureFromTearOff.parameters.length)));
|
| - registry.registerStaticUse(new StaticUse.superTearOff(node.target));
|
| - }
|
| - if (node.target is ConstructorBodyElement) {
|
| - registry.registerStaticUse(new StaticUse.constructorBodyInvoke(
|
| - node.target.declaration,
|
| - new CallStructure.unnamed(node.arguments.length)));
|
| - // A constructor body cannot be overriden or intercepted, so we can
|
| - // use the short form for this invocation.
|
| - return js.js('#.#(#)', [
|
| - visitExpression(node.receiver),
|
| - glue.instanceMethodName(node.target),
|
| - visitExpressionList(node.arguments)
|
| - ]).withSourceInformation(node.sourceInformation);
|
| - }
|
| - registry.registerStaticUse(new StaticUse.superInvoke(
|
| - node.target.declaration,
|
| - new CallStructure.unnamed(node.arguments.length)));
|
| - return js.js('#.#.call(#, #)', [
|
| - glue.prototypeAccess(node.target.enclosingClass),
|
| - glue.invocationName(node.selector),
|
| - visitExpression(node.receiver),
|
| - visitExpressionList(node.arguments)
|
| - ]).withSourceInformation(node.sourceInformation);
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitOneShotInterceptor(tree_ir.OneShotInterceptor node) {
|
| - registerMethodInvoke(node.selector, node.mask);
|
| - registry.registerUseInterceptor();
|
| - return js.js('#.#(#)', [
|
| - glue.getInterceptorLibrary(),
|
| - glue.registerOneShotInterceptor(node.selector),
|
| - visitExpressionList(node.arguments)
|
| - ]).withSourceInformation(node.sourceInformation);
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitLiteralList(tree_ir.LiteralList node) {
|
| - registry.registerInstantiatedClass(glue.listClass);
|
| - List<js.Expression> entries = visitExpressionList(node.values);
|
| - return new js.ArrayInitializer(entries);
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitLogicalOperator(tree_ir.LogicalOperator node) {
|
| - return new js.Binary(
|
| - node.operator, visitExpression(node.left), visitExpression(node.right));
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitNot(tree_ir.Not node) {
|
| - return new js.Prefix("!", visitExpression(node.operand));
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitThis(tree_ir.This node) {
|
| - return new js.This();
|
| - }
|
| -
|
| - /// Ensure that 'instanceof' checks may be performed against [class_].
|
| - ///
|
| - /// Even if the class is never instantiated, a JS constructor must be emitted
|
| - /// so the 'instanceof' expression does not throw an exception at runtime.
|
| - bool tryRegisterInstanceofCheck(ClassElement class_) {
|
| - if (glue.classWorld.isInstantiated(class_)) {
|
| - // Ensure the class remains instantiated during backend tree-shaking.
|
| - // TODO(asgerf): We could have a more precise hook to inform the emitter
|
| - // that the JS constructor function is needed, without the class being
|
| - // instantiated.
|
| - registry.registerInstantiatedClass(class_);
|
| - return true;
|
| - }
|
| - // Will throw if the JS constructor is not emitted, so do not allow the
|
| - // instanceof check. This should only happen when certain optimization
|
| - // passes are disabled, as the type check itself is trivial.
|
| - return false;
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitTypeOperator(tree_ir.TypeOperator node) {
|
| - js.Expression value = visitExpression(node.value);
|
| - List<js.Expression> typeArguments = visitExpressionList(node.typeArguments);
|
| - DartType type = node.type;
|
| - if (type is InterfaceType) {
|
| - registry.registerTypeUse(new TypeUse.isCheck(type));
|
| - ClassElement clazz = type.element;
|
| -
|
| - if (glue.isStringClass(clazz)) {
|
| - if (node.isTypeTest) {
|
| - return js.js(r'typeof # === "string"', <js.Expression>[value]);
|
| - }
|
| - // TODO(sra): Implement fast cast via calling 'stringTypeCast'.
|
| - } else if (glue.isBoolClass(clazz)) {
|
| - if (node.isTypeTest) {
|
| - return js.js(r'typeof # === "boolean"', <js.Expression>[value]);
|
| - }
|
| - // TODO(sra): Implement fast cast via calling 'boolTypeCast'.
|
| - } else if (node.isTypeTest &&
|
| - node.typeArguments.isEmpty &&
|
| - glue.mayGenerateInstanceofCheck(type) &&
|
| - tryRegisterInstanceofCheck(clazz)) {
|
| - return js.js('# instanceof #', [value, glue.constructorAccess(clazz)]);
|
| - }
|
| -
|
| - // The helper we use needs the JSArray class to exist, but for some
|
| - // reason the helper does not cause this dependency to be registered.
|
| - // TODO(asgerf): Most programs need List anyway, but we should fix this.
|
| - registry.registerInstantiatedClass(glue.listClass);
|
| -
|
| - // We use one of the two helpers:
|
| - //
|
| - // checkSubtype(value, $isT, typeArgs, $asT)
|
| - // subtypeCast(value, $isT, typeArgs, $asT)
|
| - //
|
| - // Any of the last two arguments may be null if there are no type
|
| - // arguments, and/or if no substitution is required.
|
| - Element function =
|
| - node.isTypeTest ? glue.getCheckSubtype() : glue.getSubtypeCast();
|
| -
|
| - js.Expression isT = js.quoteName(glue.getTypeTestTag(type));
|
| -
|
| - js.Expression typeArgumentArray = typeArguments.isNotEmpty
|
| - ? new js.ArrayInitializer(typeArguments)
|
| - : new js.LiteralNull();
|
| -
|
| - js.Expression asT = glue.hasStrictSubtype(clazz)
|
| - ? js.quoteName(glue.getTypeSubstitutionTag(clazz))
|
| - : new js.LiteralNull();
|
| -
|
| - return buildStaticHelperInvocation(
|
| - function, <js.Expression>[value, isT, typeArgumentArray, asT]);
|
| - } else if (type is TypeVariableType || type is FunctionType) {
|
| - registry.registerTypeUse(new TypeUse.isCheck(type));
|
| -
|
| - Element function = node.isTypeTest
|
| - ? glue.getCheckSubtypeOfRuntimeType()
|
| - : glue.getSubtypeOfRuntimeTypeCast();
|
| -
|
| - // The only type argument is the type held in the type variable.
|
| - js.Expression typeValue = typeArguments.single;
|
| -
|
| - return buildStaticHelperInvocation(
|
| - function, <js.Expression>[value, typeValue]);
|
| - }
|
| - return giveup(node, 'type check unimplemented for $type.');
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitGetTypeTestProperty(tree_ir.GetTypeTestProperty node) {
|
| - js.Expression object = visitExpression(node.object);
|
| - DartType dartType = node.dartType;
|
| - assert(dartType.isInterfaceType);
|
| - registry.registerTypeUse(new TypeUse.isCheck(dartType));
|
| - //glue.registerIsCheck(dartType, registry);
|
| - js.Expression property = glue.getTypeTestTag(dartType);
|
| - return js.js(r'#.#', [object, property]);
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitVariableUse(tree_ir.VariableUse node) {
|
| - return buildVariableAccess(node.variable)
|
| - .withSourceInformation(node.sourceInformation);
|
| - }
|
| -
|
| - js.Expression buildVariableAccess(tree_ir.Variable variable) {
|
| - return new js.VariableUse(getVariableName(variable));
|
| - }
|
| -
|
| - /// Returns the JS operator for the given built-in operator for use in a
|
| - /// compound assignment (not including the '=' sign).
|
| - String getAsCompoundOperator(BuiltinOperator operator) {
|
| - switch (operator) {
|
| - case BuiltinOperator.NumAdd:
|
| - case BuiltinOperator.StringConcatenate:
|
| - return '+';
|
| - case BuiltinOperator.NumSubtract:
|
| - return '-';
|
| - case BuiltinOperator.NumMultiply:
|
| - return '*';
|
| - case BuiltinOperator.NumDivide:
|
| - return '/';
|
| - case BuiltinOperator.NumRemainder:
|
| - return '%';
|
| - default:
|
| - throw 'Not a compoundable operator: $operator';
|
| - }
|
| - }
|
| -
|
| - bool isCompoundableBuiltin(tree_ir.Expression exp) {
|
| - return exp is tree_ir.ApplyBuiltinOperator &&
|
| - exp.arguments.length == 2 &&
|
| - isCompoundableOperator(exp.operator);
|
| - }
|
| -
|
| - bool isOneConstant(tree_ir.Expression exp) {
|
| - return exp is tree_ir.Constant && exp.value.isOne;
|
| - }
|
| -
|
| - js.Expression makeAssignment(js.Expression leftHand, tree_ir.Expression value,
|
| - {SourceInformation sourceInformation, BuiltinOperator compound}) {
|
| - if (isOneConstant(value)) {
|
| - if (compound == BuiltinOperator.NumAdd) {
|
| - return new js.Prefix('++', leftHand)
|
| - .withSourceInformation(sourceInformation);
|
| - }
|
| - if (compound == BuiltinOperator.NumSubtract) {
|
| - return new js.Prefix('--', leftHand)
|
| - .withSourceInformation(sourceInformation);
|
| - }
|
| - }
|
| - if (compound != null) {
|
| - return new js.Assignment.compound(
|
| - leftHand, getAsCompoundOperator(compound), visitExpression(value))
|
| - .withSourceInformation(sourceInformation);
|
| - }
|
| - return new js.Assignment(leftHand, visitExpression(value))
|
| - .withSourceInformation(sourceInformation);
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitAssign(tree_ir.Assign node) {
|
| - js.Expression variable = buildVariableAccess(node.variable);
|
| - if (isCompoundableBuiltin(node.value)) {
|
| - tree_ir.ApplyBuiltinOperator rhs = node.value;
|
| - tree_ir.Expression left = rhs.arguments[0];
|
| - tree_ir.Expression right = rhs.arguments[1];
|
| - if (left is tree_ir.VariableUse && left.variable == node.variable) {
|
| - return makeAssignment(variable, right,
|
| - compound: rhs.operator, sourceInformation: node.sourceInformation);
|
| - }
|
| - }
|
| - return makeAssignment(variable, node.value,
|
| - sourceInformation: node.sourceInformation);
|
| - }
|
| -
|
| - @override
|
| - void visitContinue(tree_ir.Continue node) {
|
| - tree_ir.Statement next = fallthrough.target;
|
| - if (node.target.binding == next ||
|
| - next is tree_ir.Continue && node.target == next.target) {
|
| - // Fall through to continue target or to equivalent continue.
|
| - fallthrough.use();
|
| - } else if (node.target.binding == shortContinue.target) {
|
| - // The target is the immediately enclosing loop.
|
| - shortContinue.use();
|
| - accumulator.add(new js.Continue(null));
|
| - } else {
|
| - accumulator.add(new js.Continue(makeLabel(node.target)));
|
| - }
|
| - }
|
| -
|
| - /// True if [other] is the target of [node] or is a [Break] with the same
|
| - /// target. This means jumping to [other] is equivalent to executing [node].
|
| - bool isEffectiveBreakTarget(tree_ir.Break node, tree_ir.Statement other) {
|
| - return node.target.binding.next == other ||
|
| - other is tree_ir.Break && node.target == other.target;
|
| - }
|
| -
|
| - /// True if the given break is equivalent to an unlabeled continue.
|
| - bool isShortContinue(tree_ir.Break node) {
|
| - tree_ir.Statement next = node.target.binding.next;
|
| - return next is tree_ir.Continue &&
|
| - next.target.binding == shortContinue.target;
|
| - }
|
| -
|
| - @override
|
| - void visitBreak(tree_ir.Break node) {
|
| - if (isEffectiveBreakTarget(node, fallthrough.target)) {
|
| - // Fall through to break target or to equivalent break.
|
| - fallthrough.use();
|
| - } else if (isEffectiveBreakTarget(node, shortBreak.target)) {
|
| - // Unlabeled break to the break target or to an equivalent break.
|
| - shortBreak.use();
|
| - accumulator.add(new js.Break(null));
|
| - } else if (isShortContinue(node)) {
|
| - // An unlabeled continue is better than a labeled break.
|
| - shortContinue.use();
|
| - accumulator.add(new js.Continue(null));
|
| - } else {
|
| - accumulator.add(new js.Break(makeLabel(node.target)));
|
| - }
|
| - }
|
| -
|
| - @override
|
| - visitExpressionStatement(tree_ir.ExpressionStatement node) {
|
| - js.Expression exp = visitExpression(node.expression);
|
| - if (node.next is tree_ir.Unreachable && emitUnreachableAsReturn.last) {
|
| - // Emit as 'return exp' to assist local analysis in the VM.
|
| - SourceInformation sourceInformation = node.expression.sourceInformation;
|
| - accumulator
|
| - .add(new js.Return(exp).withSourceInformation(sourceInformation));
|
| - return null;
|
| - } else {
|
| - accumulator.add(new js.ExpressionStatement(exp));
|
| - return node.next;
|
| - }
|
| - }
|
| -
|
| - bool isNullReturn(tree_ir.Statement node) {
|
| - return node is tree_ir.Return && isNull(node.value);
|
| - }
|
| -
|
| - bool isEndOfMethod(tree_ir.Statement node) {
|
| - return isNullReturn(node) ||
|
| - node is tree_ir.Break && isNullReturn(node.target.binding.next);
|
| - }
|
| -
|
| - @override
|
| - visitIf(tree_ir.If node) {
|
| - js.Expression condition = visitExpression(node.condition);
|
| - int usesBefore = fallthrough.useCount;
|
| - // Unless the 'else' part ends the method. make sure to terminate any
|
| - // uncompletable code paths in the 'then' part.
|
| - emitUnreachableAsReturn.add(!isEndOfMethod(node.elseStatement));
|
| - js.Statement thenBody = buildBodyStatement(node.thenStatement);
|
| - emitUnreachableAsReturn.removeLast();
|
| - bool thenHasFallthrough = (fallthrough.useCount > usesBefore);
|
| - if (thenHasFallthrough) {
|
| - js.Statement elseBody = buildBodyStatement(node.elseStatement);
|
| - accumulator.add(new js.If(condition, thenBody, elseBody)
|
| - .withSourceInformation(node.sourceInformation));
|
| - return null;
|
| - } else {
|
| - // The 'then' body cannot complete normally, so emit a short 'if'
|
| - // and put the 'else' body after it.
|
| - accumulator.add(new js.If.noElse(condition, thenBody)
|
| - .withSourceInformation(node.sourceInformation));
|
| - return node.elseStatement;
|
| - }
|
| - }
|
| -
|
| - @override
|
| - visitLabeledStatement(tree_ir.LabeledStatement node) {
|
| - fallthrough.push(node.next);
|
| - js.Statement body = buildBodyStatement(node.body);
|
| - fallthrough.pop();
|
| - accumulator.add(insertLabel(node.label, body));
|
| - return node.next;
|
| - }
|
| -
|
| - /// Creates a name for [label] if it does not already have one.
|
| - ///
|
| - /// This also marks the label as being used.
|
| - String makeLabel(tree_ir.Label label) {
|
| - return labelNames.putIfAbsent(label, () => 'L${labelNames.length}');
|
| - }
|
| -
|
| - /// Wraps a node in a labeled statement unless the label is unused.
|
| - js.Statement insertLabel(tree_ir.Label label, js.Statement node) {
|
| - String name = labelNames[label];
|
| - if (name == null) return node; // Label is unused.
|
| - return new js.LabeledStatement(name, node);
|
| - }
|
| -
|
| - /// Returns the current [accumulator] wrapped in a block if neccessary.
|
| - js.Statement _bodyAsStatement() {
|
| - if (accumulator.length == 0) {
|
| - return new js.EmptyStatement();
|
| - }
|
| - if (accumulator.length == 1) {
|
| - return accumulator.single;
|
| - }
|
| - return new js.Block(accumulator);
|
| - }
|
| -
|
| - /// Builds a nested statement.
|
| - js.Statement buildBodyStatement(tree_ir.Statement statement) {
|
| - List<js.Statement> savedAccumulator = accumulator;
|
| - accumulator = <js.Statement>[];
|
| - while (statement != null) {
|
| - statement = visitStatement(statement);
|
| - }
|
| - js.Statement result = _bodyAsStatement();
|
| - accumulator = savedAccumulator;
|
| - return result;
|
| - }
|
| -
|
| - js.Block buildBodyBlock(tree_ir.Statement statement) {
|
| - List<js.Statement> savedAccumulator = accumulator;
|
| - accumulator = <js.Statement>[];
|
| - while (statement != null) {
|
| - statement = visitStatement(statement);
|
| - }
|
| - js.Statement result = new js.Block(accumulator);
|
| - accumulator = savedAccumulator;
|
| - return result;
|
| - }
|
| -
|
| - js.Expression makeSequence(List<tree_ir.Expression> list) {
|
| - return list.map(visitExpression).reduce((x, y) => new js.Binary(',', x, y));
|
| - }
|
| -
|
| - @override
|
| - visitFor(tree_ir.For node) {
|
| - js.Expression condition = visitExpression(node.condition);
|
| - shortBreak.push(node.next);
|
| - shortContinue.push(node);
|
| - fallthrough.push(node);
|
| - emitUnreachableAsReturn.add(true);
|
| - js.Statement body = buildBodyStatement(node.body);
|
| - emitUnreachableAsReturn.removeLast();
|
| - fallthrough.pop();
|
| - shortContinue.pop();
|
| - shortBreak.pop();
|
| - js.Statement loopNode;
|
| - if (node.updates.isEmpty) {
|
| - loopNode = new js.While(condition, body);
|
| - } else {
|
| - // Compile as a for loop.
|
| - js.Expression init;
|
| - if (accumulator.isNotEmpty &&
|
| - accumulator.last is js.ExpressionStatement) {
|
| - // Take the preceding expression from the accumulator and use
|
| - // it as the initializer expression.
|
| - js.ExpressionStatement initStmt = accumulator.removeLast();
|
| - init = initStmt.expression;
|
| - }
|
| - js.Expression update = makeSequence(node.updates);
|
| - loopNode = new js.For(init, condition, update, body);
|
| - }
|
| - accumulator.add(insertLabel(node.label, loopNode));
|
| - return node.next;
|
| - }
|
| -
|
| - @override
|
| - void visitWhileTrue(tree_ir.WhileTrue node) {
|
| - // A short break in the while will jump to the current fallthrough target.
|
| - shortBreak.push(fallthrough.target);
|
| - shortContinue.push(node);
|
| - fallthrough.push(node);
|
| - emitUnreachableAsReturn.add(true);
|
| - js.Statement jsBody = buildBodyStatement(node.body);
|
| - emitUnreachableAsReturn.removeLast();
|
| - fallthrough.pop();
|
| - shortContinue.pop();
|
| - if (shortBreak.useCount > 0) {
|
| - // Short breaks use the current fallthrough target.
|
| - fallthrough.use();
|
| - }
|
| - shortBreak.pop();
|
| - accumulator
|
| - .add(insertLabel(node.label, new js.For(null, null, null, jsBody)));
|
| - }
|
| -
|
| - bool isNull(tree_ir.Expression node) {
|
| - return node is tree_ir.Constant && node.value.isNull;
|
| - }
|
| -
|
| - @override
|
| - void visitReturn(tree_ir.Return node) {
|
| - if (isNull(node.value) && fallthrough.target == null) {
|
| - // Do nothing. Implicitly return JS undefined by falling over the end.
|
| - registry.registerCompileTimeConstant(new NullConstantValue());
|
| - fallthrough.use();
|
| - } else {
|
| - accumulator.add(new js.Return(visitExpression(node.value))
|
| - .withSourceInformation(node.sourceInformation));
|
| - }
|
| - }
|
| -
|
| - @override
|
| - void visitThrow(tree_ir.Throw node) {
|
| - accumulator.add(new js.Throw(visitExpression(node.value)));
|
| - }
|
| -
|
| - @override
|
| - void visitUnreachable(tree_ir.Unreachable node) {
|
| - if (emitUnreachableAsReturn.last) {
|
| - // Emit a return to assist local analysis in the VM.
|
| - accumulator.add(new js.Return());
|
| - }
|
| - }
|
| -
|
| - @override
|
| - void visitTry(tree_ir.Try node) {
|
| - js.Block tryBlock = buildBodyBlock(node.tryBody);
|
| - tree_ir.Variable exceptionVariable = node.catchParameters.first;
|
| - js.VariableDeclaration exceptionParameter =
|
| - new js.VariableDeclaration(getVariableName(exceptionVariable));
|
| - js.Block catchBlock = buildBodyBlock(node.catchBody);
|
| - js.Catch catchPart = new js.Catch(exceptionParameter, catchBlock);
|
| - accumulator.add(new js.Try(tryBlock, catchPart, null));
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitCreateBox(tree_ir.CreateBox node) {
|
| - return new js.ObjectInitializer(const <js.Property>[]);
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitCreateInstance(tree_ir.CreateInstance node) {
|
| - ClassElement classElement = node.classElement;
|
| - // TODO(asgerf): To allow inlining of InvokeConstructor, CreateInstance must
|
| - // carry a DartType so we can register the instantiated type
|
| - // with its type arguments. Otherwise dataflow analysis is
|
| - // needed to reconstruct the instantiated type.
|
| - registry.registerInstantiation(classElement.rawType);
|
| - if (classElement is ClosureClassElement) {
|
| - registry.registerInstantiatedClosure(classElement.methodElement);
|
| - }
|
| - js.Expression instance = new js.New(glue.constructorAccess(classElement),
|
| - visitExpressionList(node.arguments))
|
| - .withSourceInformation(node.sourceInformation);
|
| -
|
| - tree_ir.Expression typeInformation = node.typeInformation;
|
| - if (typeInformation != null) {
|
| - FunctionElement helper = glue.getAddRuntimeTypeInformation();
|
| - js.Expression typeArguments = visitExpression(typeInformation);
|
| - return buildStaticHelperInvocation(
|
| - helper, <js.Expression>[instance, typeArguments],
|
| - sourceInformation: node.sourceInformation);
|
| - } else {
|
| - return instance;
|
| - }
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitCreateInvocationMirror(
|
| - tree_ir.CreateInvocationMirror node) {
|
| - js.Expression name = js.string(node.selector.name);
|
| - js.Expression internalName =
|
| - js.quoteName(glue.invocationName(node.selector));
|
| - js.Expression kind = js.number(node.selector.invocationMirrorKind);
|
| - js.Expression arguments =
|
| - new js.ArrayInitializer(visitExpressionList(node.arguments));
|
| - js.Expression argumentNames = new js.ArrayInitializer(
|
| - node.selector.namedArguments.map(js.string).toList(growable: false));
|
| - return buildStaticHelperInvocation(glue.createInvocationMirrorMethod,
|
| - <js.Expression>[name, internalName, kind, arguments, argumentNames]);
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitInterceptor(tree_ir.Interceptor node) {
|
| - registry.registerUseInterceptor();
|
| - // Default to all intercepted classes if they have not been computed.
|
| - // This is to ensure we can run codegen without prior optimization passes.
|
| - Set<ClassElement> interceptedClasses = node.interceptedClasses.isEmpty
|
| - ? glue.interceptedClasses
|
| - : node.interceptedClasses;
|
| - registry.registerSpecializedGetInterceptor(interceptedClasses);
|
| - js.Name helperName = glue.getInterceptorName(interceptedClasses);
|
| - js.Expression globalHolder = glue.getInterceptorLibrary();
|
| - return js.js('#.#(#)', [
|
| - globalHolder,
|
| - helperName,
|
| - visitExpression(node.input)
|
| - ]).withSourceInformation(node.sourceInformation);
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitGetField(tree_ir.GetField node) {
|
| - registry.registerStaticUse(new StaticUse.fieldGet(node.field));
|
| - return new js.PropertyAccess(visitExpression(node.object),
|
| - glue.instanceFieldPropertyName(node.field))
|
| - .withSourceInformation(node.sourceInformation);
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitSetField(tree_ir.SetField node) {
|
| - registry.registerStaticUse(new StaticUse.fieldSet(node.field));
|
| - js.PropertyAccess field = new js.PropertyAccess(
|
| - visitExpression(node.object),
|
| - glue.instanceFieldPropertyName(node.field));
|
| - return makeAssignment(field, node.value,
|
| - compound: node.compound, sourceInformation: node.sourceInformation);
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitGetStatic(tree_ir.GetStatic node) {
|
| - assert(node.element is FieldElement || node.element is FunctionElement);
|
| - if (node.element is FunctionElement) {
|
| - // Tear off a method.
|
| - registry.registerStaticUse(
|
| - new StaticUse.staticTearOff(node.element.declaration));
|
| - return glue
|
| - .isolateStaticClosureAccess(node.element)
|
| - .withSourceInformation(node.sourceInformation);
|
| - }
|
| - if (node.useLazyGetter) {
|
| - // Read a lazily initialized field.
|
| - registry.registerStaticUse(
|
| - new StaticUse.staticInit(node.element.declaration));
|
| - js.Expression getter = glue.isolateLazyInitializerAccess(node.element);
|
| - return new js.Call(getter, <js.Expression>[],
|
| - sourceInformation: node.sourceInformation);
|
| - }
|
| - // Read an eagerly initialized field.
|
| - registry
|
| - .registerStaticUse(new StaticUse.staticGet(node.element.declaration));
|
| - return glue
|
| - .staticFieldAccess(node.element)
|
| - .withSourceInformation(node.sourceInformation);
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitSetStatic(tree_ir.SetStatic node) {
|
| - assert(node.element is FieldElement);
|
| - registry
|
| - .registerStaticUse(new StaticUse.staticSet(node.element.declaration));
|
| - js.Expression field = glue.staticFieldAccess(node.element);
|
| - return makeAssignment(field, node.value,
|
| - compound: node.compound, sourceInformation: node.sourceInformation);
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitGetLength(tree_ir.GetLength node) {
|
| - return new js.PropertyAccess.field(visitExpression(node.object), 'length');
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitGetIndex(tree_ir.GetIndex node) {
|
| - return new js.PropertyAccess(
|
| - visitExpression(node.object), visitExpression(node.index));
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitSetIndex(tree_ir.SetIndex node) {
|
| - js.Expression index = new js.PropertyAccess(
|
| - visitExpression(node.object), visitExpression(node.index));
|
| - return makeAssignment(index, node.value, compound: node.compound);
|
| - }
|
| -
|
| - js.Expression buildStaticHelperInvocation(
|
| - FunctionElement helper, List<js.Expression> arguments,
|
| - {SourceInformation sourceInformation}) {
|
| - registry.registerStaticUse(new StaticUse.staticInvoke(
|
| - helper, new CallStructure.unnamed(arguments.length)));
|
| - return buildStaticInvoke(helper, arguments,
|
| - sourceInformation: sourceInformation);
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitReifyRuntimeType(tree_ir.ReifyRuntimeType node) {
|
| - js.Expression typeToString = buildStaticHelperInvocation(
|
| - glue.getRuntimeTypeToString(), [visitExpression(node.value)],
|
| - sourceInformation: node.sourceInformation);
|
| - return buildStaticHelperInvocation(
|
| - glue.getCreateRuntimeType(), [typeToString],
|
| - sourceInformation: node.sourceInformation);
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitReadTypeVariable(tree_ir.ReadTypeVariable node) {
|
| - ClassElement context = node.variable.element.enclosingClass;
|
| - js.Expression index = js.number(glue.getTypeVariableIndex(node.variable));
|
| - if (glue.needsSubstitutionForTypeVariableAccess(context)) {
|
| - js.Expression typeName = glue.getRuntimeTypeName(context);
|
| - return buildStaticHelperInvocation(glue.getRuntimeTypeArgument(),
|
| - [visitExpression(node.target), typeName, index],
|
| - sourceInformation: node.sourceInformation);
|
| - } else {
|
| - return buildStaticHelperInvocation(
|
| - glue.getTypeArgumentByIndex(), [visitExpression(node.target), index],
|
| - sourceInformation: node.sourceInformation);
|
| - }
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitTypeExpression(tree_ir.TypeExpression node) {
|
| - List<js.Expression> arguments = visitExpressionList(node.arguments);
|
| - switch (node.kind) {
|
| - case tree_ir.TypeExpressionKind.COMPLETE:
|
| - return glue.generateTypeRepresentation(
|
| - node.dartType, arguments, registry);
|
| - case tree_ir.TypeExpressionKind.INSTANCE:
|
| - // We expect only flat types for the INSTANCE representation.
|
| - assert(
|
| - node.dartType == (node.dartType.element as ClassElement).thisType);
|
| - registry.registerInstantiatedClass(glue.listClass);
|
| - return new js.ArrayInitializer(arguments);
|
| - }
|
| - }
|
| -
|
| - js.Node handleForeignCode(tree_ir.ForeignCode node) {
|
| - if (node.dependency != null) {
|
| - // Dependency is only used if [node] calls a Dart function. Currently only
|
| - // through foreign function `RAW_DART_FUNCTION_REF`.
|
| - registry.registerStaticUse(new StaticUse.staticInvoke(
|
| - node.dependency, new CallStructure.unnamed(node.arguments.length)));
|
| - }
|
| - // TODO(sra,johnniwinther): Should this be in CodegenRegistry?
|
| - glue.registerNativeBehavior(node.nativeBehavior, node);
|
| - return node.codeTemplate
|
| - .instantiate(visitExpressionList(node.arguments))
|
| - .withSourceInformation(node.sourceInformation);
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitForeignExpression(tree_ir.ForeignExpression node) {
|
| - return handleForeignCode(node);
|
| - }
|
| -
|
| - @override
|
| - void visitForeignStatement(tree_ir.ForeignStatement node) {
|
| - accumulator.add(handleForeignCode(node));
|
| - }
|
| -
|
| - @override
|
| - visitYield(tree_ir.Yield node) {
|
| - js.Expression value = visitExpression(node.input);
|
| - accumulator.add(new js.DartYield(value, node.hasStar));
|
| - return node.next;
|
| - }
|
| -
|
| - @override
|
| - visitReceiverCheck(tree_ir.ReceiverCheck node) {
|
| - js.Expression value = visitExpression(node.value);
|
| - // TODO(sra): Try to use the selector even when [useSelector] is false. The
|
| - // reason we use 'toString' is that it is always defined so avoids a slow
|
| - // lookup (in V8) of an absent property. We could use the property for the
|
| - // selector if we knew it was present. The property is present if the
|
| - // associated method was not inlined away, or if there is a noSuchMethod
|
| - // hook for that selector. We don't know these things here, but the decision
|
| - // could be deferred by creating a deferred property that was resolved after
|
| - // codegen.
|
| - js.Expression access = node.useSelector
|
| - ? js.js('#.#', [value, glue.invocationName(node.selector)])
|
| - : js.js('#.toString', [value]);
|
| - if (node.useInvoke) {
|
| - access = new js.Call(access, []);
|
| - }
|
| - if (node.condition != null) {
|
| - js.Expression condition = visitExpression(node.condition);
|
| - js.Statement body = isNullReturn(node.next)
|
| - ? new js.ExpressionStatement(access)
|
| - : new js.Return(access);
|
| - accumulator.add(new js.If.noElse(condition, body));
|
| - } else {
|
| - accumulator.add(new js.ExpressionStatement(access));
|
| - }
|
| - return node.next;
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitApplyBuiltinOperator(tree_ir.ApplyBuiltinOperator node) {
|
| - List<js.Expression> args = visitExpressionList(node.arguments);
|
| -
|
| - js.Expression createExpression() {
|
| - switch (node.operator) {
|
| - case BuiltinOperator.NumAdd:
|
| - return new js.Binary('+', args[0], args[1]);
|
| - case BuiltinOperator.NumSubtract:
|
| - return new js.Binary('-', args[0], args[1]);
|
| - case BuiltinOperator.NumMultiply:
|
| - return new js.Binary('*', args[0], args[1]);
|
| - case BuiltinOperator.NumDivide:
|
| - return new js.Binary('/', args[0], args[1]);
|
| - case BuiltinOperator.NumRemainder:
|
| - return new js.Binary('%', args[0], args[1]);
|
| - case BuiltinOperator.NumTruncatingDivideToSigned32:
|
| - return js.js('(# / #) | 0', args);
|
| - case BuiltinOperator.NumAnd:
|
| - return normalizeBitOp(js.js('# & #', args), node);
|
| - case BuiltinOperator.NumOr:
|
| - return normalizeBitOp(js.js('# | #', args), node);
|
| - case BuiltinOperator.NumXor:
|
| - return normalizeBitOp(js.js('# ^ #', args), node);
|
| - case BuiltinOperator.NumLt:
|
| - return new js.Binary('<', args[0], args[1]);
|
| - case BuiltinOperator.NumLe:
|
| - return new js.Binary('<=', args[0], args[1]);
|
| - case BuiltinOperator.NumGt:
|
| - return new js.Binary('>', args[0], args[1]);
|
| - case BuiltinOperator.NumGe:
|
| - return new js.Binary('>=', args[0], args[1]);
|
| - case BuiltinOperator.NumShl:
|
| - return normalizeBitOp(js.js('# << #', args), node);
|
| - case BuiltinOperator.NumShr:
|
| - // No normalization required since output is always uint32.
|
| - return js.js('# >>> #', args);
|
| - case BuiltinOperator.NumBitNot:
|
| - return js.js('(~#) >>> 0', args);
|
| - case BuiltinOperator.NumNegate:
|
| - return js.js('-#', args);
|
| - case BuiltinOperator.StringConcatenate:
|
| - if (args.isEmpty) return js.string('');
|
| - return args.reduce((e1, e2) => new js.Binary('+', e1, e2));
|
| - case BuiltinOperator.CharCodeAt:
|
| - return js.js('#.charCodeAt(#)', args);
|
| - case BuiltinOperator.Identical:
|
| - registry.registerStaticUse(new StaticUse.staticInvoke(
|
| - glue.identicalFunction, new CallStructure.unnamed(args.length)));
|
| - return buildStaticHelperInvocation(glue.identicalFunction, args);
|
| - case BuiltinOperator.StrictEq:
|
| - return new js.Binary('===', args[0], args[1]);
|
| - case BuiltinOperator.StrictNeq:
|
| - return new js.Binary('!==', args[0], args[1]);
|
| - case BuiltinOperator.LooseEq:
|
| - return new js.Binary('==', args[0], args[1]);
|
| - case BuiltinOperator.LooseNeq:
|
| - return new js.Binary('!=', args[0], args[1]);
|
| - case BuiltinOperator.IsFalsy:
|
| - return new js.Prefix('!', args[0]);
|
| - case BuiltinOperator.IsNumber:
|
| - return js.js('typeof # === "number"', args);
|
| - case BuiltinOperator.IsNotNumber:
|
| - return js.js('typeof # !== "number"', args);
|
| - case BuiltinOperator.IsFloor:
|
| - return js.js('Math.floor(#) === #', args);
|
| - case BuiltinOperator.IsInteger:
|
| - return js.js('typeof # === "number" && Math.floor(#) === #', args);
|
| - case BuiltinOperator.IsNotInteger:
|
| - return js.js('typeof # !== "number" || Math.floor(#) !== #', args);
|
| - case BuiltinOperator.IsUnsigned32BitInteger:
|
| - return js.js('# >>> 0 === #', args);
|
| - case BuiltinOperator.IsNotUnsigned32BitInteger:
|
| - return js.js('# >>> 0 !== #', args);
|
| - case BuiltinOperator.IsFixedLengthJSArray:
|
| - // TODO(sra): Remove boolify (i.e. !!).
|
| - return js.js(r'!!#.fixed$length', args);
|
| - case BuiltinOperator.IsExtendableJSArray:
|
| - return js.js(r'!#.fixed$length', args);
|
| - case BuiltinOperator.IsModifiableJSArray:
|
| - return js.js(r'!#.immutable$list', args);
|
| - case BuiltinOperator.IsUnmodifiableJSArray:
|
| - // TODO(sra): Remove boolify (i.e. !!).
|
| - return js.js(r'!!#.immutable$list', args);
|
| - }
|
| - }
|
| -
|
| - return createExpression().withSourceInformation(node.sourceInformation);
|
| - }
|
| -
|
| - /// Add a uint32 normalization `op >>> 0` to [op] if it is not in 31-bit
|
| - /// range.
|
| - js.Expression normalizeBitOp(
|
| - js.Expression op, tree_ir.ApplyBuiltinOperator node) {
|
| - const MAX_UINT31 = 0x7fffffff;
|
| - const MAX_UINT32 = 0xffffffff;
|
| -
|
| - int constantValue(tree_ir.Expression e) {
|
| - if (e is tree_ir.Constant) {
|
| - ConstantValue value = e.value;
|
| - if (!value.isInt) return null;
|
| - IntConstantValue intConstant = value;
|
| - if (intConstant.primitiveValue < 0) return null;
|
| - if (intConstant.primitiveValue > MAX_UINT32) return null;
|
| - return intConstant.primitiveValue;
|
| - }
|
| - return null;
|
| - }
|
| -
|
| - /// Returns a value of the form 0b0001xxxx to represent the highest bit set
|
| - /// in the result. This represents the range [0, 0b00011111], up to 32
|
| - /// bits. `null` represents a result possibly outside the uint32 range.
|
| - int maxBitOf(tree_ir.Expression e) {
|
| - if (e is tree_ir.Constant) {
|
| - return constantValue(e);
|
| - }
|
| - if (e is tree_ir.ApplyBuiltinOperator) {
|
| - if (e.operator == BuiltinOperator.NumAnd) {
|
| - int left = maxBitOf(e.arguments[0]);
|
| - int right = maxBitOf(e.arguments[1]);
|
| - if (left == null && right == null) return MAX_UINT32;
|
| - if (left == null) return right;
|
| - if (right == null) return left;
|
| - return (left < right) ? left : right;
|
| - }
|
| - if (e.operator == BuiltinOperator.NumOr ||
|
| - e.operator == BuiltinOperator.NumXor) {
|
| - int left = maxBitOf(e.arguments[0]);
|
| - int right = maxBitOf(e.arguments[1]);
|
| - if (left == null || right == null) return MAX_UINT32;
|
| - return left | right;
|
| - }
|
| - if (e.operator == BuiltinOperator.NumShr) {
|
| - int right = constantValue(e.arguments[1]);
|
| - // NumShr is JavaScript '>>>' so always generates a uint32 result.
|
| - if (right == null || right <= 0 || right > 31) return MAX_UINT32;
|
| - int left = maxBitOf(e.arguments[0]);
|
| - if (left == null) return MAX_UINT32;
|
| - return left >> right;
|
| - }
|
| - if (e.operator == BuiltinOperator.NumShl) {
|
| - int right = constantValue(e.arguments[1]);
|
| - if (right == null || right <= 0 || right > 31) return MAX_UINT32;
|
| - int left = maxBitOf(e.arguments[0]);
|
| - if (left == null) return MAX_UINT32;
|
| - if (left.bitLength + right > 31) return MAX_UINT32;
|
| - return left << right;
|
| - }
|
| - }
|
| - return null;
|
| - }
|
| -
|
| - int maxBit = maxBitOf(node);
|
| - if (maxBit != null && maxBit <= MAX_UINT31) return op;
|
| - return js.js('# >>> 0', [op]);
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitApplyBuiltinMethod(tree_ir.ApplyBuiltinMethod node) {
|
| - js.Expression receiver = visitExpression(node.receiver);
|
| - List<js.Expression> args = visitExpressionList(node.arguments);
|
| - switch (node.method) {
|
| - case BuiltinMethod.Push:
|
| - return js.js('#.push(#)', [receiver, args]);
|
| -
|
| - case BuiltinMethod.Pop:
|
| - return js.js('#.pop()', [receiver]);
|
| -
|
| - case BuiltinMethod.SetLength:
|
| - return js.js('#.length = #', [receiver, args[0]]);
|
| - }
|
| - }
|
| -
|
| - @override
|
| - js.Expression visitAwait(tree_ir.Await node) {
|
| - return new js.Await(visitExpression(node.input));
|
| - }
|
| -
|
| - /// Ensures that parameter defaults will be emitted.
|
| - ///
|
| - /// Ideally, this should be done when generating the relevant stub methods,
|
| - /// since those are the ones that actually reference the constants, but those
|
| - /// are created by the emitter when it is too late to register new constants.
|
| - ///
|
| - /// For non-static methods, we have no way of knowing if the defaults are
|
| - /// actually used, so we conservatively register them all.
|
| - void registerDefaultParameterValues(ExecutableElement element) {
|
| - if (element is! FunctionElement) return;
|
| - FunctionElement function = element;
|
| - if (function.isStatic) return; // Defaults are inlined at call sites.
|
| - function.functionSignature.forEachOptionalParameter((param) {
|
| - ConstantValue constant = glue.getDefaultParameterValue(param);
|
| - registry.registerCompileTimeConstant(constant);
|
| - });
|
| - }
|
| -}
|
|
|